diff --git a/README.md b/README.md index b57c702..c75538a 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ GitHub Action for creating a GitHub App installation access token. In order to use this action, you need to: 1. [Register new GitHub App](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app). -2. [Store the App's ID or Client ID in your repository environment variables](https://docs.github.com/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_ID`). -3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/security-guides/encrypted-secrets?tool=webui#creating-encrypted-secrets-for-a-repository) (example: `PRIVATE_KEY`). +2. [Store the App's Client ID in your repository environment variables](https://docs.github.com/actions/how-tos/write-workflows/choose-what-workflows-do/use-variables#defining-configuration-variables-for-multiple-workflows) (example: `GITHUB_APP_CLIENT_ID`). +3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets?tool=webui#creating-secrets-for-a-repository) (example: `GITHUB_APP_PRIVATE_KEY`). > [!IMPORTANT] > An installation access token expires after 1 hour. Please [see this comment](https://github.com/actions/create-github-app-token/issues/121#issuecomment-2043214796) for alternative approaches if you have long-running processes. @@ -31,8 +31,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} - uses: ./actions/staging-tests with: token: ${{ steps.app-token.outputs.token }} @@ -51,8 +51,8 @@ jobs: id: app-token with: # required - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} - uses: actions/checkout@v6 with: token: ${{ steps.app-token.outputs.token }} @@ -77,8 +77,8 @@ jobs: id: app-token with: # required - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" @@ -102,8 +102,8 @@ jobs: id: app-token with: # required - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" @@ -138,8 +138,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: peter-evans/create-or-update-comment@v4 with: @@ -160,8 +160,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} repositories: | repo1 @@ -185,8 +185,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} owner: another-owner - uses: peter-evans/create-or-update-comment@v4 with: @@ -210,8 +210,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} permission-issues: write - uses: peter-evans/create-or-update-comment@v4 @@ -252,8 +252,8 @@ jobs: - uses: actions/create-github-app-token@v3 id: app-token with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} owner: ${{ matrix.owners-and-repos.owner }} repositories: ${{ join(matrix.owners-and-repos.repos) }} - uses: octokit/request-action@v2.x @@ -281,7 +281,7 @@ jobs: id: create_token uses: actions/create-github-app-token@v3 with: - app-id: ${{ vars.GHES_APP_ID }} + client-id: ${{ vars.GHES_APP_CLIENT_ID }} private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }} owner: ${{ vars.GHES_INSTALLATION_ORG }} github-api-url: ${{ vars.GITHUB_API_URL }} @@ -310,15 +310,18 @@ If you set `HTTP_PROXY` or `HTTPS_PROXY`, also set `NODE_USE_ENV_PROXY: "1"` on NO_PROXY: github.example.com NODE_USE_ENV_PROXY: "1" with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} + private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} ``` ## Inputs -### `app-id` +### `client-id` or `app-id` -**Required:** GitHub App ID. +**Required:** GitHub App Client ID. + +> [!NOTE] +> The legacy `app-id` input is also accepted, but `client-id` is recommended. ### `private-key` @@ -331,14 +334,14 @@ steps: - name: Decode the GitHub App Private Key id: decode run: | - private_key=$(echo "${{ secrets.PRIVATE_KEY }}" | base64 -d | awk 'BEGIN {ORS="\\n"} {print}' | head -c -2) &> /dev/null + private_key=$(echo "${{ secrets.GITHUB_APP_PRIVATE_KEY }}" | base64 -d | awk 'BEGIN {ORS="\\n"} {print}' | head -c -2) &> /dev/null echo "::add-mask::$private_key" echo "private-key=$private_key" >> "$GITHUB_OUTPUT" - name: Generate GitHub App Token id: app-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ vars.APP_ID }} + client-id: ${{ vars.GITHUB_APP_CLIENT_ID }} private-key: ${{ steps.decode.outputs.private-key }} ``` diff --git a/action.yml b/action.yml index ba4e915..e5b2ddd 100644 --- a/action.yml +++ b/action.yml @@ -5,9 +5,13 @@ branding: icon: "lock" color: "gray-dark" inputs: + client-id: + description: "GitHub App Client ID" + required: false app-id: description: "GitHub App ID" - required: true + required: false + deprecationMessage: "Use 'client-id' instead." private-key: description: "GitHub App private key" required: true diff --git a/dist/main.cjs b/dist/main.cjs index e2674f2..683df24 100644 --- a/dist/main.cjs +++ b/dist/main.cjs @@ -23153,7 +23153,7 @@ async function pRetry(input, options = {}) { } // lib/main.js -async function main(appId, privateKey, owner, repositories, permissions, core, createAppAuth2, request2, skipTokenRevoke) { +async function main(clientId, privateKey, owner, repositories, permissions, core, createAppAuth2, request2, skipTokenRevoke) { let parsedOwner = ""; let parsedRepositoryNames = []; if (!owner && repositories.length === 0) { @@ -23188,7 +23188,7 @@ async function main(appId, privateKey, owner, repositories, permissions, core, c ); } const auth5 = createAppAuth2({ - appId, + appId: clientId, privateKey, request: request2 }); @@ -23307,14 +23307,17 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) { } async function run() { ensureNativeProxySupport(); - const appId = getInput("app-id"); + const clientId = getInput("client-id") || getInput("app-id"); + if (!clientId) { + throw new Error("Either 'client-id' or 'app-id' input must be set"); + } const privateKey = getInput("private-key"); const owner = getInput("owner"); const repositories = getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== ""); const skipTokenRevoke = getBooleanInput("skip-token-revoke"); const permissions = getPermissionsFromInputs(process.env); return main( - appId, + clientId, privateKey, owner, repositories, diff --git a/lib/main.js b/lib/main.js index 9ae9d78..8f5ef9a 100644 --- a/lib/main.js +++ b/lib/main.js @@ -2,7 +2,7 @@ import pRetry from "p-retry"; // @ts-check /** - * @param {string} appId + * @param {string} clientId * @param {string} privateKey * @param {string} owner * @param {string[]} repositories @@ -13,7 +13,7 @@ import pRetry from "p-retry"; * @param {boolean} skipTokenRevoke */ export async function main( - appId, + clientId, privateKey, owner, repositories, @@ -70,7 +70,7 @@ export async function main( } const auth = createAppAuth({ - appId, + appId: clientId, privateKey, request, }); diff --git a/main.js b/main.js index d8ebee4..b685126 100644 --- a/main.js +++ b/main.js @@ -18,7 +18,10 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) { async function run() { ensureNativeProxySupport(); - const appId = core.getInput("app-id"); + const clientId = core.getInput("client-id") || core.getInput("app-id"); + if (!clientId) { + throw new Error("Either 'client-id' or 'app-id' input must be set"); + } const privateKey = core.getInput("private-key"); const owner = core.getInput("owner"); const repositories = core @@ -32,7 +35,7 @@ async function run() { const permissions = getPermissionsFromInputs(process.env); return main( - appId, + clientId, privateKey, owner, repositories, diff --git a/tests/README.md b/tests/README.md index 2eeeb7d..d3ce05c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -32,5 +32,5 @@ node --test --test-update-snapshots tests/index.js We have tests both for the `main.js` and `post.js` scripts. -- If you do not expect an error, take [main-token-permissions-set.test.js](tests/main-token-permissions-set.test.js) as a starting point. -- If your test has an expected error, take [main-missing-app-id.test.js](tests/main-missing-app-id.test.js) as a starting point. +- If you do not expect an error, take [main-token-permissions-set.test.js](main-token-permissions-set.test.js) as a starting point. +- If your test has an expected error, take [main-missing-client-and-app-id.test.js](main-missing-client-and-app-id.test.js) as a starting point. diff --git a/tests/index.js.snapshot b/tests/index.js.snapshot index 06cac80..d5d5f9b 100644 --- a/tests/index.js.snapshot +++ b/tests/index.js.snapshot @@ -1,3 +1,41 @@ +exports[`action-deprecated-inputs.test.js > stdout 1`] = ` +app-id — Use 'client-id' instead. +`; + +exports[`main-app-id-fallback.test.js > stdout 1`] = ` +Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token). +::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a + +::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a + +::set-output name=installation-id::123456 + +::set-output name=app-slug::github-actions +::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a +::save-state name=expiresAt::2016-07-11T22:14:10Z +--- REQUESTS --- +GET /repos/actions/create-github-app-token/installation +POST /app/installations/123456/access_tokens +{"repositories":["create-github-app-token"]} +`; + +exports[`main-client-id-precedence.test.js > stdout 1`] = ` +Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token). +::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a + +::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a + +::set-output name=installation-id::123456 + +::set-output name=app-slug::github-actions +::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a +::save-state name=expiresAt::2016-07-11T22:14:10Z +--- REQUESTS --- +GET /repos/actions/create-github-app-token/installation +POST /app/installations/123456/access_tokens +{"repositories":["create-github-app-token"]} +`; + exports[`main-custom-github-api-url.test.js > stdout 1`] = ` Inputs 'owner' and 'repositories' are set. Creating token for the following repositories: @@ -17,6 +55,14 @@ POST /api/v3/app/installations/123456/access_tokens {"repositories":["create-github-app-token"]} `; +exports[`main-missing-client-and-app-id.test.js > stderr 1`] = ` +Either 'client-id' or 'app-id' input must be set +`; + +exports[`main-missing-client-and-app-id.test.js > stdout 1`] = ` +::error::Either 'client-id' or 'app-id' input must be set +`; + exports[`main-missing-owner.test.js > stderr 1`] = ` GITHUB_REPOSITORY_OWNER missing, must be set to '' `; diff --git a/tests/main-app-id-fallback.test.js b/tests/main-app-id-fallback.test.js new file mode 100644 index 0000000..21c6852 --- /dev/null +++ b/tests/main-app-id-fallback.test.js @@ -0,0 +1,11 @@ +import { DEFAULT_ENV, test } from "./main.js"; + +// Verify `main` falls back to `app-id` when `client-id` is not set +await test( + () => {}, + { + ...DEFAULT_ENV, + "INPUT_CLIENT-ID": "", + "INPUT_APP-ID": "123456", + } +); diff --git a/tests/main-client-id-precedence.test.js b/tests/main-client-id-precedence.test.js new file mode 100644 index 0000000..f3cf78a --- /dev/null +++ b/tests/main-client-id-precedence.test.js @@ -0,0 +1,11 @@ +import { DEFAULT_ENV, test } from "./main.js"; + +// Verify `client-id` takes precedence when both `client-id` and `app-id` are set +await test( + () => {}, + { + ...DEFAULT_ENV, + "INPUT_CLIENT-ID": "Iv1.0123456789abcdef", + "INPUT_APP-ID": "123456", + } +); diff --git a/tests/main-missing-client-and-app-id.test.js b/tests/main-missing-client-and-app-id.test.js new file mode 100644 index 0000000..06e2b35 --- /dev/null +++ b/tests/main-missing-client-and-app-id.test.js @@ -0,0 +1,20 @@ +import { DEFAULT_ENV } from "./main.js"; + +for (const [key, value] of Object.entries({ + ...DEFAULT_ENV, + "INPUT_CLIENT-ID": "", + "INPUT_APP-ID": "", +})) { + process.env[key] = value; +} + +// Log only the error message, not the full stack trace, because the stack +// trace contains environment-specific paths and ANSI codes that differ +// between local and CI environments. +const _error = console.error; +console.error = (err) => _error(err?.message ?? err); + +// Verify `main` exits with an error when neither `client-id` nor `app-id` is set. +const { default: promise } = await import("../main.js"); +await promise; +process.exitCode = 0; diff --git a/tests/main.js b/tests/main.js index 5466529..a76c39a 100644 --- a/tests/main.js +++ b/tests/main.js @@ -9,7 +9,7 @@ export const DEFAULT_ENV = { // https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs "INPUT_GITHUB-API-URL": "https://api.github.com", "INPUT_SKIP-TOKEN-REVOKE": "false", - "INPUT_APP-ID": "123456", + "INPUT_CLIENT-ID": "Iv1.0123456789abcdef", // This key is invalidated. It’s from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327. "INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA280nfuUM9w00Ib9E2rvZJ6Qu3Ua3IqR34ZlK53vn/Iobn2EL