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',