From babaff4320b432cece89fd8d07209bb3f6e98fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=99=E6=BC=A0=E4=B9=8B=E5=AD=90?= <7850715+maboloshi@users.noreply.github.com> Date: Sat, 2 Mar 2024 03:18:38 +0800 Subject: [PATCH 1/2] feat(outputs): `app-slug` and `installation-id` (#105) It is convenient to use `https://api.github.com/users/$app_slug[bot]` to obtain the corresponding account ID later. Then build `Signed-off-by: $app_slug[bot] <$id+$app_slug[bot]@users.noreply.github.com>`. Currently, there is no Linux environment to build test snapshot files --- README.md | 8 ++++ action.yml | 4 ++ dist/main.cjs | 20 ++++++---- lib/main.js | 26 ++++++++----- tests/main-repo-skew.js | 4 +- ...n-get-owner-set-repo-fail-response.test.js | 3 +- ...en-get-owner-set-to-org-repo-unset.test.js | 5 ++- ...et-owner-set-to-user-fail-response.test.js | 5 ++- ...n-get-owner-set-to-user-repo-unset.test.js | 5 ++- ...n-token-get-owner-unset-repo-unset.test.js | 5 ++- tests/main.js | 5 ++- tests/snapshots/index.js.md | 36 ++++++++++++++++++ tests/snapshots/index.js.snap | Bin 1081 -> 1131 bytes 13 files changed, 98 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3d43472..2790c6d 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,14 @@ jobs: GitHub App installation access token. +### `installation-id` + +GitHub App installation ID. + +### `app-slug` + +GitHub App slug. + ## How it works The action creates an installation access token using [the `POST /app/installations/{installation_id}/access_tokens` endpoint](https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app). By default, diff --git a/action.yml b/action.yml index e150b71..1d2909e 100644 --- a/action.yml +++ b/action.yml @@ -40,6 +40,10 @@ inputs: outputs: token: description: "GitHub installation access token" + installation-id: + description: "GitHub App installation ID" + app-slug: + description: "GitHub App slug" runs: using: "node20" main: "dist/main.cjs" diff --git a/dist/main.cjs b/dist/main.cjs index 0f42fa1..a8f5b87 100644 --- a/dist/main.cjs +++ b/dist/main.cjs @@ -29923,28 +29923,30 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp privateKey: privateKey2, request: request2 }); - let authentication; + let authentication, installationId, appSlug; if (parsedRepositoryNames) { - authentication = await pRetry(() => getTokenFromRepository(request2, auth, parsedOwner, parsedRepositoryNames), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth, parsedOwner, parsedRepositoryNames), { onFailedAttempt: (error) => { core3.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` ); }, retries: 3 - }); + })); } else { - authentication = await pRetry(() => getTokenFromOwner(request2, auth, parsedOwner), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request2, auth, parsedOwner), { onFailedAttempt: (error) => { core3.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` ); }, retries: 3 - }); + })); } core3.setSecret(authentication.token); core3.setOutput("token", authentication.token); + core3.setOutput("installation-id", installationId); + core3.setOutput("app-slug", appSlug); if (!skipTokenRevoke2) { core3.saveState("token", authentication.token); core3.setOutput("expiresAt", authentication.expiresAt); @@ -29970,7 +29972,9 @@ async function getTokenFromOwner(request2, auth, parsedOwner) { type: "installation", installationId: response.data.id }); - return authentication; + const installationId = response.data.id; + const appSlug = response.data["app_slug"]; + return { authentication, installationId, appSlug }; } async function getTokenFromRepository(request2, auth, parsedOwner, parsedRepositoryNames) { const response = await request2("GET /repos/{owner}/{repo}/installation", { @@ -29985,7 +29989,9 @@ async function getTokenFromRepository(request2, auth, parsedOwner, parsedReposit installationId: response.data.id, repositoryNames: parsedRepositoryNames.split(",") }); - return authentication; + const installationId = response.data.id; + const appSlug = response.data["app_slug"]; + return { authentication, installationId, appSlug }; } // lib/request.js diff --git a/lib/main.js b/lib/main.js index b97329d..d685277 100644 --- a/lib/main.js +++ b/lib/main.js @@ -70,35 +70,36 @@ export async function main( request, }); - let authentication; + let authentication, installationId, appSlug; // If at least one repository is set, get installation ID from that repository if (parsedRepositoryNames) { - authentication = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}` ); }, retries: 3, - }); - + })); } else { // Otherwise get the installation for the owner, which can either be an organization or a user account - authentication = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner), { + ({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromOwner(request, auth, parsedOwner), { onFailedAttempt: (error) => { core.info( `Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}` ); }, retries: 3, - }); + })); } // Register the token with the runner as a secret to ensure it is masked in logs core.setSecret(authentication.token); core.setOutput("token", authentication.token); + core.setOutput("installation-id", installationId); + core.setOutput("app-slug", appSlug); // Make token accessible to post function (so we can invalidate it) if (!skipTokenRevoke) { @@ -132,7 +133,11 @@ async function getTokenFromOwner(request, auth, parsedOwner) { type: "installation", installationId: response.data.id, }); - return authentication; + + const installationId = response.data.id; + const appSlug = response.data['app_slug']; + + return { authentication, installationId, appSlug }; } async function getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames) { @@ -152,5 +157,8 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito repositoryNames: parsedRepositoryNames.split(","), }); - return authentication; -} \ No newline at end of file + const installationId = response.data.id; + const appSlug = response.data['app_slug']; + + return { authentication, installationId, appSlug }; + } \ No newline at end of file diff --git a/tests/main-repo-skew.js b/tests/main-repo-skew.js index a3554ad..e35a531 100644 --- a/tests/main-repo-skew.js +++ b/tests/main-repo-skew.js @@ -9,6 +9,7 @@ await test((mockPool) => { const owner = process.env.INPUT_OWNER const repo = process.env.INPUT_REPOSITORIES const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; install({ now: 0, toFake: ["Date"] }); @@ -44,7 +45,8 @@ await test((mockPool) => { return { statusCode: 200, data: { - id: mockInstallationId + id: mockInstallationId, + "app_slug": mockAppSlug }, responseOptions: { headers: { diff --git a/tests/main-token-get-owner-set-repo-fail-response.test.js b/tests/main-token-get-owner-set-repo-fail-response.test.js index 14c5811..f97cf26 100644 --- a/tests/main-token-get-owner-set-repo-fail-response.test.js +++ b/tests/main-token-get-owner-set-repo-fail-response.test.js @@ -7,6 +7,7 @@ await test((mockPool) => { const owner = process.env.INPUT_OWNER; const repo = process.env.INPUT_REPOSITORIES; const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; mockPool .intercept({ @@ -32,7 +33,7 @@ await test((mockPool) => { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); }); diff --git a/tests/main-token-get-owner-set-to-org-repo-unset.test.js b/tests/main-token-get-owner-set-to-org-repo-unset.test.js index 1fa0e1f..87245f7 100644 --- a/tests/main-token-get-owner-set-to-org-repo-unset.test.js +++ b/tests/main-token-get-owner-set-to-org-repo-unset.test.js @@ -5,8 +5,9 @@ await test((mockPool) => { process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER; delete process.env.INPUT_REPOSITORIES; - // Mock installation id request + // Mock installation id and app slug request const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; mockPool .intercept({ path: `/orgs/${process.env.INPUT_OWNER}/installation`, @@ -19,7 +20,7 @@ await test((mockPool) => { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); }); diff --git a/tests/main-token-get-owner-set-to-user-fail-response.test.js b/tests/main-token-get-owner-set-to-user-fail-response.test.js index e9fba9b..318b8dc 100644 --- a/tests/main-token-get-owner-set-to-user-fail-response.test.js +++ b/tests/main-token-get-owner-set-to-user-fail-response.test.js @@ -5,8 +5,9 @@ await test((mockPool) => { process.env.INPUT_OWNER = "smockle"; delete process.env.INPUT_REPOSITORIES; - // Mock installation id request + // Mock installation ID and app slug request const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; mockPool .intercept({ path: `/orgs/${process.env.INPUT_OWNER}/installation`, @@ -30,7 +31,7 @@ await test((mockPool) => { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); }); diff --git a/tests/main-token-get-owner-set-to-user-repo-unset.test.js b/tests/main-token-get-owner-set-to-user-repo-unset.test.js index bbb802c..a73c3d7 100644 --- a/tests/main-token-get-owner-set-to-user-repo-unset.test.js +++ b/tests/main-token-get-owner-set-to-user-repo-unset.test.js @@ -5,8 +5,9 @@ await test((mockPool) => { process.env.INPUT_OWNER = "smockle"; delete process.env.INPUT_REPOSITORIES; - // Mock installation id request + // Mock installation ID and app slug request const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; mockPool .intercept({ path: `/orgs/${process.env.INPUT_OWNER}/installation`, @@ -30,7 +31,7 @@ await test((mockPool) => { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); }); diff --git a/tests/main-token-get-owner-unset-repo-unset.test.js b/tests/main-token-get-owner-unset-repo-unset.test.js index ccc20dd..e284aae 100644 --- a/tests/main-token-get-owner-unset-repo-unset.test.js +++ b/tests/main-token-get-owner-unset-repo-unset.test.js @@ -5,8 +5,9 @@ await test((mockPool) => { delete process.env.INPUT_OWNER; delete process.env.INPUT_REPOSITORIES; - // Mock installation id request + // Mock installation ID and app slug request const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; mockPool .intercept({ path: `/repos/${process.env.GITHUB_REPOSITORY}/installation`, @@ -19,7 +20,7 @@ await test((mockPool) => { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); }); diff --git a/tests/main.js b/tests/main.js index eb755b7..3e52f69 100644 --- a/tests/main.js +++ b/tests/main.js @@ -54,8 +54,9 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { // Calling `auth({ type: "app" })` to obtain a JWT doesn’t make network requests, so no need to intercept. - // Mock installation id request + // Mock installation ID and app slug request const mockInstallationId = "123456"; + const mockAppSlug = "github-actions"; const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER; const repo = encodeURIComponent( (env.INPUT_REPOSITORIES ?? env.GITHUB_REPOSITORY).split(",")[0] @@ -72,7 +73,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { }) .reply( 200, - { id: mockInstallationId }, + { id: mockInstallationId, "app_slug": mockAppSlug }, { headers: { "content-type": "application/json" } } ); diff --git a/tests/snapshots/index.js.md b/tests/snapshots/index.js.md index b42248a..21918c0 100644 --- a/tests/snapshots/index.js.md +++ b/tests/snapshots/index.js.md @@ -28,6 +28,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -85,6 +89,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -101,6 +109,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -117,6 +129,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -133,6 +149,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -150,6 +170,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -166,6 +190,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -182,6 +210,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` @@ -198,6 +230,10 @@ Generated by [AVA](https://avajs.dev). ::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␊ ␊ ::set-output name=expiresAt::2016-07-11T22:14:10Z` diff --git a/tests/snapshots/index.js.snap b/tests/snapshots/index.js.snap index 46c364e9f519d9da66f3532f78903a7714b686ca..53720f3db20c1a6b28f5ab5d2b11dba78c399cea 100644 GIT binary patch literal 1131 zcmV-x1eE(hRzV(EQxUlDTjbc2r-M(Oo}ZDLT-I7n22dA&J!uV zdJ=|I(vMzTdwETNDle_QwgwUHK-3j71kSC&!wQ`?dEul&)3nV(^z_SbP}M$H5ff+< z6VW(C7c7cU2M~urGE`$MMig~3nQ=fv;6lmVLJtHOAK3Vag#;RIt*4v~m7jJW9@{8W z1LZ>Hj<9t!iYNLkZOfz&ahtifiN5U>8(OkQpnUIt7EwPM(AszOlC zZck%SsUGPnp%(XxOvSZi$B9HDOVfx!NRah6gQz0prj)(mC^Ntn3p;Rx?H(MR9zM9+ zKH0y2{K?Vj@yWyX@uwf}pP<}Z4Gq)=(Hl^uP+^LycZ~lJs#lYW9H~>~qF69-1lJa3 za?0t|G`{U<9B;@u5)U1TFa6IwNI>>*Isd#vPeCNRBb0H zQ`-J=O4}dhN}E=tj!!)}vy9y{?{$)F@tbnw{&tf}34>H3?v zl6E3kRmCM$MQ=WP`avct?{V?=c?R-+cgXv7sfTK9^j;=O)Ms*a$UaA$>d%Bz5|^rD zs}}8lN{?SikA{ZZ!yISo7?frB`QG8@+lm8(X+{djQHgfx(StoURIe?AD@2x!NOK+{ znNZKy)-L_&VDj`jF%g3#=tnTWOBxxiI;4M`7MBIPADvfblF|r&+-kZsYL1Nta zlwspHhmD`|j^{EIc6_q_|FG3zF$yxyA(pwP&GCVDbH&b**ujg~DP389cIf$`s9&B} zW8^uE(U{z*C_ail58=X` x7|o?}@~#v9%}dL3K?TaA%vAIGl$w(DDWhYF(;LBa6J=(hUZ@-h8=y%% zKB8$)BqNA>494=miF#?dUTg)2H5@MBat711Cz+UNxfT%`V?W~}iSUq$v#gIvN^!=* zI%=rZrY&v?)2eA$I9_o2Jv`kfue-RpLpw!gi(y}8xh1*g0HV2iBE zUr9z-rboIN#Uz5ybc9!ckO*W9mJNt(x2ag~0j`toNeRFtnbo``u^zGMs$0$c@p-4JeRflo_vpg*l1^jp0iq*%#0rxr{5%|m@(CCPND86f+&>qH2MQh}Vi z9@?kkAnT*uM~B)KWGr`*ehAC<<;*vUx)6y>P+aZvD_G=(Ws&G#9aF8zeUT*Lh>GRd zD$~7vCEb&_R%ckgoxEIi*g3H?RGQclm|3W^R&#kyuPuK{=22;&o`+jkK{LQ2=gcTWYcnrU(FiTuX+<}{O9Yu@WuaL z%uhaH3je_+dCmz(d6ZH%@6E8O*$7TQw79%$xX_~Tt@wWfez}`4gBSn+w`LNI From f2acddfb5195534d487896a656232b016a682f3c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 1 Mar 2024 19:19:10 +0000 Subject: [PATCH 2/2] build(release): 1.9.0 [skip ci] # [1.9.0](https://github.com/actions/create-github-app-token/compare/v1.8.2...v1.9.0) (2024-03-01) ### Features * **outputs:** `app-slug` and `installation-id` ([#105](https://github.com/actions/create-github-app-token/issues/105)) ([babaff4](https://github.com/actions/create-github-app-token/commit/babaff4320b432cece89fd8d07209bb3f6e98fe3)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c703a6e..e076ab0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "create-github-app-token", "private": true, "type": "module", - "version": "1.8.2", + "version": "1.9.0", "description": "GitHub Action for creating a GitHub App Installation Access Token", "scripts": { "build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0",