From 294a2bb9234048fb549f65cfac9cf31204de4b04 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 28 Jun 2020 00:35:24 +0200 Subject: [PATCH 01/16] chore(scaffolder): refactoring the scaffolder to move things around like the rest --- plugins/scaffolder-backend/package.json | 3 +- .../src/scaffolder/index.ts | 1 - .../src/scaffolder/stages/store/github.ts | 15 +++++++ .../src/scaffolder/stages/templater/index.ts | 45 ++----------------- .../src/scaffolder/stages/templater/types.ts | 39 ++++++++++++++++ 5 files changed, 59 insertions(+), 44 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 47f5a31158..c6237bc726 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -24,8 +24,9 @@ "@backstage/backend-common": "^0.1.1-alpha.12", "@backstage/catalog-model": "^0.1.1-alpha.12", "@backstage/config": "^0.1.1-alpha.12", - "@types/express": "^4.17.6", + "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", + "@types/express": "^4.17.6", "compression": "^1.7.4", "cors": "^2.8.5", "dockerode": "^3.2.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index a1ee172184..2c689489e4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ export * from './stages/templater'; -export * from './stages/templater/cookiecutter'; export * from './stages/prepare'; export * from './jobs'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts new file mode 100644 index 0000000000..f3b69cc361 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -0,0 +1,15 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts index d0bbb0aa6c..0813d0ab86 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts @@ -13,45 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Writable } from 'stream'; -import Docker from 'dockerode'; -import { JsonValue } from '@backstage/config'; - -export interface RequiredTemplateValues { - component_id: string; -} - -export interface TemplaterRunOptions { - directory: string; - values: RequiredTemplateValues & Record; - logStream?: Writable; - dockerClient: Docker; -} - -export type TemplaterBase = { - // runs the templating with the values and returns the directory to push the VCS - run(opts: TemplaterRunOptions): Promise; -}; - -export interface TemplaterConfig { - templater?: TemplaterBase; -} - -class Templater implements TemplaterBase { - templater?: TemplaterBase; - - constructor({ templater }: TemplaterConfig) { - this.templater = templater; - } - - public async run(opts: TemplaterRunOptions) { - return this.templater!.run(opts); - } -} - -export const createTemplater = ( - templaterConfig: TemplaterConfig, -): TemplaterBase => { - return new Templater(templaterConfig); -}; +export * from './cookiecutter'; +export * from './types'; +export * from './helpers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts new file mode 100644 index 0000000000..5788b6b657 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Writable } from 'stream'; +import Docker from 'dockerode'; +import { JsonValue } from '@backstage/config'; + +export type RequiredTemplateValues = { + component_id: string; +}; + +export type TemplaterRunOptions = { + directory: string; + values: RequiredTemplateValues & Record; + logStream?: Writable; + dockerClient: Docker; +}; + +export type TemplaterBase = { + // runs the templating with the values and returns the directory to push the VCS + run(opts: TemplaterRunOptions): Promise; +}; + +export type TemplaterConfig = { + templater?: TemplaterBase; +}; From ee205ef23e30cf93710774042894aa260c63626a Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 28 Jun 2020 02:06:33 +0200 Subject: [PATCH 02/16] feat(scaffolder): Implementing the VCS steps in the processor --- .../hooks/post_gen_project.sh | 6 +- .../.github/workflows/build.yml | 5 +- .../src/scaffolder/stages/prepare/file.ts | 1 + .../src/scaffolder/stages/store/github.ts | 61 +++++++++++++ .../src/scaffolder/stages/store/types.ts | 23 +++++ .../stages/templater/cookiecutter.ts | 7 +- .../scaffolder-backend/src/service/router.ts | 29 +++++-- yarn.lock | 87 ++++++++++++++++++- 8 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh b/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh index 75c35f2034..1e302afa5e 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh @@ -1,7 +1,3 @@ #!/bin/sh -# Move all template files to the root folder -mv ./* ../ -cd .. -rm -rf {{cookiecutter.component_id}} -# # # # # # # # +echo "Could do something here?" diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml index d1563ba3e6..e3ca9f44e2 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml @@ -1,9 +1,6 @@ name: Frontend CI -on: - push: - paths: - - '.' +on: [push, pull_request] jobs: build: diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/file.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/file.ts index 7a924d9a99..7a8361d6ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/file.ts @@ -43,6 +43,7 @@ export class FilePreparer implements PreparerBase { await fs.copy(parentDirectory, tempDir, { filter: src => src !== location, + recursive: true, }); return tempDir; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts index f3b69cc361..a59e6c90cd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -13,3 +13,64 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { Storer } from './types'; +import { Octokit } from '@octokit/rest'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { JsonValue } from '@backstage/config'; +import { RequiredTemplateValues } from '../templater'; +import { Repository, Remote, Signature, Cred } from 'nodegit'; +import gitUrlParse from 'git-url-parse'; + +export class GithubStorer implements Storer { + private client: Octokit; + constructor({ client }: { client: Octokit }) { + this.client = client; + } + + async createRemote({ + values, + }: { + entity: TemplateEntityV1alpha1; + values: RequiredTemplateValues & Record; + }) { + const { + data: { clone_url: cloneUrl }, + } = await this.client.repos.createInOrg({ + name: values.component_id, + org: values.org as string, + }); + + console.warn(cloneUrl); + + return cloneUrl; + } + + async pushToRemote(directory: string, remote: string): Promise { + const repo = await Repository.init(directory, 0); + const index = await repo.refreshIndex(); + await index.addAll(); + await index.write(); + const oid = await index.writeTree(); + await repo.createCommit( + 'HEAD', + Signature.now('Foo bar', 'foo@bar.com'), + Signature.now('Foo bar', 'foo@bar.com'), + 'initial commit', + oid, + [], + ); + + const remoteRepo = await Remote.create(repo, 'origin', remote); + await remoteRepo.push(['refs/heads/master:refs/heads/master'], { + callbacks: { + credentials: () => { + return Cred.userpassPlaintextNew( + process.env.GITHUB_ACCESS_TOKEN as string, + 'x-oauth-basic', + ); + }, + }, + }); + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts new file mode 100644 index 0000000000..ae63a05197 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateEntityV1alpha1 } from "@backstage/catalog-model"; +import { RequiredTemplateValues } from "../templater"; +import { JsonValue } from "@backstage/config"; + +export type Storer = { + createRemote(opts: { entity: TemplateEntityV1alpha1, values: RequiredTemplateValues & Record}): Promise; + pushToRemote(directory: string, remote: string): Promise; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index cef8a64b60..5e875b6838 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -1,5 +1,3 @@ -import { TemplaterBase, TemplaterRunOptions } from '.'; - /* * Copyright 2020 Spotify AB * @@ -18,7 +16,8 @@ import { TemplaterBase, TemplaterRunOptions } from '.'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/config'; import { runDockerContainer } from './helpers'; - +import { TemplaterBase, TemplaterRunOptions } from '.'; +import path from 'path'; export class CookieCutter implements TemplaterBase { private async fetchTemplateCookieCutter( directory: string, @@ -66,6 +65,6 @@ export class CookieCutter implements TemplaterBase { dockerClient: options.dockerClient, }); - return resultDir; + return path.resolve(resultDir, options.values.component_id); } } diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 0f43c3e0de..da7dce40f9 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -22,6 +22,8 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import Docker from 'dockerode'; import {} from '@backstage/backend-common'; import { StageContext } from '../scaffolder/jobs/types'; +import { Octokit } from '@octokit/rest'; +import { GithubStorer } from '../scaffolder/stages/store/github'; export interface RouterOptions { preparers: PreparerBuilder; templater: TemplaterBase; @@ -33,9 +35,11 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const { preparers, templater, logger: parentLogger, dockerClient } = options; - const logger = parentLogger.child({ plugin: 'scaffolder' }); + const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); + const { preparers, templater, logger: parentLogger, dockerClient } = options; + const githubStorer = new GithubStorer({ client: githubClient }); + const logger = parentLogger.child({ plugin: 'scaffolder' }); const jobProcessor = new JobProcessor(); router @@ -83,7 +87,7 @@ export async function createRouter( metadata: { annotations: { 'backstage.io/managed-by-location': - 'github:https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml', + 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', }, name: 'graphql-starter', title: 'GraphQL Service', @@ -96,13 +100,17 @@ export async function createRouter( }, spec: { type: 'cookiecutter', - path: './template', + path: '.', }, }; const job = jobProcessor.create({ entity: mockEntity, - values: { component_id: 'blob' }, + values: { + component_id: `blob${Date.now()}`, + org: 'hojden', + description: 'test', + }, stages: [ { name: 'Prepare the skeleton', @@ -131,12 +139,21 @@ export async function createRouter( name: 'Create VCS Repo', handler: async (ctx: StageContext<{ resultDir: string }>) => { ctx.logger.info('Should now create the VCS repo'); + const remoteUrl = await githubStorer.createRemote({ + values: ctx.values, + entity: ctx.entity, + }); + + return { remoteUrl }; }, }, { name: 'Push to remote', - handler: async ctx => { + handler: async ( + ctx: StageContext<{ resultDir: string; remoteUrl: string }>, + ) => { ctx.logger.info('Should now push to the remote'); + await githubStorer.pushToRemote(ctx.resultDir, ctx.remoteUrl); }, }, ], diff --git a/yarn.lock b/yarn.lock index 04388bb623..18be8c3246 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2328,6 +2328,18 @@ dependencies: "@octokit/types" "^2.0.0" +"@octokit/core@^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@octokit/core/-/core-3.1.0.tgz#9c3c9b23f7504668cfa057f143ccbf0c645a0ac9" + integrity sha512-yPyQSmxIXLieEIRikk2w8AEtWkFdfG/LXcw1KvEtK3iP0ENZLW/WYQmdzOKqfSaLhooz4CJ9D+WY79C8ZliACw== + dependencies: + "@octokit/auth-token" "^2.4.0" + "@octokit/graphql" "^4.3.1" + "@octokit/request" "^5.4.0" + "@octokit/types" "^5.0.0" + before-after-hook "^2.1.0" + universal-user-agent "^5.0.0" + "@octokit/endpoint@^5.5.0": version "5.5.3" resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.3.tgz#0397d1baaca687a4c8454ba424a627699d97c978" @@ -2337,6 +2349,24 @@ is-plain-object "^3.0.0" universal-user-agent "^5.0.0" +"@octokit/endpoint@^6.0.1": + version "6.0.3" + resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487" + integrity sha512-Y900+r0gIz+cWp6ytnkibbD95ucEzDSKzlEnaWS52hbCDNcCJYO5mRmWW7HRAnDc7am+N/5Lnd8MppSaTYx1Yg== + dependencies: + "@octokit/types" "^5.0.0" + is-plain-object "^3.0.0" + universal-user-agent "^5.0.0" + +"@octokit/graphql@^4.3.1": + version "4.5.1" + resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.1.tgz#162aed1490320b88ce34775b3f6b8de945529fa9" + integrity sha512-qgMsROG9K2KxDs12CO3bySJaYoUu2aic90qpFrv7A8sEBzZ7UFGvdgPKiLw5gOPYEYbS0Xf8Tvf84tJutHPulQ== + dependencies: + "@octokit/request" "^5.3.0" + "@octokit/types" "^5.0.0" + universal-user-agent "^5.0.0" + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -2349,6 +2379,13 @@ dependencies: "@octokit/types" "^2.0.1" +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.2.3" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz#a6ad4377e7e7832fb4bdd9d421e600cb7640ac27" + integrity sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg== + dependencies: + "@octokit/types" "^5.0.0" + "@octokit/plugin-request-log@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" @@ -2362,6 +2399,14 @@ "@octokit/types" "^2.0.1" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.0.0.tgz#b02a2006dda8e908c3f8ab381dd5475ef5a810a8" + integrity sha512-emS6gysz4E9BNi9IrCl7Pm4kR+Az3MmVB0/DoDCmF4U48NbYG3weKyDlgkrz6Jbl4Mu4nDx8YWZwC4HjoTdcCA== + dependencies: + "@octokit/types" "^5.0.0" + deprecation "^2.3.1" + "@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2": version "1.2.1" resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" @@ -2371,6 +2416,15 @@ deprecation "^2.0.0" once "^1.4.0" +"@octokit/request-error@^2.0.0": + version "2.0.2" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" + integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== + dependencies: + "@octokit/types" "^5.0.1" + deprecation "^2.0.0" + once "^1.4.0" + "@octokit/request@^5.2.0": version "5.3.2" resolved "https://registry.npmjs.org/@octokit/request/-/request-5.3.2.tgz#1ca8b90a407772a1ee1ab758e7e0aced213b9883" @@ -2385,6 +2439,20 @@ once "^1.4.0" universal-user-agent "^5.0.0" +"@octokit/request@^5.3.0", "@octokit/request@^5.4.0": + version "5.4.5" + resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.5.tgz#8df65bd812047521f7e9db6ff118c06ba84ac10b" + integrity sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.0.0" + "@octokit/types" "^5.0.0" + deprecation "^2.0.0" + is-plain-object "^3.0.0" + node-fetch "^2.3.0" + once "^1.4.0" + universal-user-agent "^5.0.0" + "@octokit/rest@^16.28.4": version "16.43.1" resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz#3b11e7d1b1ac2bbeeb23b08a17df0b20947eda6b" @@ -2407,6 +2475,16 @@ once "^1.4.0" universal-user-agent "^4.0.0" +"@octokit/rest@^18.0.0": + version "18.0.0" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.0.tgz#7f401d9ce13530ad743dfd519ae62ce49bcc0358" + integrity sha512-4G/a42lry9NFGuuECnua1R1eoKkdBYJap97jYbWDNYBOUboWcM75GJ1VIcfvwDV/pW0lMPs7CEmhHoVrSV5shg== + dependencies: + "@octokit/core" "^3.0.0" + "@octokit/plugin-paginate-rest" "^2.2.0" + "@octokit/plugin-request-log" "^1.0.0" + "@octokit/plugin-rest-endpoint-methods" "4.0.0" + "@octokit/types@^2.0.0", "@octokit/types@^2.0.1": version "2.5.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-2.5.0.tgz#f1bbd147e662ae2c79717d518aac686e58257773" @@ -2414,6 +2492,13 @@ dependencies: "@types/node" ">= 8" +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@octokit/types/-/types-5.0.1.tgz#5459e9a5e9df8565dcc62c17a34491904d71971e" + integrity sha512-GorvORVwp244fGKEt3cgt/P+M0MGy4xEDbckw+K5ojEezxyMDgCaYPKVct+/eWQfZXOT7uq0xRpmrl/+hliabA== + dependencies: + "@types/node" ">= 8" + "@open-draft/until@^1.0.0": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" @@ -5364,7 +5449,7 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" -before-after-hook@^2.0.0: +before-after-hook@^2.0.0, before-after-hook@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== From d5a0a5968e8ba2475548bdd3c3ae0f2fe2c2f639 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 17:49:54 +0200 Subject: [PATCH 03/16] feat(scaffolder): making the API work by taking the entity that is pulled from the client --- .../src/scaffolder/jobs/logger.ts | 2 +- .../src/scaffolder/jobs/processor.test.ts | 6 ++- .../src/scaffolder/jobs/processor.ts | 6 +-- .../src/scaffolder/jobs/types.ts | 4 +- .../src/scaffolder/stages/store/github.ts | 14 ++--- .../stages/templater/cookiecutter.test.ts | 23 +++++---- .../src/scaffolder/stages/templater/types.ts | 3 +- .../scaffolder-backend/src/service/router.ts | 51 ++++++------------- 8 files changed, 48 insertions(+), 61 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts index d2eaa13c3b..2e88dd34fe 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts @@ -17,7 +17,7 @@ import { PassThrough } from 'stream'; import winston from 'winston'; import { JsonValue } from '@backstage/config'; -export const useLogStream = (meta: Record) => { +export const makeLogStream = (meta: Record) => { const log: string[] = []; // Create an empty stream to collect all the log lines into diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts index 501e72c078..a8cfe9f3ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts @@ -16,6 +16,7 @@ import { JobProcessor } from './processor'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { StageInput } from './types'; +import { RequiredTemplateValues } from '../stages/templater'; describe('JobProcessor', () => { const mockEntity: TemplateEntityV1alpha1 = { @@ -41,7 +42,10 @@ describe('JobProcessor', () => { }, }; - const mockValues = { component_id: 'bob' }; + const mockValues: RequiredTemplateValues = { + owner: 'blobby', + storePath: 'spotify/mock-repo', + }; describe('create', () => { it('creates should create a new job with a unique id', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts index 1c76ae252d..0a3b556941 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts @@ -20,7 +20,7 @@ import * as uuid from 'uuid'; import Docker from 'dockerode'; import { RequiredTemplateValues, TemplaterBase } from '../stages/templater'; import { PreparerBuilder } from '../stages/prepare'; -import { useLogStream } from './logger'; +import { makeLogStream } from './logger'; export type JobProcessorArguments = { preparers: PreparerBuilder; @@ -46,7 +46,7 @@ export class JobProcessor implements Processor { stages: StageInput[]; }): Job { const id = uuid.v4(); - const { logger, stream } = useLogStream({ id }); + const { logger, stream } = makeLogStream({ id }); const context: StageContext = { entity, @@ -87,7 +87,7 @@ export class JobProcessor implements Processor { for (const stage of job.stages) { // Create a logger for each stage so we can create seperate // Streams for each step. - const { logger, log, stream } = useLogStream({ + const { logger, log, stream } = makeLogStream({ id: job.id, stage: stage.name, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts index fd554e2033..73da47aed9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts @@ -31,7 +31,7 @@ export type StageContext = { export type ProcessorStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; -export interface Stage extends StageInput { +export interface StageResult extends StageInput { log: string[]; status: ProcessorStatus; startedAt?: number; @@ -47,7 +47,7 @@ export type Job = { id: string; context: StageContext; status: ProcessorStatus; - stages: Stage[]; + stages: StageResult[]; error?: Error; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts index a59e6c90cd..046a6599fb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -20,7 +20,7 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; -import gitUrlParse from 'git-url-parse'; +import gitParse from 'git-url-parse'; export class GithubStorer implements Storer { private client: Octokit; @@ -34,15 +34,15 @@ export class GithubStorer implements Storer { entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; }) { + const [owner, name] = values.owner.split('/'); + const { data: { clone_url: cloneUrl }, } = await this.client.repos.createInOrg({ - name: values.component_id, - org: values.org as string, + name, + org: owner, }); - console.warn(cloneUrl); - return cloneUrl; } @@ -54,8 +54,8 @@ export class GithubStorer implements Storer { const oid = await index.writeTree(); await repo.createCommit( 'HEAD', - Signature.now('Foo bar', 'foo@bar.com'), - Signature.now('Foo bar', 'foo@bar.com'), + Signature.now('Scaffolder', 'scaffolder@backstage.io'), + Signature.now('Scaffolder', 'scaffolder@backstage.io'), 'initial commit', oid, [], diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts index 994307e8b7..55496a63d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts @@ -45,7 +45,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', + owner: 'blobby', + storePath: 'spotify/end-repo', description: 'description', }; @@ -65,8 +66,8 @@ describe('CookieCutter Templater', () => { await fs.writeJSON(`${tempdir}/cookiecutter.json`, existingJson); const values = { - component_id: 'hello', - description: 'im something cool', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -82,8 +83,8 @@ describe('CookieCutter Templater', () => { await fs.writeFile(`${tempdir}/cookiecutter.json`, "{'"); const values = { - component_id: 'hello', - description: 'im something cool', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await expect( @@ -95,8 +96,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -122,8 +123,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; const returnPath = await cookie.run({ @@ -141,8 +142,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts index 5788b6b657..cdefa7821e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts @@ -19,7 +19,8 @@ import Docker from 'dockerode'; import { JsonValue } from '@backstage/config'; export type RequiredTemplateValues = { - component_id: string; + owner: string; + storePath: string; }; export type TemplaterRunOptions = { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index da7dce40f9..efa2f9588d 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -17,13 +17,19 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; -import { PreparerBuilder, TemplaterBase, JobProcessor } from '../scaffolder'; +import { + PreparerBuilder, + TemplaterBase, + JobProcessor, + RequiredTemplateValues, +} from '../scaffolder'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import Docker from 'dockerode'; import {} from '@backstage/backend-common'; import { StageContext } from '../scaffolder/jobs/types'; import { Octokit } from '@octokit/rest'; import { GithubStorer } from '../scaffolder/stages/store/github'; +import { JsonValue } from '@backstage/config'; export interface RouterOptions { preparers: PreparerBuilder; templater: TemplaterBase; @@ -51,7 +57,9 @@ export async function createRouter( return; } - res.send(job.stages[Number(params.index)].log.join('')); + const { log } = job.stages[Number(params.index)] ?? { log: [] }; + + res.send(log.join('')); }) .get('/v1/job/:jobId', ({ params }, res) => { const job = jobProcessor.get(params.jobId); @@ -76,41 +84,14 @@ export async function createRouter( error: job.error, }); }) - .post('/v1/jobs', async (_, res) => { - // TODO(blam): Create a unique job here and return the ID so that - // The end user can poll for updates on the current job - - // TODO(blam): Take this entity from the post body sent from the frontend - const mockEntity: TemplateEntityV1alpha1 = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Template', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', - }, - name: 'graphql-starter', - title: 'GraphQL Service', - description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', - uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', - etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', - - generation: 1, - }, - spec: { - type: 'cookiecutter', - path: '.', - }, - }; + .post('/v1/jobs', async (req, res) => { + const template: TemplateEntityV1alpha1 = req.body.template; + const values: RequiredTemplateValues & Record = + req.body.values; const job = jobProcessor.create({ - entity: mockEntity, - values: { - component_id: `blob${Date.now()}`, - org: 'hojden', - description: 'test', - }, + entity: template, + values, stages: [ { name: 'Prepare the skeleton', From d7dbc15b11f0746158f4e0d810c6128ac2816786 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 18:05:56 +0200 Subject: [PATCH 04/16] chore(scaffolder): fixing issues with typescript and code review comment: --- .../scaffolder-backend/src/scaffolder/stages/store/github.ts | 3 +-- .../src/scaffolder/stages/templater/cookiecutter.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts index 046a6599fb..8f5cc40ff9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -20,7 +20,6 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; -import gitParse from 'git-url-parse'; export class GithubStorer implements Storer { private client: Octokit; @@ -34,7 +33,7 @@ export class GithubStorer implements Storer { entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; }) { - const [owner, name] = values.owner.split('/'); + const [owner, name] = values.storePath.split('/'); const { data: { clone_url: cloneUrl }, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index 5e875b6838..3e94d67c6e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -65,6 +65,6 @@ export class CookieCutter implements TemplaterBase { dockerClient: options.dockerClient, }); - return path.resolve(resultDir, options.values.component_id); + return path.resolve(resultDir, options.values.component_id as string); } } From 9a7352c52bebeaa2bc51d9e0f2a54ba690064f19 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 21:29:21 +0200 Subject: [PATCH 05/16] chore(scaffolder): fixing broken tests --- .../src/scaffolder/stages/templater/cookiecutter.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts index 55496a63d4..27e4cd4b01 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts @@ -48,6 +48,7 @@ describe('CookieCutter Templater', () => { owner: 'blobby', storePath: 'spotify/end-repo', description: 'description', + component_id: 'newthing', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -68,6 +69,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'something', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -98,6 +100,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -125,6 +128,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; const returnPath = await cookie.run({ @@ -144,6 +148,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; await cookie.run({ From 5e372b0671a864e4e5f5eb6232ae226a47c702cf Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Jun 2020 14:52:06 +0200 Subject: [PATCH 06/16] chore(scaffolder): add tests for makeLogStream --- .../src/scaffolder/jobs/logger.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts new file mode 100644 index 0000000000..5905d26ebb --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeLogStream } from './logger'; +describe('Logger', () => { + const mockMeta = { test: 'blob' }; + + it('should return empty log lines by default', async () => { + const { log } = makeLogStream(mockMeta); + + expect(log).toEqual([]); + }); + it('should add lines to the log when using the logger that is returned', async () => { + const { logger, log } = makeLogStream(mockMeta); + + logger.info('TEST LINE'); + logger.warn('WARN LINE'); + + const [first, second] = log; + expect(log.length).toBe(2); + + expect(first).toContain('info'); + expect(first).toContain('TEST LINE'); + + expect(second).toContain('warn'); + expect(second).toContain('WARN LINE'); + }); + + it('should add lines from writing to the stream that is returned', async () => { + const { stream, log } = makeLogStream(mockMeta); + const textLine = 'SOMETHING'; + stream.write(textLine); + + expect(log).toContain(textLine); + }); +}); From 2438aaa099341b3c4f42779f48db8c84478ea288 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 1 Jul 2020 10:10:04 +0200 Subject: [PATCH 07/16] feat(scaffolder): finally found a better name than storer. It's publisher --- .../src/scaffolder/index.ts | 3 +-- .../src/scaffolder/stages/index.ts | 18 +++++++++++++ .../scaffolder/stages/publish/github.test.ts | 16 ++++++++++++ .../stages/{store => publish}/github.ts | 22 ++++++++++------ .../src/scaffolder/stages/publish/index.ts | 16 ++++++++++++ .../stages/{store => publish}/types.ts | 17 +++++++------ .../scaffolder-backend/src/service/router.ts | 25 ++++++------------- 7 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/index.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts rename plugins/scaffolder-backend/src/scaffolder/stages/{store => publish}/github.ts (80%) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts rename plugins/scaffolder-backend/src/scaffolder/stages/{store => publish}/types.ts (60%) diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index 2c689489e4..470bc230d6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -13,6 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './stages/templater'; -export * from './stages/prepare'; +export * from './stages'; export * from './jobs'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/index.ts new file mode 100644 index 0000000000..78ea30db79 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './prepare'; +export * from './publish'; +export * from './templater'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts new file mode 100644 index 0000000000..f91e8bb40e --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +describe('Github Store', () => {}); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts similarity index 80% rename from plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts rename to plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 8f5cc40ff9..9aed55fd98 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -14,25 +14,33 @@ * limitations under the License. */ -import { Storer } from './types'; +import { Publisher } from './types'; import { Octokit } from '@octokit/rest'; -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; -export class GithubStorer implements Storer { +export class GithubPublisher implements Publisher { private client: Octokit; constructor({ client }: { client: Octokit }) { this.client = client; } - async createRemote({ + async publish({ values, + directory, }: { - entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; - }) { + directory: string; + }): Promise<{ remoteUrl: string }> { + const remoteUrl = await this.createRemote(values); + await this.pushToRemote(directory, remoteUrl); + + return { remoteUrl }; + } + + private async createRemote(values: RequiredTemplateValues) { const [owner, name] = values.storePath.split('/'); const { @@ -45,7 +53,7 @@ export class GithubStorer implements Storer { return cloneUrl; } - async pushToRemote(directory: string, remote: string): Promise { + private async pushToRemote(directory: string, remote: string): Promise { const repo = await Repository.init(directory, 0); const index = await repo.refreshIndex(); await index.addAll(); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts new file mode 100644 index 0000000000..dcfd2c9c34 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './github'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts similarity index 60% rename from plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts rename to plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts index ae63a05197..a6db297bde 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TemplateEntityV1alpha1 } from "@backstage/catalog-model"; -import { RequiredTemplateValues } from "../templater"; -import { JsonValue } from "@backstage/config"; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { RequiredTemplateValues } from '../templater'; +import { JsonValue } from '@backstage/config'; -export type Storer = { - createRemote(opts: { entity: TemplateEntityV1alpha1, values: RequiredTemplateValues & Record}): Promise; - pushToRemote(directory: string, remote: string): Promise; -} +export type Publisher = { + publish(opts: { + entity: TemplateEntityV1alpha1; + values: RequiredTemplateValues & Record; + directory: string; + }): Promise<{ remoteUrl: string }>; +}; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index efa2f9588d..633be88d4c 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -22,13 +22,14 @@ import { TemplaterBase, JobProcessor, RequiredTemplateValues, + StageContext, + GithubPublisher, } from '../scaffolder'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import Docker from 'dockerode'; import {} from '@backstage/backend-common'; -import { StageContext } from '../scaffolder/jobs/types'; import { Octokit } from '@octokit/rest'; -import { GithubStorer } from '../scaffolder/stages/store/github'; + import { JsonValue } from '@backstage/config'; export interface RouterOptions { preparers: PreparerBuilder; @@ -44,7 +45,7 @@ export async function createRouter( const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); const { preparers, templater, logger: parentLogger, dockerClient } = options; - const githubStorer = new GithubStorer({ client: githubClient }); + const githubPulisher = new GithubPublisher({ client: githubClient }); const logger = parentLogger.child({ plugin: 'scaffolder' }); const jobProcessor = new JobProcessor(); @@ -117,26 +118,16 @@ export async function createRouter( }, }, { - name: 'Create VCS Repo', + name: 'Publish template', handler: async (ctx: StageContext<{ resultDir: string }>) => { - ctx.logger.info('Should now create the VCS repo'); - const remoteUrl = await githubStorer.createRemote({ + ctx.logger.info('Should not store the template'); + const { remoteUrl } = await githubPulisher.publish({ values: ctx.values, - entity: ctx.entity, + directory: ctx.resultDir, }); - return { remoteUrl }; }, }, - { - name: 'Push to remote', - handler: async ( - ctx: StageContext<{ resultDir: string; remoteUrl: string }>, - ) => { - ctx.logger.info('Should now push to the remote'); - await githubStorer.pushToRemote(ctx.resultDir, ctx.remoteUrl); - }, - }, ], }); From cc4683d185bb5c139804ce2bce3ef7379720162a Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 1 Jul 2020 16:44:09 +0200 Subject: [PATCH 08/16] chore(scaffolder): making some tests nicer and a little more readable and typescript compliant --- plugins/scaffolder-backend/package.json | 1 + .../scaffolder/stages/prepare/github.test.ts | 1 - .../publish/__mocks__/@octokit/rest/index.ts | 28 ++++++++ .../stages/publish/__mocks__/nodegit/index.ts | 39 +++++++++++ .../scaffolder/stages/publish/github.test.ts | 68 ++++++++++++++++++- .../src/scaffolder/stages/publish/github.ts | 17 ++--- 6 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c6237bc726..30de3a8b6f 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -43,6 +43,7 @@ }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.12", + "@octokit/types": "^5.0.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", "@types/nodegit": "0.26.5", diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts index 86377be6d0..f8552e063f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts @@ -19,7 +19,6 @@ const mocks = { CheckoutOptions: jest.fn(() => {}), }; jest.doMock('nodegit', () => mocks); -// require('nodegit'); import { GithubPreparer } from './github'; import { diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts new file mode 100644 index 0000000000..85bed3a223 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const mockGithubClient = { + repos: { + createInOrg: jest.fn(), + createForAuthenticatedUser: jest.fn(), + }, +}; + +export class Octokit { + constructor() { + return mockGithubClient; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts new file mode 100644 index 0000000000..aa0cb6a23b --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const mockIndex = { + addAll: jest.fn(), + write: jest.fn(), + writeTree: jest.fn().mockResolvedValue('mockoid'), +}; + +const mockRepo = { + refreshIndex: jest.fn().mockResolvedValue(mockIndex), + createCommit: jest.fn(), +}; + +const mockRemote = { + push: jest.fn(), +}; + +const Repository = { init: jest.fn().mockResolvedValue(mockRepo) }; +const Remote = { create: jest.fn().mockResolvedValue(mockRemote) }; +const Signature = { now: jest.fn() }; +const Cred = { + userpassPlaintextNew: jest.fn(), +}; + +export { Repository, Remote, Signature, Cred }; 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 f91e8bb40e..aa439a4982 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -13,4 +13,70 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -describe('Github Store', () => {}); + +jest.mock('@octokit/rest'); +jest.mock('nodegit'); + +import { Octokit } from '@octokit/rest'; +import { OctokitResponse, ReposCreateInOrgResponseData } from '@octokit/types'; +import { GithubPublisher } from './github'; + +const { mockGithubClient } = require('@octokit/rest') as { + mockGithubClient: { repos: jest.Mocked }; +}; + +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 () => { + mockGithubClient.repos.createInOrg.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + + await publisher.publish({ + values: { + isOrg: true, + storePath: 'blam/test', + owner: 'bob', + }, + directory: '/tmp/test', + }); + + expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({ + org: 'blam', + name: 'test', + }); + }); + + 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); + + await publisher.publish({ + values: { + storePath: 'blam/test', + owner: 'bob', + }, + directory: '/tmp/test', + }); + + expect( + mockGithubClient.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + name: 'test', + }); + }); + }); + + describe('publish: createGitDirectory', () => {}); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 9aed55fd98..eba7def9af 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -40,17 +40,18 @@ export class GithubPublisher implements Publisher { return { remoteUrl }; } - private async createRemote(values: RequiredTemplateValues) { + private async createRemote( + values: RequiredTemplateValues & Record, + ) { const [owner, name] = values.storePath.split('/'); - const { - data: { clone_url: cloneUrl }, - } = await this.client.repos.createInOrg({ - name, - org: owner, - }); + const repoCreationPromise = values.isOrg + ? this.client.repos.createInOrg({ name, org: owner }) + : this.client.repos.createForAuthenticatedUser({ name }); - return cloneUrl; + const { data } = await repoCreationPromise; + + return data?.clone_url; } private async pushToRemote(directory: string, remote: string): Promise { From 365e5921b22348b3dccb959b78e9e8ee5ff49a6d Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 1 Jul 2020 18:30:53 +0200 Subject: [PATCH 09/16] chore(scaffolder): added some more tests for the github publisher --- .../stages/publish/__mocks__/nodegit/index.ts | 6 +- .../scaffolder/stages/publish/github.test.ts | 80 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts index aa0cb6a23b..e3fb5000d0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts @@ -14,18 +14,18 @@ * limitations under the License. */ -const mockIndex = { +export const mockIndex = { addAll: jest.fn(), write: jest.fn(), writeTree: jest.fn().mockResolvedValue('mockoid'), }; -const mockRepo = { +export const mockRepo = { refreshIndex: jest.fn().mockResolvedValue(mockIndex), createCommit: jest.fn(), }; -const mockRemote = { +export const mockRemote = { push: jest.fn(), }; 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 aa439a4982..5e886b13aa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -18,6 +18,7 @@ jest.mock('@octokit/rest'); jest.mock('nodegit'); import { Octokit } from '@octokit/rest'; +import * as NodeGit from 'nodegit'; import { OctokitResponse, ReposCreateInOrgResponseData } from '@octokit/types'; import { GithubPublisher } from './github'; @@ -25,6 +26,16 @@ const { mockGithubClient } = require('@octokit/rest') as { mockGithubClient: { repos: jest.Mocked }; }; +const { Repository, mockRepo, mockIndex, Signature } = require('nodegit') as { + Repository: jest.Mocked<{ init: any }>; + Signature: jest.Mocked<{ now: any }>; + Cred: jest.Mocked<{ init: any }>; + Remote: jest.Mocked<{ push: any }>; + + mockIndex: jest.Mocked; + mockRepo: jest.Mocked; +}; + describe('Github Publisher', () => { const publisher = new GithubPublisher({ client: new Octokit() }); @@ -78,5 +89,72 @@ describe('Github Publisher', () => { }); }); - describe('publish: createGitDirectory', () => {}); + 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); + 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', + [], + ); + }); + }); }); From dcf062f9bf1feffc9550a9872ab9a293f0226ea5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 1 Jul 2020 19:06:14 +0200 Subject: [PATCH 10/16] chore(scaffolder): finally fixed tests --- .../scaffolder/stages/publish/github.test.ts | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 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 5e886b13aa..164ec316df 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -26,14 +26,23 @@ const { mockGithubClient } = require('@octokit/rest') as { mockGithubClient: { repos: jest.Mocked }; }; -const { Repository, mockRepo, mockIndex, Signature } = require('nodegit') as { +const { + Repository, + mockRepo, + mockIndex, + Signature, + Remote, + mockRemote, + Cred, +} = require('nodegit') as { Repository: jest.Mocked<{ init: any }>; Signature: jest.Mocked<{ now: any }>; - Cred: jest.Mocked<{ init: any }>; - Remote: jest.Mocked<{ push: any }>; + Cred: jest.Mocked<{ userpassPlaintextNew: any }>; + Remote: jest.Mocked<{ create: any }>; mockIndex: jest.Mocked; mockRepo: jest.Mocked; + mockRemote: jest.Mocked; }; describe('Github Publisher', () => { @@ -156,5 +165,39 @@ describe('Github Publisher', () => { [], ); }); + + 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', + ); + }); }); }); From 165b206963a242a1f5ffe4f69eac85b6c7dbb9dc Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 2 Jul 2020 03:25:29 +0200 Subject: [PATCH 11/16] chore(scaffolder): pull image first --- .../scaffolder/stages/templater/helpers.test.ts | 14 ++++++++++++++ .../src/scaffolder/stages/templater/helpers.ts | 1 + 2 files changed, 15 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts index d27eb4f93f..02bc801385 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts @@ -26,6 +26,9 @@ describe('helpers', () => { jest .spyOn(mockDocker, 'run') .mockResolvedValue([{ Error: null, StatusCode: 0 }]); + jest + .spyOn(mockDocker, 'pull') + .mockResolvedValue([{ Error: null, StatusCode: 0 }]); }); describe('runDockerContainer', () => { @@ -34,6 +37,17 @@ describe('helpers', () => { const templateDir = os.tmpdir(); const resultDir = os.tmpdir(); + it('will pull the docker container before running', async () => { + await runDockerContainer({ + imageName, + args, + templateDir, + resultDir, + dockerClient: mockDocker, + }); + + expect(mockDocker.pull).toHaveBeenCalledWith(imageName, {}); + }); it('should call the dockerClient run command with the correct arguments passed through', async () => { await runDockerContainer({ imageName, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index 2856a016e4..9ef85224d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -44,6 +44,7 @@ export const runDockerContainer = async ({ templateDir, dockerClient, }: RunDockerContainerOptions) => { + await dockerClient.pull(imageName, {}); const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run( imageName, args, From 74fb539ac8dca357a986056fb86e3d9abc078269 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 2 Jul 2020 05:20:36 +0200 Subject: [PATCH 12/16] chore(scaffolder): fixing some docs --- .../src/scaffolder/stages/publish/types.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts index a6db297bde..17b357a4ef 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts @@ -17,7 +17,17 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { RequiredTemplateValues } from '../templater'; import { JsonValue } from '@backstage/config'; +/** + * Publisher is in charge of taking a folder created by + * the templater, and pushing it to a remote storage + */ export type Publisher = { + /** + * + * @param opts object containing the template entity from the service + * catalog, plus the values from the form and the directory that has + * been templated + */ publish(opts: { entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; From d6b26d2c95e264adda644c8cb273f361e31b2236 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 3 Jul 2020 05:26:03 +0200 Subject: [PATCH 13/16] chore(scaffolder): some more docs for the PR --- .../src/scaffolder/jobs/logger.test.ts | 1 + .../src/scaffolder/stages/templater/types.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts index 5905d26ebb..12f893c146 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { makeLogStream } from './logger'; + describe('Logger', () => { const mockMeta = { test: 'blob' }; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts index cdefa7821e..519c24e381 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts @@ -18,11 +18,28 @@ import type { Writable } from 'stream'; import Docker from 'dockerode'; import { JsonValue } from '@backstage/config'; +/** + * Currently the required template values. The owner + * and where to store the result from templating + */ export type RequiredTemplateValues = { owner: string; storePath: string; }; +/** + * The returned directory from the templater which is ready + * to pass to the next stage of the scaffolder which is publishing + */ +export type TemplaterRunResult = { + resultDir: string; +}; + +/** + * The values that the templater will recieve. The directory of the + * skeleton, with the values from the frontend. A dedicated log stream and a docker + * client to run any templater on top of your directory. + */ export type TemplaterRunOptions = { directory: string; values: RequiredTemplateValues & Record; @@ -32,7 +49,7 @@ export type TemplaterRunOptions = { export type TemplaterBase = { // runs the templating with the values and returns the directory to push the VCS - run(opts: TemplaterRunOptions): Promise; + run(opts: TemplaterRunOptions): Promise; }; export type TemplaterConfig = { From 55982ba3e9d82852ae4b075e48f6e6fe4c44e768 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 Jul 2020 11:14:29 +0200 Subject: [PATCH 14/16] chore(scaffolder): moving the mocks up a level --- .../{stages/publish => }/__mocks__/@octokit/rest/index.ts | 0 .../scaffolder/{stages/publish => }/__mocks__/nodegit/index.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename plugins/scaffolder-backend/src/scaffolder/{stages/publish => }/__mocks__/@octokit/rest/index.ts (100%) rename plugins/scaffolder-backend/src/scaffolder/{stages/publish => }/__mocks__/nodegit/index.ts (100%) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/@octokit/rest/index.ts rename to plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/nodegit/index.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/stages/publish/__mocks__/nodegit/index.ts rename to plugins/scaffolder-backend/src/scaffolder/__mocks__/nodegit/index.ts From 7693a8d484e27809a0972d1a8426ffc7f431ca1e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 Jul 2020 11:15:53 +0200 Subject: [PATCH 15/16] chore(scaffolder): fixing linting --- .../src/scaffolder/stages/templater/cookiecutter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index 3e94d67c6e..1f2ae56959 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -18,6 +18,7 @@ import { JsonValue } from '@backstage/config'; import { runDockerContainer } from './helpers'; import { TemplaterBase, TemplaterRunOptions } from '.'; import path from 'path'; + export class CookieCutter implements TemplaterBase { private async fetchTemplateCookieCutter( directory: string, From 66016baf5c7876847fe13a33f34821dc9774fefd Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 Jul 2020 11:34:46 +0200 Subject: [PATCH 16/16] chore(scaffolder): fixing typescript woes --- .../src/scaffolder/stages/templater/cookiecutter.test.ts | 4 ++-- .../src/scaffolder/stages/templater/cookiecutter.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts index 27e4cd4b01..31bcdf189b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts @@ -131,13 +131,13 @@ describe('CookieCutter Templater', () => { component_id: 'newthing', }; - const returnPath = await cookie.run({ + const { resultDir } = await cookie.run({ directory: tempdir, values, dockerClient: mockDocker, }); - expect(returnPath.startsWith(`${tempdir}-result`)).toBeTruthy(); + expect(resultDir.startsWith(`${tempdir}-result`)).toBeTruthy(); }); it('should pass through the streamer to the run docker helper', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index 1f2ae56959..fff349cf82 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -18,6 +18,7 @@ import { JsonValue } from '@backstage/config'; import { runDockerContainer } from './helpers'; import { TemplaterBase, TemplaterRunOptions } from '.'; import path from 'path'; +import { TemplaterRunResult } from './types'; export class CookieCutter implements TemplaterBase { private async fetchTemplateCookieCutter( @@ -34,7 +35,7 @@ export class CookieCutter implements TemplaterBase { } } - public async run(options: TemplaterRunOptions): Promise { + public async run(options: TemplaterRunOptions): Promise { // First lets grab the default cookiecutter.json file const cookieCutterJson = await this.fetchTemplateCookieCutter( options.directory, @@ -66,6 +67,8 @@ export class CookieCutter implements TemplaterBase { dockerClient: options.dockerClient, }); - return path.resolve(resultDir, options.values.component_id as string); + return { + resultDir: path.resolve(resultDir, options.values.component_id as string), + }; } }