diff --git a/README.md b/README.md index 0e3bc2b..f7ec8d1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,11 @@ In order to use this action, you need to: ### Create a token for the current repository ```yaml -on: [issues] +name: Run tests on staging +on: + push: + branches: + - main jobs: hello-world: @@ -26,11 +30,10 @@ jobs: with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.PRIVATE_KEY }} - - uses: peter-evans/create-or-update-comment@v3 + github-api-url: "https://github.acme-inc.com/api/v3" + - uses: ./actions/staging-tests with: token: ${{ steps.app-token.outputs.token }} - issue-number: ${{ github.event.issue.number }} - body: "Hello, World!" ``` ### Use app token with `actions/checkout` @@ -146,7 +149,7 @@ jobs: run: echo 'matrix=[{"owner":"owner1"},{"owner":"owner2","repos":["repo1"]}]' >>"$GITHUB_OUTPUT" use-matrix: - name: '@${{ matrix.owners-and-repos.owner }} installation' + name: "@${{ matrix.owners-and-repos.owner }} installation" needs: [set-matrix] runs-on: ubuntu-latest strategy: @@ -172,6 +175,12 @@ jobs: MULTILINE_JSON_STRING: ${{ steps.get-installation-repositories.outputs.data }} ``` +### Run the workflow in a github.com repository against an organization in GitHub Enterprise Server + +```yaml +on: [push] +``` + ## Inputs ### `app-id` @@ -197,6 +206,10 @@ jobs: **Optional:** If truthy, the token will not be revoked when the current job is complete. +### `github-api-url` + +**Optional:** The URL of the GitHub REST API. Defaults to the URL of the GitHub Rest API where the workflow is run from. + ## Outputs ### `token` diff --git a/action.yml b/action.yml index ecc3188..e150b71 100644 --- a/action.yml +++ b/action.yml @@ -32,6 +32,11 @@ inputs: description: "If truthy, the token will not be revoked when the current job is complete" required: false deprecationMessage: "'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead." + # Make GitHub API configurable to support non-GitHub Cloud use cases + # see https://github.com/actions/create-github-app-token/issues/77 + github-api-url: + description: The URL of the GitHub REST API. + default: ${{ github.api_url }} outputs: token: description: "GitHub installation access token" diff --git a/dist/main.cjs b/dist/main.cjs index 0629cdb..fd717a1 100644 --- a/dist/main.cjs +++ b/dist/main.cjs @@ -3023,7 +3023,7 @@ var require_dist_node6 = __commonJS({ module2.exports = __toCommonJS2(dist_src_exports); function oauthAuthorizationUrl(options) { const clientType = options.clientType || "oauth-app"; - const baseUrl = options.baseUrl || "https://github.com"; + const baseUrl2 = options.baseUrl || "https://github.com"; const result = { clientType, allowSignup: options.allowSignup === false ? false : true, @@ -3037,7 +3037,7 @@ var require_dist_node6 = __commonJS({ const scopes = "scopes" in options ? options.scopes : []; result.scopes = typeof scopes === "string" ? scopes.split(/[,\s]+/).filter(Boolean) : scopes; } - result.url = urlBuilderAuthorize(`${baseUrl}/login/oauth/authorize`, result); + result.url = urlBuilderAuthorize(`${baseUrl2}/login/oauth/authorize`, result); return result; } function urlBuilderAuthorize(base, options) { @@ -3149,10 +3149,10 @@ var require_dist_node7 = __commonJS({ request: request2 = import_request3.request, ...options }) { - const baseUrl = requestToOAuthBaseUrl(request2); + const baseUrl2 = requestToOAuthBaseUrl(request2); return (0, import_oauth_authorization_url.oauthAuthorizationUrl)({ ...options, - baseUrl + baseUrl: baseUrl2 }); } var import_request22 = require_dist_node5(); @@ -10464,7 +10464,6 @@ async function getTokenFromRepository(request2, auth, parsedOwner, parsedReposit // lib/request.js var import_request = __toESM(require_dist_node5(), 1); var request_default = import_request.request.defaults({ - baseUrl: process.env["GITHUB_API_URL"], headers: { "user-agent": "actions/create-github-app-token" } @@ -10490,6 +10489,7 @@ var repositories = import_core.default.getInput("repositories"); var skipTokenRevoke = Boolean( import_core.default.getInput("skip-token-revoke") || import_core.default.getInput("skip_token_revoke") ); +var baseUrl = import_core.default.getInput("github-api-url").replace(/\/$/, ""); main( appId, privateKey, @@ -10497,9 +10497,7 @@ main( repositories, import_core.default, import_auth_app.createAppAuth, - request_default.defaults({ - baseUrl: process.env["GITHUB_API_URL"] - }), + request_default.defaults({ baseUrl }), skipTokenRevoke ).catch((error) => { console.error(error); diff --git a/dist/post.cjs b/dist/post.cjs index 8964d70..4993573 100644 --- a/dist/post.cjs +++ b/dist/post.cjs @@ -3030,19 +3030,14 @@ function tokenExpiresIn(expiresAt) { // lib/request.js var import_request = __toESM(require_dist_node5(), 1); var request_default = import_request.request.defaults({ - baseUrl: process.env["GITHUB_API_URL"], headers: { "user-agent": "actions/create-github-app-token" } }); // post.js -post( - import_core.default, - request_default.defaults({ - baseUrl: process.env["GITHUB_API_URL"] - }) -).catch((error) => { +var baseUrl = import_core.default.getInput("github-api-url").replace(/\/$/, ""); +post(import_core.default, request_default.defaults({ baseUrl })).catch((error) => { console.error(error); import_core.default.setFailed(error.message); }); diff --git a/lib/request.js b/lib/request.js index 729cc19..43a6fcc 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,7 +1,6 @@ import { request } from "@octokit/request"; export default request.defaults({ - baseUrl: process.env["GITHUB_API_URL"], headers: { "user-agent": "actions/create-github-app-token", }, diff --git a/main.js b/main.js index 61375d6..073fabb 100644 --- a/main.js +++ b/main.js @@ -31,6 +31,8 @@ const skipTokenRevoke = Boolean( core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke") ); +const baseUrl = core.getInput("github-api-url").replace(/\/$/, ""); + main( appId, privateKey, @@ -38,9 +40,7 @@ main( repositories, core, createAppAuth, - request.defaults({ - baseUrl: process.env["GITHUB_API_URL"], - }), + request.defaults({ baseUrl }), skipTokenRevoke ).catch((error) => { /* c8 ignore next 3 */ diff --git a/post.js b/post.js index 7f0fe83..445674b 100644 --- a/post.js +++ b/post.js @@ -5,12 +5,9 @@ import core from "@actions/core"; import { post } from "./lib/post.js"; import request from "./lib/request.js"; -post( - core, - request.defaults({ - baseUrl: process.env["GITHUB_API_URL"], - }) -).catch((error) => { +const baseUrl = core.getInput("github-api-url").replace(/\/$/, ""); + +post(core, request.defaults({ baseUrl })).catch((error) => { /* c8 ignore next 3 */ console.error(error); core.setFailed(error.message); diff --git a/tests/main-custom-github-api-url.test.js b/tests/main-custom-github-api-url.test.js new file mode 100644 index 0000000..eb2cffa --- /dev/null +++ b/tests/main-custom-github-api-url.test.js @@ -0,0 +1,13 @@ +import { test, DEFAULT_ENV } from "./main.js"; + +// Verify that main works with a custom GitHub API URL passed as `github-api-url` input +await test( + () => { + process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER; + process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY; + }, + { + ...DEFAULT_ENV, + "INPUT_GITHUB-API-URL": "https://github.acme-inc.com/api/v3", + } +); 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 9729376..14c5811 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 @@ -2,10 +2,10 @@ import { test } from "./main.js"; // Verify `main` retry when the GitHub API returns a 500 error. await test((mockPool) => { - process.env.INPUT_OWNER = 'actions' - process.env.INPUT_REPOSITORIES = 'failed-repo'; - const owner = process.env.INPUT_OWNER - const repo = process.env.INPUT_REPOSITORIES + process.env.INPUT_OWNER = "actions"; + process.env.INPUT_REPOSITORIES = "failed-repo"; + const owner = process.env.INPUT_OWNER; + const repo = process.env.INPUT_REPOSITORIES; const mockInstallationId = "123456"; mockPool @@ -18,9 +18,9 @@ await test((mockPool) => { // Intentionally omitting the `authorization` header, since JWT creation is not idempotent. }, }) - .reply(500, 'GitHub API not available') - - mockPool + .reply(500, "GitHub API not available"); + + mockPool .intercept({ path: `/repos/${owner}/${repo}/installation`, method: "GET", @@ -35,5 +35,4 @@ await test((mockPool) => { { id: mockInstallationId }, { 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 d1edf81..e9fba9b 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 @@ -17,8 +17,8 @@ await test((mockPool) => { // Intentionally omitting the `authorization` header, since JWT creation is not idempotent. }, }) - .reply(500, 'GitHub API not available') - mockPool + .reply(500, "GitHub API not available"); + mockPool .intercept({ path: `/orgs/${process.env.INPUT_OWNER}/installation`, method: "GET", diff --git a/tests/main.js b/tests/main.js index 6aba464..a4ba940 100644 --- a/tests/main.js +++ b/tests/main.js @@ -2,14 +2,15 @@ // @ts-check import { MockAgent, setGlobalDispatcher } from "undici"; -export async function test(cb = (_mockPool) => {}) { - // Set required environment variables and inputs - process.env.GITHUB_REPOSITORY_OWNER = "actions"; - process.env.GITHUB_REPOSITORY = "actions/create-github-app-token"; +export const DEFAULT_ENV = { + GITHUB_REPOSITORY_OWNER: "actions", + GITHUB_REPOSITORY: "actions/create-github-app-token", // inputs are set as environment variables with the prefix INPUT_ // https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs - process.env["INPUT_APP-ID"] = "123456"; - process.env["INPUT_PRIVATE-KEY"] = `-----BEGIN RSA PRIVATE KEY----- + "INPUT_GITHUB-API-URL": "https://api.github.com", + "INPUT_APP-ID": "123456", + // 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 Z9puc5Q/nFBU15NKwHyQNb+OG2hTCkjd1Xi9XPzEOH1r42YQmTGq8YCkUSkk6KZA 5dnhLwN9pFquT9fQgrf4r1D5GJj3rqvj8JDr1sBmunArqY5u4gziSrIohcjLIZV0 @@ -35,27 +36,33 @@ r4J2gqb0xTDfq7gLMNrIXc2QQM4gKbnJp60JQM3p9NmH8huavBZGvSvNzTwXyGG3 so0tiQKBgGQXZaxaXhYUcxYHuCkQ3V4Vsj3ezlM92xXlP32SGFm3KgFhYy9kATxw Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61 ------END RSA PRIVATE KEY-----`; // This key is invalidated. It’s from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327. +-----END RSA PRIVATE KEY-----`, +}; + +export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) { + for (const [key, value] of Object.entries(env)) { + process.env[key] = value; + } // Set up mocking + const baseUrl = new URL(env["INPUT_GITHUB-API-URL"]); + const basePath = baseUrl.pathname === '/' ? '' : baseUrl.pathname; const mockAgent = new MockAgent(); mockAgent.disableNetConnect(); setGlobalDispatcher(mockAgent); - const mockPool = mockAgent.get("https://api.github.com"); + const mockPool = mockAgent.get(baseUrl.origin); // Calling `auth({ type: "app" })` to obtain a JWT doesn’t make network requests, so no need to intercept. // Mock installation id request const mockInstallationId = "123456"; - const owner = process.env.INPUT_OWNER ?? process.env.GITHUB_REPOSITORY_OWNER; + const owner = env.INPUT_OWNER ?? env.GITHUB_REPOSITORY_OWNER; const repo = encodeURIComponent( - (process.env.INPUT_REPOSITORIES ?? process.env.GITHUB_REPOSITORY).split( - "," - )[0] + (env.INPUT_REPOSITORIES ?? env.GITHUB_REPOSITORY).split(",")[0] ); mockPool .intercept({ - path: `/repos/${owner}/${repo}/installation`, + path: `${basePath}/repos/${owner}/${repo}/installation`, method: "GET", headers: { accept: "application/vnd.github.v3+json", @@ -75,7 +82,7 @@ x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61 const mockExpiresAt = "2016-07-11T22:14:10Z"; mockPool .intercept({ - path: `/app/installations/${mockInstallationId}/access_tokens`, + path: `${basePath}/app/installations/${mockInstallationId}/access_tokens`, method: "POST", headers: { accept: "application/vnd.github.v3+json", diff --git a/tests/post-revoke-token-fail-response.test.js b/tests/post-revoke-token-fail-response.test.js index 5ce31ec..6962ca3 100644 --- a/tests/post-revoke-token-fail-response.test.js +++ b/tests/post-revoke-token-fail-response.test.js @@ -4,8 +4,14 @@ import { MockAgent, setGlobalDispatcher } from "undici"; // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions process.env.STATE_token = "secret123"; +// inputs are set as environment variables with the prefix INPUT_ +// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs +process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com"; + // 1 hour in the future, not expired -process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString(); +process.env.STATE_expiresAt = new Date( + Date.now() + 1000 * 60 * 60 +).toISOString(); const mockAgent = new MockAgent(); diff --git a/tests/post-token-set.test.js b/tests/post-token-set.test.js index 5b2479e..33697d0 100644 --- a/tests/post-token-set.test.js +++ b/tests/post-token-set.test.js @@ -4,6 +4,10 @@ import { MockAgent, setGlobalDispatcher } from "undici"; // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions process.env.STATE_token = "secret123"; +// inputs are set as environment variables with the prefix INPUT_ +// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs +process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com"; + // 1 hour in the future, not expired process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString(); diff --git a/tests/snapshots/index.js.md b/tests/snapshots/index.js.md index 50a5112..b42248a 100644 --- a/tests/snapshots/index.js.md +++ b/tests/snapshots/index.js.md @@ -16,6 +16,22 @@ Generated by [AVA](https://avajs.dev). private_key — 'private_key' is deprecated and will be removed in a future version. Use 'private-key' instead.␊ skip_token_revoke — 'skip_token_revoke' is deprecated and will be removed in a future version. Use 'skip-token-revoke' instead.` +## main-custom-github-api-url.test.js + +> stderr + + '' + +> stdout + + `owner and repositories set, creating token for repositories "actions/create-github-app-token" owned by "actions"␊ + ::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊ + ␊ + ::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊ + ::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊ + ␊ + ::set-output name=expiresAt::2016-07-11T22:14:10Z` + ## main-missing-app-id.test.js > stderr diff --git a/tests/snapshots/index.js.snap b/tests/snapshots/index.js.snap index 47808b0..46c364e 100644 Binary files a/tests/snapshots/index.js.snap and b/tests/snapshots/index.js.snap differ