diff --git a/README.md b/README.md index b57c702..a8c23b6 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,10 @@ 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/learn-github-actions/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/security-guides/encrypted-secrets?tool=webui#creating-encrypted-secrets-for-a-repository) (example: `GITHUB_APP_PRIVATE_KEY`). + +Pass the App's Client ID using the `client-id` input. The legacy `app-id` input remains available for compatibility, but is deprecated. > [!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 +33,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 +53,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 +79,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 +104,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 +140,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 +162,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 +187,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 +212,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 +254,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 +283,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 +312,24 @@ 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 +### `client-id` + +**Optional:** GitHub App Client ID. This is the recommended input. + ### `app-id` -**Required:** GitHub App ID. +**Optional:** GitHub App ID. + +> [!WARNING] +> `app-id` is deprecated. Use `client-id` instead. + +You must set either `client-id` or `app-id`. If both are set, `client-id` takes precedence. ### `private-key` @@ -331,14 +342,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..4222199 100644 --- a/tests/README.md +++ b/tests/README.md @@ -33,4 +33,4 @@ 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 your test has an expected error, take [main-missing-client-and-app-id.test.js](tests/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..1df786b 100644 --- a/tests/index.js.snapshot +++ b/tests/index.js.snapshot @@ -1,3 +1,24 @@ +exports[`action-deprecated-inputs.test.js > stdout 1`] = ` +app-id — Use 'client-id' instead. +`; + +exports[`main-client-id.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 +38,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-client-id.test.js b/tests/main-client-id.test.js new file mode 100644 index 0000000..78d96e7 --- /dev/null +++ b/tests/main-client-id.test.js @@ -0,0 +1,11 @@ +import { DEFAULT_ENV, test } from "./main.js"; + +// Verify `main` accepts a GitHub App client ID via the `client-id` input +await test( + () => {}, + { + ...DEFAULT_ENV, + "INPUT_CLIENT-ID": "Iv1.0123456789abcdef", + "INPUT_APP-ID": "", + } +); 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;