Skip to content

Instantly share code, notes, and snippets.

@jonico
Created June 20, 2023 13:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonico/105087633c507a75554967ebe37262e2 to your computer and use it in GitHub Desktop.
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"]
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 "no-reply@postman.com"
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}}
})
@jonico
Copy link
Author

jonico commented Jun 20, 2023

image

@jonico
Copy link
Author

jonico commented Jul 26, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment