diff --git a/.changeset/cool-ways-battle.md b/.changeset/cool-ways-battle.md
new file mode 100644
index 0000000000..383e2879c0
--- /dev/null
+++ b/.changeset/cool-ways-battle.md
@@ -0,0 +1,10 @@
+---
+'@backstage/plugin-scaffolder-common': patch
+---
+
+**DEPRECATED** - The `TaskSpec.metadata` and `TaskSpec.baseUrl` has been deprecated in favour of the new `TaskSpec.templateInfo`.
+The `baseUrl` is now found on the `templateInfo` object, and the name can be inferred from the `templateInfo.entityRef` property.
+
+Usages of `TaskSpec.metadata.name` or `ctx.metadata.name` in Actions should migrate to using `parseEntityRef(taskSpec.templateInfo.entityRef)` to get the parsed entity triplet.
+
+Usages of `ctx.baseUrl` in Actions should migrate to using `ctx.templateInfo.baseUrl` instead.
diff --git a/.changeset/purple-camels-accept.md b/.changeset/purple-camels-accept.md
new file mode 100644
index 0000000000..fe3da66a74
--- /dev/null
+++ b/.changeset/purple-camels-accept.md
@@ -0,0 +1,8 @@
+---
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
+'@backstage/plugin-scaffolder-backend-module-rails': patch
+---
+
+Migrated over from the deprecated `spec.metadata` to `spec.templateInfo` for the `name` and the `baseUrl` of the template.
diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts
index bba51e3f01..2c1ecd06f9 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts
+++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts
@@ -89,7 +89,10 @@ describe('fetch:cookiecutter', () => {
help: 'me',
},
},
- baseUrl: 'somebase',
+ templateInfo: {
+ entityRef: 'template:default/cookiecutter',
+ baseUrl: 'somebase',
+ },
workspacePath: mockTmpDir,
logger: getVoidLogger(),
logStream: new PassThrough(),
@@ -149,7 +152,7 @@ describe('fetch:cookiecutter', () => {
expect.objectContaining({
reader: mockReader,
integrations,
- baseUrl: mockContext.baseUrl,
+ baseUrl: mockContext.templateInfo?.baseUrl,
fetchUrl: mockContext.input.url,
outputPath: join(
mockTmpDir,
diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
index c4d7f1e3e8..0455f01989 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
+++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
@@ -222,7 +222,7 @@ export function createFetchCookiecutterAction(options: {
await fetchContents({
reader,
integrations,
- baseUrl: ctx.baseUrl,
+ baseUrl: ctx.templateInfo?.baseUrl,
fetchUrl: ctx.input.url,
outputPath: templateContentsDir,
});
diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts
index 0c7552f72f..0a6a996aa8 100644
--- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts
+++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts
@@ -62,7 +62,10 @@ describe('fetch:rails', () => {
help: 'me',
},
},
- baseUrl: 'somebase',
+ templateInfo: {
+ baseUrl: 'somebase',
+ entityRef: 'template:default/myTemplate',
+ },
workspacePath: mockTmpDir,
logger: getVoidLogger(),
logStream: new PassThrough(),
@@ -100,7 +103,7 @@ describe('fetch:rails', () => {
expect(fetchContents).toHaveBeenCalledWith({
reader: mockReader,
integrations,
- baseUrl: mockContext.baseUrl,
+ baseUrl: mockContext.templateInfo.baseUrl,
fetchUrl: mockContext.input.url,
outputPath: resolvePath(mockContext.workspacePath),
});
diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
index 2865361a8b..132929ee1c 100644
--- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
+++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
@@ -170,7 +170,7 @@ export function createFetchRailsAction(options: {
await fetchContents({
reader,
integrations,
- baseUrl: ctx.baseUrl,
+ baseUrl: ctx.templateInfo?.baseUrl,
fetchUrl: ctx.input.url,
outputPath: workDir,
});
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index b4db89b8eb..14b422048d 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -29,6 +29,7 @@ import { SpawnOptionsWithoutStdio } from 'child_process';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common';
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
+import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
import { TemplateMetadata } from '@backstage/plugin-scaffolder-common';
import { UrlReader } from '@backstage/backend-common';
import { Writable } from 'stream';
@@ -46,6 +47,7 @@ export type ActionContext = {
output(name: string, value: JsonValue): void;
createTemporaryDirectory(): Promise;
metadata?: TemplateMetadata;
+ templateInfo?: TemplateInfo;
};
// @public
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
index 9cac6d5ab8..17f49a5bd4 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
@@ -59,7 +59,7 @@ export function createFetchPlainAction(options: {
await fetchContents({
reader,
integrations,
- baseUrl: ctx.baseUrl,
+ baseUrl: ctx.templateInfo?.baseUrl,
fetchUrl: ctx.input.url,
outputPath,
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
index 2f9bfd3796..8f641fe2e2 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
@@ -75,7 +75,10 @@ describe('fetch:template', () => {
const logger = getVoidLogger();
const mockContext = (inputPatch: Partial = {}) => ({
- baseUrl: 'base-url',
+ templateInfo: {
+ baseUrl: 'base-url',
+ entityRef: 'template:default/test-template',
+ },
input: {
url: './skeleton',
targetPath: './target',
@@ -199,7 +202,7 @@ describe('fetch:template', () => {
it('uses fetchContents to retrieve the template content', () => {
expect(mockFetchContents).toHaveBeenCalledWith(
expect.objectContaining({
- baseUrl: context.baseUrl,
+ baseUrl: context.templateInfo?.baseUrl,
fetchUrl: context.input.url,
}),
);
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
index 08dfc46caf..a8ba9ac19f 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
@@ -135,7 +135,7 @@ export function createFetchTemplateAction(options: {
await fetchContents({
reader,
integrations,
- baseUrl: ctx.baseUrl,
+ baseUrl: ctx.templateInfo?.baseUrl,
fetchUrl: ctx.input.url,
outputPath: templateDir,
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
index 6008e8183f..b2227426b8 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
@@ -19,10 +19,12 @@ import { Writable } from 'stream';
import { JsonValue, JsonObject } from '@backstage/types';
import { Schema } from 'jsonschema';
import { TaskSecrets, TemplateMetadata } from '../tasks/types';
+import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
export type ActionContext = {
/**
* Base URL for the location of the task spec, typically the url of the source entity file.
+ * @deprecated please use templateInfo.baseUrl instead
*/
baseUrl?: string;
@@ -38,7 +40,11 @@ export type ActionContext = {
*/
createTemporaryDirectory(): Promise;
+ /**
+ * @deprecated please use templateInfo instead
+ */
metadata?: TemplateMetadata;
+ templateInfo?: TemplateInfo;
};
export type TemplateAction = {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts
index 9b05c43831..9f4135b3d1 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts
@@ -94,7 +94,7 @@ describe('LegacyWorkflowRunner', () => {
});
it('should pass metadata through', async () => {
- const templateName = 'template name';
+ const entityRef = `template:default/templateName`;
const task = createMockTaskWithSpec({
apiVersion: 'backstage.io/v1beta2',
steps: [
@@ -107,13 +107,13 @@ describe('LegacyWorkflowRunner', () => {
],
output: {},
values: {},
- metadata: { name: templateName },
+ templateInfo: { entityRef },
});
await runner.execute(task);
- expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
- name: templateName,
+ expect(fakeActionHandler.mock.calls[0][0].templateInfo).toEqual({
+ entityRef,
});
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
index 86e719c828..e525e6bd14 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
@@ -225,13 +225,8 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
input: JSON.stringify(input, null, 2),
});
- if (!task.spec.metadata) {
- console.warn(
- 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
- );
- }
-
await action.handler({
+ // deprecated in favor of templateInfo.baseUrl
baseUrl: task.spec.baseUrl,
logger: taskLogger,
logStream: stream,
@@ -248,7 +243,9 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutputs[name] = value;
},
+ // deprecated in favor of templateInfo
metadata: task.spec.metadata,
+ templateInfo: task.spec.templateInfo,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
index f46d969eb8..b66c69ee17 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
@@ -163,7 +163,7 @@ describe('DefaultWorkflowRunner', () => {
});
it('should pass metadata through', async () => {
- const templateName = 'template name';
+ const entityRef = `template:default/templateName`;
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
parameters: {},
@@ -176,13 +176,13 @@ describe('DefaultWorkflowRunner', () => {
input: { foo: 1 },
},
],
- metadata: { name: templateName },
+ templateInfo: { entityRef },
});
await runner.execute(task);
- expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
- name: templateName,
+ expect(fakeActionHandler.mock.calls[0][0].templateInfo).toEqual({
+ entityRef,
});
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
index 5d39d0bc00..b1177ca49c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
@@ -259,6 +259,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const stepOutput: { [outputName: string]: JsonValue } = {};
await action.handler({
+ // deprecated in favourof templateInfo.baseUrl
baseUrl: task.spec.baseUrl,
input,
secrets: task.secrets ?? {},
@@ -275,7 +276,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutput[name] = value;
},
+ // deprecated in favour of templateInfo
metadata: task.spec.metadata,
+ templateInfo: task.spec.templateInfo,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index b2978441bd..8a3645cf31 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -28,6 +28,8 @@ import { ScmIntegrations } from '@backstage/integration';
import {
TemplateEntityV1beta2,
TemplateEntityV1beta3,
+ TaskSpecV1beta3,
+ TaskSpecV1beta2,
} from '@backstage/plugin-scaffolder-common';
import express from 'express';
import Router from 'express-promise-router';
@@ -186,12 +188,15 @@ export async function createRouter(
})
.post('/v2/tasks', async (req, res) => {
const templateName: string = req.body.templateName;
+ const { kind, namespace } = { kind: 'template', namespace: 'default' };
const values = req.body.values;
const token = getBearerToken(req.headers.authorization);
const template = await findTemplate({
catalogApi: catalogClient,
entityRef: {
name: templateName,
+ kind,
+ namespace,
},
token: getBearerToken(req.headers.authorization),
});
@@ -219,32 +224,40 @@ export async function createRouter(
const baseUrl = getEntityBaseUrl(template);
+ const baseTaskSpec = {
+ baseUrl,
+ steps: template.spec.steps.map((step, index) => ({
+ ...step,
+ id: step.id ?? `step-${index + 1}`,
+ name: step.name ?? step.action,
+ })),
+ output: template.spec.output ?? {},
+
+ // deprecated in favour of templateInfo
+ metadata: { name: template.metadata?.name },
+
+ templateInfo: {
+ entityRef: stringifyEntityRef({
+ kind,
+ namespace,
+ name: template.metadata?.name,
+ }),
+ baseUrl,
+ },
+ };
+
taskSpec =
template.apiVersion === 'backstage.io/v1beta2'
- ? {
+ ? ({
+ ...baseTaskSpec,
apiVersion: template.apiVersion,
- baseUrl,
values,
- steps: template.spec.steps.map((step, index) => ({
- ...step,
- id: step.id ?? `step-${index + 1}`,
- name: step.name ?? step.action,
- })),
- output: template.spec.output ?? {},
- metadata: { name: template.metadata?.name },
- }
- : {
+ } as TaskSpecV1beta2)
+ : ({
+ ...baseTaskSpec,
apiVersion: template.apiVersion,
- baseUrl,
parameters: values,
- steps: template.spec.steps.map((step, index) => ({
- ...step,
- id: step.id ?? `step-${index + 1}`,
- name: step.name ?? step.action,
- })),
- output: template.spec.output ?? {},
- metadata: { name: template.metadata?.name },
- };
+ } as TaskSpecV1beta3);
} else {
throw new InputError(
`Unsupported apiVersion field in schema entity, ${
@@ -256,8 +269,6 @@ export async function createRouter(
const result = await taskBroker.dispatch(taskSpec, {
...req.body.secrets,
backstageToken: token,
- // This is deprecated, but we need to support it for now if people are running their own task broker.
- token: token,
});
res.status(201).json({ id: result.taskId });
diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md
index 8d2324a2dd..1872aa3ed8 100644
--- a/plugins/scaffolder-common/api-report.md
+++ b/plugins/scaffolder-common/api-report.md
@@ -15,9 +15,9 @@ export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
export interface TaskSpecV1beta2 {
// (undocumented)
apiVersion: 'backstage.io/v1beta2';
- // (undocumented)
+ // @deprecated (undocumented)
baseUrl?: string;
- // (undocumented)
+ // @deprecated (undocumented)
metadata?: TemplateMetadata;
// (undocumented)
output: {
@@ -26,6 +26,8 @@ export interface TaskSpecV1beta2 {
// (undocumented)
steps: TaskStep[];
// (undocumented)
+ templateInfo?: TemplateInfo;
+ // (undocumented)
values: JsonObject;
}
@@ -33,9 +35,9 @@ export interface TaskSpecV1beta2 {
export interface TaskSpecV1beta3 {
// (undocumented)
apiVersion: 'scaffolder.backstage.io/v1beta3';
- // (undocumented)
+ // @deprecated (undocumented)
baseUrl?: string;
- // (undocumented)
+ // @deprecated (undocumented)
metadata?: TemplateMetadata;
// (undocumented)
output: {
@@ -45,6 +47,8 @@ export interface TaskSpecV1beta3 {
parameters: JsonObject;
// (undocumented)
steps: TaskStep[];
+ // (undocumented)
+ templateInfo?: TemplateInfo;
}
// @public
@@ -116,6 +120,12 @@ export interface TemplateEntityV1beta3 extends Entity {
export const templateEntityV1beta3Validator: KindValidator;
// @public
+export type TemplateInfo = {
+ entityRef: string;
+ baseUrl?: string;
+};
+
+// @public @deprecated
export type TemplateMetadata = {
name: string;
};
diff --git a/plugins/scaffolder-common/src/TaskSpec.ts b/plugins/scaffolder-common/src/TaskSpec.ts
index 1cbe41b28c..2c7eb26d5b 100644
--- a/plugins/scaffolder-common/src/TaskSpec.ts
+++ b/plugins/scaffolder-common/src/TaskSpec.ts
@@ -21,11 +21,23 @@ import { JsonValue, JsonObject } from '@backstage/types';
* stored in the database.
*
* @public
+ * @deprecated use templateInfo on the spec instead
*/
export type TemplateMetadata = {
name: string;
};
+/**
+ * Information about a template that is stored on a task specification.
+ * Includes a stringified entityRef, and the baseUrl which is usually the relative path of the template definition
+ *
+ * @public
+ */
+export type TemplateInfo = {
+ entityRef: string;
+ baseUrl?: string;
+};
+
/**
* An individual step of a scaffolder task, as stored in the database.
*
@@ -49,11 +61,14 @@ export interface TaskStep {
*/
export interface TaskSpecV1beta2 {
apiVersion: 'backstage.io/v1beta2';
+ /** @deprecated use templateInfo.baseUrl instead */
baseUrl?: string;
values: JsonObject;
steps: TaskStep[];
output: { [name: string]: string };
+ /** @deprecated use templateInfo instead */
metadata?: TemplateMetadata;
+ templateInfo?: TemplateInfo;
}
/**
@@ -64,11 +79,14 @@ export interface TaskSpecV1beta2 {
*/
export interface TaskSpecV1beta3 {
apiVersion: 'scaffolder.backstage.io/v1beta3';
+ /** @deprecated use templateInfo.baseUrl instead */
baseUrl?: string;
parameters: JsonObject;
steps: TaskStep[];
output: { [name: string]: JsonValue };
+ /** @deprecated use templateInfo instead */
metadata?: TemplateMetadata;
+ templateInfo?: TemplateInfo;
}
/**
diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx
index 2f49f40643..a642660bac 100644
--- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx
+++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { parseEntityRef } from '@backstage/catalog-model';
import {
Content,
ErrorPage,
@@ -291,8 +292,9 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
const { output } = taskStream;
const handleStartOver = () => {
- if (!taskStream.task || !taskStream.task?.spec.metadata?.name) {
+ if (!taskStream.task || !taskStream.task?.spec.templateInfo?.entityRef) {
navigate(generatePath(rootLink()));
+ return;
}
const formData =
@@ -300,13 +302,17 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
? taskStream.task!.spec.values
: taskStream.task!.spec.parameters;
+ const { name } = parseEntityRef(
+ taskStream.task!.spec.templateInfo?.entityRef,
+ );
+
navigate(
generatePath(
`${rootLink()}/templates/:templateName?${qs.stringify({
formData: JSON.stringify(formData),
})}`,
{
- templateName: taskStream.task!.spec.metadata!.name,
+ templateName: name,
},
),
);