diff --git a/README.md b/README.md index 2bef95e..448b4f1 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ ## Limitations -Due to how GitHub handles permissions in PRs coming from forks, this action is limited to PRs based on branches. See this issue: https://github.community/t/github-actions-are-severely-limited-on-prs/18179/4 for more detail. - -I'm currently investigating a workaround via a simple bot you can easily deploy. More soon... +Due to how GitHub handles permissions in PRs coming from forks you will need to deploy an app if you want to post comments in those situations. [See below](#proxy-for-fork-based-prs). ## Usage @@ -60,6 +58,31 @@ jobs: | allow-repeats | with | A boolean flag to allow identical messages to be posted each time this action is run | no | false | | GITHUB_TOKEN | env | A valid GitHub token, can alternatively be defined in the env | maybe | | +## Proxy for Fork-based PRs + +GitHub limits `GITHUB_TOKEN` and other API access token permissions when creating a PR from a fork. This makes it impossible to add comments when your PRs are coming from forks, which is the norm for open source projects. To work around this situation I've created a simple companion app you can deploy to Cloud Run or another environment to proxy the create comment requests and escalate the token privileges. + +See this issue: https://github.community/t/github-actions-are-severely-limited-on-prs/18179/4 for more detail. + +**Example** + +```yaml +on: + pull_request: + +jobs: + pr: + runs-on: ubuntu-latest + steps: + - uses: mshick/add-pr-comment@v1 + with: + message: | + **Howdie!** + proxy-url: https://add-pr-comment-proxy-94idvmwyie-uc.a.run.app/ + proxy-secret: foobar + repo-token: ${{ secrets.GITHUB_TOKEN }} +``` + ## Features - Fast, runs in the GitHub Actions node.js runtime; no Docker pull needed. diff --git a/action.yml b/action.yml index 7537293..4c56b59 100644 --- a/action.yml +++ b/action.yml @@ -14,6 +14,12 @@ inputs: description: "Allow messages to be repeated." required: false default: "false" + proxy-url: + description: "Proxy URL for comment creation" + required: false + proxy-secret: + description: "A secret to use for authenticating against the proxy" + required: false branding: icon: message-circle color: purple diff --git a/add-pr-comment.ts b/add-pr-comment.ts index d9c08f7..16fe1ad 100644 --- a/add-pr-comment.ts +++ b/add-pr-comment.ts @@ -4,13 +4,7 @@ import {HttpClient} from '@actions/http-client' import {Endpoints, RequestHeaders, IssuesListCommentsResponseData} from '@octokit/types' type ListCommitPullsResponse = Endpoints['GET /repos/:owner/:repo/commits/:commit_sha/pulls']['response']['data'] - -interface AddPrCommentInputs { - allowRepeats: boolean - message: string - repoToken?: string - repoTokenUserLogin?: string -} +type CreateIssueCommentResponseData = Endpoints['POST /repos/:owner/:repo/issues/:issue_number/comments']['response']['data'] interface ListCommitPullsParams { repoToken: string @@ -40,6 +34,35 @@ const listCommitPulls = async (params: ListCommitPullsParams): Promise commitPullsList.length ? commitPullsList[0].number : null +interface CreateCommentProxyParams { + repoToken: string + body: string + owner: string + repo: string + issueNumber: number + proxyUrl: string + proxySecret: string +} + +const createCommentProxy = async (params: CreateCommentProxyParams): Promise => { + const {repoToken, owner, repo, issueNumber, body, proxyUrl, proxySecret} = params + + const http = new HttpClient('http-client-add-pr-comment') + + const additionalHeaders: RequestHeaders = { + authorization: `basic ${Buffer.from(proxySecret + ':').toString('base64')}`, + ['temporary-github-token']: repoToken, + } + + const response = await http.postJson( + `${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`, + {body}, + additionalHeaders, + ) + + return response.result +} + const isMessagePresent = ( message: AddPrCommentInputs['message'], comments: IssuesListCommentsResponseData, @@ -58,10 +81,21 @@ const isMessagePresent = ( }) } +interface AddPrCommentInputs { + allowRepeats: boolean + message: string + proxySecret?: string + proxyUrl?: string + repoToken?: string + repoTokenUserLogin?: string +} + const getInputs = (): AddPrCommentInputs => { return { allowRepeats: Boolean(core.getInput('allow-repeats') === 'true'), message: core.getInput('message'), + proxySecret: core.getInput('proxy-secret'), + proxyUrl: core.getInput('proxy-url'), repoToken: core.getInput('repo-token') || process.env['GITHUB_TOKEN'], repoTokenUserLogin: core.getInput('repo-token-user-login'), } @@ -69,7 +103,7 @@ const getInputs = (): AddPrCommentInputs => { const run = async (): Promise => { try { - const {allowRepeats, message, repoToken, repoTokenUserLogin} = getInputs() + const {allowRepeats, message, repoToken, repoTokenUserLogin, proxyUrl, proxySecret} = getInputs() if (!repoToken) { throw new Error('no github token provided, set one with the repo-token input or GITHUB_TOKEN env variable') @@ -125,12 +159,28 @@ const run = async (): Promise => { } if (shouldCreateComment) { - await octokit.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body: message, - }) + if (proxyUrl) { + if (!proxySecret) { + throw new Error('proxy-url defined, but proxy-secret is missing') + } + + await createCommentProxy({ + owner, + repo, + issueNumber, + body: message, + repoToken, + proxyUrl, + proxySecret, + }) + } else { + await octokit.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body: message, + }) + } core.setOutput('comment-created', 'true') } else {