Expose template metadata to custom action handler

Currently, we only expose the template name to satisfy the requirement from the issue, but more can be added easily (as long as the type is expanded accordingly).

Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
Joon Park
2021-10-21 17:54:20 +01:00
parent 2fabfd42ac
commit ecdcbd08ee
11 changed files with 103 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-scaffolder-common': patch
---
Expose template metadata to custom action handler in Scaffolder.
@@ -91,6 +91,8 @@ argument. It looks like the following:
- `createTemporaryDirectory` a function to call to give you a temporary
directory somewhere on the runner so you can store some files there rather
than polluting the `workspacePath`
- `ctx.metadata` - an object containing a `name` field, indicating the template
name. More metadata fields may be added later.
### Registering Custom Actions
+10
View File
@@ -41,6 +41,7 @@ export type ActionContext<Input extends InputBase> = {
input: Input;
output(name: string, value: JsonValue): void;
createTemporaryDirectory(): Promise<string>;
metadata?: TemplateMetadata;
};
// Warning: (ae-missing-release-tag) "CatalogEntityClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -432,6 +433,8 @@ export interface TaskSpecV1beta2 {
// (undocumented)
baseUrl?: string;
// (undocumented)
metadata?: TemplateMetadata;
// (undocumented)
output: {
[name: string]: string;
};
@@ -454,6 +457,8 @@ export interface TaskSpecV1beta3 {
// (undocumented)
baseUrl?: string;
// (undocumented)
metadata?: TemplateMetadata;
// (undocumented)
output: {
[name: string]: JsonValue;
};
@@ -558,4 +563,9 @@ export class TemplateActionRegistry {
action: TemplateAction<Parameters>,
): void;
}
// @public
export type TemplateMetadata = {
name: string;
};
```
@@ -18,6 +18,7 @@ import { Logger } from 'winston';
import { Writable } from 'stream';
import { JsonValue, JsonObject } from '@backstage/types';
import { Schema } from 'jsonschema';
import { TemplateMetadata } from '../tasks/types';
type PartialJsonObject = Partial<JsonObject>;
type PartialJsonValue = PartialJsonObject | JsonValue | undefined;
@@ -44,6 +45,8 @@ export type ActionContext<Input extends InputBase> = {
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
*/
createTemporaryDirectory(): Promise<string>;
metadata?: TemplateMetadata;
};
export type TemplateAction<Input extends InputBase> = {
@@ -146,6 +146,30 @@ describe('DefaultWorkflowRunner', () => {
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
});
it('should pass metadata through', async () => {
const templateName = 'template name';
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
parameters: {},
output: {},
steps: [
{
id: 'test',
name: 'name',
action: 'jest-validated-action',
input: { foo: 1 },
},
],
metadata: { name: templateName },
});
await runner.execute(task);
expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
name: templateName,
});
});
});
describe('conditionals', () => {
@@ -225,6 +225,12 @@ 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({
baseUrl: task.spec.baseUrl,
logger: taskLogger,
@@ -242,6 +248,7 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutputs[name] = value;
},
metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
@@ -28,6 +28,7 @@ describe('LegacyWorkflowRunner', () => {
let runner: HandlebarsWorkflowRunner;
const logger = getVoidLogger();
let actionRegistry = new TemplateActionRegistry();
let fakeActionHandler: jest.Mock;
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
@@ -59,6 +60,11 @@ describe('LegacyWorkflowRunner', () => {
ctx.output('badOutput', false);
},
});
fakeActionHandler = jest.fn();
actionRegistry.register({
id: 'test-metadata-action',
handler: fakeActionHandler,
});
runner = new HandlebarsWorkflowRunner({
actionRegistry,
@@ -87,6 +93,30 @@ describe('LegacyWorkflowRunner', () => {
);
});
it('should pass metadata through', async () => {
const templateName = 'template name';
const task = createMockTaskWithSpec({
apiVersion: 'backstage.io/v1beta2',
steps: [
{
id: 'test',
name: 'name',
action: 'test-metadata-action',
input: { foo: 1 },
},
],
output: {},
values: {},
metadata: { name: templateName },
});
await runner.execute(task);
expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
name: templateName,
});
});
describe('templating', () => {
it('should template the output', async () => {
const task = createMockTaskWithSpec({
@@ -228,6 +228,12 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const tmpDirs = new Array<string>();
const stepOutput: { [outputName: string]: JsonValue } = {};
if (!task.spec.metadata) {
console.warn(
'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
);
}
await action.handler({
baseUrl: task.spec.baseUrl,
input,
@@ -244,6 +250,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutput[name] = value;
},
metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
@@ -34,4 +34,5 @@ export type {
TaskContext,
TaskStore,
DispatchResult,
TemplateMetadata,
} from './types';
@@ -69,6 +69,15 @@ export type SerializedTaskEvent = {
createdAt: string;
};
/**
* TemplateMetadata
*
* @public
*/
export type TemplateMetadata = {
name: string;
};
/**
* TaskSpecV1beta2
*
@@ -86,6 +95,7 @@ export interface TaskSpecV1beta2 {
if?: string | boolean;
}>;
output: { [name: string]: string };
metadata?: TemplateMetadata;
}
export interface TaskStep {
@@ -107,6 +117,7 @@ export interface TaskSpecV1beta3 {
parameters: JsonObject;
steps: TaskStep[];
output: { [name: string]: JsonValue };
metadata?: TemplateMetadata;
}
/**
@@ -209,6 +209,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
metadata: { name: template.metadata?.name },
}
: {
apiVersion: template.apiVersion,
@@ -220,6 +221,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
metadata: { name: template.metadata?.name },
};
} else {
throw new InputError(