feat(scaffolder): making the API work by taking the entity that is pulled from the client

This commit is contained in:
blam
2020-06-29 17:49:54 +02:00
parent ee205ef23e
commit d5a0a5968e
8 changed files with 48 additions and 61 deletions
@@ -17,7 +17,7 @@ import { PassThrough } from 'stream';
import winston from 'winston';
import { JsonValue } from '@backstage/config';
export const useLogStream = (meta: Record<string, JsonValue>) => {
export const makeLogStream = (meta: Record<string, JsonValue>) => {
const log: string[] = [];
// Create an empty stream to collect all the log lines into
@@ -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 () => {
@@ -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,
});
@@ -31,7 +31,7 @@ export type StageContext<T = {}> = {
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;
};
@@ -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<string, JsonValue>;
}) {
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,
[],
@@ -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({
@@ -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 = {
@@ -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<string, JsonValue> =
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',