@backstage/plugin-scaffolder-backend 1.7.0 → 1.8.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 +63 -0
- package/alpha/package.json +1 -1
- package/dist/index.alpha.d.ts +1 -1
- package/dist/index.beta.d.ts +1 -1
- package/dist/index.cjs.js +176 -64
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +17 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @backstage/plugin-scaffolder-backend
|
|
2
2
|
|
|
3
|
+
## 1.8.0-next.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5921b5ce49: - The GitLab Project ID for the `publish:gitlab:merge-request` action is now passed through the query parameter `project` in the `repoUrl`. It still allows people to not use the `projectid` and use the `repoUrl` with the `owner` and `repo` query parameters instead. This makes it easier to publish to repositories instead of writing the full path to the project.
|
|
8
|
+
|
|
9
|
+
## 1.8.0-next.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- ea14eb62a2: Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels:
|
|
14
|
+
|
|
15
|
+
- `scaffolder_task_count`: Tracks successful task runs.
|
|
16
|
+
|
|
17
|
+
Labels:
|
|
18
|
+
|
|
19
|
+
- `template`: The entity ref of the scaffolded template
|
|
20
|
+
- `user`: The entity ref of the user that invoked the template run
|
|
21
|
+
- `result`: A string describing whether the task ran successfully, failed, or was skipped
|
|
22
|
+
|
|
23
|
+
- `scaffolder_task_duration`: a histogram which tracks the duration of a task run
|
|
24
|
+
|
|
25
|
+
Labels:
|
|
26
|
+
|
|
27
|
+
- `template`: The entity ref of the scaffolded template
|
|
28
|
+
- `result`: A boolean describing whether the task ran successfully
|
|
29
|
+
|
|
30
|
+
- `scaffolder_step_count`: a count that tracks each step run
|
|
31
|
+
|
|
32
|
+
Labels:
|
|
33
|
+
|
|
34
|
+
- `template`: The entity ref of the scaffolded template
|
|
35
|
+
- `step`: The name of the step that was run
|
|
36
|
+
- `result`: A string describing whether the task ran successfully, failed, or was skipped
|
|
37
|
+
|
|
38
|
+
- `scaffolder_step_duration`: a histogram which tracks the duration of each step run
|
|
39
|
+
|
|
40
|
+
Labels:
|
|
41
|
+
|
|
42
|
+
- `template`: The entity ref of the scaffolded template
|
|
43
|
+
- `step`: The name of the step that was run
|
|
44
|
+
- `result`: A string describing whether the task ran successfully, failed, or was skipped
|
|
45
|
+
|
|
46
|
+
You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 7573b65232: Internal refactor of imports to avoid circular dependencies
|
|
51
|
+
- Updated dependencies
|
|
52
|
+
- @backstage/backend-common@0.16.0-next.0
|
|
53
|
+
- @backstage/plugin-catalog-backend@1.5.1-next.0
|
|
54
|
+
- @backstage/integration@1.4.0-next.0
|
|
55
|
+
- @backstage/backend-tasks@0.3.7-next.0
|
|
56
|
+
- @backstage/catalog-model@1.1.3-next.0
|
|
57
|
+
- @backstage/plugin-auth-node@0.2.7-next.0
|
|
58
|
+
- @backstage/types@1.0.1-next.0
|
|
59
|
+
- @backstage/backend-plugin-api@0.1.4-next.0
|
|
60
|
+
- @backstage/plugin-catalog-node@1.2.1-next.0
|
|
61
|
+
- @backstage/catalog-client@1.1.2-next.0
|
|
62
|
+
- @backstage/config@1.0.4-next.0
|
|
63
|
+
- @backstage/errors@1.1.3-next.0
|
|
64
|
+
- @backstage/plugin-scaffolder-common@1.2.2-next.0
|
|
65
|
+
|
|
3
66
|
## 1.7.0
|
|
4
67
|
|
|
5
68
|
### Minor Changes
|
package/alpha/package.json
CHANGED
package/dist/index.alpha.d.ts
CHANGED
|
@@ -565,7 +565,7 @@ sourcePath?: string | undefined;
|
|
|
565
565
|
targetPath?: string | undefined;
|
|
566
566
|
token?: string | undefined;
|
|
567
567
|
commitAction?: "update" | "create" | "delete" | undefined;
|
|
568
|
-
/** @deprecated
|
|
568
|
+
/** @deprecated projectID passed as query parameters in the repoUrl */
|
|
569
569
|
projectid?: string | undefined;
|
|
570
570
|
removeSourceBranch?: boolean | undefined;
|
|
571
571
|
assignee?: string | undefined;
|
package/dist/index.beta.d.ts
CHANGED
|
@@ -565,7 +565,7 @@ sourcePath?: string | undefined;
|
|
|
565
565
|
targetPath?: string | undefined;
|
|
566
566
|
token?: string | undefined;
|
|
567
567
|
commitAction?: "update" | "create" | "delete" | undefined;
|
|
568
|
-
/** @deprecated
|
|
568
|
+
/** @deprecated projectID passed as query parameters in the repoUrl */
|
|
569
569
|
projectid?: string | undefined;
|
|
570
570
|
removeSourceBranch?: boolean | undefined;
|
|
571
571
|
assignee?: string | undefined;
|
package/dist/index.cjs.js
CHANGED
|
@@ -30,6 +30,7 @@ var winston = require('winston');
|
|
|
30
30
|
var nunjucks = require('nunjucks');
|
|
31
31
|
var lodash = require('lodash');
|
|
32
32
|
var jsonschema = require('jsonschema');
|
|
33
|
+
var promClient = require('prom-client');
|
|
33
34
|
var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
|
|
34
35
|
var express = require('express');
|
|
35
36
|
var Router = require('express-promise-router');
|
|
@@ -850,34 +851,41 @@ const parseRepoUrl = (repoUrl, integrations) => {
|
|
|
850
851
|
`No matching integration configuration for host ${host}, please check your integrations config`
|
|
851
852
|
);
|
|
852
853
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
);
|
|
854
|
+
const repo = parsed.searchParams.get("repo");
|
|
855
|
+
switch (type) {
|
|
856
|
+
case "bitbucket": {
|
|
857
|
+
if (host === "www.bitbucket.org") {
|
|
858
|
+
checkRequiredParams(parsed, "workspace");
|
|
859
859
|
}
|
|
860
|
+
checkRequiredParams(parsed, "project", "repo");
|
|
861
|
+
break;
|
|
860
862
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
863
|
+
case "gitlab": {
|
|
864
|
+
if (!project) {
|
|
865
|
+
checkRequiredParams(parsed, "owner", "repo");
|
|
866
|
+
}
|
|
867
|
+
break;
|
|
865
868
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
869
|
+
case "gerrit": {
|
|
870
|
+
checkRequiredParams(parsed, "repo");
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
default: {
|
|
874
|
+
checkRequiredParams(parsed, "repo", "owner");
|
|
875
|
+
break;
|
|
871
876
|
}
|
|
872
|
-
}
|
|
873
|
-
const repo = parsed.searchParams.get("repo");
|
|
874
|
-
if (!repo) {
|
|
875
|
-
throw new errors.InputError(
|
|
876
|
-
`Invalid repo URL passed to publisher: ${repoUrl}, missing repo`
|
|
877
|
-
);
|
|
878
877
|
}
|
|
879
878
|
return { host, owner, repo, organization, workspace, project };
|
|
880
879
|
};
|
|
880
|
+
function checkRequiredParams(repoUrl, ...params) {
|
|
881
|
+
for (let i = 0; i < params.length; i++) {
|
|
882
|
+
if (!repoUrl.searchParams.get(params[i])) {
|
|
883
|
+
throw new errors.InputError(
|
|
884
|
+
`Invalid repo URL passed to publisher: ${repoUrl.toString()}, missing ${params[i]}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
881
889
|
|
|
882
890
|
const executeShellCommand = async (options) => {
|
|
883
891
|
const {
|
|
@@ -3524,13 +3532,11 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
3524
3532
|
title,
|
|
3525
3533
|
token: providedToken
|
|
3526
3534
|
} = ctx.input;
|
|
3527
|
-
const { host, owner, repo } = parseRepoUrl(
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
console.warn(deprecationWarning);
|
|
3533
|
-
}
|
|
3535
|
+
const { host, owner, repo, project } = parseRepoUrl(
|
|
3536
|
+
repoUrl,
|
|
3537
|
+
integrations
|
|
3538
|
+
);
|
|
3539
|
+
const repoID = project ? project : `${owner}/${repo}`;
|
|
3534
3540
|
const integrationConfig = integrations.gitlab.byHost(host);
|
|
3535
3541
|
if (!integrationConfig) {
|
|
3536
3542
|
throw new errors.InputError(
|
|
@@ -3578,19 +3584,15 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
3578
3584
|
execute_filemode: file.executable
|
|
3579
3585
|
};
|
|
3580
3586
|
});
|
|
3581
|
-
const projects = await api.Projects.show(
|
|
3587
|
+
const projects = await api.Projects.show(repoID);
|
|
3582
3588
|
const { default_branch: defaultBranch } = projects;
|
|
3583
3589
|
try {
|
|
3584
|
-
await api.Branches.create(
|
|
3585
|
-
projectPath,
|
|
3586
|
-
branchName,
|
|
3587
|
-
String(defaultBranch)
|
|
3588
|
-
);
|
|
3590
|
+
await api.Branches.create(repoID, branchName, String(defaultBranch));
|
|
3589
3591
|
} catch (e) {
|
|
3590
3592
|
throw new errors.InputError(`The branch creation failed ${e}`);
|
|
3591
3593
|
}
|
|
3592
3594
|
try {
|
|
3593
|
-
await api.Commits.create(
|
|
3595
|
+
await api.Commits.create(repoID, branchName, ctx.input.title, actions);
|
|
3594
3596
|
} catch (e) {
|
|
3595
3597
|
throw new errors.InputError(
|
|
3596
3598
|
`Committing the changes to ${branchName} failed ${e}`
|
|
@@ -3598,7 +3600,7 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
3598
3600
|
}
|
|
3599
3601
|
try {
|
|
3600
3602
|
const mergeRequestUrl = await api.MergeRequests.create(
|
|
3601
|
-
|
|
3603
|
+
repoID,
|
|
3602
3604
|
branchName,
|
|
3603
3605
|
String(defaultBranch),
|
|
3604
3606
|
title,
|
|
@@ -3610,8 +3612,8 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
3610
3612
|
).then((mergeRequest) => {
|
|
3611
3613
|
return mergeRequest.web_url;
|
|
3612
3614
|
});
|
|
3613
|
-
ctx.output("projectid",
|
|
3614
|
-
ctx.output("projectPath",
|
|
3615
|
+
ctx.output("projectid", repoID);
|
|
3616
|
+
ctx.output("projectPath", repoID);
|
|
3615
3617
|
ctx.output("mergeRequestUrl", mergeRequestUrl);
|
|
3616
3618
|
} catch (e) {
|
|
3617
3619
|
throw new errors.InputError(`Merge request creation failed${e}`);
|
|
@@ -4188,6 +4190,23 @@ function generateExampleOutput(schema) {
|
|
|
4188
4190
|
return "<unknown>";
|
|
4189
4191
|
}
|
|
4190
4192
|
|
|
4193
|
+
function createCounterMetric(config) {
|
|
4194
|
+
let metric = promClient.register.getSingleMetric(config.name);
|
|
4195
|
+
if (!metric) {
|
|
4196
|
+
metric = new promClient.Counter(config);
|
|
4197
|
+
promClient.register.registerMetric(metric);
|
|
4198
|
+
}
|
|
4199
|
+
return metric;
|
|
4200
|
+
}
|
|
4201
|
+
function createHistogramMetric(config) {
|
|
4202
|
+
let metric = promClient.register.getSingleMetric(config.name);
|
|
4203
|
+
if (!metric) {
|
|
4204
|
+
metric = new promClient.Histogram(config);
|
|
4205
|
+
promClient.register.registerMetric(metric);
|
|
4206
|
+
}
|
|
4207
|
+
return metric;
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4191
4210
|
const isValidTaskSpec = (taskSpec) => {
|
|
4192
4211
|
return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
4193
4212
|
};
|
|
@@ -4217,6 +4236,7 @@ const createStepLogger = ({
|
|
|
4217
4236
|
class NunjucksWorkflowRunner {
|
|
4218
4237
|
constructor(options) {
|
|
4219
4238
|
this.options = options;
|
|
4239
|
+
this.tracker = scaffoldingTracker();
|
|
4220
4240
|
}
|
|
4221
4241
|
isSingleTemplateString(input) {
|
|
4222
4242
|
var _a, _b;
|
|
@@ -4287,16 +4307,15 @@ class NunjucksWorkflowRunner {
|
|
|
4287
4307
|
additionalTemplateGlobals: this.options.additionalTemplateGlobals
|
|
4288
4308
|
});
|
|
4289
4309
|
try {
|
|
4310
|
+
const taskTrack = await this.tracker.taskStart(task);
|
|
4290
4311
|
await fs__default["default"].ensureDir(workspacePath);
|
|
4291
|
-
await task.emitLog(
|
|
4292
|
-
`Starting up task with ${task.spec.steps.length} steps`
|
|
4293
|
-
);
|
|
4294
4312
|
const context = {
|
|
4295
4313
|
parameters: task.spec.parameters,
|
|
4296
4314
|
steps: {},
|
|
4297
4315
|
user: task.spec.user
|
|
4298
4316
|
};
|
|
4299
4317
|
for (const step of task.spec.steps) {
|
|
4318
|
+
const stepTrack = await this.tracker.stepStart(task, step);
|
|
4300
4319
|
try {
|
|
4301
4320
|
if (step.if) {
|
|
4302
4321
|
const ifResult = await this.render(
|
|
@@ -4305,17 +4324,10 @@ class NunjucksWorkflowRunner {
|
|
|
4305
4324
|
renderTemplate
|
|
4306
4325
|
);
|
|
4307
4326
|
if (!isTruthy(ifResult)) {
|
|
4308
|
-
await
|
|
4309
|
-
`Skipping step ${step.id} because it's if condition was false`,
|
|
4310
|
-
{ stepId: step.id, status: "skipped" }
|
|
4311
|
-
);
|
|
4327
|
+
await stepTrack.skipFalsy();
|
|
4312
4328
|
continue;
|
|
4313
4329
|
}
|
|
4314
4330
|
}
|
|
4315
|
-
await task.emitLog(`Beginning step ${step.name}`, {
|
|
4316
|
-
stepId: step.id,
|
|
4317
|
-
status: "processing"
|
|
4318
|
-
});
|
|
4319
4331
|
const action = this.options.actionRegistry.get(step.action);
|
|
4320
4332
|
const { taskLogger, streamLogger } = createStepLogger({ task, step });
|
|
4321
4333
|
if (task.isDryRun) {
|
|
@@ -4341,13 +4353,7 @@ class NunjucksWorkflowRunner {
|
|
|
4341
4353
|
)}`
|
|
4342
4354
|
);
|
|
4343
4355
|
if (!action.supportsDryRun) {
|
|
4344
|
-
|
|
4345
|
-
`Skipping because ${action.id} does not support dry-run`,
|
|
4346
|
-
{
|
|
4347
|
-
stepId: step.id,
|
|
4348
|
-
status: "skipped"
|
|
4349
|
-
}
|
|
4350
|
-
);
|
|
4356
|
+
await taskTrack.skipDryRun(step, action);
|
|
4351
4357
|
const outputSchema = (_c = action.schema) == null ? void 0 : _c.output;
|
|
4352
4358
|
if (outputSchema) {
|
|
4353
4359
|
context.steps[step.id] = {
|
|
@@ -4402,19 +4408,15 @@ class NunjucksWorkflowRunner {
|
|
|
4402
4408
|
await fs__default["default"].remove(tmpDir);
|
|
4403
4409
|
}
|
|
4404
4410
|
context.steps[step.id] = { output: stepOutput };
|
|
4405
|
-
await
|
|
4406
|
-
stepId: step.id,
|
|
4407
|
-
status: "completed"
|
|
4408
|
-
});
|
|
4411
|
+
await stepTrack.markSuccessful();
|
|
4409
4412
|
} catch (err) {
|
|
4410
|
-
await
|
|
4411
|
-
|
|
4412
|
-
status: "failed"
|
|
4413
|
-
});
|
|
4413
|
+
await taskTrack.markFailed(step, err);
|
|
4414
|
+
await stepTrack.markFailed();
|
|
4414
4415
|
throw err;
|
|
4415
4416
|
}
|
|
4416
4417
|
}
|
|
4417
4418
|
const output = this.render(task.spec.output, context, renderTemplate);
|
|
4419
|
+
await taskTrack.markSuccessful();
|
|
4418
4420
|
return { output };
|
|
4419
4421
|
} finally {
|
|
4420
4422
|
if (workspacePath) {
|
|
@@ -4423,6 +4425,116 @@ class NunjucksWorkflowRunner {
|
|
|
4423
4425
|
}
|
|
4424
4426
|
}
|
|
4425
4427
|
}
|
|
4428
|
+
function scaffoldingTracker() {
|
|
4429
|
+
const taskCount = createCounterMetric({
|
|
4430
|
+
name: "scaffolder_task_count",
|
|
4431
|
+
help: "Count of task runs",
|
|
4432
|
+
labelNames: ["template", "user", "result"]
|
|
4433
|
+
});
|
|
4434
|
+
const taskDuration = createHistogramMetric({
|
|
4435
|
+
name: "scaffolder_task_duration",
|
|
4436
|
+
help: "Duration of a task run",
|
|
4437
|
+
labelNames: ["template", "result"]
|
|
4438
|
+
});
|
|
4439
|
+
const stepCount = createCounterMetric({
|
|
4440
|
+
name: "scaffolder_step_count",
|
|
4441
|
+
help: "Count of step runs",
|
|
4442
|
+
labelNames: ["template", "step", "result"]
|
|
4443
|
+
});
|
|
4444
|
+
const stepDuration = createHistogramMetric({
|
|
4445
|
+
name: "scaffolder_step_duration",
|
|
4446
|
+
help: "Duration of a step runs",
|
|
4447
|
+
labelNames: ["template", "step", "result"]
|
|
4448
|
+
});
|
|
4449
|
+
async function taskStart(task) {
|
|
4450
|
+
var _a, _b;
|
|
4451
|
+
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
4452
|
+
const template = ((_a = task.spec.templateInfo) == null ? void 0 : _a.entityRef) || "";
|
|
4453
|
+
const user = ((_b = task.spec.user) == null ? void 0 : _b.ref) || "";
|
|
4454
|
+
const taskTimer = taskDuration.startTimer({
|
|
4455
|
+
template
|
|
4456
|
+
});
|
|
4457
|
+
async function skipDryRun(step, action) {
|
|
4458
|
+
task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
|
|
4459
|
+
stepId: step.id,
|
|
4460
|
+
status: "skipped"
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
async function markSuccessful() {
|
|
4464
|
+
taskCount.inc({
|
|
4465
|
+
template,
|
|
4466
|
+
user,
|
|
4467
|
+
result: "ok"
|
|
4468
|
+
});
|
|
4469
|
+
taskTimer({ result: "ok" });
|
|
4470
|
+
}
|
|
4471
|
+
async function markFailed(step, err) {
|
|
4472
|
+
await task.emitLog(String(err.stack), {
|
|
4473
|
+
stepId: step.id,
|
|
4474
|
+
status: "failed"
|
|
4475
|
+
});
|
|
4476
|
+
taskCount.inc({
|
|
4477
|
+
template,
|
|
4478
|
+
user,
|
|
4479
|
+
result: "failed"
|
|
4480
|
+
});
|
|
4481
|
+
taskTimer({ result: "failed" });
|
|
4482
|
+
}
|
|
4483
|
+
return {
|
|
4484
|
+
skipDryRun,
|
|
4485
|
+
markSuccessful,
|
|
4486
|
+
markFailed
|
|
4487
|
+
};
|
|
4488
|
+
}
|
|
4489
|
+
async function stepStart(task, step) {
|
|
4490
|
+
var _a;
|
|
4491
|
+
await task.emitLog(`Beginning step ${step.name}`, {
|
|
4492
|
+
stepId: step.id,
|
|
4493
|
+
status: "processing"
|
|
4494
|
+
});
|
|
4495
|
+
const template = ((_a = task.spec.templateInfo) == null ? void 0 : _a.entityRef) || "";
|
|
4496
|
+
const stepTimer = stepDuration.startTimer({
|
|
4497
|
+
template,
|
|
4498
|
+
step: step.name
|
|
4499
|
+
});
|
|
4500
|
+
async function markSuccessful() {
|
|
4501
|
+
await task.emitLog(`Finished step ${step.name}`, {
|
|
4502
|
+
stepId: step.id,
|
|
4503
|
+
status: "completed"
|
|
4504
|
+
});
|
|
4505
|
+
stepCount.inc({
|
|
4506
|
+
template,
|
|
4507
|
+
step: step.name,
|
|
4508
|
+
result: "ok"
|
|
4509
|
+
});
|
|
4510
|
+
stepTimer({ result: "ok" });
|
|
4511
|
+
}
|
|
4512
|
+
async function markFailed() {
|
|
4513
|
+
stepCount.inc({
|
|
4514
|
+
template,
|
|
4515
|
+
step: step.name,
|
|
4516
|
+
result: "failed"
|
|
4517
|
+
});
|
|
4518
|
+
stepTimer({ result: "failed" });
|
|
4519
|
+
}
|
|
4520
|
+
async function skipFalsy() {
|
|
4521
|
+
await task.emitLog(
|
|
4522
|
+
`Skipping step ${step.id} because its if condition was false`,
|
|
4523
|
+
{ stepId: step.id, status: "skipped" }
|
|
4524
|
+
);
|
|
4525
|
+
stepTimer({ result: "skipped" });
|
|
4526
|
+
}
|
|
4527
|
+
return {
|
|
4528
|
+
markSuccessful,
|
|
4529
|
+
markFailed,
|
|
4530
|
+
skipFalsy
|
|
4531
|
+
};
|
|
4532
|
+
}
|
|
4533
|
+
return {
|
|
4534
|
+
taskStart,
|
|
4535
|
+
stepStart
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4426
4538
|
|
|
4427
4539
|
class TaskWorker {
|
|
4428
4540
|
constructor(options) {
|