diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index ecce161c8e..252f498bd6 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -50,6 +50,7 @@ "fs-extra": "^9.0.0", "git-url-parse": "^11.4.4", "globby": "^11.0.0", + "handlebars": "^4.7.6", "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", @@ -66,9 +67,9 @@ "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", "mock-fs": "^4.13.0", + "msw": "^0.21.2", "supertest": "^4.0.2", - "yaml": "^1.10.0", - "msw": "^0.21.2" + "yaml": "^1.10.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index d3b2c098c7..fb1f4ab422 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -63,11 +63,17 @@ export class TaskAgent implements Task { }); } - async complete(result: CompletedTaskState): Promise { + async complete( + result: CompletedTaskState, + metadata?: JsonObject, + ): Promise { await this.storage.completeTask({ taskId: this.state.taskId, status: result === 'failed' ? 'failed' : 'completed', - eventBody: { message: `Run completed with status: ${result}` }, + eventBody: { + message: `Run completed with status: ${result}`, + ...metadata, + }, }); this.isDone = true; if (this.heartbeatTimeoutId) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 66d1331c5c..51260aef7d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -22,6 +22,7 @@ import { TaskBroker, Task } from './types'; import fs from 'fs-extra'; import path from 'path'; import { TemplateActionRegistry } from './TemplateConverter'; +import * as handlebars from 'handlebars'; type Options = { logger: Logger; @@ -55,7 +56,11 @@ export class TaskWorker { `Starting up work with ${task.spec.steps.length} steps`, ); - const outputs: { [name: string]: JsonValue } = {}; + const templateCtx: { + steps: { + [stepName: string]: { output: { [outputName: string]: JsonValue } }; + }; + } = { steps: {} }; for (const step of task.spec.steps) { const metadata = { stepId: step.id }; @@ -87,8 +92,24 @@ export class TaskWorker { throw new Error(`Action '${step.action}' does not exist`); } - // TODO: substitute any placeholders with output from previous steps - const parameters = step.parameters!; + const parameters: { [name: string]: JsonValue } = {}; + for (const [name, maybeTemplateStr] of Object.entries( + step.parameters ?? {}, + )) { + if (typeof maybeTemplateStr === 'string') { + const value = handlebars.compile(maybeTemplateStr, { + noEscape: true, + strict: true, + data: false, + preventIndent: true, + })(templateCtx); + parameters[name] = value; + } else { + parameters[name] = maybeTemplateStr; + } + } + + const stepOutputs: { [name: string]: JsonValue } = {}; await action.handler({ logger, @@ -96,10 +117,12 @@ export class TaskWorker { parameters, workspacePath, output(name: string, value: JsonValue) { - outputs[name] = value; + stepOutputs[name] = value; }, }); + templateCtx.steps[step.id] = { output: stepOutputs }; + await task.emitLog(`Finished step ${step.name}`, { ...metadata, status: 'completed', @@ -112,9 +135,24 @@ export class TaskWorker { throw error; } } - await task.complete('completed'); + + const output = Object.fromEntries( + Object.entries(task.spec.output).map(([name, templateStr]) => { + const value = handlebars.compile(templateStr, { + noEscape: true, + strict: true, + data: false, + preventIndent: true, + })(templateCtx); + return [name, value]; + }), + ); + + await task.complete('completed', { output }); } catch (error) { - await task.complete('failed'); + await task.complete('failed', { + error: { name: error.name, message: error.message }, + }); } } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 9c5df52c5e..48f31f73f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -76,7 +76,13 @@ export function templateEntityToSpec( }, }); - return { steps }; + return { + steps, + output: { + remoteUrl: '{{ steps.publish.output.remoteUrl }}', + catalogInfoUrl: '{{ steps.publish.output.catalogInfoUrl }}', + }, + }; } type ActionContext = { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 783528dbdf..ae18a59e41 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -49,6 +49,7 @@ export type TaskSpec = { action: string; parameters?: { [name: string]: JsonValue }; }>; + output: { [name: string]: string }; }; export type DispatchResult = { @@ -59,7 +60,7 @@ export interface Task { spec: TaskSpec; done: boolean; emitLog(message: string, metadata?: JsonValue): Promise; - complete(result: CompletedTaskState): Promise; + complete(result: CompletedTaskState, metadata?: JsonValue): Promise; getWorkspaceName(): Promise; }