Files
Stefan 952a2a7073 feat: add support for enterprise-level GitHub Apps (#263)
This pull request adds support for generating GitHub App installation
tokens for enterprise-level installations.

### What changed

- Added a new `enterprise` input to `action.yml`.
- Wired `enterprise` through `main.js` and `lib/main.js`.
- Added validation so `enterprise` cannot be combined with `owner` or
`repositories`.
- Implemented enterprise installation lookup using the direct GitHub API
route `GET /enterprises/{enterprise}/installation`, then used the
returned installation ID to mint an installation token through
`@octokit/auth-app`.
- Updated `README.md` with enterprise installation usage and input
documentation.
- Updated `dist/main.cjs` for the bundled action.
- Shared token creation retry behavior across repository, owner, and
enterprise paths so server errors and transient network errors are
retried, while client errors fail immediately.

### Tests

Added focused test coverage for:

- enterprise token creation
- enterprise token creation with explicit permissions
- enterprise installation not found
- mutual exclusivity with `owner`
- mutual exclusivity with `repositories`
- owner installation client errors are not retried
- transient network errors are retried during token creation

### Notes

- This keeps the existing repository-scoped token behavior unchanged.
- Owner, repository, and enterprise token creation now share the same
retry policy: server errors and recognized transient network errors are
retried, while client errors fail immediately. This intentionally fixes
the previous owner-path behavior that retried client errors.

Refs:
-
https://github.blog/changelog/2025-07-01-enterprise-level-access-for-github-apps-and-installation-automation-apis/
-
https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-enterprise-installation-for-the-authenticated-app

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-08 11:56:11 -07:00

473 lines
19 KiB
Plaintext

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:
- 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 /api/v3/repos/actions/create-github-app-token/installation
POST /api/v3/app/installations/123456/access_tokens
{"repositories":["create-github-app-token"]}
`;
exports[`main-enterprise-fail-response.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
Failed to create token for enterprise "test-enterprise" (attempt 1): GitHub API not available
::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 /enterprises/test-enterprise/installation
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-enterprise-installation-not-found.test.js > stderr 1`] = `
Error: No enterprise installation found matching the enterprise slug "test-enterprise".
at getTokenFromEnterprise (file://<cwd>/lib/main.js:<line>:<column>)
at process.processTicksAndRejections (node:internal/process/task_queues:<line>:<column>)
at async pRetry (file://<cwd>/node_modules/p-retry/index.js:<line>:<column>)
at async main (file://<cwd>/lib/main.js:<line>:<column>)
at async test (file://<cwd>/tests/main.js:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-installation-not-found.test.js:<line>:<column>
`;
exports[`main-enterprise-installation-not-found.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
Failed to create token for enterprise "test-enterprise" (attempt 1): No enterprise installation found matching the enterprise slug "test-enterprise".
::error::No enterprise installation found matching the enterprise slug "test-enterprise".
--- REQUESTS ---
GET /enterprises/test-enterprise/installation
`;
exports[`main-enterprise-mutual-exclusivity-owner.test.js > stderr 1`] = `
Error: Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
at main (file://<cwd>/lib/main.js:<line>:<column>)
at run (file://<cwd>/main.js:<line>:<column>)
at file://<cwd>/main.js:<line>:<column>
at ModuleJob.run (node:internal/modules/esm/module_job:<line>:<column>)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-mutual-exclusivity-owner.test.js:<line>:<column>
`;
exports[`main-enterprise-mutual-exclusivity-owner.test.js > stdout 1`] = `
::error::Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
`;
exports[`main-enterprise-mutual-exclusivity-repositories.test.js > stderr 1`] = `
Error: Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
at main (file://<cwd>/lib/main.js:<line>:<column>)
at run (file://<cwd>/main.js:<line>:<column>)
at file://<cwd>/main.js:<line>:<column>
at ModuleJob.run (node:internal/modules/esm/module_job:<line>:<column>)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-mutual-exclusivity-repositories.test.js:<line>:<column>
`;
exports[`main-enterprise-mutual-exclusivity-repositories.test.js > stdout 1`] = `
::error::Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
`;
exports[`main-enterprise-only-success.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
::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 /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-enterprise-token-permissions-set.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
::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 /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
{"permissions":{"enterprise_custom_properties_for_organizations":"read"}}
`;
exports[`main-missing-client-and-app-id.test.js > stderr 1`] = `
The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.
`;
exports[`main-missing-client-and-app-id.test.js > stdout 1`] = `
::error::The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.
`;
exports[`main-missing-owner.test.js > stderr 1`] = `
GITHUB_REPOSITORY_OWNER missing, must be set to '<owner>'
`;
exports[`main-missing-repository.test.js > stderr 1`] = `
GITHUB_REPOSITORY missing, must be set to '<owner>/<repo>'
`;
exports[`main-private-key-with-escaped-newlines.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-proxy-requires-native-support.test.js > stderr 1`] = `
A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.
`;
exports[`main-proxy-requires-native-support.test.js > stdout 1`] = `
::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.
`;
exports[`main-repo-skew.test.js > stderr 1`] = `
'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued.
[@octokit/auth-app] GitHub API time and system time are different by 30 seconds. Retrying request with the difference accounted for.
`;
exports[`main-repo-skew.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/failed-repo
::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/failed-repo/installation
GET /repos/actions/failed-repo/installation
POST /app/installations/123456/access_tokens
{"repositories":["failed-repo"]}
`;
exports[`main-token-get-owner-set-client-error.test.js > stderr 1`] = `
RequestError [HttpError]: Forbidden
at fetchWrapper (file://<cwd>/node_modules/@octokit/request/dist-bundle/index.js:<line>:<column>)
at process.processTicksAndRejections (node:internal/process/task_queues:<line>:<column>)
at async hook (file://<cwd>/node_modules/@octokit/auth-app/dist-node/index.js:<line>:<column>)
at async getTokenFromOwner (file://<cwd>/lib/main.js:<line>:<column>)
at async pRetry (file://<cwd>/node_modules/p-retry/index.js:<line>:<column>)
at async main (file://<cwd>/lib/main.js:<line>:<column>)
at async test (file://<cwd>/tests/main.js:<line>:<column>)
at async file://<cwd>/tests/main-token-get-owner-set-client-error.test.js:<line>:<column> {
status: 403,
request: {
method: 'GET',
url: 'https://api.github.com/users/smockle/installation',
headers: {
accept: 'application/vnd.github.v3+json',
'user-agent': 'actions/create-github-app-token',
authorization: 'bearer [REDACTED]'
},
request: { hook: [Function: bound hook] AsyncFunction }
},
response: {
url: 'https://api.github.com/users/smockle/installation',
status: 403,
headers: { 'content-type': 'application/json' },
data: { message: 'Forbidden' }
},
[cause]: undefined
}
`;
exports[`main-token-get-owner-set-client-error.test.js > stdout 1`] = `
Input 'repositories' is not set. Creating token for all repositories owned by smockle.
Failed to create token for "smockle" (attempt 1): Forbidden
::error::Forbidden
--- REQUESTS ---
GET /users/smockle/installation
`;
exports[`main-token-get-owner-set-fail-response.test.js > stdout 1`] = `
Input 'repositories' is not set. Creating token for all repositories owned by smockle.
Failed to create token for "smockle" (attempt 1): GitHub API not available
::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 /users/smockle/installation
GET /users/smockle/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-token-get-owner-set-repo-fail-response.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/failed-repo
Failed to create token for "actions/failed-repo" (attempt 1): GitHub API not available
::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/failed-repo/installation
GET /repos/actions/failed-repo/installation
POST /app/installations/123456/access_tokens
{"repositories":["failed-repo"]}
`;
exports[`main-token-get-owner-set-repo-network-error.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/network-repo
Failed to create token for "actions/network-repo" (attempt 1): fetch failed
::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/network-repo/installation
GET /repos/actions/network-repo/installation
POST /app/installations/123456/access_tokens
{"repositories":["network-repo"]}
`;
exports[`main-token-get-owner-set-repo-set-to-many-newline.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
- actions/toolkit
- actions/checkout
::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","toolkit","checkout"]}
`;
exports[`main-token-get-owner-set-repo-set-to-many.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
- actions/toolkit
- actions/checkout
::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","toolkit","checkout"]}
`;
exports[`main-token-get-owner-set-repo-set-to-one.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- 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-token-get-owner-set-repo-unset.test.js > stdout 1`] = `
Input 'repositories' is not set. Creating token for all repositories owned by actions.
::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 /users/actions/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-token-get-owner-unset-repo-set.test.js > stdout 1`] = `
No 'owner' input provided. Using default owner 'actions' to create token for the following repositories:
- 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-token-get-owner-unset-repo-unset.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-token-permissions-set.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"],"permissions":{"issues":"write","pull_requests":"read"}}
`;
exports[`post-proxy-requires-native-support.test.js > stderr 1`] = `
A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.
`;
exports[`post-proxy-requires-native-support.test.js > stdout 1`] = `
::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.
`;
exports[`post-revoke-token-fail-response.test.js > stdout 1`] = `
::warning::Token revocation failed:
`;
exports[`post-token-expired.test.js > stdout 1`] = `
Token expired, skipping token revocation
`;
exports[`post-token-set.test.js > stdout 1`] = `
Token revoked
`;
exports[`post-token-skipped.test.js > stdout 1`] = `
Token revocation was skipped
`;
exports[`post-token-unset.test.js > stdout 1`] = `
Token is not set
`;