scaffolder-backend: add dry-run support for actions and runner

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-04-04 10:11:10 +02:00
parent 91ae2f46d2
commit 10b7b2d9c7
4 changed files with 55 additions and 1 deletions
@@ -39,12 +39,19 @@ export type ActionContext<Input extends JsonObject> = {
createTemporaryDirectory(): Promise<string>;
templateInfo?: TemplateInfo;
/**
* Whether this action invocation is a dry-run or not.
* This will only ever be true if the actions as marked as supporting dry-runs.
*/
isDryRun?: boolean;
};
/** @public */
export type TemplateAction<Input extends JsonObject> = {
id: string;
description?: string;
supportsDryRun?: boolean;
schema?: {
input?: Schema;
output?: Schema;
@@ -23,7 +23,7 @@ import nunjucks from 'nunjucks';
import { JsonObject, JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { PassThrough } from 'stream';
import { isTruthy } from './helper';
import { generateExampleOutput, isTruthy } from './helper';
import { validate as validateJsonSchema } from 'jsonschema';
import { parseRepoUrl } from '../actions/builtin/publish/util';
import { TemplateActionRegistry } from '../actions';
@@ -236,6 +236,23 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const action = this.options.actionRegistry.get(step.action);
const { taskLogger, streamLogger } = createStepLogger({ task, step });
if (task.isDryRun && !action.supportsDryRun) {
task.emitLog(
`Skipping because ${action.id} does not support dry-run`,
);
const outputSchema = action.schema?.output;
if (outputSchema) {
context.steps[step.id] = {
output: generateExampleOutput(outputSchema) as {
[name in string]: JsonValue;
},
};
} else {
context.steps[step.id] = { output: {} };
}
continue;
}
// Secrets are only passed when templating the input to actions for security reasons
const input =
(step.input &&
@@ -15,6 +15,7 @@
*/
import { isArray } from 'lodash';
import { Schema } from 'jsonschema';
/**
* Returns true if the input is not `false`, `undefined`, `null`, `""`, `0`, or
@@ -24,3 +25,31 @@ import { isArray } from 'lodash';
export function isTruthy(value: any): boolean {
return isArray(value) ? value.length > 0 : !!value;
}
export function generateExampleOutput(schema: Schema): unknown {
const { examples } = schema as { examples?: unknown };
if (examples && Array.isArray(examples)) {
return examples[0];
}
if (schema.type === 'object') {
return Object.fromEntries(
Object.entries(schema.properties ?? {}).map(([key, value]) => [
key,
generateExampleOutput(value),
]),
);
} else if (schema.type === 'array') {
const [firstSchema] = [schema.items]?.flat();
if (firstSchema) {
return [generateExampleOutput(firstSchema)];
}
return [];
} else if (schema.type === 'string') {
return '<example>';
} else if (schema.type === 'number') {
return 0;
} else if (schema.type === 'boolean') {
return false;
}
return '<unknown>';
}
@@ -111,6 +111,7 @@ export interface TaskContext {
secrets?: TaskSecrets;
createdBy?: string;
done: boolean;
isDryRun?: boolean;
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
getWorkspaceName(): Promise<string>;