enterprise input; logic to generate ent token

This commit is contained in:
Stefan Petrushevski
2025-07-08 15:04:32 +02:00
parent 93c1f04d6f
commit cbc2930e9b
4 changed files with 178 additions and 68 deletions
+99 -38
View File
@@ -4,6 +4,7 @@ import pRetry from "p-retry";
/**
* @param {string} appId
* @param {string} privateKey
* @param {string} enterprise
* @param {string} owner
* @param {string[]} repositories
* @param {undefined | Record<string, string>} permissions
@@ -15,58 +16,70 @@ import pRetry from "p-retry";
export async function main(
appId,
privateKey,
enterprise,
owner,
repositories,
permissions,
core,
createAppAuth,
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 parsedRepositoryNames = [];
// If neither owner nor repositories are set, default to current repository
if (!owner && repositories.length === 0) {
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
parsedOwner = owner;
parsedRepositoryNames = [repo];
// Skip owner/repository parsing if enterprise is set
if (!enterprise) {
// If neither owner nor repositories are set, default to current repository
if (!owner && repositories.length === 0) {
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
parsedOwner = owner;
parsedRepositoryNames = [repo];
core.info(
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner}/${repo}).`
);
}
core.info(
`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 (owner && repositories.length === 0) {
parsedOwner = owner;
// If only an owner is set, default to all repositories from that owner
if (owner && repositories.length === 0) {
parsedOwner = owner;
core.info(
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
);
}
core.info(
`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 (!owner && repositories.length > 0) {
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
parsedRepositoryNames = repositories;
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
if (!owner && repositories.length > 0) {
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
parsedRepositoryNames = repositories;
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
.map((repo) => `\n- ${parsedOwner}/${repo}`)
.join("")}`
);
}
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
.map((repo) => `\n- ${parsedOwner}/${repo}`)
.join("")}`
);
}
// If both owner and repositories are set, use those values
if (owner && repositories.length > 0) {
parsedOwner = owner;
parsedRepositoryNames = repositories;
// If both owner and repositories are set, use those values
if (owner && repositories.length > 0) {
parsedOwner = owner;
parsedRepositoryNames = repositories;
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
);
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
);
}
} else {
core.info(`Creating enterprise installation token for enterprise "${enterprise}".`);
}
const auth = createAppAuth({
@@ -76,9 +89,22 @@ export async function main(
});
let authentication, installationId, appSlug;
// If at least one repository is set, get installation ID from that repository
if (parsedRepositoryNames.length > 0) {
// If enterprise is set, get installation ID from the enterprise
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(
() =>
getTokenFromRepository(
@@ -181,3 +207,38 @@ async function getTokenFromRepository(
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 };
}