Created
June 20, 2023 13:44
-
-
Save jonico/105087633c507a75554967ebe37262e2 to your computer and use it in GitHub Desktop.
GitHub Action based IssueOps workflow to create Postman releases and a tag directly from a pull request by issuing a comment starting with /pm-release [<release-name>] ["release notes"]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Postman IssueOps commands | |
on: | |
issue_comment: | |
types: [created] | |
jobs: | |
prechecks: | |
name: Permission pre-check | |
if: github.event.issue.pull_request != null && (startsWith(github.event.comment.body, '/pm-release') || startsWith(github.event.comment.body, '/pm-publish')) | |
outputs: | |
ref: ${{steps.prechecks.outputs.ref}} | |
eyes: ${{steps.prechecks.outputs.eyes}} | |
sha: ${{steps.prechecks.outputs.sha}} | |
runs-on: ubuntu-latest | |
steps: | |
- name: Check permissions and PR ref | |
id: prechecks | |
uses: actions/github-script@v4 | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
const reactionRes = await github.reactions.createForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
content: 'eyes' | |
}) | |
core.setOutput('eyes', reactionRes.data.id) | |
const permissionRes = await github.repos.getCollaboratorPermissionLevel( | |
{ | |
...context.repo, | |
username: context.actor | |
} | |
) | |
if (permissionRes.status !== 200) { | |
message = 'Permission check returns non-200 status: ${permissionRes.status}' | |
core.setOutput('error', message) | |
throw new Error(message) | |
} | |
const actorPermission = permissionRes.data.permission | |
if (!['admin', 'write'].includes(actorPermission)) { | |
message = '👋 __' + context.actor + '__, seems as if you have not admin/write permission to run /pm-* commands, permissions: ${actorPermission}' | |
core.setOutput('error', message) | |
throw new Error(message) | |
} | |
pr = await github.pulls.get( | |
{ | |
...context.repo, | |
pull_number: context.issue.number | |
} | |
) | |
if (pr.status !== 200) { | |
message = 'Could not retrieve PR info: ${permissionRes.status}' | |
core.setOutput('error', message) | |
throw new Error(message) | |
} | |
core.setOutput('ref', pr.data.head.ref) | |
core.setOutput('sha', pr.data.head.sha) | |
- name: Pre-Check-Failed | |
id: precheck-failed | |
if: failure() | |
uses: actions/github-script@v4 | |
env: | |
message: ${{steps.prechecks.outputs.error}} | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
const log_url = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` | |
const { message } = process.env; | |
// check if message is null or empty | |
if (!message || message.length === 0) { | |
message = 'Unknown error, [check logs](' + log_url + ') for more details.' | |
} | |
github.issues.createComment({ | |
...context.repo, | |
issue_number: context.issue.number, | |
body: message | |
}) | |
await github.reactions.createForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
content: '-1' | |
}) | |
await github.reactions.deleteForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
reaction_id: ${{steps.prechecks.outputs.eyes}} | |
}) | |
act-on-pm-release-request: | |
name: "/pm-release" | |
if: startsWith(github.event.comment.body, '/pm-release') | |
needs: [prechecks] | |
runs-on: ubuntu-latest | |
steps: | |
- name: Validating parameters | |
id: validate_params | |
env: | |
REF: ${{ needs.prechecks.outputs.ref }} | |
comment: ${{ github.event.comment.body }} | |
SHA: ${{ needs.prechecks.outputs.sha }} | |
uses: actions/github-script@v4 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const { REF, comment, SHA } = process.env; | |
// check if comment starts with '/pm-release' and is only followed by whitespaces | |
const regexCommandWithoutParameters = /^\/pm-release\s*$/ | |
// check if comment starts with '/pm-release' and is followed by release notes in double quotes | |
const regexCommandWithReleaseNotes = /^\/pm-release\s+"([^"]*)"\s*$/ | |
// check if comment starts with '/pm-release' and is followed by a release name with only alphanumeric characters and '-' and '_' | |
const regexCommandWithReleaseName = /^\/pm-release\s+([a-zA-Z0-9-_]*)\s*$/ | |
// check if comment starts with '/pm-release', is followed by a release name with only alpha numeric characters and '-' and "_" and the followed by whitespaces and then valid release notes in double quotes | |
const regexCommandWithRelaseNameAndNotes = /^\/pm-release\s+([a-zA-Z0-9-_]+)\s*"([^"]*)"\s*$/ | |
RELEASE_NAME = REF | |
RELEASE_NOTES = "### New release from " + RELEASE_NAME | |
// check which of the four regexes above matches the comment, override RELASE_NAME and RELEASE_NOTES if present, error if none of the above matches | |
if (regexCommandWithoutParameters.test(comment)) { | |
core.info("Command without parameters") | |
} else if (regexCommandWithReleaseNotes.test(comment)) { | |
RELEASE_NOTES = comment.match(regexCommandWithReleaseNotes)[1] | |
core.info("Command with release notes: " + RELEASE_NOTES) | |
} else if (regexCommandWithReleaseName.test(comment)) { | |
RELEASE_NAME = comment.match(regexCommandWithReleaseName)[1] | |
core.info("Command with release name: " + RELEASE_NAME) | |
} else if (regexCommandWithRelaseNameAndNotes.test(comment)) { | |
RELEASE_NAME = comment.match(regexCommandWithRelaseNameAndNotes)[1] | |
RELEASE_NOTES = comment.match(regexCommandWithRelaseNameAndNotes)[2] | |
core.info("Command with release name and notes: " + RELEASE_NAME + " " + RELEASE_NOTES) | |
} else { | |
message = 'Invalid command, please use \`/pm-release "<release notes>"\` or \`/pm-release <release name>\` or \`/pm-release <release name> "<release notes>"\`' | |
core.setOutput('error', message) | |
throw new Error(message) | |
} | |
core.setOutput('RELEASE_NAME', RELEASE_NAME) | |
core.setOutput('RELEASE_NOTES', RELEASE_NOTES) | |
const log_url = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` | |
const commentBody = `\ | |
👋 __${context.actor}__, creating Postman release __${RELEASE_NAME}__ for Git branch __${REF}__ and tagging it ... | |
You can watch the progress [here](${log_url}). | |
`; | |
await github.issues.createComment({ | |
...context.repo, | |
issue_number: context.issue.number, | |
body: commentBody | |
}) | |
// set commit status to pending | |
await github.repos.createCommitStatus({ | |
...context.repo, | |
context: '/pm-release', | |
sha: SHA, | |
state: 'pending', | |
description: 'Creating Postman release ' + RELEASE_NAME + ' ...', | |
target_url: log_url | |
}) | |
- name: Checkout | |
uses: actions/checkout@v2 | |
with: | |
ref: ${{ needs.prechecks.outputs.ref }} | |
- name: Install Postman CLI | |
run: | | |
curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh | |
- name: Login to Postman CLI | |
run: postman login --with-api-key ${{ secrets.POSTMAN_API_KEY }} | |
- name: Creating Postman release | |
id: create-release | |
timeout-minutes: 5 | |
env: | |
RELEASE_NAME: ${{ steps.validate_params.outputs.RELEASE_NAME }} | |
RELEASE_NOTES: ${{ steps.validate_params.outputs.RELEASE_NOTES }} | |
run: | | |
export POSTMAN_API_ID=$(grep 'id =' .postman/api | cut -d' ' -f3) | |
postman api publish "${POSTMAN_API_ID}" --name "${RELEASE_NAME}" --api-definition "postman/schemas" --release-notes "${RELEASE_NOTES}" --collections postman/collections/*.json | tee publish_output.txt | |
version_link=$(grep -o 'https://go.postman.co/apis/[^"]*' publish_output.txt) | |
if [ -z "$version_link" ]; then | |
echo "Failed to extract version link from publish output" | |
exit 1 | |
fi | |
echo "version_link=$version_link" >> $GITHUB_OUTPUT | |
- name: Create tag | |
id: create-tag | |
env: | |
RELEASE_NAME: ${{ steps.validate_params.outputs.RELEASE_NAME }} | |
RELEASE_NOTES: ${{ steps.validate_params.outputs.RELEASE_NOTES }} | |
version_link: ${{ steps.create-release.outputs.version_link }} | |
if: success() | |
run: | | |
git config --local user.email "[email protected]" | |
git config --local user.name "Postman Release Manager" | |
export RELEASE_TAG="postman-releases/${RELEASE_NAME}" | |
export RELEASE_MESSAGE="Postman release ${RELEASE_NAME}"$'\n\n'"Release-Link: ${version_link}" | |
git tag -a "$RELEASE_TAG" -m "$RELEASE_MESSAGE" | |
git push origin "$RELEASE_TAG" | |
export GITHUB_TAG_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${RELEASE_TAG}" | |
echo "GITHUB_TAG_URL=${GITHUB_TAG_URL}" >> $GITHUB_OUTPUT | |
shell: bash | |
- name: Create release succeeded | |
id: pm-release-succeeded | |
if: success() | |
uses: actions/github-script@v4 | |
env: | |
RELEASE_NAME: ${{ steps.validate_params.outputs.RELEASE_NAME }} | |
RELEASE_NOTES: ${{ steps.validate_params.outputs.RELEASE_NOTES }} | |
SHA: ${{ needs.prechecks.outputs.sha }} | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const { RELEASE_NAME, RELEASE_NOTES, SHA } = process.env; | |
const log_url = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` | |
// set commit status to success | |
await github.repos.createCommitStatus({ | |
...context.repo, | |
context: '/pm-release', | |
sha: SHA, | |
state: 'success', | |
description: 'Postman release ' + RELEASE_NAME + ' created successfully', | |
target_url: log_url | |
}) | |
const commentBody = `\ | |
### Postman release created successfully :tada: | |
* :seedling: __Release-Name__: ${RELEASE_NAME} | |
* :link: [__Version-Link__](${{ steps.create-release.outputs.version_link }}) | |
* :label: [__Git-Tag__](${{ steps.create-tag.outputs.GITHUB_TAG_URL }}) | |
<details> | |
<summary>📖 Release notes:</summary> | |
${RELEASE_NOTES} | |
</details> | |
`; | |
github.issues.createComment({ | |
...context.repo, | |
issue_number: ${{ github.event.issue.number }}, | |
body: commentBody | |
}); | |
await github.reactions.createForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
content: '+1' | |
}) | |
await github.reactions.deleteForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
reaction_id: ${{needs.prechecks.outputs.eyes}} | |
}) | |
- name: /pm-release failed | |
id: pm-release-failed | |
if: cancelled() || failure() | |
uses: actions/github-script@v4 | |
env: | |
REF: ${{ needs.prechecks.outputs.ref }} | |
message: ${{steps.validate_params.outputs.error}} | |
SHA: ${{ needs.prechecks.outputs.sha }} | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const { REF, message, SHA } = process.env; | |
const log_url = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` | |
if (message === null || message === '') { | |
errorMessage = `Creating Postman release failed for branch __${REF}__ :cry:. [View error logs](${log_url}).` | |
} else { | |
errorMessage = message | |
} | |
github.issues.createComment({ | |
...context.repo, | |
issue_number: ${{ github.event.issue.number }}, | |
body: errorMessage | |
}) | |
// set commit status to failure | |
await github.repos.createCommitStatus({ | |
...context.repo, | |
context: '/pm-release', | |
sha: SHA, | |
state: 'failure', | |
description: 'Creating Postman release failed', | |
target_url: log_url | |
}) | |
await github.reactions.createForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
content: '-1' | |
}) | |
await github.reactions.deleteForIssueComment({ | |
...context.repo, | |
comment_id: ${{github.event.comment.id}}, | |
reaction_id: ${{needs.prechecks.outputs.eyes}} | |
}) | |
Author
jonico
commented
Jun 20, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment