feat: added support for overriding the token used for publish actions.

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-01-26 16:23:05 +01:00
parent c91ba8cf2f
commit 2c34749bb6
10 changed files with 157 additions and 30 deletions
@@ -74,4 +74,15 @@ describe('getOctokit', () => {
expect(owner).toBe('owner');
expect(repo).toBe('bob');
});
it('should return an octokit client with the passed in token if it is provided', async () => {
const { client, token, owner, repo } = await octokitProvider.getOctokit(
'github.com?repo=bob&owner=owner',
{ token: 'tokenlols2' },
);
expect(client).toBeDefined();
expect(token).toBe('tokenlols2');
expect(owner).toBe('owner');
expect(repo).toBe('bob');
});
});
@@ -52,7 +52,10 @@ export class OctokitProvider {
*
* @param repoUrl - Repository URL
*/
async getOctokit(repoUrl: string): Promise<OctokitIntegration> {
async getOctokit(
repoUrl: string,
options?: { token?: string },
): Promise<OctokitIntegration> {
const { owner, repo, host } = parseRepoUrl(repoUrl, this.integrations);
if (!owner) {
@@ -65,7 +68,19 @@ export class OctokitProvider {
throw new InputError(`No integration for host ${host}`);
}
// TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's
// Short circuit the internal Github Token provider the token provided
// by the action or the caller.
if (options?.token) {
const client = new Octokit({
auth: options.token,
baseUrl: integrationConfig.apiBaseUrl,
previews: ['nebula-preview'],
});
return { client, token: options.token, owner, repo };
}
// TODO(blam): Consider changing this API to have owner, repoo interface instead of URL as the it's
// needless to create URL and then parse again the other side.
const { token } = await this.githubCredentialsProvider.getCredentials({
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
@@ -37,6 +37,7 @@ export function createGithubActionsDispatchAction(options: {
workflowId: string;
branchOrTagName: string;
workflowInputs?: { [key: string]: string };
token?: string;
}>({
id: 'github:actions:dispatch',
description:
@@ -68,18 +69,31 @@ export function createGithubActionsDispatchAction(options: {
'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ',
type: 'object',
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITHUB_TOKEN to use for authorization to GitHub',
},
},
},
},
async handler(ctx) {
const { repoUrl, workflowId, branchOrTagName, workflowInputs } =
ctx.input;
const {
repoUrl,
workflowId,
branchOrTagName,
workflowInputs,
token: providedToken,
} = ctx.input;
ctx.logger.info(
`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`,
);
const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl);
const { client, owner, repo } = await octokitProvider.getOctokit(
repoUrl,
{ token: providedToken },
);
await client.rest.actions.createWorkflowDispatch({
owner,
@@ -47,6 +47,7 @@ export function createGithubWebhookAction(options: {
active?: boolean;
contentType?: ContentType;
insecureSsl?: boolean;
token?: string;
}>({
id: 'github:webhook',
description: 'Creates webhook for a repository on GitHub.',
@@ -107,6 +108,11 @@ export function createGithubWebhookAction(options: {
type: 'boolean',
description: `Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default 'false'`,
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITHUB_TOKEN to use for authorization to GitHub',
},
},
},
},
@@ -119,11 +125,15 @@ export function createGithubWebhookAction(options: {
active = true,
contentType = 'form',
insecureSsl = false,
token: providedToken,
} = ctx.input;
ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`);
const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl);
const { client, owner, repo } = await octokitProvider.getOctokit(
repoUrl,
{ token: providedToken },
);
try {
const insecure_ssl = insecureSsl ? '1' : '0';
@@ -34,6 +34,7 @@ export function createPublishAzureAction(options: {
description?: string;
defaultBranch?: string;
sourcePath?: string;
token?: string;
}>({
id: 'publish:azure',
description:
@@ -57,10 +58,16 @@ export function createPublishAzureAction(options: {
description: `Sets the default branch on the repository. The default value is 'master'`,
},
sourcePath: {
title:
title: 'Source Path',
description:
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.',
type: 'string',
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The AZURE_TOKEN to use for authorization to Azure',
},
},
},
output: {
@@ -98,12 +105,13 @@ export function createPublishAzureAction(options: {
`No matching integration configuration for host ${host}, please check your integrations config`,
);
}
if (!integrationConfig.config.token) {
if (!integrationConfig.config.token && !ctx.input.token) {
throw new InputError(`No token provided for Azure Integration ${host}`);
}
const authHandler = getPersonalAccessTokenHandler(
integrationConfig.config.token,
);
const token = ctx.input.token ?? integrationConfig.config.token!;
const authHandler = getPersonalAccessTokenHandler(token);
const webApi = new WebApi(`https://${host}/${organization}`, authHandler);
const client = await webApi.getGitApi();
@@ -139,7 +147,7 @@ export function createPublishAzureAction(options: {
defaultBranch,
auth: {
username: 'notempty',
password: integrationConfig.config.token,
password: token,
},
logger: ctx.logger,
commitMessage: config.getOptionalString(
@@ -205,6 +205,7 @@ export function createPublishBitbucketAction(options: {
repoVisibility: 'private' | 'public';
sourcePath?: string;
enableLFS: boolean;
token?: string;
}>({
id: 'publish:bitbucket',
description:
@@ -233,15 +234,23 @@ export function createPublishBitbucketAction(options: {
description: `Sets the default branch on the repository. The default value is 'master'`,
},
sourcePath: {
title:
title: 'Source Path',
description:
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.',
type: 'string',
},
enableLFS: {
title:
title: 'Enable LFS?',
description:
'Enable LFS for the repository. Only available for hosted Bitbucket.',
type: 'boolean',
},
token: {
title: 'Authentication Token',
type: 'string',
description:
'The BITBUCKET_TOKEN to use for authorization to BitBucket',
},
},
},
output: {
@@ -296,7 +305,12 @@ export function createPublishBitbucketAction(options: {
);
}
const authorization = getAuthorizationHeader(integrationConfig.config);
const authorization = getAuthorizationHeader(
ctx.input.token
? { host: integrationConfig.config.host, token: ctx.input.token }
: integrationConfig.config,
);
const apiBaseUrl = integrationConfig.config.apiBaseUrl;
const createMethod =
@@ -320,17 +334,28 @@ export function createPublishBitbucketAction(options: {
email: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
await initRepoAndPush({
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
remoteUrl,
auth: {
let auth;
if (ctx.input.token) {
auth = {
username: 'x-token-auth',
password: ctx.input.token,
};
} else {
auth = {
username: integrationConfig.config.username
? integrationConfig.config.username
: 'x-token-auth',
password: integrationConfig.config.appPassword
? integrationConfig.config.appPassword
: integrationConfig.config.token ?? '',
},
};
}
await initRepoAndPush({
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
remoteUrl,
auth,
defaultBranch,
logger: ctx.logger,
commitMessage: config.getOptionalString(
@@ -52,6 +52,7 @@ export function createPublishGithubAction(options: {
requireCodeOwnerReviews?: boolean;
repoVisibility: 'private' | 'internal' | 'public';
collaborators: Collaborator[];
token?: string;
topics?: string[];
}>({
id: 'publish:github',
@@ -77,7 +78,8 @@ export function createPublishGithubAction(options: {
type: 'string',
},
requireCodeOwnerReviews: {
title:
title: 'Require CODEOWNER Reviews?',
description:
'Require an approved review in PR including files with a designated Code Owner',
type: 'boolean',
},
@@ -92,7 +94,8 @@ export function createPublishGithubAction(options: {
description: `Sets the default branch on the repository. The default value is 'master'`,
},
sourcePath: {
title:
title: 'Source Path',
description:
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.',
type: 'string',
},
@@ -116,6 +119,11 @@ export function createPublishGithubAction(options: {
},
},
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITHUB_TOKEN to use for authorization to GitHub',
},
topics: {
title: 'Topics',
type: 'array',
@@ -149,10 +157,12 @@ export function createPublishGithubAction(options: {
defaultBranch = 'master',
collaborators,
topics,
token: providedToken,
} = ctx.input;
const { client, token, owner, repo } = await octokitProvider.getOctokit(
repoUrl,
{ token: providedToken },
);
const user = await client.rest.users.getByUsername({
@@ -55,6 +55,7 @@ export type GithubPullRequestActionInput = {
repoUrl: string;
targetPath?: string;
sourcePath?: string;
token?: string;
};
export type ClientFactoryInput = {
@@ -63,6 +64,7 @@ export type ClientFactoryInput = {
host: string;
owner: string;
repo: string;
token?: string;
};
export const defaultClientFactory = async ({
@@ -71,13 +73,22 @@ export const defaultClientFactory = async ({
owner,
repo,
host = 'github.com',
token: providedToken,
}: ClientFactoryInput): Promise<PullRequestCreator> => {
const integrationConfig = integrations.github.byHost(host)?.config;
const OctokitPR = Octokit.plugin(createPullRequest);
if (!integrationConfig) {
throw new InputError(`No integration for host ${host}`);
}
if (providedToken) {
return new OctokitPR({
auth: providedToken,
baseUrl: integrationConfig.apiBaseUrl,
});
}
const credentialsProvider =
githubCredentialsProvider ||
SingleInstanceGithubCredentialsProvider.create(integrationConfig);
@@ -94,8 +105,6 @@ export const defaultClientFactory = async ({
);
}
const OctokitPR = Octokit.plugin(createPullRequest);
return new OctokitPR({
auth: token,
baseUrl: integrationConfig.apiBaseUrl,
@@ -151,6 +160,11 @@ export const createPublishGithubPullRequestAction = ({
title: 'Repository Subdirectory',
description: 'Subdirectory of repository to apply changes to',
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITHUB_TOKEN to use for authorization to GitHub',
},
},
},
output: {
@@ -173,6 +187,7 @@ export const createPublishGithubPullRequestAction = ({
description,
targetPath,
sourcePath,
token: providedToken,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
@@ -189,7 +204,9 @@ export const createPublishGithubPullRequestAction = ({
host,
owner,
repo,
token: providedToken,
});
const fileRoot = sourcePath
? resolveSafeChildPath(ctx.workspacePath, sourcePath)
: ctx.workspacePath;
@@ -33,6 +33,7 @@ export function createPublishGitlabAction(options: {
defaultBranch?: string;
repoVisibility: 'private' | 'internal' | 'public';
sourcePath?: string;
token?: string;
}>({
id: 'publish:gitlab',
description:
@@ -57,10 +58,16 @@ export function createPublishGitlabAction(options: {
description: `Sets the default branch on the repository. The default value is 'master'`,
},
sourcePath: {
title:
title: 'Source Path',
description:
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.',
type: 'string',
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITLAB_TOKEN to use for authorization to GitLab',
},
},
},
output: {
@@ -100,13 +107,15 @@ export function createPublishGitlabAction(options: {
);
}
if (!integrationConfig.config.token) {
if (!integrationConfig.config.token && !ctx.input.token) {
throw new InputError(`No token available for host ${host}`);
}
const token = ctx.input.token || integrationConfig.config.token!;
const client = new Gitlab({
host: integrationConfig.config.baseUrl,
token: integrationConfig.config.token,
token,
});
let { id: targetNamespace } = (await client.Namespaces.show(owner)) as {
@@ -140,7 +149,7 @@ export function createPublishGitlabAction(options: {
defaultBranch,
auth: {
username: 'oauth2',
password: integrationConfig.config.token,
password: token,
},
logger: ctx.logger,
commitMessage: config.getOptionalString(
@@ -31,6 +31,7 @@ export type GitlabMergeRequestActionInput = {
description: string;
branchName: string;
targetPath: string;
token?: string;
};
export const createPublishGitlabMergeRequestAction = (options: {
@@ -75,6 +76,11 @@ export const createPublishGitlabMergeRequestAction = (options: {
title: 'Repository Subdirectory',
description: 'Subdirectory of repository to apply changes to',
},
token: {
title: 'Authentication Token',
type: 'string',
description: 'The GITLAB_TOKEN to use for authorization to GitLab',
},
},
},
output: {
@@ -107,13 +113,15 @@ export const createPublishGitlabMergeRequestAction = (options: {
);
}
if (!integrationConfig.config.token) {
if (!integrationConfig.config.token && !ctx.input.token) {
throw new InputError(`No token available for host ${host}`);
}
const token = ctx.input.token ?? integrationConfig.config.token!;
const api = new Gitlab({
host: integrationConfig.config.baseUrl,
token: integrationConfig.config.token,
token,
});
const fileRoot = ctx.workspacePath;