scaffolder-backend: add dry-run implementation
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { TemplateAction, TemplateActionRegistry } from '../actions';
|
||||
|
||||
/** @internal */
|
||||
export class DecoratedActionsRegistry extends TemplateActionRegistry {
|
||||
constructor(
|
||||
private readonly innerRegistry: TemplateActionRegistry,
|
||||
extraActions: Array<TemplateAction<JsonObject>>,
|
||||
) {
|
||||
super();
|
||||
for (const action of extraActions) {
|
||||
this.register(action);
|
||||
}
|
||||
}
|
||||
|
||||
get(actionId: string): TemplateAction<JsonObject> {
|
||||
try {
|
||||
return super.get(actionId);
|
||||
} catch {
|
||||
return this.innerRegistry.get(actionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { pathToFileURL } from 'url';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
deserializeDirectoryContents,
|
||||
SerializedFile,
|
||||
serializeDirectoryContents,
|
||||
} from '../../lib/files';
|
||||
import { TemplateFilter } from '../../lib/templating';
|
||||
import { createTemplateAction, TemplateActionRegistry } from '../actions';
|
||||
import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner';
|
||||
import { TaskSecrets } from '../tasks/types';
|
||||
import { DecoratedActionsRegistry } from './DecoratedActionsRegistry';
|
||||
import fs from 'fs-extra';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-common';
|
||||
|
||||
interface DryRunInput {
|
||||
spec: TaskSpec;
|
||||
secrets?: TaskSecrets;
|
||||
content: SerializedFile[];
|
||||
}
|
||||
|
||||
interface DryRunResult {
|
||||
log: JsonObject[];
|
||||
content: SerializedFile[];
|
||||
output: JsonObject;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type TemplateTesterCreateOptions = {
|
||||
logger: Logger;
|
||||
integrations: ScmIntegrations;
|
||||
actionRegistry: TemplateActionRegistry;
|
||||
workingDirectory: string;
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a dry-run of the provided template.
|
||||
*
|
||||
* The provided content will be extracted into a temporary directory
|
||||
* which is then use as the base for any relative file fetch paths.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function createDryRunner(options: TemplateTesterCreateOptions) {
|
||||
return async function dryRun(input: DryRunInput): Promise<DryRunResult> {
|
||||
let contentPromise;
|
||||
|
||||
const workflowRunner = new NunjucksWorkflowRunner({
|
||||
...options,
|
||||
actionRegistry: new DecoratedActionsRegistry(options.actionRegistry, [
|
||||
createTemplateAction({
|
||||
id: 'dry-run:extract',
|
||||
supportsDryRun: true,
|
||||
async handler(ctx) {
|
||||
contentPromise = serializeDirectoryContents(ctx.workspacePath);
|
||||
await contentPromise.catch(() => {});
|
||||
},
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const dryRunId = uuid();
|
||||
const log = new Array<JsonObject>();
|
||||
const contentsPath = resolveSafeChildPath(
|
||||
options.workingDirectory,
|
||||
`dry-run-content-${dryRunId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
await deserializeDirectoryContents(contentsPath, input.content);
|
||||
|
||||
const result = await workflowRunner.execute({
|
||||
spec: {
|
||||
...input.spec,
|
||||
steps: [
|
||||
...input.spec.steps,
|
||||
{
|
||||
id: dryRunId,
|
||||
name: 'dry-run:extract',
|
||||
action: 'dry-run:extract',
|
||||
},
|
||||
],
|
||||
templateInfo: {
|
||||
entityRef: 'template:default/dry-run',
|
||||
baseUrl: pathToFileURL(
|
||||
resolveSafeChildPath(contentsPath, 'template.yaml'),
|
||||
).toString(),
|
||||
},
|
||||
},
|
||||
secrets: input.secrets,
|
||||
done: false,
|
||||
isDryRun: true,
|
||||
getWorkspaceName: async () => `dry-run-${dryRunId}`,
|
||||
async emitLog(message: string, logMetadata?: JsonObject) {
|
||||
if (logMetadata?.stepId === dryRunId) {
|
||||
return;
|
||||
}
|
||||
log.push({
|
||||
...logMetadata,
|
||||
message,
|
||||
});
|
||||
},
|
||||
async complete() {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
if (!contentPromise) {
|
||||
throw new Error('Content extraction step was skipped');
|
||||
}
|
||||
const content = await contentPromise;
|
||||
|
||||
return {
|
||||
log,
|
||||
content,
|
||||
output: result.output,
|
||||
};
|
||||
} finally {
|
||||
await fs.remove(contentsPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createDryRunner } from './createDryRunner';
|
||||
Reference in New Issue
Block a user