@intentius/chant-lexicon-gitlab 0.124.41 → 0.124.42

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.
@@ -1 +1 @@
1
- {"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAWH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAuBjG,OAAO,KAAK,EACV,oBAAoB,IAAI,YAAY,EACpC,wBAAwB,IAAI,qBAAqB,EACjD,uBAAuB,IAAI,oBAAoB,EAEhD,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAkJtB"}
1
+ {"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAWH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAwBjG,OAAO,KAAK,EACV,oBAAoB,IAAI,YAAY,EACpC,wBAAwB,IAAI,qBAAqB,EACjD,uBAAuB,IAAI,oBAAoB,EAEhD,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAkJtB"}
@@ -35,7 +35,7 @@ import { assertGitlabBranch, CI_IMAGE, ciJobName, fillCommand, GITLAB_FULL_HISTO
35
35
  import { resolveComponentGraph } from "@intentius/chant/components/driver";
36
36
  import { hasPublishStep, promoteArchivePaths } from "@intentius/chant/components/promote";
37
37
  import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/gated-wave-pipeline";
38
- import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prApplyGroup, prRecordGroup, prApplyRecordKey, prApplyWaveJobName, prLoopJobs, prLoopPostJob, prLoopSetup, PR_PLAN_NOTE_JOB, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, PR_ENV_REPORT_DIR, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
38
+ import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prApplyGroup, prRecordGroup, prApplyRecordKey, prApplyWaveJobName, prLoopJobs, GITLAB_APPLY_PR_VAR, prLoopPostJob, prLoopSetup, PR_PLAN_NOTE_JOB, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, PR_ENV_REPORT_DIR, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
39
39
  import { memberGitlabChanges, memberRepoPath, memberShellDir } from "@intentius/chant/lexicon";
40
40
  import { FORGE_TOKEN_VARS, GITLAB_DROP_FORGE_TOKENS } from "@intentius/chant/ci-handover";
41
41
  import { gitlabTradeIdTokens, gitlabTradeLines, TRADE_SPEC_ENV } from "@intentius/chant/ci-identity";
@@ -279,6 +279,17 @@ function gatedWaveGitlabPipeline(components, env, image, beforeScript, extraScri
279
279
  */
280
280
  /** The dotenv report a gated apply wave hands the green commit it applies through (#4221). */
281
281
  const WAVE_GREEN_DOTENV = "chant-green.env";
282
+ /**
283
+ * The rule of the apply before merge (#4301, ws-106): a pipeline of `branch`
284
+ * that a person started from the UI, the API or a trigger with
285
+ * `CHANT_APPLY_PR` set to the merge request's iid. GitLab builds a merge
286
+ * request's own pipeline from its own files, so the apply runs in one of the
287
+ * target branch's. The variable only names the merge request; `pr-apply
288
+ * --before-merge` reads the rest from GitLab.
289
+ */
290
+ function gitlabApplyPrRule(branch) {
291
+ return `$CI_PIPELINE_SOURCE =~ /^(web|api|trigger)$/ && $${GITLAB_APPLY_PR_VAR} =~ /^[0-9]+$/ && $CI_COMMIT_BRANCH == ${branch}`;
292
+ }
282
293
  /** The rule of a pipeline for a pushed `ci/green/` tag (#4168 step 9), which runs the apply under apply on merge. */
283
294
  const GITLAB_GREEN_TAG_RULE = '$CI_COMMIT_TAG =~ /^ci\\/green\\//';
284
295
  function prLoopGitlabPipeline(components, env, image, beforeScript, extraScript, options) {
@@ -293,7 +304,7 @@ function prLoopGitlabPipeline(components, env, image, beforeScript, extraScript,
293
304
  // #4168 step 9: a push to the branch records, and under apply on merge a ci/green/ tag pipeline applies.
294
305
  const greens = loop.waveGate
295
306
  ? waveJobs.map((job) => ({ job, name: named(prApplyWaveJobName(job.wave)), stage: prApplyWaveJobName(job.wave) }))
296
- : loop.applyOnMerge
307
+ : loop.applyOnMerge || loop.applyBeforeMerge
297
308
  ? [{ job: apply, name: named("apply"), stage: "apply" }]
298
309
  : [];
299
310
  const applies = [{ job: record, name: named("record"), stage: "record" }, ...greens];
@@ -301,7 +312,8 @@ function prLoopGitlabPipeline(components, env, image, beforeScript, extraScript,
301
312
  if (loop.branch !== undefined)
302
313
  assertGitlabBranch("The merge-request pipeline", loop.branch);
303
314
  const onMerge = gitlabPushRule(loop.branch ? JSON.stringify(loop.branch) : "$CI_DEFAULT_BRANCH");
304
- const onGreen = GITLAB_GREEN_TAG_RULE;
315
+ // #4301: the apply before merge runs in a pipeline of the target branch a person starts with CHANT_APPLY_PR set.
316
+ const onGreen = loop.applyBeforeMerge ? gitlabApplyPrRule(loop.branch ? JSON.stringify(loop.branch) : "$CI_DEFAULT_BRANCH") : GITLAB_GREEN_TAG_RULE;
305
317
  // A member's jobs start in its directory, after the image's install lines.
306
318
  const enter = rooted ? [] : [`cd ${memberShellDir(member)}`];
307
319
  // The command runs through chant ci run (#4156), which writes the job's run record, signed when the CHANT_RUNNER_KEY variable is set.
@@ -378,7 +390,7 @@ function prLoopGitlabPipeline(components, env, image, beforeScript, extraScript,
378
390
  { jobName: noteJob, component: "merge request plan note", stage: PR_PLAN_NOTE_JOB, needs: [planJob] },
379
391
  ...applies.map(({ job, name, stage }, i) => ({
380
392
  jobName: name,
381
- component: job.wave ? `green apply, wave ${job.wave}` : job.jobName === "record" ? "merge record" : "green apply",
393
+ component: job.wave ? `green apply, wave ${job.wave}` : job.jobName === "record" ? "merge record" : loop.applyBeforeMerge ? "apply before merge" : "green apply",
382
394
  stage,
383
395
  needs: i > 1 ? [applies[i - 1].name] : [],
384
396
  })),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "f0aa9cd82d2d03eb7ed4f027c87c39f5a6b1b8ca2f6e58b635249f82e99884f9",
4
+ "manifest.json": "5eadeadbe17078f803f0afde8b3d6153f381305adae156307271887f23355170",
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": "bc6817f93592903583a564a620c6bbf849d732d2ad59c960c4159c6294aaf55c"
58
+ "composite": "fcd516d1ec373a23058e366255e162fc527da7d2e62ce01a399255a37d893c91"
59
59
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitlab",
3
- "version": "0.124.41",
3
+ "version": "0.124.42",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GitLab",
6
6
  "intrinsics": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-gitlab",
3
- "version": "0.124.41",
3
+ "version": "0.124.42",
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.41",
71
- "@intentius/chant-lexicon-github": "^0.124.41"
70
+ "@intentius/chant": "^0.124.42",
71
+ "@intentius/chant-lexicon-github": "^0.124.42"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@intentius/chant-lexicon-github": {
@@ -52,6 +52,7 @@ import {
52
52
  prApplyRecordKey,
53
53
  prApplyWaveJobName,
54
54
  prLoopJobs,
55
+ GITLAB_APPLY_PR_VAR,
55
56
  prLoopPostJob,
56
57
  prLoopSetup,
57
58
  PR_PLAN_NOTE_JOB,
@@ -329,6 +330,18 @@ function gatedWaveGitlabPipeline(
329
330
  /** The dotenv report a gated apply wave hands the green commit it applies through (#4221). */
330
331
  const WAVE_GREEN_DOTENV = "chant-green.env";
331
332
 
333
+ /**
334
+ * The rule of the apply before merge (#4301, ws-106): a pipeline of `branch`
335
+ * that a person started from the UI, the API or a trigger with
336
+ * `CHANT_APPLY_PR` set to the merge request's iid. GitLab builds a merge
337
+ * request's own pipeline from its own files, so the apply runs in one of the
338
+ * target branch's. The variable only names the merge request; `pr-apply
339
+ * --before-merge` reads the rest from GitLab.
340
+ */
341
+ function gitlabApplyPrRule(branch: string): string {
342
+ return `$CI_PIPELINE_SOURCE =~ /^(web|api|trigger)$/ && $${GITLAB_APPLY_PR_VAR} =~ /^[0-9]+$/ && $CI_COMMIT_BRANCH == ${branch}`;
343
+ }
344
+
332
345
  /** The rule of a pipeline for a pushed `ci/green/` tag (#4168 step 9), which runs the apply under apply on merge. */
333
346
  const GITLAB_GREEN_TAG_RULE = '$CI_COMMIT_TAG =~ /^ci\\/green\\//';
334
347
 
@@ -351,14 +364,15 @@ function prLoopGitlabPipeline(
351
364
  // #4168 step 9: a push to the branch records, and under apply on merge a ci/green/ tag pipeline applies.
352
365
  const greens = loop.waveGate
353
366
  ? waveJobs.map((job) => ({ job, name: named(prApplyWaveJobName(job.wave!)), stage: prApplyWaveJobName(job.wave!) }))
354
- : loop.applyOnMerge
367
+ : loop.applyOnMerge || loop.applyBeforeMerge
355
368
  ? [{ job: apply, name: named("apply"), stage: "apply" }]
356
369
  : [];
357
370
  const applies = [{ job: record, name: named("record"), stage: "record" }, ...greens];
358
371
  const onMergeRequest = GITLAB_MERGE_REQUEST_SOURCE;
359
372
  if (loop.branch !== undefined) assertGitlabBranch("The merge-request pipeline", loop.branch);
360
373
  const onMerge = gitlabPushRule(loop.branch ? JSON.stringify(loop.branch) : "$CI_DEFAULT_BRANCH");
361
- const onGreen = GITLAB_GREEN_TAG_RULE;
374
+ // #4301: the apply before merge runs in a pipeline of the target branch a person starts with CHANT_APPLY_PR set.
375
+ const onGreen = loop.applyBeforeMerge ? gitlabApplyPrRule(loop.branch ? JSON.stringify(loop.branch) : "$CI_DEFAULT_BRANCH") : GITLAB_GREEN_TAG_RULE;
362
376
  // A member's jobs start in its directory, after the image's install lines.
363
377
  const enter = rooted ? [] : [`cd ${memberShellDir(member!)}`];
364
378
  // The command runs through chant ci run (#4156), which writes the job's run record, signed when the CHANT_RUNNER_KEY variable is set.
@@ -438,7 +452,7 @@ function prLoopGitlabPipeline(
438
452
  { jobName: noteJob, component: "merge request plan note", stage: PR_PLAN_NOTE_JOB, needs: [planJob] },
439
453
  ...applies.map(({ job, name, stage }, i) => ({
440
454
  jobName: name,
441
- component: job.wave ? `green apply, wave ${job.wave}` : job.jobName === "record" ? "merge record" : "green apply",
455
+ component: job.wave ? `green apply, wave ${job.wave}` : job.jobName === "record" ? "merge record" : loop.applyBeforeMerge ? "apply before merge" : "green apply",
442
456
  stage,
443
457
  needs: i > 1 ? [applies[i - 1]!.name] : [],
444
458
  })),
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The merge-request pipeline with an apply before merge (#4301, ws-106).
3
+ * GitLab builds a merge request's own pipeline from its own files, so the
4
+ * apply runs in a pipeline of the target branch a person starts with
5
+ * CHANT_APPLY_PR set, and checks out the merge request's head there.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { parseYAML } from "@intentius/chant/yaml";
10
+ import type { DriverComponent } from "@intentius/chant/components/driver";
11
+ import { pullRequestCheckout } from "@intentius/chant/components/pr-pipeline";
12
+ import { generateGitlabPipeline } from "./generate-pipeline";
13
+
14
+ const ESTATE: DriverComponent[] = [
15
+ { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
16
+ ];
17
+
18
+ interface Job { rules: Array<{ if: string }>; variables: Record<string, string>; script: string[]; resource_group?: string }
19
+
20
+ describe("the GitLab pipeline with --apply-before-merge", () => {
21
+ const doc = parseYAML(generateGitlabPipeline(ESTATE, { env: "prod", prLoop: { applyBeforeMerge: true } }).yaml) as { workflow: { rules: Array<{ if: string }> }; record: Job; apply: Job };
22
+ const RULE = '$CI_PIPELINE_SOURCE =~ /^(web|api|trigger)$/ && $CHANT_APPLY_PR =~ /^[0-9]+$/ && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH';
23
+
24
+ test("the apply runs in a target-branch pipeline started with CHANT_APPLY_PR, and a push still records", () => {
25
+ expect(doc.apply.rules).toEqual([{ if: RULE }]);
26
+ expect(doc.workflow.rules.map((r) => r.if)).toContain(RULE);
27
+ expect(doc.record.rules[0]!.if).toContain('$CI_PIPELINE_SOURCE == "push"');
28
+ expect(doc.apply.resource_group).toBe("chant-apply-prod");
29
+ });
30
+
31
+ test("it checks out the merge request's head and applies before merge from the pipeline's commit", () => {
32
+ expect(doc.apply.variables).toMatchObject({ BASE_SHA: "$CI_COMMIT_SHA", PR_NUMBER: "$CHANT_APPLY_PR" });
33
+ const at = doc.apply.script.indexOf(pullRequestCheckout("gitlab"));
34
+ expect(at).toBeGreaterThanOrEqual(0);
35
+ expect(doc.apply.script.at(-1)).toBe('chant ci run apply --in-place --base "$BASE_SHA" --pr "$PR_NUMBER" --apply-before-merge --env prod --gate pr-apply --forge gitlab');
36
+ });
37
+ });