Merge pull request #4194 from backstage/orkohunter/update-templates-store-path
templates: Update Software Templates to use updated cookiecutter.storePath
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Fixing issues with templating and full URL's as `storePath`'s
|
||||
@@ -57,7 +57,7 @@ That type looks like the following:
|
||||
export type PublisherBase = {
|
||||
publish(opts: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
directory: string;
|
||||
}): Promise<{ remoteUrl: string }>;
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ That type looks like the following:
|
||||
```ts
|
||||
export type TemplaterRunOptions = {
|
||||
directory: string;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
logStream?: Writable;
|
||||
dockerClient: Docker;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"azure-devops-node-api": "^10.1.1",
|
||||
"command-exists-promise": "^2.0.2",
|
||||
"compression": "^1.7.4",
|
||||
@@ -61,7 +62,6 @@
|
||||
"@backstage/cli": "^0.4.7",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"yaml": "^1.10.0",
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ metadata:
|
||||
name: {{cookiecutter.component_id | jsonify}}
|
||||
description: {{cookiecutter.description | jsonify}}
|
||||
annotations:
|
||||
github.com/project-slug: {{cookiecutter.storePath | jsonify}}
|
||||
backstage.io/techdocs-ref: github:https://github.com/{{cookiecutter.storePath}}
|
||||
github.com/project-slug: {{cookiecutter.destination.git.owner + "/" + cookiecutter.destination.git.name}}
|
||||
backstage.io/techdocs-ref: url:{{cookiecutter.storePath}}
|
||||
spec:
|
||||
type: documentation
|
||||
lifecycle: experimental
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ metadata:
|
||||
name: {{cookiecutter.component_id | jsonify}}
|
||||
description: {{cookiecutter.description | jsonify}}
|
||||
annotations:
|
||||
github.com/project-slug: {{cookiecutter.storePath | jsonify}}
|
||||
backstage.io/techdocs-ref: github:https://github.com/{{cookiecutter.storePath}}
|
||||
github.com/project-slug: {{cookiecutter.destination.git.owner + "/" + cookiecutter.destination.git.name}}
|
||||
backstage.io/techdocs-ref: url:{{cookiecutter.storePath}}
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: experimental
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ metadata:
|
||||
name: {{cookiecutter.component_id | jsonify}}
|
||||
description: {{cookiecutter.description | jsonify}}
|
||||
annotations:
|
||||
github.com/project-slug: {{cookiecutter.storePath | jsonify}}
|
||||
backstage.io/techdocs-ref: github:https://github.com/{{cookiecutter.storePath}}
|
||||
github.com/project-slug: {{cookiecutter.destination.git.owner + "/" + cookiecutter.destination.git.name}}
|
||||
backstage.io/techdocs-ref: url:{{cookiecutter.storePath}}
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: experimental
|
||||
|
||||
@@ -17,6 +17,7 @@ import { JobProcessor } from './processor';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { StageInput } from './types';
|
||||
import { RequiredTemplateValues } from '../stages/templater';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
|
||||
describe('JobProcessor', () => {
|
||||
const mockEntity: TemplateEntityV1alpha1 = {
|
||||
@@ -61,7 +62,10 @@ describe('JobProcessor', () => {
|
||||
|
||||
const mockValues: RequiredTemplateValues = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/mock-repo',
|
||||
storePath: 'https://github.com/backstage/mock-repo',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/backstage/mock-repo'),
|
||||
},
|
||||
};
|
||||
|
||||
describe('create', () => {
|
||||
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Processor, Job, StageContext, StageInput } from './types';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import * as uuid from 'uuid';
|
||||
import Docker from 'dockerode';
|
||||
import { RequiredTemplateValues, TemplaterBase } from '../stages/templater';
|
||||
import { TemplaterValues, TemplaterBase } from '../stages/templater';
|
||||
import { PreparerBuilder } from '../stages/prepare';
|
||||
import { makeLogStream } from './logger';
|
||||
|
||||
@@ -42,7 +41,7 @@ export class JobProcessor implements Processor {
|
||||
stages,
|
||||
}: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
stages: StageInput[];
|
||||
}): Job {
|
||||
const id = uuid.v4();
|
||||
|
||||
@@ -15,15 +15,14 @@
|
||||
*/
|
||||
import type { Writable } from 'stream';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { RequiredTemplateValues } from '../stages/templater';
|
||||
import { TemplaterValues } from '../stages/templater';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// Context will be a mutable object which is passed between stages
|
||||
// To share data, but also thinking that we can pass in functions here too
|
||||
// To maybe create sub steps or fail the entire thing, or skip stages down the line.
|
||||
export type StageContext<T = {}> = {
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
entity: TemplateEntityV1alpha1;
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
@@ -58,7 +57,7 @@ export type Processor = {
|
||||
stages,
|
||||
}: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
stages: StageInput[];
|
||||
}): Job;
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RequiredTemplateValues } from '../templater';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { TemplaterValues } from '../templater';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/**
|
||||
@@ -32,7 +31,7 @@ export type PublisherBase = {
|
||||
};
|
||||
|
||||
export type PublisherOptions = {
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
directory: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import path from 'path';
|
||||
import { RunDockerContainerOptions, RunCommandOptions } from './helpers';
|
||||
import { PassThrough } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
|
||||
const commandExists = require('command-exists-promise');
|
||||
|
||||
@@ -56,16 +57,21 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
description: 'description',
|
||||
component_id: 'newthing',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await cookie.run({ directory: tempdir, values, dockerClient: mockDocker });
|
||||
|
||||
const cookieCutterJson = await fs.readJSON(`${tempdir}/cookiecutter.json`);
|
||||
|
||||
expect(cookieCutterJson).toEqual(expect.objectContaining(values));
|
||||
expect(cookieCutterJson).toEqual(
|
||||
expect.objectContaining(JSON.parse(JSON.stringify(values))),
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge any value that is in the cookiecutter.json path already', async () => {
|
||||
@@ -78,15 +84,24 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
component_id: 'something',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await cookie.run({ directory: tempdir, values, dockerClient: mockDocker });
|
||||
|
||||
const cookieCutterJson = await fs.readJSON(`${tempdir}/cookiecutter.json`);
|
||||
|
||||
expect(cookieCutterJson).toEqual({ ...existingJson, ...values });
|
||||
expect(cookieCutterJson).toEqual({
|
||||
...existingJson,
|
||||
...values,
|
||||
destination: {
|
||||
git: expect.objectContaining({ organization: 'org', name: 'repo' }),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the cookiecutter json is malformed and not missing', async () => {
|
||||
@@ -96,7 +111,10 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
@@ -109,8 +127,11 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
component_id: 'newthing',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await cookie.run({ directory: tempdir, values, dockerClient: mockDocker });
|
||||
@@ -137,8 +158,11 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
component_id: 'newthing',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
const { resultDir } = await cookie.run({
|
||||
@@ -157,8 +181,11 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
component_id: 'newthing',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await cookie.run({
|
||||
@@ -203,8 +230,11 @@ describe('CookieCutter Templater', () => {
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
component_id: 'newthing',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
};
|
||||
|
||||
await cookie.run({
|
||||
@@ -243,7 +273,10 @@ describe('CookieCutter Templater', () => {
|
||||
directory: tempdir,
|
||||
values: {
|
||||
owner: 'blobby',
|
||||
storePath: 'backstage/end-repo',
|
||||
storePath: 'https://github.com/org/repo',
|
||||
destination: {
|
||||
git: parseGitUrl('https://github.com/org/repo'),
|
||||
},
|
||||
},
|
||||
logStream: stream,
|
||||
dockerClient: mockDocker,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { JsonValue } from '@backstage/config';
|
||||
import { runDockerContainer, runCommand } from './helpers';
|
||||
import { TemplaterBase, TemplaterRunOptions } from '.';
|
||||
import path from 'path';
|
||||
|
||||
import { TemplaterRunResult } from './types';
|
||||
|
||||
const commandExists = require('command-exists-promise');
|
||||
|
||||
@@ -70,7 +70,9 @@ export class CreateReactAppTemplater implements TemplaterBase {
|
||||
`${finalDir}/.github/workflows/main.yml`,
|
||||
);
|
||||
|
||||
extraAnnotations[GITHUB_ACTIONS_ANNOTATION] = options.values.storePath;
|
||||
extraAnnotations[
|
||||
GITHUB_ACTIONS_ANNOTATION
|
||||
] = `${options.values?.destination?.git?.owner}/${options.values?.destination?.git?.name}`;
|
||||
}
|
||||
|
||||
const componentInfo = {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
import type { Writable } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
|
||||
/**
|
||||
* Currently the required template values. The owner
|
||||
@@ -25,8 +25,13 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
export type RequiredTemplateValues = {
|
||||
owner: string;
|
||||
storePath: string;
|
||||
destination?: {
|
||||
git?: gitUrlParse.GitUrl;
|
||||
};
|
||||
};
|
||||
|
||||
export type TemplaterValues = RequiredTemplateValues & Record<string, any>;
|
||||
|
||||
/**
|
||||
* The returned directory from the templater which is ready
|
||||
* to pass to the next stage of the scaffolder which is publishing
|
||||
@@ -42,7 +47,7 @@ export type TemplaterRunResult = {
|
||||
*/
|
||||
export type TemplaterRunOptions = {
|
||||
directory: string;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
values: TemplaterValues;
|
||||
logStream?: Writable;
|
||||
dockerClient: Docker;
|
||||
};
|
||||
|
||||
@@ -107,10 +107,14 @@ describe('createRouter - working directory', () => {
|
||||
});
|
||||
|
||||
const app = express().use(router);
|
||||
await request(app).post('/v1/jobs').send({
|
||||
templateName: '',
|
||||
values: {},
|
||||
});
|
||||
await request(app)
|
||||
.post('/v1/jobs')
|
||||
.send({
|
||||
templateName: '',
|
||||
values: {
|
||||
storePath: 'https://github.com/backstage/good',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockPrepare).toBeCalledWith(expect.anything(), {
|
||||
logger: expect.anything(),
|
||||
@@ -130,10 +134,14 @@ describe('createRouter - working directory', () => {
|
||||
});
|
||||
|
||||
const app = express().use(router);
|
||||
await request(app).post('/v1/jobs').send({
|
||||
templateName: '',
|
||||
values: {},
|
||||
});
|
||||
await request(app)
|
||||
.post('/v1/jobs')
|
||||
.send({
|
||||
templateName: '',
|
||||
values: {
|
||||
storePath: 'https://github.com/backstage/goodrepo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockPrepare).toBeCalledWith(expect.anything(), {
|
||||
logger: expect.anything(),
|
||||
@@ -200,10 +208,14 @@ describe('createRouter', () => {
|
||||
|
||||
describe('POST /v1/jobs', () => {
|
||||
it('rejects template values which do not match the template schema definition', async () => {
|
||||
const response = await request(app).post('/v1/jobs').send({
|
||||
templateName: '',
|
||||
values: {},
|
||||
});
|
||||
const response = await request(app)
|
||||
.post('/v1/jobs')
|
||||
.send({
|
||||
templateName: '',
|
||||
values: {
|
||||
storePath: 'https://github.com/backstage/backstage',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config, JsonValue } from '@backstage/config';
|
||||
import { Config } from '@backstage/config';
|
||||
import fs from 'fs-extra';
|
||||
import Docker from 'dockerode';
|
||||
import express from 'express';
|
||||
@@ -23,15 +23,16 @@ import { Logger } from 'winston';
|
||||
import {
|
||||
JobProcessor,
|
||||
PreparerBuilder,
|
||||
RequiredTemplateValues,
|
||||
StageContext,
|
||||
TemplaterBuilder,
|
||||
TemplaterValues,
|
||||
PublisherBuilder,
|
||||
parseLocationAnnotation,
|
||||
FilePreparer,
|
||||
} from '../scaffolder';
|
||||
import { CatalogEntityClient } from '../lib/catalog';
|
||||
import { validate, ValidatorResult } from 'jsonschema';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
|
||||
export interface RouterOptions {
|
||||
preparers: PreparerBuilder;
|
||||
@@ -109,8 +110,12 @@ export async function createRouter(
|
||||
})
|
||||
.post('/v1/jobs', async (req, res) => {
|
||||
const templateName: string = req.body.templateName;
|
||||
const values: RequiredTemplateValues & Record<string, JsonValue> =
|
||||
req.body.values;
|
||||
const values: TemplaterValues = {
|
||||
...req.body.values,
|
||||
destination: {
|
||||
git: parseGitUrl(req.body.values.storePath),
|
||||
},
|
||||
};
|
||||
|
||||
const template = await entityClient.findTemplate(templateName);
|
||||
|
||||
@@ -123,7 +128,6 @@ export async function createRouter(
|
||||
res.status(400).json({ errors: validationResult.errors });
|
||||
return;
|
||||
}
|
||||
|
||||
const job = jobProcessor.create({
|
||||
entity: template,
|
||||
values,
|
||||
|
||||
Reference in New Issue
Block a user