scaffolder-backend: added built-in fetch:plain action + separate out fetch logic

Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-02-24 11:51:09 +01:00
committed by Johan Haals
parent 12771f5c34
commit 6706b360aa
2 changed files with 111 additions and 46 deletions
@@ -21,6 +21,100 @@ import { TemplaterBuilder, TemplaterValues } from '../stages/templater';
import { TemplateAction } from './types';
import { InputError, UrlReader } from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
import { JsonValue } from '@backstage/config';
async function fetchContents({
urlReader,
integrations,
baseUrl,
fetchUrl = '.',
outputPath,
}: {
urlReader: UrlReader;
integrations: ScmIntegrations;
baseUrl?: string;
fetchUrl?: JsonValue;
outputPath: string;
}) {
if (typeof fetchUrl !== 'string') {
throw new InputError(
`Invalid url parameter, expected string, got ${typeof fetchUrl}`,
);
}
// We handle both file locations and url ones
if (baseUrl?.startsWith('file://')) {
const basePath = baseUrl.slice('file://'.length);
if (isAbsolute(fetchUrl)) {
throw new InputError(
`Fetch URL may not be absolute for file locations, ${fetchUrl}`,
);
}
const srcDir = resolvePath(basePath, '..', fetchUrl);
await fs.copy(srcDir, outputPath);
} else {
let readUrl;
if (baseUrl) {
const integration = integrations.byUrl(baseUrl);
if (!integration) {
throw new InputError(`No integration found for location ${baseUrl}`);
}
readUrl = integration.resolveUrl({
url: fetchUrl,
base: baseUrl,
});
} else {
// If we don't have a baseUrl, check if our provided fetch url is absolute
try {
readUrl = new URL(fetchUrl).toString();
} catch {
throw new InputError(
`Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,
);
}
const res = await urlReader.readTree(readUrl);
await res.dir({ targetDir: outputPath });
}
}
}
export function createFetchPlainAction(options: {
urlReader: UrlReader;
integrations: ScmIntegrations;
}): TemplateAction {
const { urlReader, integrations } = options;
return {
id: 'fetch:plain',
async handler(ctx) {
ctx.logger.info('Fetching plain content from remote URL');
// Finally move the template result into the task workspace
const targetPath = ctx.parameters.targetPath ?? './';
if (typeof targetPath !== 'string') {
throw new InputError(
`Fetch action targetPath is not a string, got ${targetPath}`,
);
}
const outputPath = resolvePath(ctx.workspacePath, targetPath);
if (!outputPath.startsWith(ctx.workspacePath)) {
throw new InputError(
`Fetch action targetPath may not specify a path outside the working directory`,
);
}
await fetchContents({
urlReader,
integrations,
baseUrl: ctx.baseUrl,
fetchUrl: ctx.parameters.url,
outputPath,
});
},
};
}
export function createFetchCookiecutterAction(options: {
dockerClient: Docker;
@@ -42,51 +136,13 @@ export function createFetchCookiecutterAction(options: {
);
const resultDir = resolvePath(workDir, 'result');
const fetchUrl = ctx.parameters.url ?? '.';
if (typeof fetchUrl !== 'string') {
throw new InputError(
`Invalid url parameter, expected string, got ${typeof fetchUrl}`,
);
}
// We handle both file locations and url ones
if (ctx.baseUrl?.startsWith('file://')) {
const basePath = ctx.baseUrl.slice('file://'.length);
if (isAbsolute(fetchUrl)) {
throw new InputError(
`Fetch URL may not be absolute for file locations, ${fetchUrl}`,
);
}
const srcDir = resolvePath(basePath, '..', fetchUrl);
await fs.copy(srcDir, templateContentsDir);
} else {
let readUrl;
if (ctx.baseUrl) {
const integration = integrations.byUrl(ctx.baseUrl);
if (!integration) {
throw new InputError(
`No integration found for location ${ctx.baseUrl}`,
);
}
readUrl = integration.resolveUrl({
url: fetchUrl,
base: ctx.baseUrl,
});
} else {
// If we don't have a baseUrl, check if our provided fetch url is absolute
try {
readUrl = new URL(fetchUrl).toString();
} catch {
throw new InputError(
`Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,
);
}
const res = await urlReader.readTree(readUrl);
await res.dir({ targetDir: templateContentsDir });
}
}
await fetchContents({
urlReader,
integrations,
baseUrl: ctx.baseUrl,
fetchUrl: ctx.parameters.url,
outputPath: templateContentsDir,
});
const cookiecutter = templaters.get('cookiecutter');
if (!cookiecutter) {
@@ -54,7 +54,10 @@ import {
TemplateEntityV1beta2,
Entity,
} from '@backstage/catalog-model';
import { createFetchCookiecutterAction } from '../scaffolder/tasks/builtin';
import {
createFetchPlainAction,
createFetchCookiecutterAction,
} from '../scaffolder/tasks/builtin';
import { ScmIntegrations } from '@backstage/integration';
export interface RouterOptions {
@@ -128,6 +131,12 @@ export async function createRouter(
templaters,
catalogClient,
});
actionRegistry.register(
createFetchPlainAction({
urlReader,
integrations,
}),
);
actionRegistry.register(
createFetchCookiecutterAction({
urlReader,