enterprise input; logic to generate ent token
This commit is contained in:
@@ -17,6 +17,9 @@ inputs:
|
|||||||
repositories:
|
repositories:
|
||||||
description: "Comma or newline-separated list of 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
|
||||||
|
enterprise:
|
||||||
|
description: "Enterprise slug for enterprise-level app installations (cannot be used with 'owner' or 'repositories')"
|
||||||
|
required: false
|
||||||
skip-token-revoke:
|
skip-token-revoke:
|
||||||
description: "If true, the token will not be revoked when the current job is complete"
|
description: "If true, the token will not be revoked when the current job is complete"
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
Vendored
+74
-30
@@ -42523,39 +42523,46 @@ async function pRetry(input, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lib/main.js
|
// lib/main.js
|
||||||
async function main(appId2, privateKey2, owner2, repositories2, permissions2, core3, createAppAuth2, request2, skipTokenRevoke2) {
|
async function main(appId2, privateKey2, enterprise2, owner2, repositories2, permissions2, core3, createAppAuth2, request2, skipTokenRevoke2) {
|
||||||
|
if (enterprise2 && (owner2 || repositories2.length > 0)) {
|
||||||
|
throw new Error("Cannot use 'enterprise' input with 'owner' or 'repositories' inputs");
|
||||||
|
}
|
||||||
let parsedOwner = "";
|
let parsedOwner = "";
|
||||||
let parsedRepositoryNames = [];
|
let parsedRepositoryNames = [];
|
||||||
if (!owner2 && repositories2.length === 0) {
|
if (!enterprise2) {
|
||||||
const [owner3, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
|
if (!owner2 && repositories2.length === 0) {
|
||||||
parsedOwner = owner3;
|
const [owner3, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
|
||||||
parsedRepositoryNames = [repo];
|
parsedOwner = owner3;
|
||||||
core3.info(
|
parsedRepositoryNames = [repo];
|
||||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner3}/${repo}).`
|
core3.info(
|
||||||
);
|
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner3}/${repo}).`
|
||||||
}
|
);
|
||||||
if (owner2 && repositories2.length === 0) {
|
}
|
||||||
parsedOwner = owner2;
|
if (owner2 && repositories2.length === 0) {
|
||||||
core3.info(
|
parsedOwner = owner2;
|
||||||
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner2}.`
|
core3.info(
|
||||||
);
|
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner2}.`
|
||||||
}
|
);
|
||||||
if (!owner2 && repositories2.length > 0) {
|
}
|
||||||
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
if (!owner2 && repositories2.length > 0) {
|
||||||
parsedRepositoryNames = repositories2;
|
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
|
||||||
core3.info(
|
parsedRepositoryNames = repositories2;
|
||||||
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories2.map((repo) => `
|
core3.info(
|
||||||
|
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories2.map((repo) => `
|
||||||
- ${parsedOwner}/${repo}`).join("")}`
|
- ${parsedOwner}/${repo}`).join("")}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (owner2 && repositories2.length > 0) {
|
if (owner2 && repositories2.length > 0) {
|
||||||
parsedOwner = owner2;
|
parsedOwner = owner2;
|
||||||
parsedRepositoryNames = repositories2;
|
parsedRepositoryNames = repositories2;
|
||||||
core3.info(
|
core3.info(
|
||||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
|
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
|
||||||
${repositories2.map((repo) => `
|
${repositories2.map((repo) => `
|
||||||
- ${parsedOwner}/${repo}`).join("")}`
|
- ${parsedOwner}/${repo}`).join("")}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core3.info(`Creating enterprise installation token for enterprise "${enterprise2}".`);
|
||||||
}
|
}
|
||||||
const auth5 = createAppAuth2({
|
const auth5 = createAppAuth2({
|
||||||
appId: appId2,
|
appId: appId2,
|
||||||
@@ -42563,7 +42570,20 @@ async function main(appId2, privateKey2, owner2, repositories2, permissions2, co
|
|||||||
request: request2
|
request: request2
|
||||||
});
|
});
|
||||||
let authentication, installationId, appSlug;
|
let authentication, installationId, appSlug;
|
||||||
if (parsedRepositoryNames.length > 0) {
|
if (enterprise2) {
|
||||||
|
({ authentication, installationId, appSlug } = await pRetry(
|
||||||
|
() => getTokenFromEnterprise(request2, auth5, enterprise2, permissions2),
|
||||||
|
{
|
||||||
|
shouldRetry: (error) => error.status >= 500,
|
||||||
|
onFailedAttempt: (error) => {
|
||||||
|
core3.info(
|
||||||
|
`Failed to create token for enterprise "${enterprise2}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
retries: 3
|
||||||
|
}
|
||||||
|
));
|
||||||
|
} else if (parsedRepositoryNames.length > 0) {
|
||||||
({ authentication, installationId, appSlug } = await pRetry(
|
({ authentication, installationId, appSlug } = await pRetry(
|
||||||
() => getTokenFromRepository(
|
() => getTokenFromRepository(
|
||||||
request2,
|
request2,
|
||||||
@@ -42640,6 +42660,28 @@ async function getTokenFromRepository(request2, auth5, parsedOwner, parsedReposi
|
|||||||
const appSlug = response.data["app_slug"];
|
const appSlug = response.data["app_slug"];
|
||||||
return { authentication, installationId, appSlug };
|
return { authentication, installationId, appSlug };
|
||||||
}
|
}
|
||||||
|
async function getTokenFromEnterprise(request2, auth5, enterprise2, permissions2) {
|
||||||
|
const response = await request2("GET /app/installations", {
|
||||||
|
request: {
|
||||||
|
hook: auth5.hook
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const enterpriseInstallation = response.data.find(
|
||||||
|
(installation) => installation.target_type === "Enterprise"
|
||||||
|
);
|
||||||
|
if (!enterpriseInstallation) {
|
||||||
|
throw new Error(`No enterprise installation found. Available installations: ${response.data.map((i) => `${i.target_type}:${i.account?.login || "N/A"}`).join(", ")}`);
|
||||||
|
}
|
||||||
|
console.info(`### Found enterprise installation: ${JSON.stringify(enterpriseInstallation, null, 2)}`);
|
||||||
|
const authentication = await auth5({
|
||||||
|
type: "installation",
|
||||||
|
installationId: enterpriseInstallation.id,
|
||||||
|
permissions: permissions2
|
||||||
|
});
|
||||||
|
const installationId = enterpriseInstallation.id;
|
||||||
|
const appSlug = enterpriseInstallation["app_slug"];
|
||||||
|
return { authentication, installationId, appSlug };
|
||||||
|
}
|
||||||
|
|
||||||
// lib/request.js
|
// lib/request.js
|
||||||
var import_core = __toESM(require_core(), 1);
|
var import_core = __toESM(require_core(), 1);
|
||||||
@@ -42677,6 +42719,7 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) {
|
|||||||
}
|
}
|
||||||
var appId = import_core2.default.getInput("app-id");
|
var appId = import_core2.default.getInput("app-id");
|
||||||
var privateKey = import_core2.default.getInput("private-key");
|
var privateKey = import_core2.default.getInput("private-key");
|
||||||
|
var enterprise = import_core2.default.getInput("enterprise");
|
||||||
var owner = import_core2.default.getInput("owner");
|
var owner = import_core2.default.getInput("owner");
|
||||||
var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
|
var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
|
||||||
var skipTokenRevoke = import_core2.default.getBooleanInput("skip-token-revoke");
|
var skipTokenRevoke = import_core2.default.getBooleanInput("skip-token-revoke");
|
||||||
@@ -42684,6 +42727,7 @@ var permissions = getPermissionsFromInputs(process.env);
|
|||||||
var main_default = main(
|
var main_default = main(
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
|
enterprise,
|
||||||
owner,
|
owner,
|
||||||
repositories,
|
repositories,
|
||||||
permissions,
|
permissions,
|
||||||
|
|||||||
+99
-38
@@ -4,6 +4,7 @@ import pRetry from "p-retry";
|
|||||||
/**
|
/**
|
||||||
* @param {string} appId
|
* @param {string} appId
|
||||||
* @param {string} privateKey
|
* @param {string} privateKey
|
||||||
|
* @param {string} enterprise
|
||||||
* @param {string} owner
|
* @param {string} owner
|
||||||
* @param {string[]} repositories
|
* @param {string[]} repositories
|
||||||
* @param {undefined | Record<string, string>} permissions
|
* @param {undefined | Record<string, string>} permissions
|
||||||
@@ -15,58 +16,70 @@ import pRetry from "p-retry";
|
|||||||
export async function main(
|
export async function main(
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
|
enterprise,
|
||||||
owner,
|
owner,
|
||||||
repositories,
|
repositories,
|
||||||
permissions,
|
permissions,
|
||||||
core,
|
core,
|
||||||
createAppAuth,
|
createAppAuth,
|
||||||
request,
|
request,
|
||||||
skipTokenRevoke
|
skipTokenRevoke,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
// Validate mutual exclusivity of enterprise with owner/repositories
|
||||||
|
if (enterprise && (owner || repositories.length > 0)) {
|
||||||
|
throw new Error("Cannot use 'enterprise' input with 'owner' or 'repositories' inputs");
|
||||||
|
}
|
||||||
|
|
||||||
let parsedOwner = "";
|
let parsedOwner = "";
|
||||||
let parsedRepositoryNames = [];
|
let parsedRepositoryNames = [];
|
||||||
|
|
||||||
// If neither owner nor repositories are set, default to current repository
|
// Skip owner/repository parsing if enterprise is set
|
||||||
if (!owner && repositories.length === 0) {
|
if (!enterprise) {
|
||||||
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
|
// If neither owner nor repositories are set, default to current repository
|
||||||
parsedOwner = owner;
|
if (!owner && repositories.length === 0) {
|
||||||
parsedRepositoryNames = [repo];
|
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
|
||||||
|
parsedOwner = owner;
|
||||||
|
parsedRepositoryNames = [repo];
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner}/${repo}).`
|
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner}/${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.length === 0) {
|
if (owner && repositories.length === 0) {
|
||||||
parsedOwner = owner;
|
parsedOwner = owner;
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
|
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.length > 0) {
|
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(
|
||||||
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
|
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
|
||||||
.map((repo) => `\n- ${parsedOwner}/${repo}`)
|
.map((repo) => `\n- ${parsedOwner}/${repo}`)
|
||||||
.join("")}`
|
.join("")}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both owner and repositories are set, use those values
|
// If both owner and repositories are set, use those values
|
||||||
if (owner && repositories.length > 0) {
|
if (owner && repositories.length > 0) {
|
||||||
parsedOwner = owner;
|
parsedOwner = owner;
|
||||||
parsedRepositoryNames = repositories;
|
parsedRepositoryNames = repositories;
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
|
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
|
||||||
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
|
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.info(`Creating enterprise installation token for enterprise "${enterprise}".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth = createAppAuth({
|
const auth = createAppAuth({
|
||||||
@@ -76,9 +89,22 @@ 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 enterprise is set, get installation ID from the enterprise
|
||||||
if (parsedRepositoryNames.length > 0) {
|
if (enterprise) {
|
||||||
|
({ authentication, installationId, appSlug } = await pRetry(
|
||||||
|
() => getTokenFromEnterprise(request, auth, enterprise, permissions),
|
||||||
|
{
|
||||||
|
shouldRetry: (error) => error.status >= 500,
|
||||||
|
onFailedAttempt: (error) => {
|
||||||
|
core.info(
|
||||||
|
`Failed to create token for enterprise "${enterprise}" (attempt ${error.attemptNumber}): ${error.message}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
retries: 3,
|
||||||
|
}
|
||||||
|
));
|
||||||
|
} else if (parsedRepositoryNames.length > 0) {
|
||||||
({ authentication, installationId, appSlug } = await pRetry(
|
({ authentication, installationId, appSlug } = await pRetry(
|
||||||
() =>
|
() =>
|
||||||
getTokenFromRepository(
|
getTokenFromRepository(
|
||||||
@@ -181,3 +207,38 @@ async function getTokenFromRepository(
|
|||||||
|
|
||||||
return { authentication, installationId, appSlug };
|
return { authentication, installationId, appSlug };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getTokenFromEnterprise(request, auth, enterprise, permissions) {
|
||||||
|
// Get all installations and find the enterprise one
|
||||||
|
// https://docs.github.com/rest/apps/apps#list-installations-for-the-authenticated-app
|
||||||
|
// Note: Currently we do not have a way to get the installation for an enterprise directly,
|
||||||
|
// so as a workaround we need to list all installations and filter for the enterprise one.
|
||||||
|
const response = await request("GET /app/installations", {
|
||||||
|
request: {
|
||||||
|
hook: auth.hook,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the enterprise installation
|
||||||
|
const enterpriseInstallation = response.data.find(
|
||||||
|
installation => installation.target_type === "Enterprise"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!enterpriseInstallation) {
|
||||||
|
throw new Error(`No enterprise installation found. Available installations: ${response.data.map(i => `${i.target_type}:${i.account?.login || 'N/A'}`).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`### Found enterprise installation: ${JSON.stringify(enterpriseInstallation, null, 2)}`);
|
||||||
|
|
||||||
|
// Get token for the enterprise installation
|
||||||
|
const authentication = await auth({
|
||||||
|
type: "installation",
|
||||||
|
installationId: enterpriseInstallation.id,
|
||||||
|
permissions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const installationId = enterpriseInstallation.id;
|
||||||
|
const appSlug = enterpriseInstallation["app_slug"];
|
||||||
|
|
||||||
|
return { authentication, installationId, appSlug };
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ if (!process.env.GITHUB_REPOSITORY_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 enterprise = core.getInput("enterprise");
|
||||||
const owner = core.getInput("owner");
|
const owner = core.getInput("owner");
|
||||||
const repositories = core
|
const repositories = core
|
||||||
.getInput("repositories")
|
.getInput("repositories")
|
||||||
@@ -32,6 +33,7 @@ const permissions = getPermissionsFromInputs(process.env);
|
|||||||
export default main(
|
export default main(
|
||||||
appId,
|
appId,
|
||||||
privateKey,
|
privateKey,
|
||||||
|
enterprise,
|
||||||
owner,
|
owner,
|
||||||
repositories,
|
repositories,
|
||||||
permissions,
|
permissions,
|
||||||
|
|||||||
Reference in New Issue
Block a user