Skip to content

Commit

Permalink
feat: github-api-url (actions#88)
Browse files Browse the repository at this point in the history
closes actions#77

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
  • Loading branch information
gr2m and parkerbxyz authored Jan 26, 2024
1 parent c4fa18d commit 837e275
Show file tree
Hide file tree
Showing 15 changed files with 107 additions and 55 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`
Expand Down Expand Up @@ -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:
Expand All @@ -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`
Expand All @@ -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`
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 6 additions & 8 deletions dist/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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"
}
Expand All @@ -10490,16 +10489,15 @@ 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,
owner,
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);
Expand Down
9 changes: 2 additions & 7 deletions dist/post.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
1 change: 0 additions & 1 deletion lib/request.js
Original file line number Diff line number Diff line change
@@ -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",
},
Expand Down
6 changes: 3 additions & 3 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ const skipTokenRevoke = Boolean(
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
);

const baseUrl = core.getInput("github-api-url").replace(/\/$/, "");

main(
appId,
privateKey,
owner,
repositories,
core,
createAppAuth,
request.defaults({
baseUrl: process.env["GITHUB_API_URL"],
}),
request.defaults({ baseUrl }),
skipTokenRevoke
).catch((error) => {
/* c8 ignore next 3 */
Expand Down
9 changes: 3 additions & 6 deletions post.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions tests/main-custom-github-api-url.test.js
Original file line number Diff line number Diff line change
@@ -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",
}
);
15 changes: 7 additions & 8 deletions tests/main-token-get-owner-set-repo-fail-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -35,5 +35,4 @@ await test((mockPool) => {
{ id: mockInstallationId },
{ headers: { "content-type": "application/json" } }
);

});
4 changes: 2 additions & 2 deletions tests/main-token-get-owner-set-to-user-fail-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 21 additions & 14 deletions tests/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion tests/post-revoke-token-fail-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
4 changes: 4 additions & 0 deletions tests/post-token-set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
16 changes: 16 additions & 0 deletions tests/snapshots/index.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified tests/snapshots/index.js.snap
Binary file not shown.

0 comments on commit 837e275

Please sign in to comment.