@backstage/plugin-scaffolder-backend 1.3.0-next.0 → 1.3.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.cjs.js +305 -67
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/package.json +14 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @backstage/plugin-scaffolder-backend
|
|
2
2
|
|
|
3
|
+
## 1.3.0-next.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c042c5eaff: Add an option to not protect the default branch.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
- @backstage/backend-common@0.13.6-next.1
|
|
14
|
+
- @backstage/catalog-client@1.0.3-next.0
|
|
15
|
+
- @backstage/integration@1.2.1-next.1
|
|
16
|
+
- @backstage/plugin-catalog-backend@1.2.0-next.1
|
|
17
|
+
- @backstage/catalog-model@1.0.3-next.0
|
|
18
|
+
- @backstage/plugin-scaffolder-common@1.1.1-next.0
|
|
19
|
+
|
|
3
20
|
## 1.3.0-next.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -18,8 +18,8 @@ var azureDevopsNodeApi = require('azure-devops-node-api');
|
|
|
18
18
|
var fetch = require('node-fetch');
|
|
19
19
|
var crypto = require('crypto');
|
|
20
20
|
var octokit = require('octokit');
|
|
21
|
-
var lodash = require('lodash');
|
|
22
21
|
var octokitPluginCreatePullRequest = require('octokit-plugin-create-pull-request');
|
|
22
|
+
var limiterFactory = require('p-limit');
|
|
23
23
|
var node = require('@gitbeaker/node');
|
|
24
24
|
var webhooks = require('@octokit/webhooks');
|
|
25
25
|
var uuid = require('uuid');
|
|
@@ -27,12 +27,15 @@ var luxon = require('luxon');
|
|
|
27
27
|
var ObservableImpl = require('zen-observable');
|
|
28
28
|
var winston = require('winston');
|
|
29
29
|
var nunjucks = require('nunjucks');
|
|
30
|
+
var lodash = require('lodash');
|
|
30
31
|
var jsonschema = require('jsonschema');
|
|
32
|
+
var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
|
|
31
33
|
var express = require('express');
|
|
32
34
|
var Router = require('express-promise-router');
|
|
35
|
+
var zod = require('zod');
|
|
36
|
+
var url = require('url');
|
|
33
37
|
var os = require('os');
|
|
34
38
|
var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
|
|
35
|
-
var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
|
|
36
39
|
|
|
37
40
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
38
41
|
|
|
@@ -60,6 +63,7 @@ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
|
60
63
|
var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
|
|
61
64
|
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
62
65
|
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
|
|
66
|
+
var limiterFactory__default = /*#__PURE__*/_interopDefaultLegacy(limiterFactory);
|
|
63
67
|
var ObservableImpl__default = /*#__PURE__*/_interopDefaultLegacy(ObservableImpl);
|
|
64
68
|
var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
|
|
65
69
|
var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
|
|
@@ -117,6 +121,18 @@ function createCatalogRegisterAction(options) {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
]
|
|
124
|
+
},
|
|
125
|
+
output: {
|
|
126
|
+
type: "object",
|
|
127
|
+
required: ["catalogInfoUrl"],
|
|
128
|
+
properties: {
|
|
129
|
+
entityRef: {
|
|
130
|
+
type: "string"
|
|
131
|
+
},
|
|
132
|
+
catalogInfoUrl: {
|
|
133
|
+
type: "string"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
120
136
|
}
|
|
121
137
|
},
|
|
122
138
|
async handler(ctx) {
|
|
@@ -190,6 +206,7 @@ function createCatalogWriteAction() {
|
|
|
190
206
|
}
|
|
191
207
|
}
|
|
192
208
|
},
|
|
209
|
+
supportsDryRun: true,
|
|
193
210
|
async handler(ctx) {
|
|
194
211
|
ctx.logStream.write(`Writing catalog-info.yaml`);
|
|
195
212
|
const { filePath, entity } = ctx.input;
|
|
@@ -221,6 +238,7 @@ function createDebugLogAction() {
|
|
|
221
238
|
}
|
|
222
239
|
}
|
|
223
240
|
},
|
|
241
|
+
supportsDryRun: true,
|
|
224
242
|
async handler(ctx) {
|
|
225
243
|
var _a, _b;
|
|
226
244
|
ctx.logger.info(JSON.stringify(ctx.input, null, 2));
|
|
@@ -306,6 +324,7 @@ function createFetchPlainAction(options) {
|
|
|
306
324
|
}
|
|
307
325
|
}
|
|
308
326
|
},
|
|
327
|
+
supportsDryRun: true,
|
|
309
328
|
async handler(ctx) {
|
|
310
329
|
var _a, _b;
|
|
311
330
|
ctx.logger.info("Fetching plain content from remote URL");
|
|
@@ -472,6 +491,7 @@ function createFetchTemplateAction(options) {
|
|
|
472
491
|
}
|
|
473
492
|
}
|
|
474
493
|
},
|
|
494
|
+
supportsDryRun: true,
|
|
475
495
|
async handler(ctx) {
|
|
476
496
|
var _a, _b;
|
|
477
497
|
ctx.logger.info("Fetching template content from remote URL");
|
|
@@ -504,13 +524,15 @@ function createFetchTemplateAction(options) {
|
|
|
504
524
|
cwd: templateDir,
|
|
505
525
|
dot: true,
|
|
506
526
|
onlyFiles: false,
|
|
507
|
-
markDirectories: true
|
|
527
|
+
markDirectories: true,
|
|
528
|
+
followSymbolicLinks: false
|
|
508
529
|
});
|
|
509
530
|
const nonTemplatedEntries = new Set((await Promise.all((ctx.input.copyWithoutRender || []).map((pattern) => globby__default["default"](pattern, {
|
|
510
531
|
cwd: templateDir,
|
|
511
532
|
dot: true,
|
|
512
533
|
onlyFiles: false,
|
|
513
|
-
markDirectories: true
|
|
534
|
+
markDirectories: true,
|
|
535
|
+
followSymbolicLinks: false
|
|
514
536
|
})))).flat());
|
|
515
537
|
const { cookiecutterCompat, values } = ctx.input;
|
|
516
538
|
const context = {
|
|
@@ -585,6 +607,7 @@ const createFilesystemDeleteAction = () => {
|
|
|
585
607
|
}
|
|
586
608
|
}
|
|
587
609
|
},
|
|
610
|
+
supportsDryRun: true,
|
|
588
611
|
async handler(ctx) {
|
|
589
612
|
var _a;
|
|
590
613
|
if (!Array.isArray((_a = ctx.input) == null ? void 0 : _a.files)) {
|
|
@@ -639,6 +662,7 @@ const createFilesystemRenameAction = () => {
|
|
|
639
662
|
}
|
|
640
663
|
}
|
|
641
664
|
},
|
|
665
|
+
supportsDryRun: true,
|
|
642
666
|
async handler(ctx) {
|
|
643
667
|
var _a, _b;
|
|
644
668
|
if (!Array.isArray((_a = ctx.input) == null ? void 0 : _a.files)) {
|
|
@@ -827,11 +851,6 @@ const parseRepoUrl = (repoUrl, integrations) => {
|
|
|
827
851
|
}
|
|
828
852
|
return { host, owner, repo, organization, workspace, project };
|
|
829
853
|
};
|
|
830
|
-
const isExecutable = (fileMode) => {
|
|
831
|
-
const executeBitMask = 73;
|
|
832
|
-
const res = fileMode & executeBitMask;
|
|
833
|
-
return res > 0;
|
|
834
|
-
};
|
|
835
854
|
|
|
836
855
|
function createPublishAzureAction(options) {
|
|
837
856
|
const { integrations, config } = options;
|
|
@@ -1796,6 +1815,11 @@ function createPublishGithubAction(options) {
|
|
|
1796
1815
|
type: "string",
|
|
1797
1816
|
description: `Sets the default branch on the repository. The default value is 'master'`
|
|
1798
1817
|
},
|
|
1818
|
+
protectDefaultBranch: {
|
|
1819
|
+
title: "Protect Default Branch",
|
|
1820
|
+
type: "boolean",
|
|
1821
|
+
description: `Protect the default branch after creating the repository. The default value is 'true'`
|
|
1822
|
+
},
|
|
1799
1823
|
deleteBranchOnMerge: {
|
|
1800
1824
|
title: "Delete Branch On Merge",
|
|
1801
1825
|
type: "boolean",
|
|
@@ -1893,6 +1917,7 @@ function createPublishGithubAction(options) {
|
|
|
1893
1917
|
requiredStatusCheckContexts = [],
|
|
1894
1918
|
repoVisibility = "private",
|
|
1895
1919
|
defaultBranch = "master",
|
|
1920
|
+
protectDefaultBranch = true,
|
|
1896
1921
|
deleteBranchOnMerge = false,
|
|
1897
1922
|
gitCommitMessage = "initial commit",
|
|
1898
1923
|
gitAuthorName,
|
|
@@ -2013,19 +2038,21 @@ function createPublishGithubAction(options) {
|
|
|
2013
2038
|
commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"),
|
|
2014
2039
|
gitAuthorInfo
|
|
2015
2040
|
});
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2041
|
+
if (protectDefaultBranch) {
|
|
2042
|
+
try {
|
|
2043
|
+
await enableBranchProtectionOnDefaultRepoBranch({
|
|
2044
|
+
owner,
|
|
2045
|
+
client,
|
|
2046
|
+
repoName: newRepo.name,
|
|
2047
|
+
logger: ctx.logger,
|
|
2048
|
+
defaultBranch,
|
|
2049
|
+
requireCodeOwnerReviews,
|
|
2050
|
+
requiredStatusCheckContexts
|
|
2051
|
+
});
|
|
2052
|
+
} catch (e) {
|
|
2053
|
+
errors.assertError(e);
|
|
2054
|
+
ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
|
|
2055
|
+
}
|
|
2029
2056
|
}
|
|
2030
2057
|
ctx.output("remoteUrl", remoteUrl);
|
|
2031
2058
|
ctx.output("repoContentsUrl", repoContentsUrl);
|
|
@@ -2033,6 +2060,41 @@ function createPublishGithubAction(options) {
|
|
|
2033
2060
|
});
|
|
2034
2061
|
}
|
|
2035
2062
|
|
|
2063
|
+
const DEFAULT_GLOB_PATTERNS = ["./**", "!.git"];
|
|
2064
|
+
const isExecutable = (fileMode) => {
|
|
2065
|
+
if (!fileMode) {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
const executeBitMask = 73;
|
|
2069
|
+
const res = fileMode & executeBitMask;
|
|
2070
|
+
return res > 0;
|
|
2071
|
+
};
|
|
2072
|
+
async function serializeDirectoryContents(sourcePath, options) {
|
|
2073
|
+
var _a;
|
|
2074
|
+
const paths = await globby__default["default"]((_a = options == null ? void 0 : options.globPatterns) != null ? _a : DEFAULT_GLOB_PATTERNS, {
|
|
2075
|
+
cwd: sourcePath,
|
|
2076
|
+
dot: true,
|
|
2077
|
+
gitignore: options == null ? void 0 : options.gitignore,
|
|
2078
|
+
followSymbolicLinks: false,
|
|
2079
|
+
objectMode: true,
|
|
2080
|
+
stats: true
|
|
2081
|
+
});
|
|
2082
|
+
const limiter = limiterFactory__default["default"](10);
|
|
2083
|
+
return Promise.all(paths.map(async ({ path: path$1, stats }) => ({
|
|
2084
|
+
path: path$1,
|
|
2085
|
+
content: await limiter(async () => fs__default["default"].readFile(path.join(sourcePath, path$1))),
|
|
2086
|
+
executable: isExecutable(stats == null ? void 0 : stats.mode)
|
|
2087
|
+
})));
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
async function deserializeDirectoryContents(targetPath, files) {
|
|
2091
|
+
for (const file of files) {
|
|
2092
|
+
const filePath = backendCommon.resolveSafeChildPath(targetPath, file.path);
|
|
2093
|
+
await fs__default["default"].ensureDir(path.dirname(filePath));
|
|
2094
|
+
await fs__default["default"].writeFile(filePath, file.content);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2036
2098
|
class GithubResponseError extends errors.CustomErrorBase {
|
|
2037
2099
|
}
|
|
2038
2100
|
const defaultClientFactory = async ({
|
|
@@ -2148,38 +2210,28 @@ const createPublishGithubPullRequestAction = ({
|
|
|
2148
2210
|
token: providedToken
|
|
2149
2211
|
});
|
|
2150
2212
|
const fileRoot = sourcePath ? backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath;
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
gitignore: true,
|
|
2154
|
-
dot: true
|
|
2213
|
+
const directoryContents = await serializeDirectoryContents(fileRoot, {
|
|
2214
|
+
gitignore: true
|
|
2155
2215
|
});
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
const base64EncodedContent = fs__default["default"].readFileSync(absPath).toString("base64");
|
|
2159
|
-
const fileStat = fs__default["default"].statSync(absPath);
|
|
2160
|
-
const githubTreeItemMode = isExecutable(fileStat.mode) ? "100755" : "100644";
|
|
2161
|
-
const encoding = "base64";
|
|
2162
|
-
return {
|
|
2163
|
-
encoding,
|
|
2164
|
-
content: base64EncodedContent,
|
|
2165
|
-
mode: githubTreeItemMode
|
|
2166
|
-
};
|
|
2167
|
-
}));
|
|
2168
|
-
const repoFilePaths = localFilePaths.map((repoFilePath) => {
|
|
2169
|
-
return targetPath ? `${targetPath}/${repoFilePath}` : repoFilePath;
|
|
2170
|
-
});
|
|
2171
|
-
const changes = [
|
|
2216
|
+
const files = Object.fromEntries(directoryContents.map((file) => [
|
|
2217
|
+
targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
|
|
2172
2218
|
{
|
|
2173
|
-
|
|
2174
|
-
|
|
2219
|
+
mode: file.executable ? "100755" : "100644",
|
|
2220
|
+
encoding: "base64",
|
|
2221
|
+
content: file.content.toString("base64")
|
|
2175
2222
|
}
|
|
2176
|
-
];
|
|
2223
|
+
]));
|
|
2177
2224
|
try {
|
|
2178
2225
|
const response = await client.createPullRequest({
|
|
2179
2226
|
owner,
|
|
2180
2227
|
repo,
|
|
2181
2228
|
title,
|
|
2182
|
-
changes
|
|
2229
|
+
changes: [
|
|
2230
|
+
{
|
|
2231
|
+
files,
|
|
2232
|
+
commit: title
|
|
2233
|
+
}
|
|
2234
|
+
],
|
|
2183
2235
|
body: description,
|
|
2184
2236
|
head: branchName,
|
|
2185
2237
|
draft
|
|
@@ -2387,7 +2439,6 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
2387
2439
|
const repoUrl = ctx.input.repoUrl;
|
|
2388
2440
|
const { host } = parseRepoUrl(repoUrl, integrations);
|
|
2389
2441
|
const integrationConfig = integrations.gitlab.byHost(host);
|
|
2390
|
-
const actions = [];
|
|
2391
2442
|
const destinationBranch = ctx.input.branchName;
|
|
2392
2443
|
if (!integrationConfig) {
|
|
2393
2444
|
throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
|
|
@@ -2401,23 +2452,17 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
2401
2452
|
host: integrationConfig.config.baseUrl,
|
|
2402
2453
|
[tokenType]: token
|
|
2403
2454
|
});
|
|
2404
|
-
const
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
gitignore: true,
|
|
2408
|
-
dot: true
|
|
2409
|
-
});
|
|
2410
|
-
const fileContents = await Promise.all(localFilePaths.map((p) => fs.readFile(backendCommon.resolveSafeChildPath(fileRoot, p))));
|
|
2411
|
-
const repoFilePaths = localFilePaths.map((repoFilePath) => {
|
|
2412
|
-
return repoFilePath;
|
|
2455
|
+
const targetPath = backendCommon.resolveSafeChildPath(ctx.workspacePath, ctx.input.targetPath);
|
|
2456
|
+
const fileContents = await serializeDirectoryContents(targetPath, {
|
|
2457
|
+
gitignore: true
|
|
2413
2458
|
});
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
}
|
|
2459
|
+
const actions = fileContents.map((file) => ({
|
|
2460
|
+
action: "create",
|
|
2461
|
+
filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
|
|
2462
|
+
encoding: "base64",
|
|
2463
|
+
content: file.content.toString("base64"),
|
|
2464
|
+
execute_filemode: file.executable
|
|
2465
|
+
}));
|
|
2421
2466
|
const projects = await api.Projects.show(ctx.input.projectid);
|
|
2422
2467
|
const { default_branch: defaultBranch } = projects;
|
|
2423
2468
|
try {
|
|
@@ -3106,6 +3151,32 @@ class StorageTaskBroker {
|
|
|
3106
3151
|
function isTruthy(value) {
|
|
3107
3152
|
return lodash.isArray(value) ? value.length > 0 : !!value;
|
|
3108
3153
|
}
|
|
3154
|
+
function generateExampleOutput(schema) {
|
|
3155
|
+
var _a, _b;
|
|
3156
|
+
const { examples } = schema;
|
|
3157
|
+
if (examples && Array.isArray(examples)) {
|
|
3158
|
+
return examples[0];
|
|
3159
|
+
}
|
|
3160
|
+
if (schema.type === "object") {
|
|
3161
|
+
return Object.fromEntries(Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
|
|
3162
|
+
key,
|
|
3163
|
+
generateExampleOutput(value)
|
|
3164
|
+
]));
|
|
3165
|
+
} else if (schema.type === "array") {
|
|
3166
|
+
const [firstSchema] = (_b = [schema.items]) == null ? void 0 : _b.flat();
|
|
3167
|
+
if (firstSchema) {
|
|
3168
|
+
return [generateExampleOutput(firstSchema)];
|
|
3169
|
+
}
|
|
3170
|
+
return [];
|
|
3171
|
+
} else if (schema.type === "string") {
|
|
3172
|
+
return "<example>";
|
|
3173
|
+
} else if (schema.type === "number") {
|
|
3174
|
+
return 0;
|
|
3175
|
+
} else if (schema.type === "boolean") {
|
|
3176
|
+
return false;
|
|
3177
|
+
}
|
|
3178
|
+
return "<unknown>";
|
|
3179
|
+
}
|
|
3109
3180
|
|
|
3110
3181
|
const isValidTaskSpec = (taskSpec) => {
|
|
3111
3182
|
return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
@@ -3175,7 +3246,7 @@ class NunjucksWorkflowRunner {
|
|
|
3175
3246
|
});
|
|
3176
3247
|
}
|
|
3177
3248
|
async execute(task) {
|
|
3178
|
-
var _a, _b, _c, _d;
|
|
3249
|
+
var _a, _b, _c, _d, _e;
|
|
3179
3250
|
if (!isValidTaskSpec(task.spec)) {
|
|
3180
3251
|
throw new errors.InputError("Wrong template version executed with the workflow engine");
|
|
3181
3252
|
}
|
|
@@ -3210,8 +3281,23 @@ class NunjucksWorkflowRunner {
|
|
|
3210
3281
|
});
|
|
3211
3282
|
const action = this.options.actionRegistry.get(step.action);
|
|
3212
3283
|
const { taskLogger, streamLogger } = createStepLogger({ task, step });
|
|
3213
|
-
|
|
3214
|
-
|
|
3284
|
+
if (task.isDryRun && !action.supportsDryRun) {
|
|
3285
|
+
task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
|
|
3286
|
+
stepId: step.id,
|
|
3287
|
+
status: "skipped"
|
|
3288
|
+
});
|
|
3289
|
+
const outputSchema = (_a = action.schema) == null ? void 0 : _a.output;
|
|
3290
|
+
if (outputSchema) {
|
|
3291
|
+
context.steps[step.id] = {
|
|
3292
|
+
output: generateExampleOutput(outputSchema)
|
|
3293
|
+
};
|
|
3294
|
+
} else {
|
|
3295
|
+
context.steps[step.id] = { output: {} };
|
|
3296
|
+
}
|
|
3297
|
+
continue;
|
|
3298
|
+
}
|
|
3299
|
+
const input = (_c = step.input && this.render(step.input, { ...context, secrets: (_b = task.secrets) != null ? _b : {} }, renderTemplate)) != null ? _c : {};
|
|
3300
|
+
if ((_d = action.schema) == null ? void 0 : _d.input) {
|
|
3215
3301
|
const validateResult = jsonschema.validate(input, action.schema.input);
|
|
3216
3302
|
if (!validateResult.valid) {
|
|
3217
3303
|
const errors$1 = validateResult.errors.join(", ");
|
|
@@ -3222,7 +3308,7 @@ class NunjucksWorkflowRunner {
|
|
|
3222
3308
|
const stepOutput = {};
|
|
3223
3309
|
await action.handler({
|
|
3224
3310
|
input,
|
|
3225
|
-
secrets: (
|
|
3311
|
+
secrets: (_e = task.secrets) != null ? _e : {},
|
|
3226
3312
|
logger: taskLogger,
|
|
3227
3313
|
logStream: streamLogger,
|
|
3228
3314
|
workspacePath,
|
|
@@ -3311,6 +3397,95 @@ class TaskWorker {
|
|
|
3311
3397
|
}
|
|
3312
3398
|
}
|
|
3313
3399
|
|
|
3400
|
+
class DecoratedActionsRegistry extends TemplateActionRegistry {
|
|
3401
|
+
constructor(innerRegistry, extraActions) {
|
|
3402
|
+
super();
|
|
3403
|
+
this.innerRegistry = innerRegistry;
|
|
3404
|
+
for (const action of extraActions) {
|
|
3405
|
+
this.register(action);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
get(actionId) {
|
|
3409
|
+
try {
|
|
3410
|
+
return super.get(actionId);
|
|
3411
|
+
} catch {
|
|
3412
|
+
return this.innerRegistry.get(actionId);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
function createDryRunner(options) {
|
|
3418
|
+
return async function dryRun(input) {
|
|
3419
|
+
let contentPromise;
|
|
3420
|
+
const workflowRunner = new NunjucksWorkflowRunner({
|
|
3421
|
+
...options,
|
|
3422
|
+
actionRegistry: new DecoratedActionsRegistry(options.actionRegistry, [
|
|
3423
|
+
createTemplateAction({
|
|
3424
|
+
id: "dry-run:extract",
|
|
3425
|
+
supportsDryRun: true,
|
|
3426
|
+
async handler(ctx) {
|
|
3427
|
+
contentPromise = serializeDirectoryContents(ctx.workspacePath);
|
|
3428
|
+
await contentPromise.catch(() => {
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
})
|
|
3432
|
+
])
|
|
3433
|
+
});
|
|
3434
|
+
const dryRunId = uuid.v4();
|
|
3435
|
+
const log = new Array();
|
|
3436
|
+
const contentsPath = backendCommon.resolveSafeChildPath(options.workingDirectory, `dry-run-content-${dryRunId}`);
|
|
3437
|
+
try {
|
|
3438
|
+
await deserializeDirectoryContents(contentsPath, input.directoryContents);
|
|
3439
|
+
const result = await workflowRunner.execute({
|
|
3440
|
+
spec: {
|
|
3441
|
+
...input.spec,
|
|
3442
|
+
steps: [
|
|
3443
|
+
...input.spec.steps,
|
|
3444
|
+
{
|
|
3445
|
+
id: dryRunId,
|
|
3446
|
+
name: "dry-run:extract",
|
|
3447
|
+
action: "dry-run:extract"
|
|
3448
|
+
}
|
|
3449
|
+
],
|
|
3450
|
+
templateInfo: {
|
|
3451
|
+
entityRef: "template:default/dry-run",
|
|
3452
|
+
baseUrl: url.pathToFileURL(backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")).toString()
|
|
3453
|
+
}
|
|
3454
|
+
},
|
|
3455
|
+
secrets: input.secrets,
|
|
3456
|
+
done: false,
|
|
3457
|
+
isDryRun: true,
|
|
3458
|
+
getWorkspaceName: async () => `dry-run-${dryRunId}`,
|
|
3459
|
+
async emitLog(message, logMetadata) {
|
|
3460
|
+
if ((logMetadata == null ? void 0 : logMetadata.stepId) === dryRunId) {
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
log.push({
|
|
3464
|
+
body: {
|
|
3465
|
+
...logMetadata,
|
|
3466
|
+
message
|
|
3467
|
+
}
|
|
3468
|
+
});
|
|
3469
|
+
},
|
|
3470
|
+
async complete() {
|
|
3471
|
+
throw new Error("Not implemented");
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3474
|
+
if (!contentPromise) {
|
|
3475
|
+
throw new Error("Content extraction step was skipped");
|
|
3476
|
+
}
|
|
3477
|
+
const directoryContents = await contentPromise;
|
|
3478
|
+
return {
|
|
3479
|
+
log,
|
|
3480
|
+
directoryContents,
|
|
3481
|
+
output: result.output
|
|
3482
|
+
};
|
|
3483
|
+
} finally {
|
|
3484
|
+
await fs__default["default"].remove(contentsPath);
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3314
3489
|
async function getWorkingDirectory(config, logger) {
|
|
3315
3490
|
if (!config.has("backend.workingDirectory")) {
|
|
3316
3491
|
return os__default["default"].tmpdir();
|
|
@@ -3408,6 +3583,13 @@ async function createRouter(options) {
|
|
|
3408
3583
|
});
|
|
3409
3584
|
actionsToRegister.forEach((action) => actionRegistry.register(action));
|
|
3410
3585
|
workers.forEach((worker) => worker.start());
|
|
3586
|
+
const dryRunner = createDryRunner({
|
|
3587
|
+
actionRegistry,
|
|
3588
|
+
integrations,
|
|
3589
|
+
logger,
|
|
3590
|
+
workingDirectory,
|
|
3591
|
+
additionalTemplateFilters
|
|
3592
|
+
});
|
|
3411
3593
|
router.get("/v2/templates/:namespace/:kind/:name/parameter-schema", async (req, res) => {
|
|
3412
3594
|
var _a, _b;
|
|
3413
3595
|
const { namespace, kind, name } = req.params;
|
|
@@ -3562,6 +3744,62 @@ data: ${JSON.stringify(event)}
|
|
|
3562
3744
|
subscription.unsubscribe();
|
|
3563
3745
|
clearTimeout(timeout);
|
|
3564
3746
|
});
|
|
3747
|
+
}).post("/v2/dry-run", async (req, res) => {
|
|
3748
|
+
var _a, _b, _c;
|
|
3749
|
+
const bodySchema = zod.z.object({
|
|
3750
|
+
template: zod.z.unknown(),
|
|
3751
|
+
values: zod.z.record(zod.z.unknown()),
|
|
3752
|
+
secrets: zod.z.record(zod.z.string()).optional(),
|
|
3753
|
+
directoryContents: zod.z.array(zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() }))
|
|
3754
|
+
});
|
|
3755
|
+
const body = await bodySchema.parseAsync(req.body).catch((e) => {
|
|
3756
|
+
throw new errors.InputError(`Malformed request: ${e}`);
|
|
3757
|
+
});
|
|
3758
|
+
const template = body.template;
|
|
3759
|
+
if (!await pluginScaffolderCommon.templateEntityV1beta3Validator.check(template)) {
|
|
3760
|
+
throw new errors.InputError("Input template is not a template");
|
|
3761
|
+
}
|
|
3762
|
+
const { token } = parseBearerToken(req.headers.authorization);
|
|
3763
|
+
for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
|
|
3764
|
+
const result2 = jsonschema.validate(body.values, parameters);
|
|
3765
|
+
if (!result2.valid) {
|
|
3766
|
+
res.status(400).json({ errors: result2.errors });
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
const steps = template.spec.steps.map((step, index) => {
|
|
3771
|
+
var _a2, _b2;
|
|
3772
|
+
return {
|
|
3773
|
+
...step,
|
|
3774
|
+
id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
|
|
3775
|
+
name: (_b2 = step.name) != null ? _b2 : step.action
|
|
3776
|
+
};
|
|
3777
|
+
});
|
|
3778
|
+
const result = await dryRunner({
|
|
3779
|
+
spec: {
|
|
3780
|
+
apiVersion: template.apiVersion,
|
|
3781
|
+
steps,
|
|
3782
|
+
output: (_b = template.spec.output) != null ? _b : {},
|
|
3783
|
+
parameters: body.values
|
|
3784
|
+
},
|
|
3785
|
+
directoryContents: ((_c = body.directoryContents) != null ? _c : []).map((file) => ({
|
|
3786
|
+
path: file.path,
|
|
3787
|
+
content: Buffer.from(file.base64Content, "base64")
|
|
3788
|
+
})),
|
|
3789
|
+
secrets: {
|
|
3790
|
+
...body.secrets,
|
|
3791
|
+
...token && { backstageToken: token }
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3794
|
+
res.status(200).json({
|
|
3795
|
+
...result,
|
|
3796
|
+
steps,
|
|
3797
|
+
directoryContents: result.directoryContents.map((file) => ({
|
|
3798
|
+
path: file.path,
|
|
3799
|
+
executable: file.executable,
|
|
3800
|
+
base64Content: file.content.toString("base64")
|
|
3801
|
+
}))
|
|
3802
|
+
});
|
|
3565
3803
|
});
|
|
3566
3804
|
const app = express__default["default"]();
|
|
3567
3805
|
app.set("logger", logger);
|