diff --git a/.changeset/loud-owls-beam.md b/.changeset/loud-owls-beam.md new file mode 100644 index 0000000000..764bfdf72a --- /dev/null +++ b/.changeset/loud-owls-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Added githubApp authentication to the scaffolder-backend plugin diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts index 5b74ee81ca..81ccdeb026 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts @@ -18,14 +18,20 @@ import path from 'path'; import { Git } from '@backstage/backend-common'; import { PreparerBase, PreparerOptions } from './types'; import parseGitUrl from 'git-url-parse'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { + GitHubIntegrationConfig, + GithubCredentialsProvider, +} from '@backstage/integration'; export class GithubPreparer implements PreparerBase { static fromConfig(config: GitHubIntegrationConfig) { - return new GithubPreparer({ token: config.token }); + const credentialsProvider = GithubCredentialsProvider.create(config); + return new GithubPreparer({ credentialsProvider }); } - constructor(private readonly config: { token?: string }) {} + constructor( + private readonly config: { credentialsProvider: GithubCredentialsProvider }, + ) {} async prepare({ url, workspacePath, logger }: PreparerOptions) { const parsedGitUrl = parseGitUrl(url); @@ -36,10 +42,14 @@ export class GithubPreparer implements PreparerBase { parsedGitUrl.filepath ?? '', ); - const git = this.config.token + const { token } = await this.config.credentialsProvider.getCredentials({ + url, + }); + + const git = token ? Git.fromAuth({ username: 'x-access-token', - password: this.config.token, + password: token, logger, }) : Git.fromAuth({ logger }); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 407a1633c2..a449eddd5d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -16,7 +16,10 @@ import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { initRepoAndPush } from './helpers'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { + GitHubIntegrationConfig, + GithubCredentialsProvider, +} from '@backstage/integration'; import parseGitUrl from 'git-url-parse'; import { Octokit } from '@octokit/rest'; import path from 'path'; @@ -28,27 +31,24 @@ export class GithubPublisher implements PublisherBase { config: GitHubIntegrationConfig, { repoVisibility }: { repoVisibility: RepoVisibilityOptions }, ) { - if (!config.token) { + if (!config.token && !config.apps) { return undefined; } - const githubClient = new Octokit({ - auth: config.token, - baseUrl: config.apiBaseUrl, - }); + const credentialsProvider = GithubCredentialsProvider.create(config); return new GithubPublisher({ - token: config.token, - client: githubClient, + credentialsProvider, repoVisibility, + apiBaseUrl: config.apiBaseUrl, }); } constructor( private readonly config: { - token: string; - client: Octokit; + credentialsProvider: GithubCredentialsProvider; repoVisibility: RepoVisibilityOptions; + apiBaseUrl: string | undefined; }, ) {} @@ -59,9 +59,25 @@ export class GithubPublisher implements PublisherBase { }: PublisherOptions): Promise { const { owner, name } = parseGitUrl(values.storePath); + const { token } = await this.config.credentialsProvider.getCredentials({ + url: values.storePath, + }); + + if (!token) { + throw new Error( + `No token could be acquired for URL: ${values.storePath}`, + ); + } + + const client = new Octokit({ + auth: token, + baseUrl: this.config.apiBaseUrl, + }); + const description = values.description as string; const access = values.access as string; const remoteUrl = await this.createRemote({ + client, description, access, name, @@ -73,7 +89,7 @@ export class GithubPublisher implements PublisherBase { remoteUrl, auth: { username: 'x-access-token', - password: this.config.token, + password: token, }, logger, }); @@ -86,27 +102,28 @@ export class GithubPublisher implements PublisherBase { } private async createRemote(opts: { + client: Octokit; access: string; name: string; owner: string; description: string; }) { - const { access, description, owner, name } = opts; + const { client, access, description, owner, name } = opts; - const user = await this.config.client.users.getByUsername({ + const user = await client.users.getByUsername({ username: owner, }); const repoCreationPromise = user.data.type === 'Organization' - ? this.config.client.repos.createInOrg({ + ? client.repos.createInOrg({ name, org: owner, private: this.config.repoVisibility !== 'public', visibility: this.config.repoVisibility, description, }) - : this.config.client.repos.createForAuthenticatedUser({ + : client.repos.createForAuthenticatedUser({ name, private: this.config.repoVisibility === 'private', description, @@ -116,7 +133,7 @@ export class GithubPublisher implements PublisherBase { if (access?.startsWith(`${owner}/`)) { const [, team] = access.split('/'); - await this.config.client.teams.addOrUpdateRepoPermissionsInOrg({ + await client.teams.addOrUpdateRepoPermissionsInOrg({ org: owner, team_slug: team, owner, @@ -125,7 +142,7 @@ export class GithubPublisher implements PublisherBase { }); // no need to add access if it's the person who own's the personal account } else if (access && access !== owner) { - await this.config.client.repos.addCollaborator({ + await client.repos.addCollaborator({ owner, repo: name, username: access,