From 4c0c0bfd509244ccea0ea1720991a675cb86d03a Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 11 Sep 2020 09:28:07 -0400 Subject: [PATCH 01/10] Add the ability to configure repository visibility via config --- packages/backend/src/plugins/scaffolder.ts | 19 +- .../scaffolder/stages/publish/github.test.ts | 318 ++++++++++-------- .../src/scaffolder/stages/publish/github.ts | 31 +- 3 files changed, 219 insertions(+), 149 deletions(-) diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index a9e5f185ec..abbcdae1ae 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -23,12 +23,16 @@ import { GithubPublisher, CreateReactAppTemplater, Templaters, + RepoVisilityOptions, } from '@backstage/plugin-scaffolder-backend'; import { Octokit } from '@octokit/rest'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; -export default async function createPlugin({ logger }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); @@ -42,8 +46,17 @@ export default async function createPlugin({ logger }: PluginEnvironment) { preparers.register('file', filePreparer); preparers.register('github', githubPreparer); - const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); - const publisher = new GithubPublisher({ client: githubClient }); + const githubToken = config.getString('scaffolder.github.token'); + const repoVisibility = config.getString( + 'scaffolder.github.visibility', + ) as RepoVisilityOptions; + + const githubClient = new Octokit({ auth: githubToken }); + const publisher = new GithubPublisher({ + client: githubClient, + token: githubToken, + repoVisibility, + }); const dockerClient = new Docker(); return await createRouter({ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 718c01438c..dd1152d3d5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -53,14 +53,78 @@ const { }; describe('GitHub Publisher', () => { - const publisher = new GithubPublisher({ client: new Octokit() }); - beforeEach(() => { jest.clearAllMocks(); }); - describe('publish: createRemoteInGithub', () => { - it('should use octokit to create a repo in an organisation if the organisation property is set', async () => { + describe('with public repo visibility', () => { + const publisher = new GithubPublisher({ + client: new Octokit(), + token: 'abc', + repoVisibility: 'public', + }); + + describe('publish: createRemoteInGithub', () => { + it('should use octokit to create a repo in an organisation if the organisation property is set', async () => { + mockGithubClient.repos.createInOrg.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + + await publisher.publish({ + values: { + storePath: 'blam/test', + owner: 'bob', + }, + directory: '/tmp/test', + }); + + expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({ + org: 'blam', + name: 'test', + private: false, + visibility: 'public', + }); + }); + + it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => { + mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'User', + }, + } as OctokitResponse); + + await publisher.publish({ + values: { + storePath: 'blam/test', + owner: 'bob', + }, + directory: '/tmp/test', + }); + + expect( + mockGithubClient.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + name: 'test', + }); + }); + }); + + describe('publish: createGitDirectory', () => { + const values = { + isOrg: true, + storePath: 'blam/test', + owner: 'lols', + }; + + const mockDir = '/tmp/test/dir'; + mockGithubClient.repos.createInOrg.mockResolvedValue({ data: { clone_url: 'mockclone', @@ -72,8 +136,116 @@ describe('GitHub Publisher', () => { }, } as OctokitResponse); + it('should call init on the repo with the directory', async () => { + await publisher.publish({ + values, + directory: mockDir, + }); + + expect(Repository.init).toHaveBeenCalledWith(mockDir, 0); + }); + + it('should call refresh index on the index and write the new files', async () => { + await publisher.publish({ + values, + directory: mockDir, + }); + + expect(mockRepo.refreshIndex).toHaveBeenCalled(); + }); + + it('should call add all files and write', async () => { + await publisher.publish({ + values, + directory: mockDir, + }); + + expect(mockIndex.addAll).toHaveBeenCalled(); + expect(mockIndex.write).toHaveBeenCalled(); + expect(mockIndex.writeTree).toHaveBeenCalled(); + }); + + it('should create a commit with on head with the right name and commiter', async () => { + const mockSignature = { mockSignature: 'bloblly' }; + Signature.now.mockReturnValue(mockSignature); + + await publisher.publish({ + values, + directory: mockDir, + }); + + expect(Signature.now).toHaveBeenCalledTimes(2); + expect(Signature.now).toHaveBeenCalledWith( + 'Scaffolder', + 'scaffolder@backstage.io', + ); + + expect(mockRepo.createCommit).toHaveBeenCalledWith( + 'HEAD', + mockSignature, + mockSignature, + 'initial commit', + 'mockoid', + [], + ); + }); + + it('creates a remote with the repo and remote', async () => { + await publisher.publish({ + values, + directory: mockDir, + }); + + expect(Remote.create).toHaveBeenCalledWith( + mockRepo, + 'origin', + 'mockclone', + ); + }); + + it('shoud push to the remote repo', async () => { + await publisher.publish({ + values, + directory: mockDir, + }); + + const [remotes, { callbacks }] = mockRemote.push.mock + .calls[0] as NodeGit.PushOptions[]; + + expect(remotes).toEqual(['refs/heads/master:refs/heads/master']); + + callbacks?.credentials?.(); + + expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith( + 'abc', + 'x-oauth-basic', + ); + }); + }); + }); + + describe('with internal repo visibility', () => { + const publisher = new GithubPublisher({ + client: new Octokit(), + token: 'abc', + repoVisibility: 'internal', + }); + + it('creates a repo in an organisation with internal visibility', async () => { + mockGithubClient.repos.createInOrg.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'Organization', + }, + } as OctokitResponse); + await publisher.publish({ values: { + isOrg: true, storePath: 'blam/test', owner: 'bob', }, @@ -83,143 +255,9 @@ describe('GitHub Publisher', () => { expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({ org: 'blam', name: 'test', + private: true, + visibility: 'internal', }); }); - - it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => { - mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - clone_url: 'mockclone', - }, - } as OctokitResponse); - mockGithubClient.users.getByUsername.mockResolvedValue({ - data: { - type: 'User', - }, - } as OctokitResponse); - - await publisher.publish({ - values: { - storePath: 'blam/test', - owner: 'bob', - }, - directory: '/tmp/test', - }); - - expect( - mockGithubClient.repos.createForAuthenticatedUser, - ).toHaveBeenCalledWith({ - name: 'test', - }); - }); - }); - - describe('publish: createGitDirectory', () => { - const values = { - isOrg: true, - storePath: 'blam/test', - owner: 'lols', - }; - - const mockDir = '/tmp/test/dir'; - - mockGithubClient.repos.createInOrg.mockResolvedValue({ - data: { - clone_url: 'mockclone', - }, - } as OctokitResponse); - mockGithubClient.users.getByUsername.mockResolvedValue({ - data: { - type: 'Organization', - }, - } as OctokitResponse); - - it('should call init on the repo with the directory', async () => { - await publisher.publish({ - values, - directory: mockDir, - }); - - expect(Repository.init).toHaveBeenCalledWith(mockDir, 0); - }); - - it('should call refresh index on the index and write the new files', async () => { - await publisher.publish({ - values, - directory: mockDir, - }); - - expect(mockRepo.refreshIndex).toHaveBeenCalled(); - }); - - it('should call add all files and write', async () => { - await publisher.publish({ - values, - directory: mockDir, - }); - - expect(mockIndex.addAll).toHaveBeenCalled(); - expect(mockIndex.write).toHaveBeenCalled(); - expect(mockIndex.writeTree).toHaveBeenCalled(); - }); - - it('should create a commit with on head with the right name and commiter', async () => { - const mockSignature = { mockSignature: 'bloblly' }; - Signature.now.mockReturnValue(mockSignature); - - await publisher.publish({ - values, - directory: mockDir, - }); - - expect(Signature.now).toHaveBeenCalledTimes(2); - expect(Signature.now).toHaveBeenCalledWith( - 'Scaffolder', - 'scaffolder@backstage.io', - ); - - expect(mockRepo.createCommit).toHaveBeenCalledWith( - 'HEAD', - mockSignature, - mockSignature, - 'initial commit', - 'mockoid', - [], - ); - }); - - it('creates a remote with the repo and remote', async () => { - await publisher.publish({ - values, - directory: mockDir, - }); - - expect(Remote.create).toHaveBeenCalledWith( - mockRepo, - 'origin', - 'mockclone', - ); - }); - - it('shoud push to the remote repo', async () => { - await publisher.publish({ - values, - directory: mockDir, - }); - - const [remotes, { callbacks }] = mockRemote.push.mock - .calls[0] as NodeGit.PushOptions[]; - - expect(remotes).toEqual(['refs/heads/master:refs/heads/master']); - - process.env.GITHUb_ACCESS_TOKEN = 'blob'; - - callbacks?.credentials?.(); - - expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith( - process.env.GITHUB_ACCESS_TOKEN, - 'x-oauth-basic', - ); - }); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index f3306e3662..22a46ddf1b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -21,10 +21,27 @@ import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; +export type RepoVisilityOptions = 'private' | 'internal' | 'public'; + +interface GithubPublisherParams { + client: Octokit; + token: string; + repoVisibility: RepoVisilityOptions; +} + export class GithubPublisher implements PublisherBase { private client: Octokit; - constructor({ client }: { client: Octokit }) { + private token: string; + private repoVisibility: RepoVisilityOptions; + + constructor({ + client, + token, + repoVisibility = 'public', + }: GithubPublisherParams) { this.client = client; + this.token = token; + this.repoVisibility = repoVisibility; } async publish({ @@ -49,7 +66,12 @@ export class GithubPublisher implements PublisherBase { const repoCreationPromise = user.data.type === 'Organization' - ? this.client.repos.createInOrg({ name, org: owner }) + ? this.client.repos.createInOrg({ + name, + org: owner, + private: this.repoVisibility !== 'public', + visibility: this.repoVisibility, + }) : this.client.repos.createForAuthenticatedUser({ name }); const { data } = await repoCreationPromise; @@ -76,10 +98,7 @@ export class GithubPublisher implements PublisherBase { await remoteRepo.push(['refs/heads/master:refs/heads/master'], { callbacks: { credentials: () => { - return Cred.userpassPlaintextNew( - process.env.GITHUB_ACCESS_TOKEN as string, - 'x-oauth-basic', - ); + return Cred.userpassPlaintextNew(this.token, 'x-oauth-basic'); }, }, }); From 6ea4ac7b17e2ab6643e25873bcfb22906eb71d0a Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 11 Sep 2020 10:11:11 -0400 Subject: [PATCH 02/10] Add config for scaffolder to create-app's app-config.yaml --- .../create-app/templates/default-app/app-config.yaml.hbs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 7b86b4f50e..7282b732b4 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -47,6 +47,13 @@ lighthouse: auth: providers: {} +scaffolder: + github: + token: + $secret: + env: GITHUB_ACCESS_TOKEN + visibility: public # or 'internal' or 'private' + catalog: locations: # Backstage example components From a7d5ade5a16599e5797d03b9dec3bd42d9716dc9 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Tue, 15 Sep 2020 16:31:35 -0400 Subject: [PATCH 03/10] Merge GitHub scaffolding instructions --- .../software-templates/installation.md | 40 ++++++++++++++----- .../backend/src/plugins/scaffolder.ts | 19 +++++++-- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index eea78e3571..14bdc1318d 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -90,17 +90,19 @@ import { GithubPublisher, CreateReactAppTemplater, Templaters, + RepoVisilityOptions, } from '@backstage/plugin-scaffolder-backend'; import { Octokit } from '@octokit/rest'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; -export default async function createPlugin({ logger }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); - - // Register default templaters templaters.register('cookiecutter', cookiecutterTemplater); templaters.register('cra', craTemplater); @@ -108,13 +110,20 @@ export default async function createPlugin({ logger }: PluginEnvironment) { const githubPreparer = new GithubPreparer(); const preparers = new Preparers(); - // Register default preparers preparers.register('file', filePreparer); preparers.register('github', githubPreparer); - // Create GitHub client with your access token from environment variables - const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); - const publisher = new GithubPublisher({ client: githubClient }); + const githubToken = config.getString('scaffolder.github.token'); + const repoVisibility = config.getString( + 'scaffolder.github.visibility', + ) as RepoVisilityOptions; + + const githubClient = new Octokit({ auth: githubToken }); + const publisher = new GithubPublisher({ + client: githubClient, + token: githubToken, + repoVisibility, + }); const dockerClient = new Docker(); return await createRouter({ @@ -177,8 +186,21 @@ docs on creating private GitHub access tokens is available Note that the need for private GitHub access tokens will be replaced with GitHub Apps integration further down the line. -The GitHub access token is passed along using the `GITHUB_ACCESS_TOKEN` -environment variable. +> **Right now it is only possible to scaffold repositories inside GitHub +> organizations, and not under personal accounts.** + +The Github access token is retrieved from environment variables by the +config. The config file needs to specify what environment variable the +token is retrieved from. Your config should have the following objects. + +```yaml +scaffolder: + github: + token: + $secret: + env: GITHUB_ACCESS_TOKEN + visibility: public # or 'internal' or 'private' +``` ### Running the Backend diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index ffa10cc3c6..fecef240df 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -7,12 +7,16 @@ import { GithubPublisher, CreateReactAppTemplater, Templaters, + RepoVisilityOptions, } from '@backstage/plugin-scaffolder-backend'; import { Octokit } from '@octokit/rest'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; -export default async function createPlugin({ logger }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); @@ -26,8 +30,17 @@ export default async function createPlugin({ logger }: PluginEnvironment) { preparers.register('file', filePreparer); preparers.register('github', githubPreparer); - const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); - const publisher = new GithubPublisher({ client: githubClient }); + const githubToken = config.getString('scaffolder.github.token'); + const repoVisibility = config.getString( + 'scaffolder.github.visibility', + ) as RepoVisilityOptions; + + const githubClient = new Octokit({ auth: githubToken }); + const publisher = new GithubPublisher({ + client: githubClient, + token: githubToken, + repoVisibility, + }); const dockerClient = new Docker(); return await createRouter({ From e9e3c79295315a2be6c7924af3701d339bd79ed5 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 11 Sep 2020 11:12:27 -0400 Subject: [PATCH 04/10] Run prettier on docs/features/software-templates/installation.md --- docs/features/software-templates/installation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 14bdc1318d..b1c7712f85 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -189,9 +189,9 @@ Apps integration further down the line. > **Right now it is only possible to scaffold repositories inside GitHub > organizations, and not under personal accounts.** -The Github access token is retrieved from environment variables by the -config. The config file needs to specify what environment variable the -token is retrieved from. Your config should have the following objects. +The Github access token is retrieved from environment variables by the config. +The config file needs to specify what environment variable the token is +retrieved from. Your config should have the following objects. ```yaml scaffolder: From 404df7d0b248e7ddb44dd51fa95487432b5fac50 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 11 Sep 2020 13:48:53 -0400 Subject: [PATCH 05/10] Provide environment variable for scaffolder.github.token --- packages/e2e-test/src/e2e-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/e2e-test.ts index 5dfac68d6a..2049a720a0 100644 --- a/packages/e2e-test/src/e2e-test.ts +++ b/packages/e2e-test/src/e2e-test.ts @@ -196,6 +196,7 @@ async function createApp( env: { ...process.env, APP_CONFIG_app_baseUrl: '"http://localhost:3001"', + APP_CONFIG_scaffolder_github_token: '"abc"', }, }); From d72577006df1af999c626ff8e3e664c2cda9da16 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 11 Sep 2020 14:49:28 -0400 Subject: [PATCH 06/10] Add missing environment variables from tests --- packages/e2e-test/src/e2e-test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/e2e-test.ts index 2049a720a0..10d3cc9950 100644 --- a/packages/e2e-test/src/e2e-test.ts +++ b/packages/e2e-test/src/e2e-test.ts @@ -196,7 +196,6 @@ async function createApp( env: { ...process.env, APP_CONFIG_app_baseUrl: '"http://localhost:3001"', - APP_CONFIG_scaffolder_github_token: '"abc"', }, }); @@ -273,6 +272,10 @@ async function createPlugin(pluginName: string, appDir: string) { async function testAppServe(pluginName: string, appDir: string) { const startApp = spawnPiped(['yarn', 'start'], { cwd: appDir, + env: { + ...process.env, + GITHUB_ACCESS_TOKEN: 'abc', + }, }); Browser.localhost('localhost', 3000); @@ -343,6 +346,10 @@ async function testBackendStart(appDir: string, isPostgres: boolean) { const child = spawnPiped(['yarn', 'workspace', 'backend', 'start'], { cwd: appDir, + env: { + ...process.env, + GITHUB_ACCESS_TOKEN: 'abc', + }, }); let stdout = ''; From 6d3ce751004c675aaa516a2fed9bdc462f8b5eda Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Tue, 15 Sep 2020 07:29:42 -0400 Subject: [PATCH 07/10] Apply requested chanegs --- docs/features/software-templates/installation.md | 2 ++ .../src/scaffolder/stages/publish/github.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index b1c7712f85..726f5faead 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -103,6 +103,7 @@ export default async function createPlugin({ const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); + // Register default templaters templaters.register('cookiecutter', cookiecutterTemplater); templaters.register('cra', craTemplater); @@ -110,6 +111,7 @@ export default async function createPlugin({ const githubPreparer = new GithubPreparer(); const preparers = new Preparers(); + // Register default preparers preparers.register('file', filePreparer); preparers.register('github', githubPreparer); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index dd1152d3d5..90fdd106a1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -231,7 +231,7 @@ describe('GitHub Publisher', () => { repoVisibility: 'internal', }); - it('creates a repo in an organisation with internal visibility', async () => { + it('creates a repo in an organisation if the organisation with private visibility', async () => { mockGithubClient.repos.createInOrg.mockResolvedValue({ data: { clone_url: 'mockclone', From 449b5c3f1564ecfe93fd3b8c8371e8dc8dc564b1 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Tue, 15 Sep 2020 16:38:38 -0400 Subject: [PATCH 08/10] Fix prettier errors --- .../src/scaffolder/stages/publish/github.test.ts | 2 +- .../src/scaffolder/stages/publish/github.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 90fdd106a1..2c47fa1a2f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -242,7 +242,7 @@ describe('GitHub Publisher', () => { type: 'Organization', }, } as OctokitResponse); - + await publisher.publish({ values: { isOrg: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 22a46ddf1b..671c3472bc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -67,11 +67,11 @@ export class GithubPublisher implements PublisherBase { const repoCreationPromise = user.data.type === 'Organization' ? this.client.repos.createInOrg({ - name, - org: owner, - private: this.repoVisibility !== 'public', - visibility: this.repoVisibility, - }) + name, + org: owner, + private: this.repoVisibility !== 'public', + visibility: this.repoVisibility, + }) : this.client.repos.createForAuthenticatedUser({ name }); const { data } = await repoCreationPromise; From 9ad2b8186bd680db9d45fe4d2ed44615ecfa465a Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Wed, 16 Sep 2020 08:19:09 -0400 Subject: [PATCH 09/10] Clean up installation --- docs/features/software-templates/installation.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 726f5faead..ef0afb76f7 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -188,10 +188,7 @@ docs on creating private GitHub access tokens is available Note that the need for private GitHub access tokens will be replaced with GitHub Apps integration further down the line. -> **Right now it is only possible to scaffold repositories inside GitHub -> organizations, and not under personal accounts.** - -The Github access token is retrieved from environment variables by the config. +The Github access token is retrieved from environment variables via the config. The config file needs to specify what environment variable the token is retrieved from. Your config should have the following objects. @@ -204,6 +201,11 @@ scaffolder: visibility: public # or 'internal' or 'private' ``` +You can configure who can see the new repositories that the scaffolder creates +by specifying `visibility` option. Valid options are `public`, `private` and +`internal`. `internal` options is for GitHub Enterprise clients, which means +public within the organization. + ### Running the Backend Finally, make sure you have a local Docker daemon running, and start up the From f5702ddc8e9741a1508d62f9afd0b99aef01fcbd Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Wed, 16 Sep 2020 08:53:18 -0400 Subject: [PATCH 10/10] Add ability to flag user repositories as private --- .../scaffolder/stages/publish/github.test.ts | 39 ++++++++++++++++++- .../src/scaffolder/stages/publish/github.ts | 5 ++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 2c47fa1a2f..5411602a21 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -112,6 +112,7 @@ describe('GitHub Publisher', () => { mockGithubClient.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ name: 'test', + private: false, }); }); }); @@ -231,7 +232,7 @@ describe('GitHub Publisher', () => { repoVisibility: 'internal', }); - it('creates a repo in an organisation if the organisation with private visibility', async () => { + it('creates a private repository in the organization with visibility set to internal', async () => { mockGithubClient.repos.createInOrg.mockResolvedValue({ data: { clone_url: 'mockclone', @@ -260,4 +261,40 @@ describe('GitHub Publisher', () => { }); }); }); + + describe('private visibility in a user account', () => { + const publisher = new GithubPublisher({ + client: new Octokit(), + token: 'abc', + repoVisibility: 'private', + }); + + it('creates a private repository', async () => { + mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'User', + }, + } as OctokitResponse); + + await publisher.publish({ + values: { + storePath: 'blam/test', + owner: 'bob', + }, + directory: '/tmp/test', + }); + + expect( + mockGithubClient.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + name: 'test', + private: true, + }); + }); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 671c3472bc..a8f3eaaa02 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -72,7 +72,10 @@ export class GithubPublisher implements PublisherBase { private: this.repoVisibility !== 'public', visibility: this.repoVisibility, }) - : this.client.repos.createForAuthenticatedUser({ name }); + : this.client.repos.createForAuthenticatedUser({ + name, + private: this.repoVisibility === 'private', + }); const { data } = await repoCreationPromise;