Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8210939678 | ||
|
|
9ec88c41ee | ||
|
|
d400084c45 | ||
|
|
20fd86373f | ||
|
|
5804f049e1 | ||
|
|
84daa2c0f0 | ||
|
|
6d98b259d9 | ||
|
|
3629f23f43 | ||
|
|
4bb2d37925 | ||
|
|
9f83520638 | ||
|
|
10f155294b | ||
|
|
49ce228ea7 | ||
|
|
c08c5ace34 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
app_id: ${{ vars.RELEASER_APP_ID }}
|
app_id: ${{ vars.RELEASER_APP_ID }}
|
||||||
private_key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
|
private_key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
- run: npm install --no-save @semantic-release/git semantic-release-plugin-github-breaking-version-tag
|
- run: npm install --no-save @semantic-release/git semantic-release-plugin-github-breaking-version-tag
|
||||||
|
|||||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
name: test
|
name: test
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -7,13 +10,28 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
integration:
|
||||||
|
name: Integration
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16.16"
|
node-version: 20
|
||||||
|
cache: "npm"
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm test
|
||||||
|
|
||||||
|
end-to-end:
|
||||||
|
name: End-to-End
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# do not run from forks, as forks don’t have access to repository secrets
|
||||||
|
if: github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|||||||
88
README.md
88
README.md
@@ -10,7 +10,7 @@ In order to use this action, you need to:
|
|||||||
2. [Store the App's ID in your repository environment variables](https://docs.github.com/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_ID`)
|
2. [Store the App's 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`)
|
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`)
|
||||||
|
|
||||||
### Minimal usage
|
### Create a token for the current repository
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
on: [issues]
|
on: [issues]
|
||||||
@@ -57,6 +57,73 @@ jobs:
|
|||||||
github_token: ${{ steps.app-token.outputs.token }}
|
github_token: ${{ steps.app-token.outputs.token }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Create a token for all repositories in the current owner's installation
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hello-world:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app_id: ${{ vars.APP_ID }}
|
||||||
|
private_key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
- uses: peter-evans/create-or-update-comment@v3
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: "Hello, World!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a token for multiple repositories in the current owner's installation
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on: [issues]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hello-world:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app_id: ${{ vars.APP_ID }}
|
||||||
|
private_key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
repositories: "repo1,repo2"
|
||||||
|
- uses: peter-evans/create-or-update-comment@v3
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: "Hello, World!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a token for all repositories in another owner's installation
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on: [issues]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hello-world:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app_id: ${{ vars.APP_ID }}
|
||||||
|
private_key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
owner: another-owner
|
||||||
|
- uses: peter-evans/create-or-update-comment@v3
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: "Hello, World!"
|
||||||
|
```
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
### `app_id`
|
### `app_id`
|
||||||
@@ -67,6 +134,21 @@ jobs:
|
|||||||
|
|
||||||
**Required:** GitHub App private key.
|
**Required:** GitHub App private key.
|
||||||
|
|
||||||
|
### `owner`
|
||||||
|
|
||||||
|
**Optional:** GitHub App installation owner. If empty, defaults to the current repository owner.
|
||||||
|
|
||||||
|
### `repositories`
|
||||||
|
|
||||||
|
**Optional:** Comma-separated list of repositories to grant access to.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If `owner` is set and `repositories` is empty, access will be scoped to all repositories in the provided repository owner's installation. If `owner` and `repositories` are empty, access will be scoped to only the current repository.
|
||||||
|
|
||||||
|
### `skip_token_revoke`
|
||||||
|
|
||||||
|
**Optional:** If truthy, the token will not be revoked when the current job is complete.
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
### `token`
|
### `token`
|
||||||
@@ -77,10 +159,10 @@ GitHub App installation access token.
|
|||||||
|
|
||||||
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,
|
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,
|
||||||
|
|
||||||
1. The token is scoped to the current repository.
|
1. The token is scoped to the current repository or `repositories` if set.
|
||||||
2. The token inherits all the installation's permissions.
|
2. The token inherits all the installation's permissions.
|
||||||
3. The token is set as output `token` which can be used in subsequent steps.
|
3. The token is set as output `token` which can be used in subsequent steps.
|
||||||
4. The token is revoked in the `post` step of the action, which means it cannot be passed to another job.
|
4. Unless the `skip_token_revoke` input is set to a truthy value, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
|
||||||
5. The token is masked, it cannot be logged accidentally.
|
5. The token is masked, it cannot be logged accidentally.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ inputs:
|
|||||||
private_key:
|
private_key:
|
||||||
description: "GitHub App private key"
|
description: "GitHub App private key"
|
||||||
required: true
|
required: true
|
||||||
|
owner:
|
||||||
|
description: "GitHub App owner (defaults to current repository owner)"
|
||||||
|
required: false
|
||||||
|
repositories:
|
||||||
|
description: "Repositories to install the GitHub App on (defaults to current repository if owner is unset)"
|
||||||
|
required: false
|
||||||
|
skip_token_revoke:
|
||||||
|
description: "If truthy, the token will not be revoked when the current job is complete"
|
||||||
|
required: false
|
||||||
outputs:
|
outputs:
|
||||||
token:
|
token:
|
||||||
description: "GitHub installation access token"
|
description: "GitHub installation access token"
|
||||||
|
|||||||
6009
dist/main.cjs
vendored
6009
dist/main.cjs
vendored
File diff suppressed because it is too large
Load Diff
58
dist/post.cjs
vendored
58
dist/post.cjs
vendored
@@ -569,7 +569,12 @@ var require_proxy = __commonJS({
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
if (proxyVar) {
|
if (proxyVar) {
|
||||||
|
try {
|
||||||
return new URL(proxyVar);
|
return new URL(proxyVar);
|
||||||
|
} catch (_a) {
|
||||||
|
if (!proxyVar.startsWith("http://") && !proxyVar.startsWith("https://"))
|
||||||
|
return new URL(`http://${proxyVar}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
@@ -1001,6 +1006,19 @@ var require_lib = __commonJS({
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
readBodyBuffer() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const chunks = [];
|
||||||
|
this.message.on("data", (chunk) => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
this.message.on("end", () => {
|
||||||
|
resolve(Buffer.concat(chunks));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
exports.HttpClientResponse = HttpClientResponse;
|
exports.HttpClientResponse = HttpClientResponse;
|
||||||
function isHttps(requestUrl) {
|
function isHttps(requestUrl) {
|
||||||
@@ -1602,7 +1620,7 @@ var require_oidc_utils = __commonJS({
|
|||||||
|
|
||||||
Error Code : ${error.statusCode}
|
Error Code : ${error.statusCode}
|
||||||
|
|
||||||
Error Message: ${error.result.message}`);
|
Error Message: ${error.message}`);
|
||||||
});
|
});
|
||||||
const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
|
const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
|
||||||
if (!id_token) {
|
if (!id_token) {
|
||||||
@@ -2278,7 +2296,7 @@ var require_dist_node2 = __commonJS({
|
|||||||
});
|
});
|
||||||
module2.exports = __toCommonJS2(dist_src_exports);
|
module2.exports = __toCommonJS2(dist_src_exports);
|
||||||
var import_universal_user_agent = require_dist_node();
|
var import_universal_user_agent = require_dist_node();
|
||||||
var VERSION = "9.0.0";
|
var VERSION = "9.0.1";
|
||||||
var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`;
|
var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`;
|
||||||
var DEFAULTS = {
|
var DEFAULTS = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -2793,7 +2811,7 @@ var require_dist_node5 = __commonJS({
|
|||||||
module2.exports = __toCommonJS2(dist_src_exports);
|
module2.exports = __toCommonJS2(dist_src_exports);
|
||||||
var import_endpoint = require_dist_node2();
|
var import_endpoint = require_dist_node2();
|
||||||
var import_universal_user_agent = require_dist_node();
|
var import_universal_user_agent = require_dist_node();
|
||||||
var VERSION = "8.1.1";
|
var VERSION = "8.1.2";
|
||||||
var import_is_plain_object = require_is_plain_object();
|
var import_is_plain_object = require_is_plain_object();
|
||||||
var import_request_error = require_dist_node4();
|
var import_request_error = require_dist_node4();
|
||||||
function getBufferResponse(response) {
|
function getBufferResponse(response) {
|
||||||
@@ -2951,32 +2969,46 @@ var require_dist_node5 = __commonJS({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// post.js
|
// post.js
|
||||||
var import_core2 = __toESM(require_core(), 1);
|
var import_core = __toESM(require_core(), 1);
|
||||||
var import_request = __toESM(require_dist_node5(), 1);
|
|
||||||
|
|
||||||
// lib/post.js
|
// lib/post.js
|
||||||
var import_core = __toESM(require_core(), 1);
|
async function post(core2, request2) {
|
||||||
async function post(core3, request2) {
|
const skipTokenRevoke = Boolean(core2.getInput("skip_token_revoke"));
|
||||||
const token = core3.getState("token");
|
if (skipTokenRevoke) {
|
||||||
if (!token)
|
core2.info("Token revocation was skipped");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
const token = core2.getState("token");
|
||||||
|
if (!token) {
|
||||||
|
core2.info("Token is not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
await request2("DELETE /installation/token", {
|
await request2("DELETE /installation/token", {
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `token ${token}`
|
authorization: `token ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
core3.info("Token revoked");
|
core2.info("Token revoked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.js
|
||||||
post(
|
post(
|
||||||
import_core2.default,
|
import_core.default,
|
||||||
import_request.request.defaults({
|
request_default.defaults({
|
||||||
baseUrl: process.env["GITHUB_API_URL"]
|
baseUrl: process.env["GITHUB_API_URL"]
|
||||||
})
|
})
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
import_core2.default.setFailed(error.message);
|
import_core.default.setFailed(error.message);
|
||||||
});
|
});
|
||||||
/*! Bundled license information:
|
/*! Bundled license information:
|
||||||
|
|
||||||
|
|||||||
122
lib/main.js
122
lib/main.js
@@ -1,62 +1,130 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import core from "@actions/core";
|
|
||||||
import { createAppAuth } from "@octokit/auth-app";
|
|
||||||
import { request } from "@octokit/request";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} appId
|
* @param {string} appId
|
||||||
* @param {string} privateKey
|
* @param {string} privateKey
|
||||||
* @param {string} repository
|
* @param {string} owner
|
||||||
* @param {core} core
|
* @param {string} repositories
|
||||||
* @param {createAppAuth} createAppAuth
|
* @param {import("@actions/core")} core
|
||||||
* @param {request} request
|
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
|
||||||
|
* @param {import("@octokit/request").request} request
|
||||||
|
* @param {boolean} skipTokenRevoke
|
||||||
*/
|
*/
|
||||||
export async function main(
|
export async function main(
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
repository,
|
owner,
|
||||||
|
repositories,
|
||||||
core,
|
core,
|
||||||
createAppAuth,
|
createAppAuth,
|
||||||
request
|
request,
|
||||||
|
skipTokenRevoke
|
||||||
) {
|
) {
|
||||||
// Get owner and repo name from GITHUB_REPOSITORY
|
let parsedOwner = "";
|
||||||
const [owner, repo] = repository.split("/");
|
let parsedRepositoryNames = "";
|
||||||
|
|
||||||
|
// If neither owner nor repositories are set, default to current repository
|
||||||
|
if (!owner && !repositories) {
|
||||||
|
[parsedOwner, parsedRepositoryNames] = String(
|
||||||
|
process.env.GITHUB_REPOSITORY
|
||||||
|
).split("/");
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only an owner is set, default to all repositories from that owner
|
||||||
|
if (owner && !repositories) {
|
||||||
|
parsedOwner = owner;
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
`repositories not set, creating token for all repositories for given owner "${owner}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
|
||||||
|
if (!owner && repositories) {
|
||||||
|
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
||||||
|
parsedRepositoryNames = repositories;
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
`owner not set, creating owner for given repositories "${repositories}" in current owner ("${parsedOwner}")`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both owner and repositories are set, use those values
|
||||||
|
if (owner && repositories) {
|
||||||
|
parsedOwner = owner;
|
||||||
|
parsedRepositoryNames = repositories;
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
`owner and repositories set, creating token for repositories "${repositories}" owned by "${owner}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const auth = createAppAuth({
|
const auth = createAppAuth({
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
|
request,
|
||||||
});
|
});
|
||||||
|
|
||||||
const appAuthentication = await auth({
|
const appAuthentication = await auth({
|
||||||
type: "app",
|
type: "app",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the installation ID
|
let authentication;
|
||||||
// https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
// If at least one repository is set, get installation ID from that repository
|
||||||
const { data: installation } = await request(
|
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
||||||
"GET /repos/{owner}/{repo}/installation",
|
if (parsedRepositoryNames) {
|
||||||
{
|
const response = await request("GET /repos/{owner}/{repo}/installation", {
|
||||||
owner,
|
owner: parsedOwner,
|
||||||
repo,
|
repo: parsedRepositoryNames.split(",")[0],
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `bearer ${appAuthentication.token}`,
|
authorization: `bearer ${appAuthentication.token}`,
|
||||||
},
|
},
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a new installation token
|
|
||||||
const authentication = await auth({
|
|
||||||
type: "installation",
|
|
||||||
installationId: installation.id,
|
|
||||||
repositoryNames: [repo],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get token for given repositories
|
||||||
|
authentication = await auth({
|
||||||
|
type: "installation",
|
||||||
|
installationId: response.data.id,
|
||||||
|
repositoryNames: parsedRepositoryNames.split(","),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise get the installation for the owner, which can either be an organization or a user account
|
||||||
|
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
||||||
|
const response = await request("GET /orgs/{org}/installation", {
|
||||||
|
org: parsedOwner,
|
||||||
|
headers: {
|
||||||
|
authorization: `bearer ${appAuthentication.token}`,
|
||||||
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.status !== 404) throw error;
|
||||||
|
|
||||||
|
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
|
||||||
|
return request("GET /users/{username}/installation", {
|
||||||
|
username: parsedOwner,
|
||||||
|
headers: {
|
||||||
|
authorization: `bearer ${appAuthentication.token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get token for for all repositories of the given installation
|
||||||
|
authentication = await auth({
|
||||||
|
type: "installation",
|
||||||
|
installationId: response.data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Register the token with the runner as a secret to ensure it is masked in logs
|
// Register the token with the runner as a secret to ensure it is masked in logs
|
||||||
core.setSecret(authentication.token);
|
core.setSecret(authentication.token);
|
||||||
|
|
||||||
core.setOutput("token", authentication.token);
|
core.setOutput("token", authentication.token);
|
||||||
|
|
||||||
// Make token accessible to post function (so we can invalidate it)
|
// Make token accessible to post function (so we can invalidate it)
|
||||||
|
if (!skipTokenRevoke) {
|
||||||
core.saveState("token", authentication.token);
|
core.saveState("token", authentication.token);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
19
lib/post.js
19
lib/post.js
@@ -1,16 +1,23 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import core from "@actions/core";
|
|
||||||
import { request } from "@octokit/request";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {core} core
|
* @param {import("@actions/core")} core
|
||||||
* @param {request} request
|
* @param {import("@octokit/request").request} request
|
||||||
*/
|
*/
|
||||||
export async function post(core, request) {
|
export async function post(core, request) {
|
||||||
|
const skipTokenRevoke = Boolean(core.getInput("skip_token_revoke"));
|
||||||
|
|
||||||
|
if (skipTokenRevoke) {
|
||||||
|
core.info("Token revocation was skipped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const token = core.getState("token");
|
const token = core.getState("token");
|
||||||
|
|
||||||
if (!token) return;
|
if (!token) {
|
||||||
|
core.info("Token is not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await request("DELETE /installation/token", {
|
await request("DELETE /installation/token", {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
8
lib/request.js
Normal file
8
lib/request.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { request } from "@octokit/request";
|
||||||
|
|
||||||
|
export default request.defaults({
|
||||||
|
baseUrl: process.env["GITHUB_API_URL"],
|
||||||
|
headers: {
|
||||||
|
"user-agent": "actions/create-github-app-token",
|
||||||
|
},
|
||||||
|
});
|
||||||
16
main.js
16
main.js
@@ -2,28 +2,36 @@
|
|||||||
|
|
||||||
import core from "@actions/core";
|
import core from "@actions/core";
|
||||||
import { createAppAuth } from "@octokit/auth-app";
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { request } from "@octokit/request";
|
|
||||||
|
|
||||||
import { main } from "./lib/main.js";
|
import { main } from "./lib/main.js";
|
||||||
|
import request from "./lib/request.js";
|
||||||
|
|
||||||
if (!process.env.GITHUB_REPOSITORY) {
|
if (!process.env.GITHUB_REPOSITORY) {
|
||||||
throw new Error("GITHUB_REPOSITORY missing, must be set to '<owner>/<repo>'");
|
throw new Error("GITHUB_REPOSITORY missing, must be set to '<owner>/<repo>'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!process.env.GITHUB_REPOSITORY_OWNER) {
|
||||||
|
throw new Error("GITHUB_REPOSITORY_OWNER missing, must be set to '<owner>'");
|
||||||
|
}
|
||||||
|
|
||||||
const appId = core.getInput("app_id");
|
const appId = core.getInput("app_id");
|
||||||
const privateKey = core.getInput("private_key");
|
const privateKey = core.getInput("private_key");
|
||||||
|
const owner = core.getInput("owner");
|
||||||
|
const repositories = core.getInput("repositories");
|
||||||
|
|
||||||
const repository = process.env.GITHUB_REPOSITORY;
|
const skipTokenRevoke = Boolean(core.getInput("skip_token_revoke"));
|
||||||
|
|
||||||
main(
|
main(
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
repository,
|
owner,
|
||||||
|
repositories,
|
||||||
core,
|
core,
|
||||||
createAppAuth,
|
createAppAuth,
|
||||||
request.defaults({
|
request.defaults({
|
||||||
baseUrl: process.env["GITHUB_API_URL"],
|
baseUrl: process.env["GITHUB_API_URL"],
|
||||||
})
|
}),
|
||||||
|
skipTokenRevoke
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
|
|||||||
2232
package-lock.json
generated
2232
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -2,21 +2,24 @@
|
|||||||
"name": "create-github-app-token",
|
"name": "create-github-app-token",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.2.0",
|
"version": "1.4.0",
|
||||||
"description": "GitHub Action for creating a GitHub App Installation Access Token",
|
"description": "GitHub Action for creating a GitHub App Installation Access Token",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node16.16",
|
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node16.16",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "ava tests/index.js"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@octokit/auth-app": "^6.0.0",
|
"@octokit/auth-app": "^6.0.1",
|
||||||
"@octokit/request": "^8.1.1"
|
"@octokit/request": "^8.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"ava": "^5.3.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"esbuild": "^0.19.2"
|
"esbuild": "^0.19.4",
|
||||||
|
"execa": "^8.0.1",
|
||||||
|
"undici": "^5.25.2"
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
"branches": [
|
"branches": [
|
||||||
|
|||||||
2
post.js
2
post.js
@@ -1,9 +1,9 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import core from "@actions/core";
|
import core from "@actions/core";
|
||||||
import { request } from "@octokit/request";
|
|
||||||
|
|
||||||
import { post } from "./lib/post.js";
|
import { post } from "./lib/post.js";
|
||||||
|
import request from "./lib/request.js";
|
||||||
|
|
||||||
post(
|
post(
|
||||||
core,
|
core,
|
||||||
|
|||||||
19
tests/README.md
Normal file
19
tests/README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Tests
|
||||||
|
|
||||||
|
Add one test file per scenario. You can run them in isolation with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node tests/post-token-set.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests are run together in [tests/index.js](index.js), which can be executed with ava
|
||||||
|
|
||||||
|
```
|
||||||
|
npx ava tests/index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
or with npm
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test
|
||||||
|
```
|
||||||
14
tests/index.js
Normal file
14
tests/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { readdirSync } from "node:fs";
|
||||||
|
|
||||||
|
import { execa } from "execa";
|
||||||
|
import test from "ava";
|
||||||
|
|
||||||
|
const tests = readdirSync("tests").filter((file) => file.endsWith(".test.js"));
|
||||||
|
|
||||||
|
for (const file of tests) {
|
||||||
|
test(file, async (t) => {
|
||||||
|
const { stderr, stdout } = await execa("node", [`tests/${file}`]);
|
||||||
|
t.snapshot(stderr, "stderr");
|
||||||
|
t.snapshot(stdout, "stdout");
|
||||||
|
});
|
||||||
|
}
|
||||||
25
tests/post-token-set.test.js
Normal file
25
tests/post-token-set.test.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { MockAgent, setGlobalDispatcher } from "undici";
|
||||||
|
|
||||||
|
// state variables are set as environment variables with the prefix STATE_
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
const mockAgent = new MockAgent();
|
||||||
|
|
||||||
|
setGlobalDispatcher(mockAgent);
|
||||||
|
|
||||||
|
// Provide the base url to the request
|
||||||
|
const mockPool = mockAgent.get("https://api.github.com");
|
||||||
|
|
||||||
|
// intercept the request
|
||||||
|
mockPool
|
||||||
|
.intercept({
|
||||||
|
path: "/installation/token",
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
authorization: "token secret123",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.reply(204);
|
||||||
|
|
||||||
|
await import("../post.js");
|
||||||
29
tests/post-token-skipped.test.js
Normal file
29
tests/post-token-skipped.test.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { MockAgent, setGlobalDispatcher } from "undici";
|
||||||
|
|
||||||
|
// state variables are set as environment variables with the prefix STATE_
|
||||||
|
// 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_SKIP_TOKEN_REVOKE = "true";
|
||||||
|
|
||||||
|
const mockAgent = new MockAgent();
|
||||||
|
|
||||||
|
setGlobalDispatcher(mockAgent);
|
||||||
|
|
||||||
|
// Provide the base url to the request
|
||||||
|
const mockPool = mockAgent.get("https://api.github.com");
|
||||||
|
|
||||||
|
// intercept the request
|
||||||
|
mockPool
|
||||||
|
.intercept({
|
||||||
|
path: "/installation/token",
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
authorization: "token secret123",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.reply(204);
|
||||||
|
|
||||||
|
await import("../post.js");
|
||||||
5
tests/post-token-unset.test.js
Normal file
5
tests/post-token-unset.test.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// state variables are set as environment variables with the prefix STATE_
|
||||||
|
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
|
||||||
|
delete process.env.STATE_token;
|
||||||
|
|
||||||
|
await import("../post.js");
|
||||||
35
tests/snapshots/index.js.md
Normal file
35
tests/snapshots/index.js.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Snapshot report for `tests/index.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `index.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## post-token-set.test.js
|
||||||
|
|
||||||
|
> stderr
|
||||||
|
|
||||||
|
''
|
||||||
|
|
||||||
|
> stdout
|
||||||
|
|
||||||
|
'Token revoked'
|
||||||
|
|
||||||
|
## post-token-skipped.test.js
|
||||||
|
|
||||||
|
> stderr
|
||||||
|
|
||||||
|
''
|
||||||
|
|
||||||
|
> stdout
|
||||||
|
|
||||||
|
'Token revocation was skipped'
|
||||||
|
|
||||||
|
## post-token-unset.test.js
|
||||||
|
|
||||||
|
> stderr
|
||||||
|
|
||||||
|
''
|
||||||
|
|
||||||
|
> stdout
|
||||||
|
|
||||||
|
'Token is not set'
|
||||||
BIN
tests/snapshots/index.js.snap
Normal file
BIN
tests/snapshots/index.js.snap
Normal file
Binary file not shown.
Reference in New Issue
Block a user