diff --git a/.github/workflows/approveops.yml b/.github/workflows/approveops.yml deleted file mode 100644 index a0d5412..0000000 --- a/.github/workflows/approveops.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: ApproveOps -on: - issue_comment: - types: [created, edited] - -env: - team-name: 'approver-team' - -jobs: - approveops: - runs-on: ubuntu-latest - if: contains(github.event.comment.body, '/run-migration') - # optional - if we want to use the output to determine if we run the migration job or not - outputs: - approved: ${{ steps.check-approval.outputs.approved }} - - steps: - - uses: tibdex/github-app-token@v1 - id: get_installation_token - with: - app-id: 170284 - private_key: ${{ secrets.PRIVATE_KEY }} - - # if you don't use a GitHub app, remove the ${{ steps.get_installation_token.outputs.token }} below and create your own PAT secret - - id: check-approval - name: check if there is an approve command from authorized party - run: | - users=$(curl -sLX GET 'https://api.github.com/orgs/${{ github.repository_owner }}/teams/${{ env.team-name }}/members' \ - --header "Accept: application/vnd.github.v3+json" \ - --header "Authorization: Bearer ${{ steps.get_installation_token.outputs.token }}" | jq -c '.[].login') - approveCommand="/approve" - comments=$(curl -sLX GET '${{ github.event.comment.issue_url }}/comments' \ - --header "Accept: application/vnd.github.v3+json" \ - --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}") - authorized=false - for comment in $(echo $comments | jq -r '.[] | @base64'); do - body=$(echo $comment | base64 --decode | jq -r '.body') - actor=$(echo $comment | base64 --decode | jq -r '.user.login') - if [[ $body == *"$approveCommand"* ]]; then - echo "Approve command found..." - echo $users | grep -q $actor && echo "Found $actor in users" && authorized=true || echo "Not found $actor in users" - else - echo "Approve command not found..." - fi - done - if $authorized; then - echo "Authorized" - echo "::set-output name=approved::true" - else - echo "Not authorized" - echo "::set-output name=approved::false" - echo "::error title=Not Approved::There is no /approve command in the comments from someone in the ${{ github.repository_owner }}/${{ env.team-name }} team" - fi - - if: ${{ steps.check-approval.outputs.approved == 'false' }} - name: Create completed comment - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ steps.get_installation_token.outputs.token }} # or ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - body: | - Hey, @${{ github.event.comment.user.login }}! - :cry: No one approved your run yet! Have someone from the @${{ github.repository_owner }}/${{ env.team-name }} run `/approve` and then try your command again - :no_entry_sign: :no_entry: Marking the workflow run as failed - - if: ${{ steps.check-approval.outputs.approved == 'true' }} - name: Create completed comment - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ steps.get_installation_token.outputs.token }} # or ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - body: | - Hey, @${{ github.event.comment.user.login }}! - :tada: You were able to run the migration because someone approved!! :tada: - - # optional - if we just want the action run stop right here - # - if: ${{ steps.check-approval.outputs.approved == 'false' }} - # name: exit if not approved - # run: exit 1 - - migration: - runs-on: ubuntu-latest - needs: approveops - # optional - if we want to use the output to determine if we run the migration job or not - if: ${{ needs.approveops.outputs.approved == 'true' }} - - steps: - - run: echo "run migration!" diff --git a/README.md b/README.md index bac9c17..edd5fc5 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,58 @@ See the following guide on this action: https://josh-ops.com/posts/github-approv ## Usage ```yml -- name: ApproveOps - Approvals in IssueOps - uses: joshjohanning/approveops@v1 - id: check-approval - with: - app-id: 170284 # The GitHub App ID; ie: 170284 - app-private-key: ${{ secrets.PRIVATE_KEY }} # Private key for the GitHub App that is installed on the repo; e.g.: ${{ secrets.PRIVATE_KEY }} - team-name: approver-team # The name of the team in GitHub to check for the approval command; e.g.: approver-team - fail-if-approval-not-found: false # Fail the action (show the action run as red) if the command is not found in the comments from someone in the approver team" - post-successful-approval-comment: true # Boolean whether to post successful approval comment - successful-approval-comment: ':tada: You were able to run the workflow because someone left an approval in the comments!! :tada:' # Comment to post if there is an approval is found +name: ApproveOps +on: + issue_comment: + types: [created] + +env: + approver_team_name: 'approver-team' + approval_command: '/approve' + +jobs: + approveops: + runs-on: ubuntu-latest + if: contains(github.event.comment.body, '/do-stuff') + + steps: + # get the app's installation token + - uses: tibdex/github-app-token@v1 + id: get_installation_token + with: + app_id: 170284 + private_key: ${{ secrets.PRIVATE_KEY }} + + - name: ApproveOps - Approvals in IssueOps + uses: joshjohanning/approveops@v2 + id: check-approval + with: + token: ${{ steps.get_installation_token.outputs.token }} # use a github app token or a PAT + approve-command: '${{ env.approval_command }}' # Optional, defaults to '/approve', the command to look for in the comments + team-name: ${{ env.approver_team_name }} # The name of the team in GitHub to check for the approval command; e.g.: approver-team + fail-if-approval-not-found: false # Optional, defaults to true, fail the action (show the action run as red) if the command is not found in the comments from someone in the approver team" + post-successful-approval-comment: true # Optional, defaults to true, whether to post successful approval comment + successful-approval-comment: ':tada: You were able to run the workflow because someone left an approval in the comments!! :tada:' # Optional, comment to post if an approval is found ``` ## Prerequisites 1. Create a GitHub team and add at least one member -1. You will need a Github App with the following permissions: - - **read-only** on `Organization / Members` to list the members of the team - - **read & write** on `Repository / Issues` to create the comment -1. Generate a `PRIVATE_KEY` for the GitHub app and store it as a repo or organizational secret -1. Capture the `APP ID` to use as an input for this action +2. Authentication options: + - GitHub App + - If you are using a GitHub Github App, it will need following permissions: + - **read & write** on `Repository / Issues` to create the comment + - **read-only** on `Organization / Members` to list the members of the team + - Generate a `PRIVATE_KEY` for the GitHub app and store it as a repo or organizational secret + - Note the `APP ID` to use as an input for an action like `tibdex/github-app-token@v1` + - Classic PAT + - If you are using a classic PAT, it will need the following scopes: + - `repo` - to create the comment + - `read:org` - to list the members of the team + - Fine-grained PAT + - If you are using a fine-grained PAT, it will need following permissions (same as GitHub App): + - **read & write** on `Repository / Issues` to create the comment + - **read-only** on `Organization / Members` to list the members of the team See the following guide on creating a GitHub app: https://josh-ops.com/posts/github-apps/ @@ -34,6 +66,24 @@ Notes: - A Personal Access Token (PAT) is not used since we want the comment to show as from a bot - The `github.token` is not used since the token can't provide hyperlinks for @ mentions since it doesn't have the scope for org teams, only repository data +## Breaking Changes + +### v1 to v2 + +Extracting the logic for generating a GitHub App's installation token so that you can either use an alternative action or method to retrieve the token or to be able use a GitHub PAT instead. + +Added/removed the following inputs: + +| Input | Action | Required | Note | +|-------------------|---------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `token` | Added | Yes | GitHub App installation token or PAT that has access to read+write comments and list the org team's membership; ie `${{ steps.get_installation_token.outputs.token }}` | +| `approve-command` | Added | No | Optional, defaults to `/approve`, the command to look for in the comments | +| `app-id` | Removed | Yes | The app ID for a GitHub App ie `170284` | +| `app-private-key` | Removed | Yes | The private key for a GitHub App, ie: `${{ secrets.APP_PRIVATE_KEY }}` | + +Removed the following dependency: +- `tibdex/github-app-token@v1` + ## Screenshots ![approveops](https://user-images.githubusercontent.com/19912012/154545687-8d64a775-eec2-4ec7-90dc-901b2d6d39a5.png) diff --git a/action.yml b/action.yml index 31cd65a..d56bfa9 100644 --- a/action.yml +++ b/action.yml @@ -6,14 +6,15 @@ branding: color: "blue" inputs: + approve-command: + description: "The command to look for in the comments; e.g.: /approve" + required: true + default: '/approve' team-name: description: "The name of the team in GitHub to check for the approval command; e.g.: approver-team" required: true - app-private-key: - description: "Private key for the GitHub App that is installed on the repo; e.g.: secrets.PRIVATE_KEY" - required: true - app-id: - description: "The GitHub App ID; ie: 170284" + token: + description: "GitHub App installation token or PAT that has access to read the comments and check the org team's membership" required: true fail-if-approval-not-found: description: "Fail the action (i.e. show the action run as red) if the command is not found in the comments from someone in the approver team" @@ -24,7 +25,7 @@ inputs: required: true default: 'true' successful-approval-comment: - description: "Comment to post if there is an approval is found" + description: "Comment to post if an approval is found" required: true default: ":tada: You were able to run the workflow because someone left an approval in the comments!! :tada:" @@ -32,29 +33,21 @@ outputs: approved: description: "Returns 'true' if the approval command was found in a comment from someone in the approver team, otherwise 'false'" value: ${{ steps.check-approval.outputs.approved }} - + runs: using: "composite" steps: - - uses: tibdex/github-app-token@v1 - id: get_installation_token - with: - app_id: ${{ inputs.app-id }} - private_key: ${{ inputs.app-private-key }} - - id: check-approval name: check if there is an approve command from authorized party + env: + GH_TOKEN: ${{ inputs.token }} shell: bash run: | - # "checking for a /approve command in the comments from someone in the approver team" - users=$(curl -sLX GET 'https://api.github.com/orgs/${{ github.repository_owner }}/teams/${{ inputs.team-name }}/members' \ - --header "Accept: application/vnd.github.v3+json" \ - --header "Authorization: Bearer ${{ steps.get_installation_token.outputs.token }}" | jq -c '.[].login') - approveCommand="/approve" - comments=$(curl -sLX GET '${{ github.event.comment.issue_url }}/comments?&per_page=100' \ - --header "Accept: application/vnd.github.v3+json" \ - --header "Authorization: Bearer ${{ steps.get_installation_token.outputs.token }}") + # "checking for a ${{ inputs.approve-command }} command in the comments from someone in the approver team" + users=$(gh api --paginate '/orgs/${{ github.repository_owner }}/teams/${{ inputs.team-name }}/members' | jq -c '.[].login') + approveCommand="${{ inputs.approve-command }}" authorized=false + comments=$(gh api --paginate '${{ github.event.comment.issue_url }}/comments') for comment in $(echo $comments | jq -r '.[] | @base64'); do body=$(echo $comment | base64 --decode | jq -r '.body' | tr -d ' ' | tr -d '\r\n') actor=$(echo $comment | base64 --decode | jq -r '.user.login') @@ -69,42 +62,44 @@ runs: done if $authorized; then echo "Approval authorized by $actor" - echo "::set-output name=approved::true" + echo "approved=true" >> $GITHUB_OUTPUT else echo "Approval not found or not authorized" - echo "::set-output name=approved::false" + echo "approved=false" >> $GITHUB_OUTPUT if !(${{ inputs.fail-if-approval-not-found }}); then - echo "::notice title=Not Approved::There is no /approve command in the comments from someone in the ${{ github.repository_owner }}/${{ inputs.team-name }} team" + echo "::notice title=Not Approved::There is no ${{ inputs.approve-command }} command in the comments from someone in the ${{ github.repository_owner }}/${{ inputs.team-name }} team" fi fi - if: ${{ steps.check-approval.outputs.approved == 'false' && inputs.fail-if-approval-not-found == 'true' }} name: Create completed comment - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2.1.1 with: - token: ${{ steps.get_installation_token.outputs.token }} + token: ${{ inputs.token }} issue-number: ${{ github.event.issue.number }} body: | Hey, @${{ github.event.comment.user.login }}! - :cry: No one approved your run yet! Have someone from the @${{ github.repository_owner }}/${{ inputs.team-name }} team run `/approve` and then try your command again - :no_entry_sign: :no_entry: Marking the [workflow run](${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}) as failed + :cry: No one approved your run yet! Have someone from the @${{ github.repository_owner }}/${{ inputs.team-name }} team run `${{ inputs.approve-command }}` and then try your command again + + _:no_entry_sign: :no_entry: Marking the [workflow run](${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}) as failed_ - if: ${{ steps.check-approval.outputs.approved == 'false' && inputs.fail-if-approval-not-found == 'false' }} name: Create completed comment - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2.1.1 with: - token: ${{ steps.get_installation_token.outputs.token }} + token: ${{ inputs.token }} issue-number: ${{ github.event.issue.number }} body: | Hey, @${{ github.event.comment.user.login }}! - :cry: No one approved your run yet! Have someone from the @${{ github.repository_owner }}/${{ inputs.team-name }} team run `/approve` and then try your command again - :warning: :pause_button: The [workflow run](${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}) wasn't marked as failed + :cry: No one approved your run yet! Have someone from the @${{ github.repository_owner }}/${{ inputs.team-name }} team run `${{ inputs.approve-command }}` and then try your command again + + _:warning: :pause_button: See [workflow run](${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}) for reference_ - if: ${{ steps.check-approval.outputs.approved == 'true' && inputs.post-successful-approval-comment == 'true' }} name: Create completed comment - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2.1.1 with: - token: ${{ steps.get_installation_token.outputs.token }} + token: ${{ inputs.token }} issue-number: ${{ github.event.issue.number }} body: | Hey, @${{ github.event.comment.user.login }}! @@ -113,7 +108,7 @@ runs: # if specified, exit with an error if approval is not found - name: exit and fail workflow if not approved if: ${{ inputs.fail-if-approval-not-found == 'true' && steps.check-approval.outputs.approved == 'false' }} - uses: actions/github-script@v6 + uses: actions/github-script@v6.4.0 with: script: | - core.setFailed("There is no /approve command in the comments from someone in the ${{ github.repository_owner }}/${{ inputs.team-name }} team"); + core.setFailed("There is no ${{ inputs.approve-command }} command in the comments from someone in the ${{ github.repository_owner }}/${{ inputs.team-name }} team");