diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts index 0d4353c857..e2449917ab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts @@ -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'); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts index a3eeec4373..3ac66a0023 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts @@ -52,7 +52,10 @@ export class OctokitProvider { * * @param repoUrl - Repository URL */ - async getOctokit(repoUrl: string): Promise { + async getOctokit( + repoUrl: string, + options?: { token?: string }, + ): Promise { 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( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index 1f7f0f60b9..0c8b4d4aab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -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, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts index 39e8c3aa70..660c7793f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts @@ -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'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index 7e394a8d8b..2fd26c9de8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -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( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index fbfe87b076..bc0be1a309 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -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( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 081f9616ac..c91c703eaa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -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({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 841d4c7a82..50d54c93c8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -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 => { 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; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index 5941f52746..be81db45d8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -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( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts index 2ac369249b..9ed4efe748 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -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;