@intentius/chant-lexicon-gitlab 0.124.23 → 0.124.24
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/dist/components/generate-op-waves-pipeline.d.ts +6 -0
- package/dist/components/generate-op-waves-pipeline.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +3 -3
- package/src/components/__golden__/op-waves-jobs.yml +86 -0
- package/src/components/generate-op-waves-pipeline.test.ts +40 -0
- package/src/components/generate-op-waves-pipeline.ts +33 -4
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
*
|
|
19
19
|
* A `pr-review` wave (#3684) adds `<name>-record-plans`, run in merge request
|
|
20
20
|
* pipelines only, which records the digests the merge request's head plans.
|
|
21
|
+
*
|
|
22
|
+
* `runner` (on the spec, a wave or a job group, #3914) is the job's `tags:`; a
|
|
23
|
+
* GitHub runner group is a spec error here. A wave's `jobs` renders its
|
|
24
|
+
* deciding job with the wave's own settings and a job per group (per share of
|
|
25
|
+
* a group) with the group's: its tags, environment, variables over the
|
|
26
|
+
* wave's, setup after the wave's, and `id_tokens:` from its `idTokens`.
|
|
21
27
|
*/
|
|
22
28
|
import { type OpWavesSpec } from "@intentius/chant/op/op-waves";
|
|
23
29
|
import type { OpWavesPipelineOptions, OpWavesPipelineResult } from "@intentius/chant/lexicon";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAkBH,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAG9F,gHAAgH;AAChH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAyFvH"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "a373d4af5482711a1b501907a60d10deb1e50ea9dcbf70941eb4b1e54126ace0",
|
|
5
5
|
"meta.json": "db082fbf90c5941c498b2bee0f106103449c1a4c257716ca2220f0dd490450b2",
|
|
6
6
|
"types/index.d.ts": "a048a66ebe20da0cb5fc889054cac69044d9bf882e2c56b5c5bf113d1f3ba57c",
|
|
7
7
|
"rules/artifact-no-expiry.ts": "0ca5bb972d4a542126e8a0229d43b55658394cb7dd9b9923b2c590bec1c2ad7d",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"skills/chant-gitlab-migrate.md": "4853d04980560b379e5b0f7267dcb0a1e5d5879cb9e9931681ce76b2a5fe69d8",
|
|
56
56
|
"skills/chant-gitlab-patterns.md": "6d9a44e9e8de4c3820be9d65381b0d2ede4cb3626aaaf0011e21838a9fbf556e"
|
|
57
57
|
},
|
|
58
|
-
"composite": "
|
|
58
|
+
"composite": "3342dfc812c8e79be300ba1c27098440d0f91be83a87ec2f36e6d355caafe1d8"
|
|
59
59
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.24",
|
|
4
4
|
"description": "GitLab CI lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^5.9.3"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@intentius/chant": "^0.124.
|
|
71
|
-
"@intentius/chant-lexicon-github": "^0.124.
|
|
70
|
+
"@intentius/chant": "^0.124.24",
|
|
71
|
+
"@intentius/chant-lexicon-github": "^0.124.24"
|
|
72
72
|
},
|
|
73
73
|
"peerDependenciesMeta": {
|
|
74
74
|
"@intentius/chant-lexicon-github": {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# chant Op waves "migrations": one stage per wave, each needing the one before.
|
|
2
|
+
# A wave that waits at its gate exits 3 and stops the waves after it; approve the
|
|
3
|
+
# digest it prints, then retry the job.
|
|
4
|
+
|
|
5
|
+
stages:
|
|
6
|
+
- wave-1-dev
|
|
7
|
+
- wave-2-prod
|
|
8
|
+
|
|
9
|
+
wave-1-dev:
|
|
10
|
+
stage: wave-1-dev
|
|
11
|
+
image: node:22
|
|
12
|
+
tags:
|
|
13
|
+
- docker
|
|
14
|
+
- dev
|
|
15
|
+
resource_group: migrations-dev
|
|
16
|
+
variables:
|
|
17
|
+
GIT_DEPTH: '0'
|
|
18
|
+
rules:
|
|
19
|
+
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
|
|
20
|
+
script:
|
|
21
|
+
- chant run wave --spec waves.json --wave 1
|
|
22
|
+
|
|
23
|
+
wave-2-prod-decide:
|
|
24
|
+
stage: wave-2-prod
|
|
25
|
+
image: node:22
|
|
26
|
+
tags:
|
|
27
|
+
- plan
|
|
28
|
+
resource_group: migrations-prod
|
|
29
|
+
id_tokens:
|
|
30
|
+
PLAN_TOKEN:
|
|
31
|
+
aud: plan.example
|
|
32
|
+
variables:
|
|
33
|
+
GIT_DEPTH: '0'
|
|
34
|
+
rules:
|
|
35
|
+
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
|
|
36
|
+
needs:
|
|
37
|
+
- wave-1-dev
|
|
38
|
+
script:
|
|
39
|
+
- chant run wave --spec waves.json --wave 2 --decide
|
|
40
|
+
artifacts:
|
|
41
|
+
when: always
|
|
42
|
+
paths:
|
|
43
|
+
- .chant/op-waves/migrations/wave-2.json
|
|
44
|
+
expire_in: '30 days'
|
|
45
|
+
|
|
46
|
+
wave-2-prod-us:
|
|
47
|
+
stage: wave-2-prod
|
|
48
|
+
image: node:22
|
|
49
|
+
tags:
|
|
50
|
+
- us-apply
|
|
51
|
+
resource_group: migrations-prod
|
|
52
|
+
environment:
|
|
53
|
+
name: prod-us
|
|
54
|
+
id_tokens:
|
|
55
|
+
CLOUD_TOKEN:
|
|
56
|
+
aud: us.example
|
|
57
|
+
variables:
|
|
58
|
+
GIT_DEPTH: '0'
|
|
59
|
+
REGION: us
|
|
60
|
+
rules:
|
|
61
|
+
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
|
|
62
|
+
needs:
|
|
63
|
+
- wave-2-prod-decide
|
|
64
|
+
script:
|
|
65
|
+
- echo us
|
|
66
|
+
- chant run wave --spec waves.json --wave 2 --group us
|
|
67
|
+
|
|
68
|
+
wave-2-prod-eu:
|
|
69
|
+
stage: wave-2-prod
|
|
70
|
+
image: node:22
|
|
71
|
+
tags:
|
|
72
|
+
- eu-apply
|
|
73
|
+
resource_group: migrations-prod
|
|
74
|
+
environment:
|
|
75
|
+
name: prod-eu
|
|
76
|
+
id_tokens:
|
|
77
|
+
CLOUD_TOKEN:
|
|
78
|
+
aud: eu.example
|
|
79
|
+
variables:
|
|
80
|
+
GIT_DEPTH: '0'
|
|
81
|
+
rules:
|
|
82
|
+
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
|
|
83
|
+
needs:
|
|
84
|
+
- wave-2-prod-decide
|
|
85
|
+
script:
|
|
86
|
+
- chant run wave --spec waves.json --wave 2 --group eu
|
|
@@ -69,3 +69,43 @@ describe("a wave's environment is checked as an Op's is (#3765)", () => {
|
|
|
69
69
|
expect(generateGitlabOpWavesPipeline(one({ name: "dev", url: "$CI_ENVIRONMENT_URL" }), { specFile: "waves.json" }).files[0]!.yaml).toContain("url: '$CI_ENVIRONMENT_URL'");
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
describe("a runner per wave and job groups per wave (#3914)", () => {
|
|
74
|
+
const grouped: OpWavesSpec = {
|
|
75
|
+
...spec,
|
|
76
|
+
runner: "plan",
|
|
77
|
+
waves: [
|
|
78
|
+
{ name: "dev", runs: [{ target: "dev" }], gate: "never", runner: ["docker", "dev"] },
|
|
79
|
+
{
|
|
80
|
+
name: "prod",
|
|
81
|
+
runs: [{ target: "us/a" }, { target: "eu/a" }],
|
|
82
|
+
idTokens: { PLAN_TOKEN: "plan.example" },
|
|
83
|
+
jobs: [
|
|
84
|
+
{ name: "us", runs: ["us/*"], runner: "us-apply", environment: { name: "prod-us" }, variables: { REGION: "us" }, setup: [{ run: "echo us" }], idTokens: { CLOUD_TOKEN: "us.example" } },
|
|
85
|
+
{ name: "eu", runs: ["eu/*"], runner: "eu-apply", environment: { name: "prod-eu" }, idTokens: { CLOUD_TOKEN: "eu.example" } },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
test("the rendered pipeline matches its golden", async () => {
|
|
92
|
+
await expect(generateGitlabOpWavesPipeline(grouped, { specFile: "waves.json" }).files[0]!.yaml).toMatchFileSnapshot("./__golden__/op-waves-jobs.yml");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("tags, environment, id_tokens, variables and the group's command on each job", () => {
|
|
96
|
+
const yaml = generateGitlabOpWavesPipeline(grouped, { specFile: "waves.json" }).files[0]!.yaml;
|
|
97
|
+
expect(yaml).toMatch(/wave-1-dev:\n\s+stage: wave-1-dev\n\s+image: node:22\n\s+tags:\n\s+- docker\n\s+- dev\n/);
|
|
98
|
+
expect(yaml).toMatch(/wave-2-prod-decide:\n\s+stage: wave-2-prod\n\s+image: node:22\n\s+tags:\n\s+- plan\n\s+resource_group: migrations-prod\n\s+id_tokens:\n\s+PLAN_TOKEN:\n\s+aud: plan.example\n/);
|
|
99
|
+
expect(yaml).toMatch(
|
|
100
|
+
/wave-2-prod-us:\n\s+stage: wave-2-prod\n\s+image: node:22\n\s+tags:\n\s+- us-apply\n\s+resource_group: migrations-prod\n\s+environment:\n\s+name: prod-us\n\s+id_tokens:\n\s+CLOUD_TOKEN:\n\s+aud: us.example\n\s+variables:\n\s+GIT_DEPTH: '0'\n\s+REGION: us\n[\s\S]*?needs:\n\s+- wave-2-prod-decide\n\s+script:\n\s+- echo us\n\s+- chant run wave --spec waves.json --wave 2 --group us\n/,
|
|
101
|
+
);
|
|
102
|
+
expect(yaml).toMatch(/wave-2-prod-eu:[\s\S]*?id_tokens:\n\s+CLOUD_TOKEN:\n\s+aud: eu.example\n/);
|
|
103
|
+
expect(yaml).not.toContain("_AUDIENCE");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("a runner group is a spec error naming the wave and the field", () => {
|
|
107
|
+
const withGroup = (runner: { group: string }) => ({ ...grouped, waves: [grouped.waves[0]!, { ...grouped.waves[1]!, jobs: [{ ...grouped.waves[1]!.jobs![0]!, runner }, grouped.waves[1]!.jobs![1]!] }] });
|
|
108
|
+
expect(() => generateGitlabOpWavesPipeline(withGroup({ group: "apply" }), { specFile: "waves.json" })).toThrow(/wave "prod" job group "us" runner is the runner group "apply", which only GitHub has/);
|
|
109
|
+
expect(() => generateGitlabOpWavesPipeline({ ...spec, runner: { group: "apply" } }, { specFile: "waves.json" })).toThrow(/Op waves "migrations": runner is the runner group/);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -18,22 +18,31 @@
|
|
|
18
18
|
*
|
|
19
19
|
* A `pr-review` wave (#3684) adds `<name>-record-plans`, run in merge request
|
|
20
20
|
* pipelines only, which records the digests the merge request's head plans.
|
|
21
|
+
*
|
|
22
|
+
* `runner` (on the spec, a wave or a job group, #3914) is the job's `tags:`; a
|
|
23
|
+
* GitHub runner group is a spec error here. A wave's `jobs` renders its
|
|
24
|
+
* deciding job with the wave's own settings and a job per group (per share of
|
|
25
|
+
* a group) with the group's: its tags, environment, variables over the
|
|
26
|
+
* wave's, setup after the wave's, and `id_tokens:` from its `idTokens`.
|
|
21
27
|
*/
|
|
22
28
|
|
|
23
29
|
import {
|
|
24
30
|
assertGitlabBranch,
|
|
25
31
|
assertGitlabEnvironment,
|
|
32
|
+
assertRunnerLabels,
|
|
26
33
|
CI_IMAGE,
|
|
27
34
|
environmentDoc,
|
|
28
35
|
GITLAB_FULL_HISTORY,
|
|
29
36
|
GITLAB_MERGE_REQUEST_SOURCE,
|
|
30
37
|
GITLAB_SCHEDULE_SOURCE,
|
|
38
|
+
gitlabIdTokensDoc,
|
|
31
39
|
gitlabPushRule,
|
|
32
40
|
gitlabSetupScript,
|
|
41
|
+
gitlabTags,
|
|
33
42
|
nonEmpty,
|
|
34
43
|
pushBranches,
|
|
35
44
|
} from "@intentius/chant/ci-jobs";
|
|
36
|
-
import { opWaveJobs, opWavesResumeCommand, type OpWavesSpec } from "@intentius/chant/op/op-waves";
|
|
45
|
+
import { opWaveGroupOf, opWaveJobs, opWavesResumeCommand, type OpWavesSpec } from "@intentius/chant/op/op-waves";
|
|
37
46
|
import type { OpWavesPipelineOptions, OpWavesPipelineResult } from "@intentius/chant/lexicon";
|
|
38
47
|
import { emitGitlabFile } from "./ci-jobs";
|
|
39
48
|
|
|
@@ -49,6 +58,18 @@ export function generateGitlabOpWavesPipeline(spec: OpWavesSpec, options: OpWave
|
|
|
49
58
|
// The setup and environment each wave's jobs carry, checked as an Op's are.
|
|
50
59
|
const setupOf = spec.waves.map((wave) => gitlabSetupScript(`${subject} wave "${wave.name}"`, wave.setup));
|
|
51
60
|
for (const wave of spec.waves) if (wave.environment) assertGitlabEnvironment(`${spec.name}/${wave.name}`, wave.environment);
|
|
61
|
+
// #3914: GitLab picks a runner by tags, and each job group carries its own setup and environment.
|
|
62
|
+
assertRunnerLabels(subject, "runner", spec.runner, "GitLab");
|
|
63
|
+
const groupSetup = new Map<string, string[]>();
|
|
64
|
+
for (const wave of spec.waves) {
|
|
65
|
+
assertRunnerLabels(subject, `wave "${wave.name}" runner`, wave.runner, "GitLab");
|
|
66
|
+
for (const group of wave.jobs ?? []) {
|
|
67
|
+
assertRunnerLabels(subject, `wave "${wave.name}" job group "${group.name}" runner`, group.runner, "GitLab");
|
|
68
|
+
if (group.environment) assertGitlabEnvironment(`${spec.name}/${wave.name}/${group.name}`, group.environment);
|
|
69
|
+
groupSetup.set(`${wave.name}/${group.name}`, gitlabSetupScript(`${subject} wave "${wave.name}" job group "${group.name}"`, group.setup));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const specTags = gitlabTags(spec.runner);
|
|
52
73
|
|
|
53
74
|
const byReview = spec.waves.some((w) => w.approval === "pr-review");
|
|
54
75
|
const stages = [...(byReview ? ["plans"] : []), ...spec.waves.map((wave, i) => `wave-${i + 1}-${wave.name}`)];
|
|
@@ -62,6 +83,7 @@ export function generateGitlabOpWavesPipeline(spec: OpWavesSpec, options: OpWave
|
|
|
62
83
|
{
|
|
63
84
|
stage: "plans",
|
|
64
85
|
image,
|
|
86
|
+
...(specTags ? { tags: specTags } : {}),
|
|
65
87
|
variables: { ...GITLAB_FULL_HISTORY },
|
|
66
88
|
rules: [{ if: GITLAB_MERGE_REQUEST_SOURCE }],
|
|
67
89
|
script: [...(options.beforeScript ?? []), ["chant", "run", "wave", "--spec", options.specFile, "--record-plans"].join(" ")],
|
|
@@ -70,15 +92,21 @@ export function generateGitlabOpWavesPipeline(spec: OpWavesSpec, options: OpWave
|
|
|
70
92
|
}
|
|
71
93
|
for (const job of jobs) {
|
|
72
94
|
const wave = spec.waves[job.wave - 1]!;
|
|
73
|
-
const
|
|
95
|
+
const group = opWaveGroupOf(spec, job);
|
|
96
|
+
const setup = [...setupOf[job.wave - 1]!, ...(group ? groupSetup.get(`${wave.name}/${group.name}`)! : [])];
|
|
97
|
+
const tags = gitlabTags(group?.runner ?? wave.runner ?? spec.runner);
|
|
98
|
+
const environment = group?.environment ?? wave.environment;
|
|
99
|
+
const idTokens = gitlabIdTokensDoc(group?.idTokens ?? wave.idTokens);
|
|
74
100
|
entries.push([
|
|
75
101
|
job.jobName,
|
|
76
102
|
{
|
|
77
103
|
stage: stages[job.wave - 1 + (byReview ? 1 : 0)],
|
|
78
104
|
image,
|
|
105
|
+
...(tags ? { tags } : {}),
|
|
79
106
|
resource_group: `${spec.name}-${wave.name}`,
|
|
80
|
-
...(
|
|
81
|
-
|
|
107
|
+
...(environment ? { environment: environmentDoc(environment) } : {}),
|
|
108
|
+
...(idTokens ? { id_tokens: idTokens } : {}),
|
|
109
|
+
variables: { ...GITLAB_FULL_HISTORY, ...(wave.variables ?? {}), ...(group?.variables ?? {}) },
|
|
82
110
|
rules,
|
|
83
111
|
...(job.needs.length > 0 ? { needs: job.needs } : {}),
|
|
84
112
|
script: [...setup, ...(options.beforeScript ?? []), job.command.join(" "), ...(options.extraScript ?? [])],
|
|
@@ -92,6 +120,7 @@ export function generateGitlabOpWavesPipeline(spec: OpWavesSpec, options: OpWave
|
|
|
92
120
|
{
|
|
93
121
|
stage: "resume",
|
|
94
122
|
image,
|
|
123
|
+
...(specTags ? { tags: specTags } : {}),
|
|
95
124
|
rules: [{ if: GITLAB_SCHEDULE_SOURCE }],
|
|
96
125
|
script: [...(options.beforeScript ?? []), opWavesResumeCommand(spec).join(" ")],
|
|
97
126
|
},
|