@backstage/plugin-scaffolder-backend 1.4.0 → 1.5.0-next.2
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 +44 -0
- package/alpha/package.json +1 -1
- package/dist/index.alpha.d.ts +22 -2
- package/dist/index.beta.d.ts +22 -2
- package/dist/index.cjs.js +930 -296
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +22 -2
- package/package.json +9 -9
package/dist/index.cjs.js
CHANGED
|
@@ -147,7 +147,9 @@ function createCatalogRegisterAction(options) {
|
|
|
147
147
|
const { repoContentsUrl, catalogInfoPath = "/catalog-info.yaml" } = input;
|
|
148
148
|
const integration = integrations.byUrl(repoContentsUrl);
|
|
149
149
|
if (!integration) {
|
|
150
|
-
throw new errors.InputError(
|
|
150
|
+
throw new errors.InputError(
|
|
151
|
+
`No integration found for host ${repoContentsUrl}`
|
|
152
|
+
);
|
|
151
153
|
}
|
|
152
154
|
catalogInfoUrl = integration.resolveUrl({
|
|
153
155
|
base: repoContentsUrl,
|
|
@@ -155,22 +157,32 @@ function createCatalogRegisterAction(options) {
|
|
|
155
157
|
});
|
|
156
158
|
}
|
|
157
159
|
ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
|
|
158
|
-
await catalogClient.addLocation(
|
|
159
|
-
|
|
160
|
-
target: catalogInfoUrl
|
|
161
|
-
}, ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
|
|
162
|
-
try {
|
|
163
|
-
const result = await catalogClient.addLocation({
|
|
164
|
-
dryRun: true,
|
|
160
|
+
await catalogClient.addLocation(
|
|
161
|
+
{
|
|
165
162
|
type: "url",
|
|
166
163
|
target: catalogInfoUrl
|
|
167
|
-
},
|
|
164
|
+
},
|
|
165
|
+
((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
|
|
166
|
+
);
|
|
167
|
+
try {
|
|
168
|
+
const result = await catalogClient.addLocation(
|
|
169
|
+
{
|
|
170
|
+
dryRun: true,
|
|
171
|
+
type: "url",
|
|
172
|
+
target: catalogInfoUrl
|
|
173
|
+
},
|
|
174
|
+
((_b = ctx.secrets) == null ? void 0 : _b.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
|
|
175
|
+
);
|
|
168
176
|
if (result.entities.length > 0) {
|
|
169
177
|
const { entities } = result;
|
|
170
178
|
let entity;
|
|
171
|
-
entity = entities.find(
|
|
179
|
+
entity = entities.find(
|
|
180
|
+
(e) => !e.metadata.name.startsWith("generated-") && e.kind === "Component"
|
|
181
|
+
);
|
|
172
182
|
if (!entity) {
|
|
173
|
-
entity = entities.find(
|
|
183
|
+
entity = entities.find(
|
|
184
|
+
(e) => !e.metadata.name.startsWith("generated-")
|
|
185
|
+
);
|
|
174
186
|
}
|
|
175
187
|
if (!entity) {
|
|
176
188
|
entity = entities[0];
|
|
@@ -213,7 +225,10 @@ function createCatalogWriteAction() {
|
|
|
213
225
|
ctx.logStream.write(`Writing catalog-info.yaml`);
|
|
214
226
|
const { filePath, entity } = ctx.input;
|
|
215
227
|
const path = filePath != null ? filePath : "catalog-info.yaml";
|
|
216
|
-
await fs__default["default"].writeFile(
|
|
228
|
+
await fs__default["default"].writeFile(
|
|
229
|
+
backendCommon.resolveSafeChildPath(ctx.workspacePath, path),
|
|
230
|
+
yaml__namespace.stringify(entity)
|
|
231
|
+
);
|
|
217
232
|
}
|
|
218
233
|
});
|
|
219
234
|
}
|
|
@@ -249,18 +264,22 @@ function createDebugLogAction() {
|
|
|
249
264
|
}
|
|
250
265
|
if ((_b = ctx.input) == null ? void 0 : _b.listWorkspace) {
|
|
251
266
|
const files = await recursiveReadDir(ctx.workspacePath);
|
|
252
|
-
ctx.logStream.write(
|
|
253
|
-
|
|
267
|
+
ctx.logStream.write(
|
|
268
|
+
`Workspace:
|
|
269
|
+
${files.map((f) => ` - ${path.relative(ctx.workspacePath, f)}`).join("\n")}`
|
|
270
|
+
);
|
|
254
271
|
}
|
|
255
272
|
}
|
|
256
273
|
});
|
|
257
274
|
}
|
|
258
275
|
async function recursiveReadDir(dir) {
|
|
259
276
|
const subdirs = await fs.readdir(dir);
|
|
260
|
-
const files = await Promise.all(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
277
|
+
const files = await Promise.all(
|
|
278
|
+
subdirs.map(async (subdir) => {
|
|
279
|
+
const res = path.join(dir, subdir);
|
|
280
|
+
return (await fs.stat(res)).isDirectory() ? recursiveReadDir(res) : [res];
|
|
281
|
+
})
|
|
282
|
+
);
|
|
264
283
|
return files.reduce((a, f) => a.concat(f), []);
|
|
265
284
|
}
|
|
266
285
|
|
|
@@ -295,7 +314,9 @@ async function fetchContents({
|
|
|
295
314
|
base: baseUrl
|
|
296
315
|
});
|
|
297
316
|
} else {
|
|
298
|
-
throw new errors.InputError(
|
|
317
|
+
throw new errors.InputError(
|
|
318
|
+
`Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`
|
|
319
|
+
);
|
|
299
320
|
}
|
|
300
321
|
const res = await reader.readTree(readUrl);
|
|
301
322
|
await fs__default["default"].ensureDir(outputPath);
|
|
@@ -424,13 +445,21 @@ class SecureTemplater {
|
|
|
424
445
|
sandbox.parseRepoUrl = (url) => JSON.stringify(parseRepoUrl(url));
|
|
425
446
|
}
|
|
426
447
|
if (additionalTemplateFilters) {
|
|
427
|
-
sandbox.additionalTemplateFilters = Object.fromEntries(
|
|
428
|
-
filterName,
|
|
429
|
-
|
|
430
|
-
|
|
448
|
+
sandbox.additionalTemplateFilters = Object.fromEntries(
|
|
449
|
+
Object.entries(additionalTemplateFilters).filter(([_, filterFunction]) => !!filterFunction).map(([filterName, filterFunction]) => [
|
|
450
|
+
filterName,
|
|
451
|
+
(...args) => JSON.stringify(filterFunction(...args))
|
|
452
|
+
])
|
|
453
|
+
);
|
|
431
454
|
}
|
|
432
455
|
const vm = new vm2.VM({ sandbox });
|
|
433
|
-
const nunjucksSource = await fs__default["default"].readFile(
|
|
456
|
+
const nunjucksSource = await fs__default["default"].readFile(
|
|
457
|
+
backendCommon.resolvePackagePath(
|
|
458
|
+
"@backstage/plugin-scaffolder-backend",
|
|
459
|
+
"assets/nunjucks.js.txt"
|
|
460
|
+
),
|
|
461
|
+
"utf-8"
|
|
462
|
+
);
|
|
434
463
|
vm.run(mkScript(nunjucksSource));
|
|
435
464
|
const render = (template, values) => {
|
|
436
465
|
if (!vm) {
|
|
@@ -510,12 +539,16 @@ function createFetchTemplateAction(options) {
|
|
|
510
539
|
const targetPath = (_a = ctx.input.targetPath) != null ? _a : "./";
|
|
511
540
|
const outputDir = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath);
|
|
512
541
|
if (ctx.input.copyWithoutRender && ctx.input.copyWithoutTemplating) {
|
|
513
|
-
throw new errors.InputError(
|
|
542
|
+
throw new errors.InputError(
|
|
543
|
+
"Fetch action input copyWithoutRender and copyWithoutTemplating can not be used at the same time"
|
|
544
|
+
);
|
|
514
545
|
}
|
|
515
546
|
let copyOnlyPatterns;
|
|
516
547
|
let renderFilename;
|
|
517
548
|
if (ctx.input.copyWithoutRender) {
|
|
518
|
-
ctx.logger.warn(
|
|
549
|
+
ctx.logger.warn(
|
|
550
|
+
"[Deprecated] Please use copyWithoutTemplating instead."
|
|
551
|
+
);
|
|
519
552
|
copyOnlyPatterns = ctx.input.copyWithoutRender;
|
|
520
553
|
renderFilename = false;
|
|
521
554
|
} else {
|
|
@@ -523,10 +556,14 @@ function createFetchTemplateAction(options) {
|
|
|
523
556
|
renderFilename = true;
|
|
524
557
|
}
|
|
525
558
|
if (copyOnlyPatterns && !Array.isArray(copyOnlyPatterns)) {
|
|
526
|
-
throw new errors.InputError(
|
|
559
|
+
throw new errors.InputError(
|
|
560
|
+
"Fetch action input copyWithoutRender/copyWithoutTemplating must be an Array"
|
|
561
|
+
);
|
|
527
562
|
}
|
|
528
563
|
if (ctx.input.templateFileExtension && (copyOnlyPatterns || ctx.input.cookiecutterCompat)) {
|
|
529
|
-
throw new errors.InputError(
|
|
564
|
+
throw new errors.InputError(
|
|
565
|
+
"Fetch action input extension incompatible with copyWithoutRender/copyWithoutTemplating and cookiecutterCompat"
|
|
566
|
+
);
|
|
530
567
|
}
|
|
531
568
|
let extension = false;
|
|
532
569
|
if (ctx.input.templateFileExtension) {
|
|
@@ -550,18 +587,27 @@ function createFetchTemplateAction(options) {
|
|
|
550
587
|
markDirectories: true,
|
|
551
588
|
followSymbolicLinks: false
|
|
552
589
|
});
|
|
553
|
-
const nonTemplatedEntries = new Set(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
590
|
+
const nonTemplatedEntries = new Set(
|
|
591
|
+
(await Promise.all(
|
|
592
|
+
(copyOnlyPatterns || []).map(
|
|
593
|
+
(pattern) => globby__default["default"](pattern, {
|
|
594
|
+
cwd: templateDir,
|
|
595
|
+
dot: true,
|
|
596
|
+
onlyFiles: false,
|
|
597
|
+
markDirectories: true,
|
|
598
|
+
followSymbolicLinks: false
|
|
599
|
+
})
|
|
600
|
+
)
|
|
601
|
+
)).flat()
|
|
602
|
+
);
|
|
560
603
|
const { cookiecutterCompat, values } = ctx.input;
|
|
561
604
|
const context = {
|
|
562
605
|
[cookiecutterCompat ? "cookiecutter" : "values"]: values
|
|
563
606
|
};
|
|
564
|
-
ctx.logger.info(
|
|
607
|
+
ctx.logger.info(
|
|
608
|
+
`Processing ${allEntriesInTemplate.length} template files/directories with input values`,
|
|
609
|
+
ctx.input.values
|
|
610
|
+
);
|
|
565
611
|
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
566
612
|
cookiecutterCompat: ctx.input.cookiecutterCompat,
|
|
567
613
|
additionalTemplateFilters
|
|
@@ -591,22 +637,34 @@ function createFetchTemplateAction(options) {
|
|
|
591
637
|
continue;
|
|
592
638
|
}
|
|
593
639
|
if (!renderContents && !extension) {
|
|
594
|
-
ctx.logger.info(
|
|
640
|
+
ctx.logger.info(
|
|
641
|
+
`Copying file/directory ${location} without processing.`
|
|
642
|
+
);
|
|
595
643
|
}
|
|
596
644
|
if (location.endsWith("/")) {
|
|
597
|
-
ctx.logger.info(
|
|
645
|
+
ctx.logger.info(
|
|
646
|
+
`Writing directory ${location} to template output path.`
|
|
647
|
+
);
|
|
598
648
|
await fs__default["default"].ensureDir(outputPath);
|
|
599
649
|
} else {
|
|
600
650
|
const inputFilePath = backendCommon.resolveSafeChildPath(templateDir, location);
|
|
601
651
|
const stats = await fs__default["default"].promises.lstat(inputFilePath);
|
|
602
652
|
if (stats.isSymbolicLink() || await isbinaryfile.isBinaryFile(inputFilePath)) {
|
|
603
|
-
ctx.logger.info(
|
|
653
|
+
ctx.logger.info(
|
|
654
|
+
`Copying file binary or symbolic link at ${location}, to template output path.`
|
|
655
|
+
);
|
|
604
656
|
await fs__default["default"].copy(inputFilePath, outputPath);
|
|
605
657
|
} else {
|
|
606
658
|
const statsObj = await fs__default["default"].stat(inputFilePath);
|
|
607
|
-
ctx.logger.info(
|
|
659
|
+
ctx.logger.info(
|
|
660
|
+
`Writing file ${location} to template output path with mode ${statsObj.mode}.`
|
|
661
|
+
);
|
|
608
662
|
const inputFileContents = await fs__default["default"].readFile(inputFilePath, "utf-8");
|
|
609
|
-
await fs__default["default"].outputFile(
|
|
663
|
+
await fs__default["default"].outputFile(
|
|
664
|
+
outputPath,
|
|
665
|
+
renderContents ? renderTemplate(inputFileContents, context) : inputFileContents,
|
|
666
|
+
{ mode: statsObj.mode }
|
|
667
|
+
);
|
|
610
668
|
}
|
|
611
669
|
}
|
|
612
670
|
}
|
|
@@ -703,15 +761,23 @@ const createFilesystemRenameAction = () => {
|
|
|
703
761
|
if (!file.from || !file.to) {
|
|
704
762
|
throw new errors.InputError("each file must have a from and to property");
|
|
705
763
|
}
|
|
706
|
-
const sourceFilepath = backendCommon.resolveSafeChildPath(
|
|
764
|
+
const sourceFilepath = backendCommon.resolveSafeChildPath(
|
|
765
|
+
ctx.workspacePath,
|
|
766
|
+
file.from
|
|
767
|
+
);
|
|
707
768
|
const destFilepath = backendCommon.resolveSafeChildPath(ctx.workspacePath, file.to);
|
|
708
769
|
try {
|
|
709
770
|
await fs__default["default"].move(sourceFilepath, destFilepath, {
|
|
710
771
|
overwrite: (_b = file.overwrite) != null ? _b : false
|
|
711
772
|
});
|
|
712
|
-
ctx.logger.info(
|
|
773
|
+
ctx.logger.info(
|
|
774
|
+
`File ${sourceFilepath} renamed to ${destFilepath} successfully`
|
|
775
|
+
);
|
|
713
776
|
} catch (err) {
|
|
714
|
-
ctx.logger.error(
|
|
777
|
+
ctx.logger.error(
|
|
778
|
+
`Failed to rename file ${sourceFilepath} to ${destFilepath}:`,
|
|
779
|
+
err
|
|
780
|
+
);
|
|
715
781
|
throw err;
|
|
716
782
|
}
|
|
717
783
|
}
|
|
@@ -721,7 +787,10 @@ const createFilesystemRenameAction = () => {
|
|
|
721
787
|
|
|
722
788
|
const getRepoSourceDirectory = (workspacePath, sourcePath) => {
|
|
723
789
|
if (sourcePath) {
|
|
724
|
-
const safeSuffix = path.normalize(sourcePath).replace(
|
|
790
|
+
const safeSuffix = path.normalize(sourcePath).replace(
|
|
791
|
+
/^(\.\.(\/|\\|$))+/,
|
|
792
|
+
""
|
|
793
|
+
);
|
|
725
794
|
const path$1 = path.join(workspacePath, safeSuffix);
|
|
726
795
|
if (!backendCommon.isChildPath(workspacePath, path$1)) {
|
|
727
796
|
throw new Error("Invalid source path");
|
|
@@ -736,7 +805,9 @@ const parseRepoUrl = (repoUrl, integrations) => {
|
|
|
736
805
|
try {
|
|
737
806
|
parsed = new URL(`https://${repoUrl}`);
|
|
738
807
|
} catch (error) {
|
|
739
|
-
throw new errors.InputError(
|
|
808
|
+
throw new errors.InputError(
|
|
809
|
+
`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
|
|
810
|
+
);
|
|
740
811
|
}
|
|
741
812
|
const host = parsed.host;
|
|
742
813
|
const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
|
|
@@ -745,25 +816,35 @@ const parseRepoUrl = (repoUrl, integrations) => {
|
|
|
745
816
|
const project = (_d = parsed.searchParams.get("project")) != null ? _d : void 0;
|
|
746
817
|
const type = (_e = integrations.byHost(host)) == null ? void 0 : _e.type;
|
|
747
818
|
if (!type) {
|
|
748
|
-
throw new errors.InputError(
|
|
819
|
+
throw new errors.InputError(
|
|
820
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
821
|
+
);
|
|
749
822
|
}
|
|
750
823
|
if (type === "bitbucket") {
|
|
751
824
|
if (host === "bitbucket.org") {
|
|
752
825
|
if (!workspace) {
|
|
753
|
-
throw new errors.InputError(
|
|
826
|
+
throw new errors.InputError(
|
|
827
|
+
`Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`
|
|
828
|
+
);
|
|
754
829
|
}
|
|
755
830
|
}
|
|
756
831
|
if (!project) {
|
|
757
|
-
throw new errors.InputError(
|
|
832
|
+
throw new errors.InputError(
|
|
833
|
+
`Invalid repo URL passed to publisher: ${repoUrl}, missing project`
|
|
834
|
+
);
|
|
758
835
|
}
|
|
759
836
|
} else {
|
|
760
837
|
if (!owner && type !== "gerrit") {
|
|
761
|
-
throw new errors.InputError(
|
|
838
|
+
throw new errors.InputError(
|
|
839
|
+
`Invalid repo URL passed to publisher: ${repoUrl}, missing owner`
|
|
840
|
+
);
|
|
762
841
|
}
|
|
763
842
|
}
|
|
764
843
|
const repo = parsed.searchParams.get("repo");
|
|
765
844
|
if (!repo) {
|
|
766
|
-
throw new errors.InputError(
|
|
845
|
+
throw new errors.InputError(
|
|
846
|
+
`Invalid repo URL passed to publisher: ${repoUrl}, missing repo`
|
|
847
|
+
);
|
|
767
848
|
}
|
|
768
849
|
return { host, owner, repo, organization, workspace, project };
|
|
769
850
|
};
|
|
@@ -788,7 +869,9 @@ const executeShellCommand = async (options) => {
|
|
|
788
869
|
});
|
|
789
870
|
process.on("close", (code) => {
|
|
790
871
|
if (code !== 0) {
|
|
791
|
-
return reject(
|
|
872
|
+
return reject(
|
|
873
|
+
new Error(`Command ${command} failed, exit code: ${code}`)
|
|
874
|
+
);
|
|
792
875
|
}
|
|
793
876
|
return resolve();
|
|
794
877
|
});
|
|
@@ -805,8 +888,7 @@ async function initRepoAndPush({
|
|
|
805
888
|
}) {
|
|
806
889
|
var _a, _b;
|
|
807
890
|
const git = backendCommon.Git.fromAuth({
|
|
808
|
-
|
|
809
|
-
password: auth.password,
|
|
891
|
+
...auth,
|
|
810
892
|
logger
|
|
811
893
|
});
|
|
812
894
|
await git.init({
|
|
@@ -834,6 +916,39 @@ async function initRepoAndPush({
|
|
|
834
916
|
remote: "origin"
|
|
835
917
|
});
|
|
836
918
|
}
|
|
919
|
+
async function commitAndPushRepo({
|
|
920
|
+
dir,
|
|
921
|
+
auth,
|
|
922
|
+
logger,
|
|
923
|
+
commitMessage,
|
|
924
|
+
gitAuthorInfo,
|
|
925
|
+
branch = "master",
|
|
926
|
+
remoteRef
|
|
927
|
+
}) {
|
|
928
|
+
var _a, _b;
|
|
929
|
+
const git = backendCommon.Git.fromAuth({
|
|
930
|
+
...auth,
|
|
931
|
+
logger
|
|
932
|
+
});
|
|
933
|
+
await git.fetch({ dir });
|
|
934
|
+
await git.checkout({ dir, ref: branch });
|
|
935
|
+
await git.add({ dir, filepath: "." });
|
|
936
|
+
const authorInfo = {
|
|
937
|
+
name: (_a = gitAuthorInfo == null ? void 0 : gitAuthorInfo.name) != null ? _a : "Scaffolder",
|
|
938
|
+
email: (_b = gitAuthorInfo == null ? void 0 : gitAuthorInfo.email) != null ? _b : "scaffolder@backstage.io"
|
|
939
|
+
};
|
|
940
|
+
await git.commit({
|
|
941
|
+
dir,
|
|
942
|
+
message: commitMessage,
|
|
943
|
+
author: authorInfo,
|
|
944
|
+
committer: authorInfo
|
|
945
|
+
});
|
|
946
|
+
await git.push({
|
|
947
|
+
dir,
|
|
948
|
+
remote: "origin",
|
|
949
|
+
remoteRef: remoteRef != null ? remoteRef : `refs/heads/${branch}`
|
|
950
|
+
});
|
|
951
|
+
}
|
|
837
952
|
const enableBranchProtectionOnDefaultRepoBranch = async ({
|
|
838
953
|
repoName,
|
|
839
954
|
client,
|
|
@@ -866,8 +981,12 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
|
|
|
866
981
|
});
|
|
867
982
|
} catch (e) {
|
|
868
983
|
errors.assertError(e);
|
|
869
|
-
if (e.message.includes(
|
|
870
|
-
|
|
984
|
+
if (e.message.includes(
|
|
985
|
+
"Upgrade to GitHub Pro or make this repository public to enable this feature"
|
|
986
|
+
)) {
|
|
987
|
+
logger.warn(
|
|
988
|
+
"Branch protection was not enabled as it requires GitHub Pro for private repositories"
|
|
989
|
+
);
|
|
871
990
|
} else {
|
|
872
991
|
throw e;
|
|
873
992
|
}
|
|
@@ -909,10 +1028,14 @@ async function getOctokitOptions(options) {
|
|
|
909
1028
|
}
|
|
910
1029
|
const githubCredentialsProvider = credentialsProvider != null ? credentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
|
911
1030
|
const { token: credentialProviderToken } = await githubCredentialsProvider.getCredentials({
|
|
912
|
-
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1031
|
+
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1032
|
+
repo
|
|
1033
|
+
)}`
|
|
913
1034
|
});
|
|
914
1035
|
if (!credentialProviderToken) {
|
|
915
|
-
throw new errors.InputError(
|
|
1036
|
+
throw new errors.InputError(
|
|
1037
|
+
`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`
|
|
1038
|
+
);
|
|
916
1039
|
}
|
|
917
1040
|
return {
|
|
918
1041
|
auth: credentialProviderToken,
|
|
@@ -949,9 +1072,13 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
|
|
|
949
1072
|
} catch (e) {
|
|
950
1073
|
errors.assertError(e);
|
|
951
1074
|
if (e.message === "Resource not accessible by integration") {
|
|
952
|
-
logger.warn(
|
|
1075
|
+
logger.warn(
|
|
1076
|
+
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`
|
|
1077
|
+
);
|
|
953
1078
|
}
|
|
954
|
-
throw new Error(
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`
|
|
1081
|
+
);
|
|
955
1082
|
}
|
|
956
1083
|
if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
|
|
957
1084
|
const [, team] = access.split("/");
|
|
@@ -992,7 +1119,9 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
|
|
|
992
1119
|
} catch (e) {
|
|
993
1120
|
errors.assertError(e);
|
|
994
1121
|
const name = extractCollaboratorName(collaborator);
|
|
995
|
-
logger.warn(
|
|
1122
|
+
logger.warn(
|
|
1123
|
+
`Skipping ${collaborator.access} access for ${name}, ${e.message}`
|
|
1124
|
+
);
|
|
996
1125
|
}
|
|
997
1126
|
}
|
|
998
1127
|
}
|
|
@@ -1042,7 +1171,9 @@ async function initRepoPushAndProtect(remoteUrl, password, workspacePath, source
|
|
|
1042
1171
|
});
|
|
1043
1172
|
} catch (e) {
|
|
1044
1173
|
errors.assertError(e);
|
|
1045
|
-
logger.warn(
|
|
1174
|
+
logger.warn(
|
|
1175
|
+
`Skipping: default branch protection on '${repo}', ${e.message}`
|
|
1176
|
+
);
|
|
1046
1177
|
}
|
|
1047
1178
|
}
|
|
1048
1179
|
}
|
|
@@ -1100,17 +1231,21 @@ function createGithubActionsDispatchAction(options) {
|
|
|
1100
1231
|
workflowInputs,
|
|
1101
1232
|
token: providedToken
|
|
1102
1233
|
} = ctx.input;
|
|
1103
|
-
ctx.logger.info(
|
|
1234
|
+
ctx.logger.info(
|
|
1235
|
+
`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`
|
|
1236
|
+
);
|
|
1104
1237
|
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
|
1105
1238
|
if (!owner) {
|
|
1106
1239
|
throw new errors.InputError("Invalid repository owner provided in repoUrl");
|
|
1107
1240
|
}
|
|
1108
|
-
const client = new octokit.Octokit(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1241
|
+
const client = new octokit.Octokit(
|
|
1242
|
+
await getOctokitOptions({
|
|
1243
|
+
integrations,
|
|
1244
|
+
repoUrl,
|
|
1245
|
+
credentialsProvider: githubCredentialsProvider,
|
|
1246
|
+
token: providedToken
|
|
1247
|
+
})
|
|
1248
|
+
);
|
|
1114
1249
|
await client.rest.actions.createWorkflowDispatch({
|
|
1115
1250
|
owner,
|
|
1116
1251
|
repo,
|
|
@@ -1166,12 +1301,14 @@ function createGithubIssuesLabelAction(options) {
|
|
|
1166
1301
|
if (!owner) {
|
|
1167
1302
|
throw new errors.InputError("Invalid repository owner provided in repoUrl");
|
|
1168
1303
|
}
|
|
1169
|
-
const client = new octokit.Octokit(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1304
|
+
const client = new octokit.Octokit(
|
|
1305
|
+
await getOctokitOptions({
|
|
1306
|
+
integrations,
|
|
1307
|
+
credentialsProvider: githubCredentialsProvider,
|
|
1308
|
+
repoUrl,
|
|
1309
|
+
token: providedToken
|
|
1310
|
+
})
|
|
1311
|
+
);
|
|
1175
1312
|
try {
|
|
1176
1313
|
await client.rest.issues.addLabels({
|
|
1177
1314
|
owner,
|
|
@@ -1181,7 +1318,9 @@ function createGithubIssuesLabelAction(options) {
|
|
|
1181
1318
|
});
|
|
1182
1319
|
} catch (e) {
|
|
1183
1320
|
errors.assertError(e);
|
|
1184
|
-
ctx.logger.warn(
|
|
1321
|
+
ctx.logger.warn(
|
|
1322
|
+
`Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`
|
|
1323
|
+
);
|
|
1185
1324
|
}
|
|
1186
1325
|
}
|
|
1187
1326
|
});
|
|
@@ -1380,7 +1519,21 @@ function createGithubRepoCreateAction(options) {
|
|
|
1380
1519
|
if (!owner) {
|
|
1381
1520
|
throw new errors.InputError("Invalid repository owner provided in repoUrl");
|
|
1382
1521
|
}
|
|
1383
|
-
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
|
1522
|
+
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
|
1523
|
+
client,
|
|
1524
|
+
repo,
|
|
1525
|
+
owner,
|
|
1526
|
+
repoVisibility,
|
|
1527
|
+
description,
|
|
1528
|
+
deleteBranchOnMerge,
|
|
1529
|
+
allowMergeCommit,
|
|
1530
|
+
allowSquashMerge,
|
|
1531
|
+
allowRebaseMerge,
|
|
1532
|
+
access,
|
|
1533
|
+
collaborators,
|
|
1534
|
+
topics,
|
|
1535
|
+
ctx.logger
|
|
1536
|
+
);
|
|
1384
1537
|
ctx.output("remoteUrl", newRepo.clone_url);
|
|
1385
1538
|
}
|
|
1386
1539
|
});
|
|
@@ -1444,7 +1597,25 @@ function createGithubRepoPushAction(options) {
|
|
|
1444
1597
|
const targetRepo = await client.rest.repos.get({ owner, repo });
|
|
1445
1598
|
const remoteUrl = targetRepo.data.clone_url;
|
|
1446
1599
|
const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`;
|
|
1447
|
-
await initRepoPushAndProtect(
|
|
1600
|
+
await initRepoPushAndProtect(
|
|
1601
|
+
remoteUrl,
|
|
1602
|
+
octokitOptions.auth,
|
|
1603
|
+
ctx.workspacePath,
|
|
1604
|
+
ctx.input.sourcePath,
|
|
1605
|
+
defaultBranch,
|
|
1606
|
+
protectDefaultBranch,
|
|
1607
|
+
protectEnforceAdmins,
|
|
1608
|
+
owner,
|
|
1609
|
+
client,
|
|
1610
|
+
repo,
|
|
1611
|
+
requireCodeOwnerReviews,
|
|
1612
|
+
requiredStatusCheckContexts,
|
|
1613
|
+
config,
|
|
1614
|
+
ctx.logger,
|
|
1615
|
+
gitCommitMessage,
|
|
1616
|
+
gitAuthorName,
|
|
1617
|
+
gitAuthorEmail
|
|
1618
|
+
);
|
|
1448
1619
|
ctx.output("remoteUrl", remoteUrl);
|
|
1449
1620
|
ctx.output("repoContentsUrl", repoContentsUrl);
|
|
1450
1621
|
}
|
|
@@ -1536,12 +1707,14 @@ function createGithubWebhookAction(options) {
|
|
|
1536
1707
|
if (!owner) {
|
|
1537
1708
|
throw new errors.InputError("Invalid repository owner provided in repoUrl");
|
|
1538
1709
|
}
|
|
1539
|
-
const client = new octokit.Octokit(
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1710
|
+
const client = new octokit.Octokit(
|
|
1711
|
+
await getOctokitOptions({
|
|
1712
|
+
integrations,
|
|
1713
|
+
credentialsProvider: githubCredentialsProvider,
|
|
1714
|
+
repoUrl,
|
|
1715
|
+
token: providedToken
|
|
1716
|
+
})
|
|
1717
|
+
);
|
|
1545
1718
|
try {
|
|
1546
1719
|
const insecure_ssl = insecureSsl ? "1" : "0";
|
|
1547
1720
|
await client.rest.repos.createWebhook({
|
|
@@ -1559,7 +1732,9 @@ function createGithubWebhookAction(options) {
|
|
|
1559
1732
|
ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
|
|
1560
1733
|
} catch (e) {
|
|
1561
1734
|
errors.assertError(e);
|
|
1562
|
-
ctx.logger.warn(
|
|
1735
|
+
ctx.logger.warn(
|
|
1736
|
+
`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`
|
|
1737
|
+
);
|
|
1563
1738
|
}
|
|
1564
1739
|
}
|
|
1565
1740
|
});
|
|
@@ -1638,13 +1813,20 @@ function createPublishAzureAction(options) {
|
|
|
1638
1813
|
gitAuthorName,
|
|
1639
1814
|
gitAuthorEmail
|
|
1640
1815
|
} = ctx.input;
|
|
1641
|
-
const { owner, repo, host, organization } = parseRepoUrl(
|
|
1816
|
+
const { owner, repo, host, organization } = parseRepoUrl(
|
|
1817
|
+
repoUrl,
|
|
1818
|
+
integrations
|
|
1819
|
+
);
|
|
1642
1820
|
if (!organization) {
|
|
1643
|
-
throw new errors.InputError(
|
|
1821
|
+
throw new errors.InputError(
|
|
1822
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing organization`
|
|
1823
|
+
);
|
|
1644
1824
|
}
|
|
1645
1825
|
const integrationConfig = integrations.azure.byHost(host);
|
|
1646
1826
|
if (!integrationConfig) {
|
|
1647
|
-
throw new errors.InputError(
|
|
1827
|
+
throw new errors.InputError(
|
|
1828
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
1829
|
+
);
|
|
1648
1830
|
}
|
|
1649
1831
|
if (!integrationConfig.config.token && !ctx.input.token) {
|
|
1650
1832
|
throw new errors.InputError(`No token provided for Azure Integration ${host}`);
|
|
@@ -1656,12 +1838,16 @@ function createPublishAzureAction(options) {
|
|
|
1656
1838
|
const createOptions = { name: repo };
|
|
1657
1839
|
const returnedRepo = await client.createRepository(createOptions, owner);
|
|
1658
1840
|
if (!returnedRepo) {
|
|
1659
|
-
throw new errors.InputError(
|
|
1660
|
-
|
|
1841
|
+
throw new errors.InputError(
|
|
1842
|
+
`Unable to create the repository with Organization ${organization}, Project ${owner} and Repo ${repo}.
|
|
1843
|
+
Please make sure that both the Org and Project are typed corrected and exist.`
|
|
1844
|
+
);
|
|
1661
1845
|
}
|
|
1662
1846
|
const remoteUrl = returnedRepo.remoteUrl;
|
|
1663
1847
|
if (!remoteUrl) {
|
|
1664
|
-
throw new errors.InputError(
|
|
1848
|
+
throw new errors.InputError(
|
|
1849
|
+
"No remote URL returned from create repository for Azure"
|
|
1850
|
+
);
|
|
1665
1851
|
}
|
|
1666
1852
|
const repoContentsUrl = remoteUrl;
|
|
1667
1853
|
const gitAuthorInfo = {
|
|
@@ -1712,12 +1898,17 @@ const createBitbucketCloudRepository = async (opts) => {
|
|
|
1712
1898
|
};
|
|
1713
1899
|
let response;
|
|
1714
1900
|
try {
|
|
1715
|
-
response = await fetch__default["default"](
|
|
1901
|
+
response = await fetch__default["default"](
|
|
1902
|
+
`${apiBaseUrl}/repositories/${workspace}/${repo}`,
|
|
1903
|
+
options
|
|
1904
|
+
);
|
|
1716
1905
|
} catch (e) {
|
|
1717
1906
|
throw new Error(`Unable to create repository, ${e}`);
|
|
1718
1907
|
}
|
|
1719
1908
|
if (response.status !== 200) {
|
|
1720
|
-
throw new Error(
|
|
1909
|
+
throw new Error(
|
|
1910
|
+
`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
|
|
1911
|
+
);
|
|
1721
1912
|
}
|
|
1722
1913
|
const r = await response.json();
|
|
1723
1914
|
let remoteUrl = "";
|
|
@@ -1757,7 +1948,9 @@ const createBitbucketServerRepository = async (opts) => {
|
|
|
1757
1948
|
throw new Error(`Unable to create repository, ${e}`);
|
|
1758
1949
|
}
|
|
1759
1950
|
if (response.status !== 201) {
|
|
1760
|
-
throw new Error(
|
|
1951
|
+
throw new Error(
|
|
1952
|
+
`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
|
|
1953
|
+
);
|
|
1761
1954
|
}
|
|
1762
1955
|
const r = await response.json();
|
|
1763
1956
|
let remoteUrl = "";
|
|
@@ -1769,15 +1962,20 @@ const createBitbucketServerRepository = async (opts) => {
|
|
|
1769
1962
|
const repoContentsUrl = `${r.links.self[0].href}`;
|
|
1770
1963
|
return { remoteUrl, repoContentsUrl };
|
|
1771
1964
|
};
|
|
1772
|
-
const getAuthorizationHeader$
|
|
1965
|
+
const getAuthorizationHeader$1 = (config) => {
|
|
1773
1966
|
if (config.username && config.appPassword) {
|
|
1774
|
-
const buffer = Buffer.from(
|
|
1967
|
+
const buffer = Buffer.from(
|
|
1968
|
+
`${config.username}:${config.appPassword}`,
|
|
1969
|
+
"utf8"
|
|
1970
|
+
);
|
|
1775
1971
|
return `Basic ${buffer.toString("base64")}`;
|
|
1776
1972
|
}
|
|
1777
1973
|
if (config.token) {
|
|
1778
1974
|
return `Bearer ${config.token}`;
|
|
1779
1975
|
}
|
|
1780
|
-
throw new Error(
|
|
1976
|
+
throw new Error(
|
|
1977
|
+
`Authorization has not been provided for Bitbucket. Please add either username + appPassword or token to the Integrations config`
|
|
1978
|
+
);
|
|
1781
1979
|
};
|
|
1782
1980
|
const performEnableLFS$1 = async (opts) => {
|
|
1783
1981
|
const { authorization, host, project, repo } = opts;
|
|
@@ -1787,9 +1985,14 @@ const performEnableLFS$1 = async (opts) => {
|
|
|
1787
1985
|
Authorization: authorization
|
|
1788
1986
|
}
|
|
1789
1987
|
};
|
|
1790
|
-
const { ok, status, statusText } = await fetch__default["default"](
|
|
1988
|
+
const { ok, status, statusText } = await fetch__default["default"](
|
|
1989
|
+
`https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`,
|
|
1990
|
+
options
|
|
1991
|
+
);
|
|
1791
1992
|
if (!ok)
|
|
1792
|
-
throw new Error(
|
|
1993
|
+
throw new Error(
|
|
1994
|
+
`Failed to enable LFS in the repository, ${status}: ${statusText}`
|
|
1995
|
+
);
|
|
1793
1996
|
};
|
|
1794
1997
|
function createPublishBitbucketAction(options) {
|
|
1795
1998
|
const { integrations, config } = options;
|
|
@@ -1867,7 +2070,9 @@ function createPublishBitbucketAction(options) {
|
|
|
1867
2070
|
},
|
|
1868
2071
|
async handler(ctx) {
|
|
1869
2072
|
var _a;
|
|
1870
|
-
ctx.logger.warn(
|
|
2073
|
+
ctx.logger.warn(
|
|
2074
|
+
`[Deprecated] Please migrate the use of action "publish:bitbucket" to "publish:bitbucketCloud" or "publish:bitbucketServer".`
|
|
2075
|
+
);
|
|
1871
2076
|
const {
|
|
1872
2077
|
repoUrl,
|
|
1873
2078
|
description,
|
|
@@ -1878,24 +2083,35 @@ function createPublishBitbucketAction(options) {
|
|
|
1878
2083
|
gitAuthorName,
|
|
1879
2084
|
gitAuthorEmail
|
|
1880
2085
|
} = ctx.input;
|
|
1881
|
-
const { workspace, project, repo, host } = parseRepoUrl(
|
|
2086
|
+
const { workspace, project, repo, host } = parseRepoUrl(
|
|
2087
|
+
repoUrl,
|
|
2088
|
+
integrations
|
|
2089
|
+
);
|
|
1882
2090
|
if (host === "bitbucket.org") {
|
|
1883
2091
|
if (!workspace) {
|
|
1884
|
-
throw new errors.InputError(
|
|
2092
|
+
throw new errors.InputError(
|
|
2093
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
|
|
2094
|
+
);
|
|
1885
2095
|
}
|
|
1886
2096
|
}
|
|
1887
2097
|
if (!project) {
|
|
1888
|
-
throw new errors.InputError(
|
|
2098
|
+
throw new errors.InputError(
|
|
2099
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
|
|
2100
|
+
);
|
|
1889
2101
|
}
|
|
1890
2102
|
const integrationConfig = integrations.bitbucket.byHost(host);
|
|
1891
2103
|
if (!integrationConfig) {
|
|
1892
|
-
throw new errors.InputError(
|
|
2104
|
+
throw new errors.InputError(
|
|
2105
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
2106
|
+
);
|
|
1893
2107
|
}
|
|
1894
|
-
const authorization = getAuthorizationHeader$
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2108
|
+
const authorization = getAuthorizationHeader$1(
|
|
2109
|
+
ctx.input.token ? {
|
|
2110
|
+
host: integrationConfig.config.host,
|
|
2111
|
+
apiBaseUrl: integrationConfig.config.apiBaseUrl,
|
|
2112
|
+
token: ctx.input.token
|
|
2113
|
+
} : integrationConfig.config
|
|
2114
|
+
);
|
|
1899
2115
|
const apiBaseUrl = integrationConfig.config.apiBaseUrl;
|
|
1900
2116
|
const createMethod = host === "bitbucket.org" ? createBitbucketCloudRepository : createBitbucketServerRepository;
|
|
1901
2117
|
const { remoteUrl, repoContentsUrl } = await createMethod({
|
|
@@ -1968,12 +2184,17 @@ const createRepository$1 = async (opts) => {
|
|
|
1968
2184
|
};
|
|
1969
2185
|
let response;
|
|
1970
2186
|
try {
|
|
1971
|
-
response = await fetch__default["default"](
|
|
2187
|
+
response = await fetch__default["default"](
|
|
2188
|
+
`${apiBaseUrl}/repositories/${workspace}/${repo}`,
|
|
2189
|
+
options
|
|
2190
|
+
);
|
|
1972
2191
|
} catch (e) {
|
|
1973
2192
|
throw new Error(`Unable to create repository, ${e}`);
|
|
1974
2193
|
}
|
|
1975
2194
|
if (response.status !== 200) {
|
|
1976
|
-
throw new Error(
|
|
2195
|
+
throw new Error(
|
|
2196
|
+
`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
|
|
2197
|
+
);
|
|
1977
2198
|
}
|
|
1978
2199
|
const r = await response.json();
|
|
1979
2200
|
let remoteUrl = "";
|
|
@@ -1985,15 +2206,20 @@ const createRepository$1 = async (opts) => {
|
|
|
1985
2206
|
const repoContentsUrl = `${r.links.html.href}/src/${mainBranch}`;
|
|
1986
2207
|
return { remoteUrl, repoContentsUrl };
|
|
1987
2208
|
};
|
|
1988
|
-
const getAuthorizationHeader
|
|
2209
|
+
const getAuthorizationHeader = (config) => {
|
|
1989
2210
|
if (config.username && config.appPassword) {
|
|
1990
|
-
const buffer = Buffer.from(
|
|
2211
|
+
const buffer = Buffer.from(
|
|
2212
|
+
`${config.username}:${config.appPassword}`,
|
|
2213
|
+
"utf8"
|
|
2214
|
+
);
|
|
1991
2215
|
return `Basic ${buffer.toString("base64")}`;
|
|
1992
2216
|
}
|
|
1993
2217
|
if (config.token) {
|
|
1994
2218
|
return `Bearer ${config.token}`;
|
|
1995
2219
|
}
|
|
1996
|
-
throw new Error(
|
|
2220
|
+
throw new Error(
|
|
2221
|
+
`Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`
|
|
2222
|
+
);
|
|
1997
2223
|
};
|
|
1998
2224
|
function createPublishBitbucketCloudAction(options) {
|
|
1999
2225
|
const { integrations, config } = options;
|
|
@@ -2056,18 +2282,29 @@ function createPublishBitbucketCloudAction(options) {
|
|
|
2056
2282
|
defaultBranch = "master",
|
|
2057
2283
|
repoVisibility = "private"
|
|
2058
2284
|
} = ctx.input;
|
|
2059
|
-
const { workspace, project, repo, host } = parseRepoUrl(
|
|
2285
|
+
const { workspace, project, repo, host } = parseRepoUrl(
|
|
2286
|
+
repoUrl,
|
|
2287
|
+
integrations
|
|
2288
|
+
);
|
|
2060
2289
|
if (!workspace) {
|
|
2061
|
-
throw new errors.InputError(
|
|
2290
|
+
throw new errors.InputError(
|
|
2291
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
|
|
2292
|
+
);
|
|
2062
2293
|
}
|
|
2063
2294
|
if (!project) {
|
|
2064
|
-
throw new errors.InputError(
|
|
2295
|
+
throw new errors.InputError(
|
|
2296
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
|
|
2297
|
+
);
|
|
2065
2298
|
}
|
|
2066
2299
|
const integrationConfig = integrations.bitbucketCloud.byHost(host);
|
|
2067
2300
|
if (!integrationConfig) {
|
|
2068
|
-
throw new errors.InputError(
|
|
2301
|
+
throw new errors.InputError(
|
|
2302
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
2303
|
+
);
|
|
2069
2304
|
}
|
|
2070
|
-
const authorization = getAuthorizationHeader
|
|
2305
|
+
const authorization = getAuthorizationHeader(
|
|
2306
|
+
ctx.input.token ? { token: ctx.input.token } : integrationConfig.config
|
|
2307
|
+
);
|
|
2071
2308
|
const apiBaseUrl = integrationConfig.config.apiBaseUrl;
|
|
2072
2309
|
const { remoteUrl, repoContentsUrl } = await createRepository$1({
|
|
2073
2310
|
authorization,
|
|
@@ -2091,7 +2328,9 @@ function createPublishBitbucketCloudAction(options) {
|
|
|
2091
2328
|
};
|
|
2092
2329
|
} else {
|
|
2093
2330
|
if (!integrationConfig.config.username || !integrationConfig.config.appPassword) {
|
|
2094
|
-
throw new Error(
|
|
2331
|
+
throw new Error(
|
|
2332
|
+
"Credentials for Bitbucket Cloud integration required for this action."
|
|
2333
|
+
);
|
|
2095
2334
|
}
|
|
2096
2335
|
auth = {
|
|
2097
2336
|
username: integrationConfig.config.username,
|
|
@@ -2104,7 +2343,9 @@ function createPublishBitbucketCloudAction(options) {
|
|
|
2104
2343
|
auth,
|
|
2105
2344
|
defaultBranch,
|
|
2106
2345
|
logger: ctx.logger,
|
|
2107
|
-
commitMessage: config.getOptionalString(
|
|
2346
|
+
commitMessage: config.getOptionalString(
|
|
2347
|
+
"scaffolder.defaultCommitMessage"
|
|
2348
|
+
),
|
|
2108
2349
|
gitAuthorInfo
|
|
2109
2350
|
});
|
|
2110
2351
|
ctx.output("remoteUrl", remoteUrl);
|
|
@@ -2141,7 +2382,9 @@ const createRepository = async (opts) => {
|
|
|
2141
2382
|
throw new Error(`Unable to create repository, ${e}`);
|
|
2142
2383
|
}
|
|
2143
2384
|
if (response.status !== 201) {
|
|
2144
|
-
throw new Error(
|
|
2385
|
+
throw new Error(
|
|
2386
|
+
`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
|
|
2387
|
+
);
|
|
2145
2388
|
}
|
|
2146
2389
|
const r = await response.json();
|
|
2147
2390
|
let remoteUrl = "";
|
|
@@ -2153,9 +2396,6 @@ const createRepository = async (opts) => {
|
|
|
2153
2396
|
const repoContentsUrl = `${r.links.self[0].href}`;
|
|
2154
2397
|
return { remoteUrl, repoContentsUrl };
|
|
2155
2398
|
};
|
|
2156
|
-
const getAuthorizationHeader = (config) => {
|
|
2157
|
-
return `Bearer ${config.token}`;
|
|
2158
|
-
};
|
|
2159
2399
|
const performEnableLFS = async (opts) => {
|
|
2160
2400
|
const { authorization, host, project, repo } = opts;
|
|
2161
2401
|
const options = {
|
|
@@ -2164,9 +2404,14 @@ const performEnableLFS = async (opts) => {
|
|
|
2164
2404
|
Authorization: authorization
|
|
2165
2405
|
}
|
|
2166
2406
|
};
|
|
2167
|
-
const { ok, status, statusText } = await fetch__default["default"](
|
|
2407
|
+
const { ok, status, statusText } = await fetch__default["default"](
|
|
2408
|
+
`https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`,
|
|
2409
|
+
options
|
|
2410
|
+
);
|
|
2168
2411
|
if (!ok)
|
|
2169
|
-
throw new Error(
|
|
2412
|
+
throw new Error(
|
|
2413
|
+
`Failed to enable LFS in the repository, ${status}: ${statusText}`
|
|
2414
|
+
);
|
|
2170
2415
|
};
|
|
2171
2416
|
function createPublishBitbucketServerAction(options) {
|
|
2172
2417
|
const { integrations, config } = options;
|
|
@@ -2238,17 +2483,28 @@ function createPublishBitbucketServerAction(options) {
|
|
|
2238
2483
|
} = ctx.input;
|
|
2239
2484
|
const { project, repo, host } = parseRepoUrl(repoUrl, integrations);
|
|
2240
2485
|
if (!project) {
|
|
2241
|
-
throw new errors.InputError(
|
|
2486
|
+
throw new errors.InputError(
|
|
2487
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
|
|
2488
|
+
);
|
|
2242
2489
|
}
|
|
2243
2490
|
const integrationConfig = integrations.bitbucketServer.byHost(host);
|
|
2244
2491
|
if (!integrationConfig) {
|
|
2245
|
-
throw new errors.InputError(
|
|
2492
|
+
throw new errors.InputError(
|
|
2493
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
2494
|
+
);
|
|
2246
2495
|
}
|
|
2247
2496
|
const token = (_a = ctx.input.token) != null ? _a : integrationConfig.config.token;
|
|
2248
|
-
|
|
2249
|
-
|
|
2497
|
+
const authConfig = {
|
|
2498
|
+
...integrationConfig.config,
|
|
2499
|
+
...{ token }
|
|
2500
|
+
};
|
|
2501
|
+
const reqOpts = integration.getBitbucketServerRequestOptions(authConfig);
|
|
2502
|
+
const authorization = reqOpts.headers.Authorization;
|
|
2503
|
+
if (!authorization) {
|
|
2504
|
+
throw new Error(
|
|
2505
|
+
`Authorization has not been provided for ${integrationConfig.config.host}. Please add either (a) a user login auth token, or (b) a token or (c) username + password to the integration config.`
|
|
2506
|
+
);
|
|
2250
2507
|
}
|
|
2251
|
-
const authorization = getAuthorizationHeader({ token });
|
|
2252
2508
|
const apiBaseUrl = integrationConfig.config.apiBaseUrl;
|
|
2253
2509
|
const { remoteUrl, repoContentsUrl } = await createRepository({
|
|
2254
2510
|
authorization,
|
|
@@ -2262,9 +2518,11 @@ function createPublishBitbucketServerAction(options) {
|
|
|
2262
2518
|
name: config.getOptionalString("scaffolder.defaultAuthor.name"),
|
|
2263
2519
|
email: config.getOptionalString("scaffolder.defaultAuthor.email")
|
|
2264
2520
|
};
|
|
2265
|
-
const auth = {
|
|
2266
|
-
|
|
2267
|
-
|
|
2521
|
+
const auth = authConfig.token ? {
|
|
2522
|
+
token
|
|
2523
|
+
} : {
|
|
2524
|
+
username: authConfig.username,
|
|
2525
|
+
password: authConfig.password
|
|
2268
2526
|
};
|
|
2269
2527
|
await initRepoAndPush({
|
|
2270
2528
|
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
|
|
@@ -2272,7 +2530,9 @@ function createPublishBitbucketServerAction(options) {
|
|
|
2272
2530
|
auth,
|
|
2273
2531
|
defaultBranch,
|
|
2274
2532
|
logger: ctx.logger,
|
|
2275
|
-
commitMessage: config.getOptionalString(
|
|
2533
|
+
commitMessage: config.getOptionalString(
|
|
2534
|
+
"scaffolder.defaultCommitMessage"
|
|
2535
|
+
),
|
|
2276
2536
|
gitAuthorInfo
|
|
2277
2537
|
});
|
|
2278
2538
|
if (enableLFS) {
|
|
@@ -2327,9 +2587,14 @@ const createGerritProject = async (config, options) => {
|
|
|
2327
2587
|
"Content-Type": "application/json"
|
|
2328
2588
|
}
|
|
2329
2589
|
};
|
|
2330
|
-
const response = await fetch__default["default"](
|
|
2590
|
+
const response = await fetch__default["default"](
|
|
2591
|
+
`${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`,
|
|
2592
|
+
fetchOptions
|
|
2593
|
+
);
|
|
2331
2594
|
if (response.status !== 201) {
|
|
2332
|
-
throw new Error(
|
|
2595
|
+
throw new Error(
|
|
2596
|
+
`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
|
|
2597
|
+
);
|
|
2333
2598
|
}
|
|
2334
2599
|
};
|
|
2335
2600
|
const generateCommitMessage = (config, commitSubject) => {
|
|
@@ -2408,13 +2673,20 @@ function createPublishGerritAction(options) {
|
|
|
2408
2673
|
gitCommitMessage = "initial commit",
|
|
2409
2674
|
sourcePath
|
|
2410
2675
|
} = ctx.input;
|
|
2411
|
-
const { repo, host, owner, workspace } = parseRepoUrl(
|
|
2676
|
+
const { repo, host, owner, workspace } = parseRepoUrl(
|
|
2677
|
+
repoUrl,
|
|
2678
|
+
integrations
|
|
2679
|
+
);
|
|
2412
2680
|
const integrationConfig = integrations.gerrit.byHost(host);
|
|
2413
2681
|
if (!integrationConfig) {
|
|
2414
|
-
throw new errors.InputError(
|
|
2682
|
+
throw new errors.InputError(
|
|
2683
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
2684
|
+
);
|
|
2415
2685
|
}
|
|
2416
2686
|
if (!workspace) {
|
|
2417
|
-
throw new errors.InputError(
|
|
2687
|
+
throw new errors.InputError(
|
|
2688
|
+
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
|
|
2689
|
+
);
|
|
2418
2690
|
}
|
|
2419
2691
|
await createGerritProject(integrationConfig.config, {
|
|
2420
2692
|
description,
|
|
@@ -2447,6 +2719,115 @@ function createPublishGerritAction(options) {
|
|
|
2447
2719
|
});
|
|
2448
2720
|
}
|
|
2449
2721
|
|
|
2722
|
+
const generateGerritChangeId = () => {
|
|
2723
|
+
const changeId = crypto__default["default"].randomBytes(20).toString("hex");
|
|
2724
|
+
return `I${changeId}`;
|
|
2725
|
+
};
|
|
2726
|
+
function createPublishGerritReviewAction(options) {
|
|
2727
|
+
const { integrations, config } = options;
|
|
2728
|
+
return createTemplateAction({
|
|
2729
|
+
id: "publish:gerrit:review",
|
|
2730
|
+
description: "Creates a new Gerrit review.",
|
|
2731
|
+
schema: {
|
|
2732
|
+
input: {
|
|
2733
|
+
type: "object",
|
|
2734
|
+
required: ["repoUrl", "gitCommitMessage"],
|
|
2735
|
+
properties: {
|
|
2736
|
+
repoUrl: {
|
|
2737
|
+
title: "Repository Location",
|
|
2738
|
+
type: "string"
|
|
2739
|
+
},
|
|
2740
|
+
branch: {
|
|
2741
|
+
title: "Repository branch",
|
|
2742
|
+
type: "string",
|
|
2743
|
+
description: "Branch of the repository the review will be created on"
|
|
2744
|
+
},
|
|
2745
|
+
sourcePath: {
|
|
2746
|
+
type: "string",
|
|
2747
|
+
title: "Working Subdirectory",
|
|
2748
|
+
description: "Subdirectory of working directory containing the repository"
|
|
2749
|
+
},
|
|
2750
|
+
gitCommitMessage: {
|
|
2751
|
+
title: "Git Commit Message",
|
|
2752
|
+
type: "string",
|
|
2753
|
+
description: `Sets the commit message on the repository.`
|
|
2754
|
+
},
|
|
2755
|
+
gitAuthorName: {
|
|
2756
|
+
title: "Default Author Name",
|
|
2757
|
+
type: "string",
|
|
2758
|
+
description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
|
|
2759
|
+
},
|
|
2760
|
+
gitAuthorEmail: {
|
|
2761
|
+
title: "Default Author Email",
|
|
2762
|
+
type: "string",
|
|
2763
|
+
description: `Sets the default author email for the commit.`
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
output: {
|
|
2768
|
+
type: "object",
|
|
2769
|
+
properties: {
|
|
2770
|
+
reviewUrl: {
|
|
2771
|
+
title: "A URL to the review",
|
|
2772
|
+
type: "string"
|
|
2773
|
+
},
|
|
2774
|
+
repoContentsUrl: {
|
|
2775
|
+
title: "A URL to the root of the repository",
|
|
2776
|
+
type: "string"
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
async handler(ctx) {
|
|
2782
|
+
var _a;
|
|
2783
|
+
const {
|
|
2784
|
+
repoUrl,
|
|
2785
|
+
branch = "master",
|
|
2786
|
+
sourcePath,
|
|
2787
|
+
gitAuthorName,
|
|
2788
|
+
gitAuthorEmail,
|
|
2789
|
+
gitCommitMessage
|
|
2790
|
+
} = ctx.input;
|
|
2791
|
+
const { host, repo } = parseRepoUrl(repoUrl, integrations);
|
|
2792
|
+
if (!gitCommitMessage) {
|
|
2793
|
+
throw new errors.InputError(`Missing gitCommitMessage input`);
|
|
2794
|
+
}
|
|
2795
|
+
const integrationConfig = integrations.gerrit.byHost(host);
|
|
2796
|
+
if (!integrationConfig) {
|
|
2797
|
+
throw new errors.InputError(
|
|
2798
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
const auth = {
|
|
2802
|
+
username: integrationConfig.config.username,
|
|
2803
|
+
password: integrationConfig.config.password
|
|
2804
|
+
};
|
|
2805
|
+
const gitAuthorInfo = {
|
|
2806
|
+
name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
|
|
2807
|
+
email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
|
|
2808
|
+
};
|
|
2809
|
+
const changeId = generateGerritChangeId();
|
|
2810
|
+
const commitMessage = `${gitCommitMessage}
|
|
2811
|
+
|
|
2812
|
+
Change-Id: ${changeId}`;
|
|
2813
|
+
await commitAndPushRepo({
|
|
2814
|
+
dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
|
|
2815
|
+
auth,
|
|
2816
|
+
logger: ctx.logger,
|
|
2817
|
+
commitMessage,
|
|
2818
|
+
gitAuthorInfo,
|
|
2819
|
+
branch,
|
|
2820
|
+
remoteRef: `refs/for/${branch}`
|
|
2821
|
+
});
|
|
2822
|
+
const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${branch}`;
|
|
2823
|
+
const reviewUrl = `${integrationConfig.config.baseUrl}/#/q/${changeId}`;
|
|
2824
|
+
(_a = ctx.logger) == null ? void 0 : _a.info(`Review available on ${reviewUrl}`);
|
|
2825
|
+
ctx.output("repoContentsUrl", repoContentsUrl);
|
|
2826
|
+
ctx.output("reviewUrl", reviewUrl);
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2450
2831
|
function createPublishGithubAction(options) {
|
|
2451
2832
|
const { integrations, config, githubCredentialsProvider } = options;
|
|
2452
2833
|
return createTemplateAction({
|
|
@@ -2520,10 +2901,42 @@ function createPublishGithubAction(options) {
|
|
|
2520
2901
|
if (!owner) {
|
|
2521
2902
|
throw new errors.InputError("Invalid repository owner provided in repoUrl");
|
|
2522
2903
|
}
|
|
2523
|
-
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
|
2904
|
+
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
|
2905
|
+
client,
|
|
2906
|
+
repo,
|
|
2907
|
+
owner,
|
|
2908
|
+
repoVisibility,
|
|
2909
|
+
description,
|
|
2910
|
+
deleteBranchOnMerge,
|
|
2911
|
+
allowMergeCommit,
|
|
2912
|
+
allowSquashMerge,
|
|
2913
|
+
allowRebaseMerge,
|
|
2914
|
+
access,
|
|
2915
|
+
collaborators,
|
|
2916
|
+
topics,
|
|
2917
|
+
ctx.logger
|
|
2918
|
+
);
|
|
2524
2919
|
const remoteUrl = newRepo.clone_url;
|
|
2525
2920
|
const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`;
|
|
2526
|
-
await initRepoPushAndProtect(
|
|
2921
|
+
await initRepoPushAndProtect(
|
|
2922
|
+
remoteUrl,
|
|
2923
|
+
octokitOptions.auth,
|
|
2924
|
+
ctx.workspacePath,
|
|
2925
|
+
ctx.input.sourcePath,
|
|
2926
|
+
defaultBranch,
|
|
2927
|
+
protectDefaultBranch,
|
|
2928
|
+
protectEnforceAdmins,
|
|
2929
|
+
owner,
|
|
2930
|
+
client,
|
|
2931
|
+
repo,
|
|
2932
|
+
requireCodeOwnerReviews,
|
|
2933
|
+
requiredStatusCheckContexts,
|
|
2934
|
+
config,
|
|
2935
|
+
ctx.logger,
|
|
2936
|
+
gitCommitMessage,
|
|
2937
|
+
gitAuthorName,
|
|
2938
|
+
gitAuthorEmail
|
|
2939
|
+
);
|
|
2527
2940
|
ctx.output("remoteUrl", remoteUrl);
|
|
2528
2941
|
ctx.output("repoContentsUrl", repoContentsUrl);
|
|
2529
2942
|
}
|
|
@@ -2550,11 +2963,15 @@ async function serializeDirectoryContents(sourcePath, options) {
|
|
|
2550
2963
|
stats: true
|
|
2551
2964
|
});
|
|
2552
2965
|
const limiter = limiterFactory__default["default"](10);
|
|
2553
|
-
return Promise.all(
|
|
2554
|
-
path: path$1,
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2966
|
+
return Promise.all(
|
|
2967
|
+
paths.map(async ({ path: path$1, stats }) => ({
|
|
2968
|
+
path: path$1,
|
|
2969
|
+
content: await limiter(
|
|
2970
|
+
async () => fs__default["default"].readFile(path.join(sourcePath, path$1))
|
|
2971
|
+
),
|
|
2972
|
+
executable: isExecutable(stats == null ? void 0 : stats.mode)
|
|
2973
|
+
}))
|
|
2974
|
+
);
|
|
2558
2975
|
}
|
|
2559
2976
|
|
|
2560
2977
|
async function deserializeDirectoryContents(targetPath, files) {
|
|
@@ -2575,7 +2992,9 @@ const defaultClientFactory = async ({
|
|
|
2575
2992
|
host = "github.com",
|
|
2576
2993
|
token: providedToken
|
|
2577
2994
|
}) => {
|
|
2578
|
-
const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(
|
|
2995
|
+
const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(
|
|
2996
|
+
encodeURIComponent
|
|
2997
|
+
);
|
|
2579
2998
|
const octokitOptions = await getOctokitOptions({
|
|
2580
2999
|
integrations,
|
|
2581
3000
|
credentialsProvider: githubCredentialsProvider,
|
|
@@ -2636,6 +3055,22 @@ const createPublishGithubPullRequestAction = ({
|
|
|
2636
3055
|
title: "Authentication Token",
|
|
2637
3056
|
type: "string",
|
|
2638
3057
|
description: "The token to use for authorization to GitHub"
|
|
3058
|
+
},
|
|
3059
|
+
reviewers: {
|
|
3060
|
+
title: "Pull Request Reviewers",
|
|
3061
|
+
type: "array",
|
|
3062
|
+
items: {
|
|
3063
|
+
type: "string"
|
|
3064
|
+
},
|
|
3065
|
+
description: "The users that will be added as reviewers to the pull request"
|
|
3066
|
+
},
|
|
3067
|
+
teamReviewers: {
|
|
3068
|
+
title: "Pull Request Team Reviewers",
|
|
3069
|
+
type: "array",
|
|
3070
|
+
items: {
|
|
3071
|
+
type: "string"
|
|
3072
|
+
},
|
|
3073
|
+
description: "The teams that will be added as reviewers to the pull request"
|
|
2639
3074
|
}
|
|
2640
3075
|
}
|
|
2641
3076
|
},
|
|
@@ -2665,11 +3100,15 @@ const createPublishGithubPullRequestAction = ({
|
|
|
2665
3100
|
draft,
|
|
2666
3101
|
targetPath,
|
|
2667
3102
|
sourcePath,
|
|
2668
|
-
token: providedToken
|
|
3103
|
+
token: providedToken,
|
|
3104
|
+
reviewers,
|
|
3105
|
+
teamReviewers
|
|
2669
3106
|
} = ctx.input;
|
|
2670
3107
|
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
|
|
2671
3108
|
if (!owner) {
|
|
2672
|
-
throw new errors.InputError(
|
|
3109
|
+
throw new errors.InputError(
|
|
3110
|
+
`No owner provided for host: ${host}, and repo ${repo}`
|
|
3111
|
+
);
|
|
2673
3112
|
}
|
|
2674
3113
|
const client = await clientFactory({
|
|
2675
3114
|
integrations,
|
|
@@ -2683,14 +3122,16 @@ const createPublishGithubPullRequestAction = ({
|
|
|
2683
3122
|
const directoryContents = await serializeDirectoryContents(fileRoot, {
|
|
2684
3123
|
gitignore: true
|
|
2685
3124
|
});
|
|
2686
|
-
const files = Object.fromEntries(
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
3125
|
+
const files = Object.fromEntries(
|
|
3126
|
+
directoryContents.map((file) => [
|
|
3127
|
+
targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
|
|
3128
|
+
{
|
|
3129
|
+
mode: file.executable ? "100755" : "100644",
|
|
3130
|
+
encoding: "base64",
|
|
3131
|
+
content: file.content.toString("base64")
|
|
3132
|
+
}
|
|
3133
|
+
])
|
|
3134
|
+
);
|
|
2694
3135
|
try {
|
|
2695
3136
|
const response = await client.createPullRequest({
|
|
2696
3137
|
owner,
|
|
@@ -2709,13 +3150,46 @@ const createPublishGithubPullRequestAction = ({
|
|
|
2709
3150
|
if (!response) {
|
|
2710
3151
|
throw new GithubResponseError("null response from Github");
|
|
2711
3152
|
}
|
|
3153
|
+
const pullRequestNumber = response.data.number;
|
|
3154
|
+
if (reviewers || teamReviewers) {
|
|
3155
|
+
const pullRequest = { owner, repo, number: pullRequestNumber };
|
|
3156
|
+
await requestReviewersOnPullRequest(
|
|
3157
|
+
pullRequest,
|
|
3158
|
+
reviewers,
|
|
3159
|
+
teamReviewers,
|
|
3160
|
+
client,
|
|
3161
|
+
ctx.logger
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
2712
3164
|
ctx.output("remoteUrl", response.data.html_url);
|
|
2713
|
-
ctx.output("pullRequestNumber",
|
|
3165
|
+
ctx.output("pullRequestNumber", pullRequestNumber);
|
|
2714
3166
|
} catch (e) {
|
|
2715
3167
|
throw new GithubResponseError("Pull request creation failed", e);
|
|
2716
3168
|
}
|
|
2717
3169
|
}
|
|
2718
3170
|
});
|
|
3171
|
+
async function requestReviewersOnPullRequest(pr, reviewers, teamReviewers, client, logger) {
|
|
3172
|
+
var _a, _b, _c, _d;
|
|
3173
|
+
try {
|
|
3174
|
+
const result = await client.rest.pulls.requestReviewers({
|
|
3175
|
+
owner: pr.owner,
|
|
3176
|
+
repo: pr.repo,
|
|
3177
|
+
pull_number: pr.number,
|
|
3178
|
+
reviewers,
|
|
3179
|
+
team_reviewers: teamReviewers
|
|
3180
|
+
});
|
|
3181
|
+
const addedUsers = (_b = (_a = result.data.requested_reviewers) == null ? void 0 : _a.join(", ")) != null ? _b : "";
|
|
3182
|
+
const addedTeams = (_d = (_c = result.data.requested_teams) == null ? void 0 : _c.join(", ")) != null ? _d : "";
|
|
3183
|
+
logger.info(
|
|
3184
|
+
`Added users [${addedUsers}] and teams [${addedTeams}] as reviewers to Pull request ${pr.number}`
|
|
3185
|
+
);
|
|
3186
|
+
} catch (e) {
|
|
3187
|
+
logger.error(
|
|
3188
|
+
`Failure when adding reviewers to Pull request ${pr.number}`,
|
|
3189
|
+
e
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
2719
3193
|
};
|
|
2720
3194
|
|
|
2721
3195
|
function createPublishGitlabAction(options) {
|
|
@@ -2800,11 +3274,15 @@ function createPublishGitlabAction(options) {
|
|
|
2800
3274
|
} = ctx.input;
|
|
2801
3275
|
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
|
|
2802
3276
|
if (!owner) {
|
|
2803
|
-
throw new errors.InputError(
|
|
3277
|
+
throw new errors.InputError(
|
|
3278
|
+
`No owner provided for host: ${host}, and repo ${repo}`
|
|
3279
|
+
);
|
|
2804
3280
|
}
|
|
2805
3281
|
const integrationConfig = integrations.gitlab.byHost(host);
|
|
2806
3282
|
if (!integrationConfig) {
|
|
2807
|
-
throw new errors.InputError(
|
|
3283
|
+
throw new errors.InputError(
|
|
3284
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
3285
|
+
);
|
|
2808
3286
|
}
|
|
2809
3287
|
if (!integrationConfig.config.token && !ctx.input.token) {
|
|
2810
3288
|
throw new errors.InputError(`No token available for host ${host}`);
|
|
@@ -2900,6 +3378,12 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
2900
3378
|
type: "string",
|
|
2901
3379
|
description: "The token to use for authorization to GitLab"
|
|
2902
3380
|
},
|
|
3381
|
+
commitAction: {
|
|
3382
|
+
title: "Commit action",
|
|
3383
|
+
type: "string",
|
|
3384
|
+
enum: ["create", "update", "delete"],
|
|
3385
|
+
description: "The action to be used for git commit. Defaults to create."
|
|
3386
|
+
},
|
|
2903
3387
|
removeSourceBranch: {
|
|
2904
3388
|
title: "Delete source branch",
|
|
2905
3389
|
type: "boolean",
|
|
@@ -2944,7 +3428,9 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
2944
3428
|
const integrationConfig = integrations.gitlab.byHost(host);
|
|
2945
3429
|
const destinationBranch = ctx.input.branchName;
|
|
2946
3430
|
if (!integrationConfig) {
|
|
2947
|
-
throw new errors.InputError(
|
|
3431
|
+
throw new errors.InputError(
|
|
3432
|
+
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
3433
|
+
);
|
|
2948
3434
|
}
|
|
2949
3435
|
if (!integrationConfig.config.token && !ctx.input.token) {
|
|
2950
3436
|
throw new errors.InputError(`No token available for host ${host}`);
|
|
@@ -2962,38 +3448,63 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
2962
3448
|
const assigneeUser = await api.Users.username(assignee);
|
|
2963
3449
|
assigneeId = assigneeUser[0].id;
|
|
2964
3450
|
} catch (e) {
|
|
2965
|
-
ctx.logger.warn(
|
|
3451
|
+
ctx.logger.warn(
|
|
3452
|
+
`Failed to find gitlab user id for ${assignee}: ${e}. Proceeding with MR creation without an assignee.`
|
|
3453
|
+
);
|
|
2966
3454
|
}
|
|
2967
3455
|
}
|
|
2968
|
-
const targetPath = backendCommon.resolveSafeChildPath(
|
|
3456
|
+
const targetPath = backendCommon.resolveSafeChildPath(
|
|
3457
|
+
ctx.workspacePath,
|
|
3458
|
+
ctx.input.targetPath
|
|
3459
|
+
);
|
|
2969
3460
|
const fileContents = await serializeDirectoryContents(targetPath, {
|
|
2970
3461
|
gitignore: true
|
|
2971
3462
|
});
|
|
2972
|
-
const actions = fileContents.map((file) =>
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
3463
|
+
const actions = fileContents.map((file) => {
|
|
3464
|
+
var _a2;
|
|
3465
|
+
return {
|
|
3466
|
+
action: (_a2 = ctx.input.commitAction) != null ? _a2 : "create",
|
|
3467
|
+
filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
|
|
3468
|
+
encoding: "base64",
|
|
3469
|
+
content: file.content.toString("base64"),
|
|
3470
|
+
execute_filemode: file.executable
|
|
3471
|
+
};
|
|
3472
|
+
});
|
|
2979
3473
|
const projects = await api.Projects.show(projectPath);
|
|
2980
3474
|
const { default_branch: defaultBranch } = projects;
|
|
2981
3475
|
try {
|
|
2982
|
-
await api.Branches.create(
|
|
3476
|
+
await api.Branches.create(
|
|
3477
|
+
projectPath,
|
|
3478
|
+
destinationBranch,
|
|
3479
|
+
String(defaultBranch)
|
|
3480
|
+
);
|
|
2983
3481
|
} catch (e) {
|
|
2984
3482
|
throw new errors.InputError(`The branch creation failed ${e}`);
|
|
2985
3483
|
}
|
|
2986
3484
|
try {
|
|
2987
|
-
await api.Commits.create(
|
|
3485
|
+
await api.Commits.create(
|
|
3486
|
+
projectPath,
|
|
3487
|
+
destinationBranch,
|
|
3488
|
+
ctx.input.title,
|
|
3489
|
+
actions
|
|
3490
|
+
);
|
|
2988
3491
|
} catch (e) {
|
|
2989
|
-
throw new errors.InputError(
|
|
3492
|
+
throw new errors.InputError(
|
|
3493
|
+
`Committing the changes to ${destinationBranch} failed ${e}`
|
|
3494
|
+
);
|
|
2990
3495
|
}
|
|
2991
3496
|
try {
|
|
2992
|
-
const mergeRequestUrl = await api.MergeRequests.create(
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3497
|
+
const mergeRequestUrl = await api.MergeRequests.create(
|
|
3498
|
+
projectPath,
|
|
3499
|
+
destinationBranch,
|
|
3500
|
+
String(defaultBranch),
|
|
3501
|
+
ctx.input.title,
|
|
3502
|
+
{
|
|
3503
|
+
description: ctx.input.description,
|
|
3504
|
+
removeSourceBranch: ctx.input.removeSourceBranch ? ctx.input.removeSourceBranch : false,
|
|
3505
|
+
assigneeId
|
|
3506
|
+
}
|
|
3507
|
+
).then((mergeRequest) => {
|
|
2997
3508
|
return mergeRequest.web_url;
|
|
2998
3509
|
});
|
|
2999
3510
|
ctx.output("projectid", projectPath);
|
|
@@ -3029,6 +3540,10 @@ const createBuiltinActions = (options) => {
|
|
|
3029
3540
|
integrations,
|
|
3030
3541
|
config
|
|
3031
3542
|
}),
|
|
3543
|
+
createPublishGerritReviewAction({
|
|
3544
|
+
integrations,
|
|
3545
|
+
config
|
|
3546
|
+
}),
|
|
3032
3547
|
createPublishGithubAction({
|
|
3033
3548
|
integrations,
|
|
3034
3549
|
config,
|
|
@@ -3097,14 +3612,18 @@ class TemplateActionRegistry {
|
|
|
3097
3612
|
}
|
|
3098
3613
|
register(action) {
|
|
3099
3614
|
if (this.actions.has(action.id)) {
|
|
3100
|
-
throw new errors.ConflictError(
|
|
3615
|
+
throw new errors.ConflictError(
|
|
3616
|
+
`Template action with ID '${action.id}' has already been registered`
|
|
3617
|
+
);
|
|
3101
3618
|
}
|
|
3102
3619
|
this.actions.set(action.id, action);
|
|
3103
3620
|
}
|
|
3104
3621
|
get(actionId) {
|
|
3105
3622
|
const action = this.actions.get(actionId);
|
|
3106
3623
|
if (!action) {
|
|
3107
|
-
throw new errors.NotFoundError(
|
|
3624
|
+
throw new errors.NotFoundError(
|
|
3625
|
+
`Template action with ID '${actionId}' is not registered.`
|
|
3626
|
+
);
|
|
3108
3627
|
}
|
|
3109
3628
|
return action;
|
|
3110
3629
|
}
|
|
@@ -3113,7 +3632,10 @@ class TemplateActionRegistry {
|
|
|
3113
3632
|
}
|
|
3114
3633
|
}
|
|
3115
3634
|
|
|
3116
|
-
const migrationsDir = backendCommon.resolvePackagePath(
|
|
3635
|
+
const migrationsDir = backendCommon.resolvePackagePath(
|
|
3636
|
+
"@backstage/plugin-scaffolder-backend",
|
|
3637
|
+
"migrations"
|
|
3638
|
+
);
|
|
3117
3639
|
const parseSqlDateToIsoString = (input) => {
|
|
3118
3640
|
if (typeof input === "string") {
|
|
3119
3641
|
return luxon.DateTime.fromSQL(input, { zone: "UTC" }).toISO();
|
|
@@ -3228,10 +3750,14 @@ class DatabaseTaskStore {
|
|
|
3228
3750
|
}
|
|
3229
3751
|
}
|
|
3230
3752
|
async listStaleTasks({ timeoutS }) {
|
|
3231
|
-
const rawRows = await this.db("tasks").where("status", "processing").andWhere(
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3753
|
+
const rawRows = await this.db("tasks").where("status", "processing").andWhere(
|
|
3754
|
+
"last_heartbeat_at",
|
|
3755
|
+
"<=",
|
|
3756
|
+
this.db.client.config.client.includes("sqlite3") ? this.db.raw(`datetime('now', ?)`, [`-${timeoutS} seconds`]) : this.db.raw(`dateadd('second', ?, ?)`, [
|
|
3757
|
+
`-${timeoutS}`,
|
|
3758
|
+
this.db.fn.now()
|
|
3759
|
+
])
|
|
3760
|
+
);
|
|
3235
3761
|
const tasks = rawRows.map((row) => ({
|
|
3236
3762
|
taskId: row.id
|
|
3237
3763
|
}));
|
|
@@ -3246,7 +3772,9 @@ class DatabaseTaskStore {
|
|
|
3246
3772
|
if (status === "failed" || status === "completed") {
|
|
3247
3773
|
oldStatus = "processing";
|
|
3248
3774
|
} else {
|
|
3249
|
-
throw new Error(
|
|
3775
|
+
throw new Error(
|
|
3776
|
+
`Invalid status update of run '${taskId}' to status '${status}'`
|
|
3777
|
+
);
|
|
3250
3778
|
}
|
|
3251
3779
|
await this.db.transaction(async (tx) => {
|
|
3252
3780
|
const [task] = await tx("tasks").where({
|
|
@@ -3256,7 +3784,9 @@ class DatabaseTaskStore {
|
|
|
3256
3784
|
throw new Error(`No task with taskId ${taskId} found`);
|
|
3257
3785
|
}
|
|
3258
3786
|
if (task.status !== oldStatus) {
|
|
3259
|
-
throw new errors.ConflictError(
|
|
3787
|
+
throw new errors.ConflictError(
|
|
3788
|
+
`Refusing to update status of run '${taskId}' to status '${status}' as it is currently '${task.status}', expected '${oldStatus}'`
|
|
3789
|
+
);
|
|
3260
3790
|
}
|
|
3261
3791
|
const updateCount = await tx("tasks").where({
|
|
3262
3792
|
id: taskId,
|
|
@@ -3265,7 +3795,9 @@ class DatabaseTaskStore {
|
|
|
3265
3795
|
status
|
|
3266
3796
|
});
|
|
3267
3797
|
if (updateCount !== 1) {
|
|
3268
|
-
throw new errors.ConflictError(
|
|
3798
|
+
throw new errors.ConflictError(
|
|
3799
|
+
`Failed to update status to '${status}' for taskId ${taskId}`
|
|
3800
|
+
);
|
|
3269
3801
|
}
|
|
3270
3802
|
await tx("task_events").insert({
|
|
3271
3803
|
task_id: taskId,
|
|
@@ -3305,7 +3837,9 @@ class DatabaseTaskStore {
|
|
|
3305
3837
|
createdAt: parseSqlDateToIsoString(event.created_at)
|
|
3306
3838
|
};
|
|
3307
3839
|
} catch (error) {
|
|
3308
|
-
throw new Error(
|
|
3840
|
+
throw new Error(
|
|
3841
|
+
`Failed to parse event body from event taskId=${taskId} id=${event.id}, ${error}`
|
|
3842
|
+
);
|
|
3309
3843
|
}
|
|
3310
3844
|
});
|
|
3311
3845
|
return { events };
|
|
@@ -3366,7 +3900,10 @@ class TaskManager {
|
|
|
3366
3900
|
this.startTimeout();
|
|
3367
3901
|
} catch (error) {
|
|
3368
3902
|
this.isDone = true;
|
|
3369
|
-
this.logger.error(
|
|
3903
|
+
this.logger.error(
|
|
3904
|
+
`Heartbeat for task ${this.task.taskId} failed`,
|
|
3905
|
+
error
|
|
3906
|
+
);
|
|
3370
3907
|
}
|
|
3371
3908
|
}, 1e3);
|
|
3372
3909
|
}
|
|
@@ -3387,7 +3924,9 @@ class StorageTaskBroker {
|
|
|
3387
3924
|
}
|
|
3388
3925
|
async list(options) {
|
|
3389
3926
|
if (!this.storage.list) {
|
|
3390
|
-
throw new Error(
|
|
3927
|
+
throw new Error(
|
|
3928
|
+
"TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
|
|
3929
|
+
);
|
|
3391
3930
|
}
|
|
3392
3931
|
return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
|
|
3393
3932
|
}
|
|
@@ -3395,12 +3934,16 @@ class StorageTaskBroker {
|
|
|
3395
3934
|
for (; ; ) {
|
|
3396
3935
|
const pendingTask = await this.storage.claimTask();
|
|
3397
3936
|
if (pendingTask) {
|
|
3398
|
-
return TaskManager.create(
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3937
|
+
return TaskManager.create(
|
|
3938
|
+
{
|
|
3939
|
+
taskId: pendingTask.id,
|
|
3940
|
+
spec: pendingTask.spec,
|
|
3941
|
+
secrets: pendingTask.secrets,
|
|
3942
|
+
createdBy: pendingTask.createdBy
|
|
3943
|
+
},
|
|
3944
|
+
this.storage,
|
|
3945
|
+
this.logger
|
|
3946
|
+
);
|
|
3404
3947
|
}
|
|
3405
3948
|
await this.waitForDispatch();
|
|
3406
3949
|
}
|
|
@@ -3438,19 +3981,21 @@ class StorageTaskBroker {
|
|
|
3438
3981
|
}
|
|
3439
3982
|
async vacuumTasks(options) {
|
|
3440
3983
|
const { tasks } = await this.storage.listStaleTasks(options);
|
|
3441
|
-
await Promise.all(
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3984
|
+
await Promise.all(
|
|
3985
|
+
tasks.map(async (task) => {
|
|
3986
|
+
try {
|
|
3987
|
+
await this.storage.completeTask({
|
|
3988
|
+
taskId: task.taskId,
|
|
3989
|
+
status: "failed",
|
|
3990
|
+
eventBody: {
|
|
3991
|
+
message: "The task was cancelled because the task worker lost connection to the task broker"
|
|
3992
|
+
}
|
|
3993
|
+
});
|
|
3994
|
+
} catch (error) {
|
|
3995
|
+
this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);
|
|
3996
|
+
}
|
|
3997
|
+
})
|
|
3998
|
+
);
|
|
3454
3999
|
}
|
|
3455
4000
|
waitForDispatch() {
|
|
3456
4001
|
return this.deferredDispatch.promise;
|
|
@@ -3471,10 +4016,12 @@ function generateExampleOutput(schema) {
|
|
|
3471
4016
|
return examples[0];
|
|
3472
4017
|
}
|
|
3473
4018
|
if (schema.type === "object") {
|
|
3474
|
-
return Object.fromEntries(
|
|
3475
|
-
key,
|
|
3476
|
-
|
|
3477
|
-
|
|
4019
|
+
return Object.fromEntries(
|
|
4020
|
+
Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
|
|
4021
|
+
key,
|
|
4022
|
+
generateExampleOutput(value)
|
|
4023
|
+
])
|
|
4024
|
+
);
|
|
3478
4025
|
} else if (schema.type === "array") {
|
|
3479
4026
|
const [firstSchema] = (_b = [schema.items]) == null ? void 0 : _b.flat();
|
|
3480
4027
|
if (firstSchema) {
|
|
@@ -3501,7 +4048,11 @@ const createStepLogger = ({
|
|
|
3501
4048
|
const metadata = { stepId: step.id };
|
|
3502
4049
|
const taskLogger = winston__namespace.createLogger({
|
|
3503
4050
|
level: process.env.LOG_LEVEL || "info",
|
|
3504
|
-
format: winston__namespace.format.combine(
|
|
4051
|
+
format: winston__namespace.format.combine(
|
|
4052
|
+
winston__namespace.format.colorize(),
|
|
4053
|
+
winston__namespace.format.timestamp(),
|
|
4054
|
+
winston__namespace.format.simple()
|
|
4055
|
+
),
|
|
3505
4056
|
defaultMeta: {}
|
|
3506
4057
|
});
|
|
3507
4058
|
const streamLogger = new stream.PassThrough();
|
|
@@ -3521,13 +4072,17 @@ class NunjucksWorkflowRunner {
|
|
|
3521
4072
|
isSingleTemplateString(input) {
|
|
3522
4073
|
var _a, _b;
|
|
3523
4074
|
const { parser, nodes } = nunjucks__default["default"];
|
|
3524
|
-
const parsed = parser.parse(
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
4075
|
+
const parsed = parser.parse(
|
|
4076
|
+
input,
|
|
4077
|
+
{},
|
|
4078
|
+
{
|
|
4079
|
+
autoescape: false,
|
|
4080
|
+
tags: {
|
|
4081
|
+
variableStart: "${{",
|
|
4082
|
+
variableEnd: "}}"
|
|
4083
|
+
}
|
|
3529
4084
|
}
|
|
3530
|
-
|
|
4085
|
+
);
|
|
3531
4086
|
return parsed.children.length === 1 && !(((_b = (_a = parsed.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b[0]) instanceof nodes.TemplateData);
|
|
3532
4087
|
}
|
|
3533
4088
|
render(input, context, renderTemplate) {
|
|
@@ -3536,7 +4091,10 @@ class NunjucksWorkflowRunner {
|
|
|
3536
4091
|
if (typeof value === "string") {
|
|
3537
4092
|
try {
|
|
3538
4093
|
if (this.isSingleTemplateString(value)) {
|
|
3539
|
-
const wrappedDumped = value.replace(
|
|
4094
|
+
const wrappedDumped = value.replace(
|
|
4095
|
+
/\${{(.+)}}/g,
|
|
4096
|
+
"${{ ( $1 ) | dump }}"
|
|
4097
|
+
);
|
|
3540
4098
|
const templated2 = renderTemplate(wrappedDumped, context);
|
|
3541
4099
|
if (templated2 === "") {
|
|
3542
4100
|
return void 0;
|
|
@@ -3544,7 +4102,9 @@ class NunjucksWorkflowRunner {
|
|
|
3544
4102
|
return JSON.parse(templated2);
|
|
3545
4103
|
}
|
|
3546
4104
|
} catch (ex) {
|
|
3547
|
-
this.options.logger.error(
|
|
4105
|
+
this.options.logger.error(
|
|
4106
|
+
`Failed to parse template string: ${value} with error ${ex.message}`
|
|
4107
|
+
);
|
|
3548
4108
|
}
|
|
3549
4109
|
const templated = renderTemplate(value, context);
|
|
3550
4110
|
if (templated === "") {
|
|
@@ -3561,9 +4121,14 @@ class NunjucksWorkflowRunner {
|
|
|
3561
4121
|
async execute(task) {
|
|
3562
4122
|
var _a, _b, _c, _d, _e;
|
|
3563
4123
|
if (!isValidTaskSpec(task.spec)) {
|
|
3564
|
-
throw new errors.InputError(
|
|
4124
|
+
throw new errors.InputError(
|
|
4125
|
+
"Wrong template version executed with the workflow engine"
|
|
4126
|
+
);
|
|
3565
4127
|
}
|
|
3566
|
-
const workspacePath = path__default["default"].join(
|
|
4128
|
+
const workspacePath = path__default["default"].join(
|
|
4129
|
+
this.options.workingDirectory,
|
|
4130
|
+
await task.getWorkspaceName()
|
|
4131
|
+
);
|
|
3567
4132
|
const { integrations } = this.options;
|
|
3568
4133
|
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
3569
4134
|
parseRepoUrl(url) {
|
|
@@ -3573,7 +4138,9 @@ class NunjucksWorkflowRunner {
|
|
|
3573
4138
|
});
|
|
3574
4139
|
try {
|
|
3575
4140
|
await fs__default["default"].ensureDir(workspacePath);
|
|
3576
|
-
await task.emitLog(
|
|
4141
|
+
await task.emitLog(
|
|
4142
|
+
`Starting up task with ${task.spec.steps.length} steps`
|
|
4143
|
+
);
|
|
3577
4144
|
const context = {
|
|
3578
4145
|
parameters: task.spec.parameters,
|
|
3579
4146
|
steps: {},
|
|
@@ -3582,9 +4149,16 @@ class NunjucksWorkflowRunner {
|
|
|
3582
4149
|
for (const step of task.spec.steps) {
|
|
3583
4150
|
try {
|
|
3584
4151
|
if (step.if) {
|
|
3585
|
-
const ifResult = await this.render(
|
|
4152
|
+
const ifResult = await this.render(
|
|
4153
|
+
step.if,
|
|
4154
|
+
context,
|
|
4155
|
+
renderTemplate
|
|
4156
|
+
);
|
|
3586
4157
|
if (!isTruthy(ifResult)) {
|
|
3587
|
-
await task.emitLog(
|
|
4158
|
+
await task.emitLog(
|
|
4159
|
+
`Skipping step ${step.id} because it's if condition was false`,
|
|
4160
|
+
{ stepId: step.id, status: "skipped" }
|
|
4161
|
+
);
|
|
3588
4162
|
continue;
|
|
3589
4163
|
}
|
|
3590
4164
|
}
|
|
@@ -3595,10 +4169,13 @@ class NunjucksWorkflowRunner {
|
|
|
3595
4169
|
const action = this.options.actionRegistry.get(step.action);
|
|
3596
4170
|
const { taskLogger, streamLogger } = createStepLogger({ task, step });
|
|
3597
4171
|
if (task.isDryRun && !action.supportsDryRun) {
|
|
3598
|
-
task.emitLog(
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
4172
|
+
task.emitLog(
|
|
4173
|
+
`Skipping because ${action.id} does not support dry-run`,
|
|
4174
|
+
{
|
|
4175
|
+
stepId: step.id,
|
|
4176
|
+
status: "skipped"
|
|
4177
|
+
}
|
|
4178
|
+
);
|
|
3602
4179
|
const outputSchema = (_a = action.schema) == null ? void 0 : _a.output;
|
|
3603
4180
|
if (outputSchema) {
|
|
3604
4181
|
context.steps[step.id] = {
|
|
@@ -3609,12 +4186,21 @@ class NunjucksWorkflowRunner {
|
|
|
3609
4186
|
}
|
|
3610
4187
|
continue;
|
|
3611
4188
|
}
|
|
3612
|
-
const input = (_c = step.input && this.render(
|
|
4189
|
+
const input = (_c = step.input && this.render(
|
|
4190
|
+
step.input,
|
|
4191
|
+
{ ...context, secrets: (_b = task.secrets) != null ? _b : {} },
|
|
4192
|
+
renderTemplate
|
|
4193
|
+
)) != null ? _c : {};
|
|
3613
4194
|
if ((_d = action.schema) == null ? void 0 : _d.input) {
|
|
3614
|
-
const validateResult = jsonschema.validate(
|
|
4195
|
+
const validateResult = jsonschema.validate(
|
|
4196
|
+
input,
|
|
4197
|
+
action.schema.input
|
|
4198
|
+
);
|
|
3615
4199
|
if (!validateResult.valid) {
|
|
3616
4200
|
const errors$1 = validateResult.errors.join(", ");
|
|
3617
|
-
throw new errors.InputError(
|
|
4201
|
+
throw new errors.InputError(
|
|
4202
|
+
`Invalid input passed to action ${action.id}, ${errors$1}`
|
|
4203
|
+
);
|
|
3618
4204
|
}
|
|
3619
4205
|
}
|
|
3620
4206
|
const tmpDirs = new Array();
|
|
@@ -3626,7 +4212,9 @@ class NunjucksWorkflowRunner {
|
|
|
3626
4212
|
logStream: streamLogger,
|
|
3627
4213
|
workspacePath,
|
|
3628
4214
|
createTemporaryDirectory: async () => {
|
|
3629
|
-
const tmpDir = await fs__default["default"].mkdtemp(
|
|
4215
|
+
const tmpDir = await fs__default["default"].mkdtemp(
|
|
4216
|
+
`${workspacePath}_step-${step.id}-`
|
|
4217
|
+
);
|
|
3630
4218
|
tmpDirs.push(tmpDir);
|
|
3631
4219
|
return tmpDir;
|
|
3632
4220
|
},
|
|
@@ -3697,9 +4285,13 @@ class TaskWorker {
|
|
|
3697
4285
|
async runOneTask(task) {
|
|
3698
4286
|
try {
|
|
3699
4287
|
if (task.spec.apiVersion !== "scaffolder.backstage.io/v1beta3") {
|
|
3700
|
-
throw new Error(
|
|
4288
|
+
throw new Error(
|
|
4289
|
+
`Unsupported Template apiVersion ${task.spec.apiVersion}`
|
|
4290
|
+
);
|
|
3701
4291
|
}
|
|
3702
|
-
const { output } = await this.options.runners.workflowRunner.execute(
|
|
4292
|
+
const { output } = await this.options.runners.workflowRunner.execute(
|
|
4293
|
+
task
|
|
4294
|
+
);
|
|
3703
4295
|
await task.complete("completed", { output });
|
|
3704
4296
|
} catch (error) {
|
|
3705
4297
|
errors.assertError(error);
|
|
@@ -3746,7 +4338,10 @@ function createDryRunner(options) {
|
|
|
3746
4338
|
});
|
|
3747
4339
|
const dryRunId = uuid.v4();
|
|
3748
4340
|
const log = new Array();
|
|
3749
|
-
const contentsPath = backendCommon.resolveSafeChildPath(
|
|
4341
|
+
const contentsPath = backendCommon.resolveSafeChildPath(
|
|
4342
|
+
options.workingDirectory,
|
|
4343
|
+
`dry-run-content-${dryRunId}`
|
|
4344
|
+
);
|
|
3750
4345
|
try {
|
|
3751
4346
|
await deserializeDirectoryContents(contentsPath, input.directoryContents);
|
|
3752
4347
|
const result = await workflowRunner.execute({
|
|
@@ -3762,7 +4357,9 @@ function createDryRunner(options) {
|
|
|
3762
4357
|
],
|
|
3763
4358
|
templateInfo: {
|
|
3764
4359
|
entityRef: "template:default/dry-run",
|
|
3765
|
-
baseUrl: url.pathToFileURL(
|
|
4360
|
+
baseUrl: url.pathToFileURL(
|
|
4361
|
+
backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")
|
|
4362
|
+
).toString()
|
|
3766
4363
|
}
|
|
3767
4364
|
},
|
|
3768
4365
|
secrets: input.secrets,
|
|
@@ -3809,7 +4406,9 @@ async function getWorkingDirectory(config, logger) {
|
|
|
3809
4406
|
logger.info(`using working directory: ${workingDirectory}`);
|
|
3810
4407
|
} catch (err) {
|
|
3811
4408
|
errors.assertError(err);
|
|
3812
|
-
logger.error(
|
|
4409
|
+
logger.error(
|
|
4410
|
+
`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
|
|
4411
|
+
);
|
|
3813
4412
|
throw err;
|
|
3814
4413
|
}
|
|
3815
4414
|
return workingDirectory;
|
|
@@ -3838,7 +4437,9 @@ async function findTemplate(options) {
|
|
|
3838
4437
|
}
|
|
3839
4438
|
const template = await catalogApi.getEntityByRef(entityRef, { token });
|
|
3840
4439
|
if (!template) {
|
|
3841
|
-
throw new errors.NotFoundError(
|
|
4440
|
+
throw new errors.NotFoundError(
|
|
4441
|
+
`Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
|
|
4442
|
+
);
|
|
3842
4443
|
}
|
|
3843
4444
|
return template;
|
|
3844
4445
|
}
|
|
@@ -3900,31 +4501,38 @@ async function createRouter(options) {
|
|
|
3900
4501
|
workingDirectory,
|
|
3901
4502
|
additionalTemplateFilters
|
|
3902
4503
|
});
|
|
3903
|
-
router.get(
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
|
|
3914
|
-
res.json({
|
|
3915
|
-
title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
|
|
3916
|
-
steps: parameters.map((schema) => {
|
|
3917
|
-
var _a2;
|
|
3918
|
-
return {
|
|
3919
|
-
title: (_a2 = schema.title) != null ? _a2 : "Fill in template parameters",
|
|
3920
|
-
schema
|
|
3921
|
-
};
|
|
3922
|
-
})
|
|
4504
|
+
router.get(
|
|
4505
|
+
"/v2/templates/:namespace/:kind/:name/parameter-schema",
|
|
4506
|
+
async (req, res) => {
|
|
4507
|
+
var _a, _b;
|
|
4508
|
+
const { namespace, kind, name } = req.params;
|
|
4509
|
+
const { token } = parseBearerToken(req.headers.authorization);
|
|
4510
|
+
const template = await findTemplate({
|
|
4511
|
+
catalogApi: catalogClient,
|
|
4512
|
+
entityRef: { kind, namespace, name },
|
|
4513
|
+
token
|
|
3923
4514
|
});
|
|
3924
|
-
|
|
3925
|
-
|
|
4515
|
+
if (isSupportedTemplate(template)) {
|
|
4516
|
+
const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
|
|
4517
|
+
res.json({
|
|
4518
|
+
title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
|
|
4519
|
+
description: template.metadata.description,
|
|
4520
|
+
steps: parameters.map((schema) => {
|
|
4521
|
+
var _a2;
|
|
4522
|
+
return {
|
|
4523
|
+
title: (_a2 = schema.title) != null ? _a2 : "Please enter the following information",
|
|
4524
|
+
description: schema.description,
|
|
4525
|
+
schema
|
|
4526
|
+
};
|
|
4527
|
+
})
|
|
4528
|
+
});
|
|
4529
|
+
} else {
|
|
4530
|
+
throw new errors.InputError(
|
|
4531
|
+
`Unsupported apiVersion field in schema entity, ${template.apiVersion}`
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
3926
4534
|
}
|
|
3927
|
-
|
|
4535
|
+
).get("/v2/actions", async (_req, res) => {
|
|
3928
4536
|
const actionsList = actionRegistry.list().map((action) => {
|
|
3929
4537
|
return {
|
|
3930
4538
|
id: action.id,
|
|
@@ -3939,7 +4547,9 @@ async function createRouter(options) {
|
|
|
3939
4547
|
const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
|
|
3940
4548
|
defaultKind: "template"
|
|
3941
4549
|
});
|
|
3942
|
-
const { token, entityRef: userEntityRef } = parseBearerToken(
|
|
4550
|
+
const { token, entityRef: userEntityRef } = parseBearerToken(
|
|
4551
|
+
req.headers.authorization
|
|
4552
|
+
);
|
|
3943
4553
|
const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
|
|
3944
4554
|
let auditLog = `Scaffolding task for ${templateRef}`;
|
|
3945
4555
|
if (userEntityRef) {
|
|
@@ -3953,7 +4563,9 @@ async function createRouter(options) {
|
|
|
3953
4563
|
token
|
|
3954
4564
|
});
|
|
3955
4565
|
if (!isSupportedTemplate(template)) {
|
|
3956
|
-
throw new errors.InputError(
|
|
4566
|
+
throw new errors.InputError(
|
|
4567
|
+
`Unsupported apiVersion field in schema entity, ${template.apiVersion}`
|
|
4568
|
+
);
|
|
3957
4569
|
}
|
|
3958
4570
|
for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
|
|
3959
4571
|
const result2 = jsonschema.validate(values, parameters);
|
|
@@ -4003,7 +4615,9 @@ async function createRouter(options) {
|
|
|
4003
4615
|
throw new errors.InputError("createdBy query parameter must be a string");
|
|
4004
4616
|
}
|
|
4005
4617
|
if (!taskBroker.list) {
|
|
4006
|
-
throw new Error(
|
|
4618
|
+
throw new Error(
|
|
4619
|
+
"TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
|
|
4620
|
+
);
|
|
4007
4621
|
}
|
|
4008
4622
|
const tasks = await taskBroker.list({
|
|
4009
4623
|
createdBy: userEntityRef
|
|
@@ -4028,23 +4642,30 @@ async function createRouter(options) {
|
|
|
4028
4642
|
});
|
|
4029
4643
|
const subscription = taskBroker.event$({ taskId, after }).subscribe({
|
|
4030
4644
|
error: (error) => {
|
|
4031
|
-
logger.error(
|
|
4645
|
+
logger.error(
|
|
4646
|
+
`Received error from event stream when observing taskId '${taskId}', ${error}`
|
|
4647
|
+
);
|
|
4648
|
+
res.end();
|
|
4032
4649
|
},
|
|
4033
4650
|
next: ({ events }) => {
|
|
4034
4651
|
var _a;
|
|
4035
4652
|
let shouldUnsubscribe = false;
|
|
4036
4653
|
for (const event of events) {
|
|
4037
|
-
res.write(
|
|
4654
|
+
res.write(
|
|
4655
|
+
`event: ${event.type}
|
|
4038
4656
|
data: ${JSON.stringify(event)}
|
|
4039
4657
|
|
|
4040
|
-
`
|
|
4658
|
+
`
|
|
4659
|
+
);
|
|
4041
4660
|
if (event.type === "completion") {
|
|
4042
4661
|
shouldUnsubscribe = true;
|
|
4043
4662
|
}
|
|
4044
4663
|
}
|
|
4045
4664
|
(_a = res.flush) == null ? void 0 : _a.call(res);
|
|
4046
|
-
if (shouldUnsubscribe)
|
|
4665
|
+
if (shouldUnsubscribe) {
|
|
4047
4666
|
subscription.unsubscribe();
|
|
4667
|
+
res.end();
|
|
4668
|
+
}
|
|
4048
4669
|
}
|
|
4049
4670
|
});
|
|
4050
4671
|
req.on("close", () => {
|
|
@@ -4059,7 +4680,9 @@ data: ${JSON.stringify(event)}
|
|
|
4059
4680
|
}, 3e4);
|
|
4060
4681
|
const subscription = taskBroker.event$({ taskId, after }).subscribe({
|
|
4061
4682
|
error: (error) => {
|
|
4062
|
-
logger.error(
|
|
4683
|
+
logger.error(
|
|
4684
|
+
`Received error from event stream when observing taskId '${taskId}', ${error}`
|
|
4685
|
+
);
|
|
4063
4686
|
},
|
|
4064
4687
|
next: ({ events }) => {
|
|
4065
4688
|
clearTimeout(timeout);
|
|
@@ -4077,7 +4700,9 @@ data: ${JSON.stringify(event)}
|
|
|
4077
4700
|
template: zod.z.unknown(),
|
|
4078
4701
|
values: zod.z.record(zod.z.unknown()),
|
|
4079
4702
|
secrets: zod.z.record(zod.z.string()).optional(),
|
|
4080
|
-
directoryContents: zod.z.array(
|
|
4703
|
+
directoryContents: zod.z.array(
|
|
4704
|
+
zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() })
|
|
4705
|
+
)
|
|
4081
4706
|
});
|
|
4082
4707
|
const body = await bodySchema.parseAsync(req.body).catch((e) => {
|
|
4083
4708
|
throw new errors.InputError(`Malformed request: ${e}`);
|
|
@@ -4144,7 +4769,9 @@ function parseBearerToken(header) {
|
|
|
4144
4769
|
throw new TypeError("Expected Bearer with JWT");
|
|
4145
4770
|
}
|
|
4146
4771
|
const [_header, rawPayload, _signature] = token.split(".");
|
|
4147
|
-
const payload = JSON.parse(
|
|
4772
|
+
const payload = JSON.parse(
|
|
4773
|
+
Buffer.from(rawPayload, "base64").toString()
|
|
4774
|
+
);
|
|
4148
4775
|
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
4149
4776
|
throw new TypeError("Malformed JWT payload");
|
|
4150
4777
|
}
|
|
@@ -4183,24 +4810,28 @@ class ScaffolderEntitiesProcessor {
|
|
|
4183
4810
|
defaultKind: "Group",
|
|
4184
4811
|
defaultNamespace: selfRef.namespace
|
|
4185
4812
|
});
|
|
4186
|
-
emit(
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4813
|
+
emit(
|
|
4814
|
+
pluginCatalogBackend.processingResult.relation({
|
|
4815
|
+
source: selfRef,
|
|
4816
|
+
type: catalogModel.RELATION_OWNED_BY,
|
|
4817
|
+
target: {
|
|
4818
|
+
kind: targetRef.kind,
|
|
4819
|
+
namespace: targetRef.namespace,
|
|
4820
|
+
name: targetRef.name
|
|
4821
|
+
}
|
|
4822
|
+
})
|
|
4823
|
+
);
|
|
4824
|
+
emit(
|
|
4825
|
+
pluginCatalogBackend.processingResult.relation({
|
|
4826
|
+
source: {
|
|
4827
|
+
kind: targetRef.kind,
|
|
4828
|
+
namespace: targetRef.namespace,
|
|
4829
|
+
name: targetRef.name
|
|
4830
|
+
},
|
|
4831
|
+
type: catalogModel.RELATION_OWNER_OF,
|
|
4832
|
+
target: selfRef
|
|
4833
|
+
})
|
|
4834
|
+
);
|
|
4204
4835
|
}
|
|
4205
4836
|
}
|
|
4206
4837
|
return entity;
|
|
@@ -4216,7 +4847,9 @@ const scaffolderCatalogModule = backendPluginApi.createBackendModule({
|
|
|
4216
4847
|
catalogProcessingExtensionPoint: pluginCatalogNode.catalogProcessingExtentionPoint
|
|
4217
4848
|
},
|
|
4218
4849
|
async init({ catalogProcessingExtensionPoint }) {
|
|
4219
|
-
catalogProcessingExtensionPoint.addProcessor(
|
|
4850
|
+
catalogProcessingExtensionPoint.addProcessor(
|
|
4851
|
+
new ScaffolderEntitiesProcessor()
|
|
4852
|
+
);
|
|
4220
4853
|
}
|
|
4221
4854
|
});
|
|
4222
4855
|
}
|
|
@@ -4246,6 +4879,7 @@ exports.createPublishBitbucketCloudAction = createPublishBitbucketCloudAction;
|
|
|
4246
4879
|
exports.createPublishBitbucketServerAction = createPublishBitbucketServerAction;
|
|
4247
4880
|
exports.createPublishFileAction = createPublishFileAction;
|
|
4248
4881
|
exports.createPublishGerritAction = createPublishGerritAction;
|
|
4882
|
+
exports.createPublishGerritReviewAction = createPublishGerritReviewAction;
|
|
4249
4883
|
exports.createPublishGithubAction = createPublishGithubAction;
|
|
4250
4884
|
exports.createPublishGithubPullRequestAction = createPublishGithubPullRequestAction;
|
|
4251
4885
|
exports.createPublishGitlabAction = createPublishGitlabAction;
|