feat: allow repositories input to be comma or newline-separated (#169)
Resolves https://github.com/actions/create-github-app-token/issues/106 - Fixes the parsing to cope with whitespace in the input string. - Allows the input to be comma or newline-separated. (I've done this for all array-type inputs in my own actions, but I'm happy to remove this if you only want to support comma-separated.) - Added tests for parsing comma and newline-separated inputs.
This commit is contained in:
@@ -163,7 +163,9 @@ jobs:
|
|||||||
app-id: ${{ vars.APP_ID }}
|
app-id: ${{ vars.APP_ID }}
|
||||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||||
owner: ${{ github.repository_owner }}
|
owner: ${{ github.repository_owner }}
|
||||||
repositories: "repo1,repo2"
|
repositories: |
|
||||||
|
repo1
|
||||||
|
repo2
|
||||||
- uses: peter-evans/create-or-update-comment@v3
|
- uses: peter-evans/create-or-update-comment@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
@@ -302,7 +304,7 @@ steps:
|
|||||||
|
|
||||||
### `repositories`
|
### `repositories`
|
||||||
|
|
||||||
**Optional:** Comma-separated list of repositories to grant access to.
|
**Optional:** Comma or newline-separated list of repositories to grant access to.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!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.
|
> 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.
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ inputs:
|
|||||||
description: "The owner of the GitHub App installation (defaults to current repository owner)"
|
description: "The owner of the GitHub App installation (defaults to current repository owner)"
|
||||||
required: false
|
required: false
|
||||||
repositories:
|
repositories:
|
||||||
description: "Repositories to install the GitHub App on (defaults to current repository if owner is unset)"
|
description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)"
|
||||||
required: false
|
required: false
|
||||||
skip-token-revoke:
|
skip-token-revoke:
|
||||||
description: "If truthy, the token will not be revoked when the current job is complete"
|
description: "If truthy, the token will not be revoked when the current job is complete"
|
||||||
|
|||||||
Vendored
+16
-14
@@ -39700,33 +39700,35 @@ async function pRetry(input, options) {
|
|||||||
// lib/main.js
|
// lib/main.js
|
||||||
async function main(appId2, privateKey2, owner2, repositories2, core3, createAppAuth2, request2, skipTokenRevoke2) {
|
async function main(appId2, privateKey2, owner2, repositories2, core3, createAppAuth2, request2, skipTokenRevoke2) {
|
||||||
let parsedOwner = "";
|
let parsedOwner = "";
|
||||||
let parsedRepositoryNames = "";
|
let parsedRepositoryNames = [];
|
||||||
if (!owner2 && !repositories2) {
|
if (!owner2 && repositories2.length === 0) {
|
||||||
[parsedOwner, parsedRepositoryNames] = String(
|
const [owner3, repo] = String(
|
||||||
process.env.GITHUB_REPOSITORY
|
process.env.GITHUB_REPOSITORY
|
||||||
).split("/");
|
).split("/");
|
||||||
|
parsedOwner = owner3;
|
||||||
|
parsedRepositoryNames = [repo];
|
||||||
core3.info(
|
core3.info(
|
||||||
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
|
`owner and repositories not set, creating token for the current repository ("${repo}")`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (owner2 && !repositories2) {
|
if (owner2 && repositories2.length === 0) {
|
||||||
parsedOwner = owner2;
|
parsedOwner = owner2;
|
||||||
core3.info(
|
core3.info(
|
||||||
`repositories not set, creating token for all repositories for given owner "${owner2}"`
|
`repositories not set, creating token for all repositories for given owner "${owner2}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!owner2 && repositories2) {
|
if (!owner2 && repositories2.length > 0) {
|
||||||
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
||||||
parsedRepositoryNames = repositories2;
|
parsedRepositoryNames = repositories2;
|
||||||
core3.info(
|
core3.info(
|
||||||
`owner not set, creating owner for given repositories "${repositories2}" in current owner ("${parsedOwner}")`
|
`owner not set, creating owner for given repositories "${repositories2.join(",")}" in current owner ("${parsedOwner}")`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (owner2 && repositories2) {
|
if (owner2 && repositories2.length > 0) {
|
||||||
parsedOwner = owner2;
|
parsedOwner = owner2;
|
||||||
parsedRepositoryNames = repositories2;
|
parsedRepositoryNames = repositories2;
|
||||||
core3.info(
|
core3.info(
|
||||||
`owner and repositories set, creating token for repositories "${repositories2}" owned by "${owner2}"`
|
`owner and repositories set, creating token for repositories "${repositories2.join(",")}" owned by "${owner2}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const auth5 = createAppAuth2({
|
const auth5 = createAppAuth2({
|
||||||
@@ -39735,11 +39737,11 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp
|
|||||||
request: request2
|
request: request2
|
||||||
});
|
});
|
||||||
let authentication, installationId, appSlug;
|
let authentication, installationId, appSlug;
|
||||||
if (parsedRepositoryNames) {
|
if (parsedRepositoryNames.length > 0) {
|
||||||
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames), {
|
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames), {
|
||||||
onFailedAttempt: (error) => {
|
onFailedAttempt: (error) => {
|
||||||
core3.info(
|
core3.info(
|
||||||
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
|
`Failed to create token for "${parsedRepositoryNames.join(",")}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
retries: 3
|
retries: 3
|
||||||
@@ -39789,7 +39791,7 @@ async function getTokenFromOwner(request2, auth5, parsedOwner) {
|
|||||||
async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames) {
|
async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames) {
|
||||||
const response = await request2("GET /repos/{owner}/{repo}/installation", {
|
const response = await request2("GET /repos/{owner}/{repo}/installation", {
|
||||||
owner: parsedOwner,
|
owner: parsedOwner,
|
||||||
repo: parsedRepositoryNames.split(",")[0],
|
repo: parsedRepositoryNames[0],
|
||||||
request: {
|
request: {
|
||||||
hook: auth5.hook
|
hook: auth5.hook
|
||||||
}
|
}
|
||||||
@@ -39797,7 +39799,7 @@ async function getTokenFromRepository(request2, auth5, parsedOwner, parsedReposi
|
|||||||
const authentication = await auth5({
|
const authentication = await auth5({
|
||||||
type: "installation",
|
type: "installation",
|
||||||
installationId: response.data.id,
|
installationId: response.data.id,
|
||||||
repositoryNames: parsedRepositoryNames.split(",")
|
repositoryNames: parsedRepositoryNames
|
||||||
});
|
});
|
||||||
const installationId = response.data.id;
|
const installationId = response.data.id;
|
||||||
const appSlug = response.data["app_slug"];
|
const appSlug = response.data["app_slug"];
|
||||||
@@ -39847,7 +39849,7 @@ if (!privateKey) {
|
|||||||
throw new Error("Input required and not supplied: private-key");
|
throw new Error("Input required and not supplied: private-key");
|
||||||
}
|
}
|
||||||
var owner = import_core2.default.getInput("owner");
|
var owner = import_core2.default.getInput("owner");
|
||||||
var repositories = import_core2.default.getInput("repositories");
|
var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
|
||||||
var skipTokenRevoke = Boolean(
|
var skipTokenRevoke = Boolean(
|
||||||
import_core2.default.getInput("skip-token-revoke") || import_core2.default.getInput("skip_token_revoke")
|
import_core2.default.getInput("skip-token-revoke") || import_core2.default.getInput("skip_token_revoke")
|
||||||
);
|
);
|
||||||
|
|||||||
+17
-15
@@ -5,7 +5,7 @@ import pRetry from "p-retry";
|
|||||||
* @param {string} appId
|
* @param {string} appId
|
||||||
* @param {string} privateKey
|
* @param {string} privateKey
|
||||||
* @param {string} owner
|
* @param {string} owner
|
||||||
* @param {string} repositories
|
* @param {string[]} repositories
|
||||||
* @param {import("@actions/core")} core
|
* @param {import("@actions/core")} core
|
||||||
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
|
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
|
||||||
* @param {import("@octokit/request").request} request
|
* @param {import("@octokit/request").request} request
|
||||||
@@ -22,21 +22,23 @@ export async function main(
|
|||||||
skipTokenRevoke
|
skipTokenRevoke
|
||||||
) {
|
) {
|
||||||
let parsedOwner = "";
|
let parsedOwner = "";
|
||||||
let parsedRepositoryNames = "";
|
let parsedRepositoryNames = [];
|
||||||
|
|
||||||
// If neither owner nor repositories are set, default to current repository
|
// If neither owner nor repositories are set, default to current repository
|
||||||
if (!owner && !repositories) {
|
if (!owner && repositories.length === 0) {
|
||||||
[parsedOwner, parsedRepositoryNames] = String(
|
const [owner, repo] = String(
|
||||||
process.env.GITHUB_REPOSITORY
|
process.env.GITHUB_REPOSITORY
|
||||||
).split("/");
|
).split("/");
|
||||||
|
parsedOwner = owner;
|
||||||
|
parsedRepositoryNames = [repo];
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
|
`owner and repositories not set, creating token for the current repository ("${repo}")`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only an owner is set, default to all repositories from that owner
|
// If only an owner is set, default to all repositories from that owner
|
||||||
if (owner && !repositories) {
|
if (owner && repositories.length === 0) {
|
||||||
parsedOwner = owner;
|
parsedOwner = owner;
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
@@ -45,22 +47,22 @@ export async function main(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
|
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
|
||||||
if (!owner && repositories) {
|
if (!owner && repositories.length > 0) {
|
||||||
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
||||||
parsedRepositoryNames = repositories;
|
parsedRepositoryNames = repositories;
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`owner not set, creating owner for given repositories "${repositories}" in current owner ("${parsedOwner}")`
|
`owner not set, creating owner for given repositories "${repositories.join(',')}" in current owner ("${parsedOwner}")`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both owner and repositories are set, use those values
|
// If both owner and repositories are set, use those values
|
||||||
if (owner && repositories) {
|
if (owner && repositories.length > 0) {
|
||||||
parsedOwner = owner;
|
parsedOwner = owner;
|
||||||
parsedRepositoryNames = repositories;
|
parsedRepositoryNames = repositories;
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`owner and repositories set, creating token for repositories "${repositories}" owned by "${owner}"`
|
`owner and repositories set, creating token for repositories "${repositories.join(',')}" owned by "${owner}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +75,11 @@ export async function main(
|
|||||||
let authentication, installationId, appSlug;
|
let authentication, installationId, appSlug;
|
||||||
// If at least one repository is set, get installation ID from that repository
|
// If at least one repository is set, get installation ID from that repository
|
||||||
|
|
||||||
if (parsedRepositoryNames) {
|
if (parsedRepositoryNames.length > 0) {
|
||||||
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), {
|
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), {
|
||||||
onFailedAttempt: (error) => {
|
onFailedAttempt: (error) => {
|
||||||
core.info(
|
core.info(
|
||||||
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
|
`Failed to create token for "${parsedRepositoryNames.join(',')}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
retries: 3,
|
retries: 3,
|
||||||
@@ -144,7 +146,7 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito
|
|||||||
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
|
||||||
const response = await request("GET /repos/{owner}/{repo}/installation", {
|
const response = await request("GET /repos/{owner}/{repo}/installation", {
|
||||||
owner: parsedOwner,
|
owner: parsedOwner,
|
||||||
repo: parsedRepositoryNames.split(",")[0],
|
repo: parsedRepositoryNames[0],
|
||||||
request: {
|
request: {
|
||||||
hook: auth.hook,
|
hook: auth.hook,
|
||||||
},
|
},
|
||||||
@@ -154,11 +156,11 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito
|
|||||||
const authentication = await auth({
|
const authentication = await auth({
|
||||||
type: "installation",
|
type: "installation",
|
||||||
installationId: response.data.id,
|
installationId: response.data.id,
|
||||||
repositoryNames: parsedRepositoryNames.split(","),
|
repositoryNames: parsedRepositoryNames,
|
||||||
});
|
});
|
||||||
|
|
||||||
const installationId = response.data.id;
|
const installationId = response.data.id;
|
||||||
const appSlug = response.data['app_slug'];
|
const appSlug = response.data['app_slug'];
|
||||||
|
|
||||||
return { authentication, installationId, appSlug };
|
return { authentication, installationId, appSlug };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ if (!privateKey) {
|
|||||||
throw new Error("Input required and not supplied: private-key");
|
throw new Error("Input required and not supplied: private-key");
|
||||||
}
|
}
|
||||||
const owner = core.getInput("owner");
|
const owner = core.getInput("owner");
|
||||||
const repositories = core.getInput("repositories");
|
const repositories = core.getInput("repositories")
|
||||||
|
.split(/[\n,]+/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(x => x !== '');
|
||||||
|
|
||||||
const skipTokenRevoke = Boolean(
|
const skipTokenRevoke = Boolean(
|
||||||
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
|
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "create-github-app-token",
|
"name": "create-github-app-token",
|
||||||
"version": "1.10.3",
|
"version": "1.10.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "create-github-app-token",
|
"name": "create-github-app-token",
|
||||||
"version": "1.10.3",
|
"version": "1.10.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { test } from "./main.js";
|
||||||
|
|
||||||
|
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a list of repos).
|
||||||
|
await test(() => {
|
||||||
|
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||||
|
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||||
|
// Intentional unnecessary whitespace to test parsing to array
|
||||||
|
process.env.INPUT_REPOSITORIES = `\n ${currentRepoName}\ntoolkit \n\n checkout \n`;
|
||||||
|
});
|
||||||
@@ -4,5 +4,6 @@ import { test } from "./main.js";
|
|||||||
await test(() => {
|
await test(() => {
|
||||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||||
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
|
||||||
process.env.INPUT_REPOSITORIES = `${currentRepoName},toolkit`;
|
// Intentional unnecessary whitespace to test parsing to array
|
||||||
|
process.env.INPUT_REPOSITORIES = ` ${currentRepoName}, toolkit ,checkout`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,6 +134,25 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||||
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
::save-state name=expiresAt::2016-07-11T22:14:10Z`
|
||||||
|
|
||||||
|
## main-token-get-owner-set-repo-set-to-many-newline.test.js
|
||||||
|
|
||||||
|
> stderr
|
||||||
|
|
||||||
|
''
|
||||||
|
|
||||||
|
> stdout
|
||||||
|
|
||||||
|
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit,checkout" 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`
|
||||||
|
|
||||||
## main-token-get-owner-set-repo-set-to-many.test.js
|
## main-token-get-owner-set-repo-set-to-many.test.js
|
||||||
|
|
||||||
> stderr
|
> stderr
|
||||||
@@ -142,7 +161,7 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
|
|
||||||
> stdout
|
> stdout
|
||||||
|
|
||||||
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit" owned by "actions"␊
|
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit,checkout" owned by "actions"␊
|
||||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||||
␊
|
␊
|
||||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user