feat(scaffolder): making the API work by taking the entity that is pulled from the client
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user