diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 190c880280..d746cf859f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,7 +26,7 @@ import fs from 'fs-extra'; import path from 'path'; import nunjucks from 'nunjucks'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; -import { InputError, NotAllowedError } from '@backstage/errors'; +import { InputError, NotAllowedError, stringifyError } from '@backstage/errors'; import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; @@ -353,9 +353,22 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { key: string, fn: () => Promise, ) { - const value = await fn(); - task.updateCheckpoint?.(key, value); - return value; + try { + const value = await fn(); + task.updateCheckpoint?.({ + key, + status: 'success', + value, + }); + return value; + } catch (err) { + task.updateCheckpoint?.({ + key, + status: 'failed', + reason: stringifyError(err), + }); + throw err; + } }, createTemporaryDirectory: async () => { const tmpDir = await fs.mkdtemp( diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 8762253436..01722edba9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -16,7 +16,11 @@ import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { TaskSecrets, TaskState } from '@backstage/plugin-scaffolder-node'; +import { + TaskSecrets, + TaskState, + UpdateCheckpointOptions, +} from '@backstage/plugin-scaffolder-node'; import { JsonObject, Observable } from '@backstage/types'; import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; @@ -91,8 +95,12 @@ export class TaskManager implements TaskContext { }); } - async updateCheckpoint?(key: string, value: JsonObject): Promise { - this.task.state = { [key]: value }; + async updateCheckpoint?(options: UpdateCheckpointOptions): Promise { + if (this.task.state) { + this.task.state[options.key] = { ...options }; + } else { + this.task.state = { [options.key]: options }; + } await this.storage.saveCheckpoint?.({ taskId: this.task.taskId, state: this.task.state, diff --git a/plugins/scaffolder-backend/src/util/defineCheckpoint.ts b/plugins/scaffolder-backend/src/util/defineCheckpoint.ts index 6656005684..c349bdc7f3 100644 --- a/plugins/scaffolder-backend/src/util/defineCheckpoint.ts +++ b/plugins/scaffolder-backend/src/util/defineCheckpoint.ts @@ -16,15 +16,11 @@ import { JsonObject } from '@backstage/types'; -export type DefineCheckpointProps = { +export const defineCheckpoint = async (props: { checkpoint?: (key: string, fn: () => Promise) => Promise; key: string; fn: () => Promise; -}; - -export const defineCheckpoint = async ( - props: DefineCheckpointProps, -): Promise => { +}): Promise => { const { checkpoint, fn, key } = props; return checkpoint ? checkpoint?.(key, async () => { diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 99638e48af..4e66f1c0e3 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -26,4 +26,5 @@ export type { TaskEventType, TaskState, TaskStatus, + UpdateCheckpointOptions, } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index 7136211671..f57353c917 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -31,7 +31,14 @@ export type TaskSecrets = Record & { * * @public */ -export type TaskState = Record; +export type TaskState = { + [key: string]: + | { status: 'failed'; reason: string } + | { + status: 'success'; + value: JsonObject; + }; +}; /** * The status of each step of the Task @@ -108,6 +115,24 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; +/** + * The options passed to {@link TaskBroker.updateCheckpoint} + * Parameters to store the result of the executed checkpoint + * + * @public + */ +export type UpdateCheckpointOptions = + | { + key: string; + status: 'success'; + value: JsonObject; + } + | { + key: string; + status: 'failed'; + reason: string; + }; + /** * Task * @@ -126,7 +151,7 @@ export interface TaskContext { emitLog(message: string, logMetadata?: JsonObject): Promise; - updateCheckpoint?(key: string, value: JsonObject): Promise; + updateCheckpoint?(options: UpdateCheckpointOptions): Promise; getWorkspaceName(): Promise; }