@intentius/chant-lexicon-github 0.124.40 → 0.124.41
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/ci-jobs.d.ts +9 -0
- package/dist/components/ci-jobs.d.ts.map +1 -1
- package/dist/components/ci-jobs.js +12 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +205 -14
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/__fixtures__/pr-loop-build.github.golden.yml +76 -10
- package/src/components/__fixtures__/pr-loop-record.github.golden.yml +70 -8
- package/src/components/__fixtures__/pr-loop-waves.github.golden.yml +72 -10
- package/src/components/__fixtures__/pr-loop.github.golden.yml +70 -8
- package/src/components/ci-jobs.ts +13 -0
- package/src/components/generate-pipeline.test.ts +3 -0
- package/src/components/generate-pipeline.ts +221 -13
- package/src/components/generate-pr-env-pipeline.test.ts +206 -0
- package/src/components/generate-pr-pipeline.handover.test.ts +167 -0
- package/src/components/generate-pr-pipeline.identity.test.ts +219 -0
- package/src/components/generate-pr-pipeline.test.ts +29 -9
- package/src/components/generate-pr-waves-pipeline.test.ts +6 -3
|
@@ -23,6 +23,15 @@ export declare function downloadArtifactStep(name: string, artifact: string, pat
|
|
|
23
23
|
* replaces it. Nothing to keep is not an error.
|
|
24
24
|
*/
|
|
25
25
|
export declare function keepRecordStep(name: string, artifact: string, path: string): Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* The `if:` of a wave job after the first (#4291): it runs when the wave
|
|
28
|
+
* before it failed, so a failed member blocks only the members that depend
|
|
29
|
+
* on it, and the next wave's job blocks those and runs the rest. It does not
|
|
30
|
+
* run when the run was cancelled, or when the job before it was skipped
|
|
31
|
+
* (its own condition was false). `condition` is the job's own condition,
|
|
32
|
+
* which still has to hold.
|
|
33
|
+
*/
|
|
34
|
+
export declare function afterEarlierWave(previous: string, condition?: string): string;
|
|
26
35
|
/** A `concurrency:` mapping: one run at a time per group. */
|
|
27
36
|
export declare function concurrency(group: string, cancelInProgress?: boolean): Record<string, unknown>;
|
|
28
37
|
/** The job token, as a step's `GITHUB_TOKEN`; `gh` reads `GH_TOKEN` too, for a step that writes. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D,qGAAqG;AACrG,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAC5D,eAAO,MAAM,iBAAiB,iCAAiC,CAAC;AAEhE,iIAAiI;AACjI,wBAAgB,YAAY,CAAC,WAAW,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEjF;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1G;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOpG;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5F;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,EAAE,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAW/D"}
|
|
1
|
+
{"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D,qGAAqG;AACrG,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAC5D,eAAO,MAAM,iBAAiB,iCAAiC,CAAC;AAEhE,iIAAiI;AACjI,wBAAgB,YAAY,CAAC,WAAW,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEjF;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1G;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOpG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5F;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,EAAE,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAW/D"}
|
|
@@ -38,6 +38,18 @@ export function keepRecordStep(name, artifact, path) {
|
|
|
38
38
|
with: { name: artifact, path, "if-no-files-found": "ignore", "include-hidden-files": true, overwrite: true },
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The `if:` of a wave job after the first (#4291): it runs when the wave
|
|
43
|
+
* before it failed, so a failed member blocks only the members that depend
|
|
44
|
+
* on it, and the next wave's job blocks those and runs the rest. It does not
|
|
45
|
+
* run when the run was cancelled, or when the job before it was skipped
|
|
46
|
+
* (its own condition was false). `condition` is the job's own condition,
|
|
47
|
+
* which still has to hold.
|
|
48
|
+
*/
|
|
49
|
+
export function afterEarlierWave(previous, condition) {
|
|
50
|
+
const runs = `!cancelled() && needs.${previous}.result != 'skipped'`;
|
|
51
|
+
return condition ? `${runs} && (${condition})` : runs;
|
|
52
|
+
}
|
|
41
53
|
/** A `concurrency:` mapping: one run at a time per group. */
|
|
42
54
|
export function concurrency(group, cancelInProgress = false) {
|
|
43
55
|
return { group, "cancel-in-progress": cancelInProgress };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AA2BjG,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;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,mIAAmI;IACnI,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAsaD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAWpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CA+JnB;AA4CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAGtB"}
|
|
@@ -58,10 +58,15 @@
|
|
|
58
58
|
import { resolveComponentGraph } from "@intentius/chant/components/driver";
|
|
59
59
|
import { hasPublishStep, promoteArchivePaths } from "@intentius/chant/components/promote";
|
|
60
60
|
import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/gated-wave-pipeline";
|
|
61
|
-
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
62
|
-
import {
|
|
61
|
+
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
62
|
+
import { CHECKOUT_TOKEN_ENV, checkoutScript, dropForgeTokens, handoverOutputs, handoverTrap, takeOverScript } from "@intentius/chant/ci-handover";
|
|
63
|
+
import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
|
|
64
|
+
import { GREEN_TAG_PATTERN, GREEN_TICK_WORKFLOW, 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_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
|
|
63
65
|
import { memberRepoPath } from "@intentius/chant/lexicon";
|
|
64
|
-
import { checkoutStep, concurrency, downloadArtifactStep, emitWorkflowYAML, keepRecordStep, runSteps, UPLOAD_ARTIFACT } from "./ci-jobs.js";
|
|
66
|
+
import { afterEarlierWave, checkoutStep, concurrency, downloadArtifactStep, emitWorkflowYAML, keepRecordStep, runSteps, UPLOAD_ARTIFACT } from "./ci-jobs.js";
|
|
67
|
+
import { actionRef } from "../action-pins.js";
|
|
68
|
+
/** The plan job's last step, whose outputs carry its report to the plan-note job (#4295). */
|
|
69
|
+
const PLAN_STEP_ID = "plan";
|
|
65
70
|
/** The output a gated apply wave hands the green commit it applies through, and the step that sets it (#4221). */
|
|
66
71
|
const WAVE_GREEN_OUTPUT = "green-sha";
|
|
67
72
|
const WAVE_STEP_ID = "apply";
|
|
@@ -89,6 +94,8 @@ function gatedWaveGithubDoc(components, env, image, beforeScript, extraScript, o
|
|
|
89
94
|
steps.push(...runSteps(prLoopSetup(image)), ...runSteps(beforeScript), { run: job.command.join(" ") }, ...runSteps(extraScript));
|
|
90
95
|
steps.push(keepRecordStep(`Upload the record for wave ${job.wave}`, waveRecordArtifact(job.wave), GATED_WAVE_RECORD));
|
|
91
96
|
jobsDoc[job.jobName] = {
|
|
97
|
+
// #4291: a wave runs after one that failed, and blocks only what depends on the failure.
|
|
98
|
+
...(job.needs.length > 0 ? { if: afterEarlierWave(job.needs[0]) } : {}),
|
|
92
99
|
"runs-on": GITHUB_RUNNER,
|
|
93
100
|
...(job.needs.length > 0 ? { needs: job.needs } : {}),
|
|
94
101
|
container: image,
|
|
@@ -133,6 +140,26 @@ function gatedWaveGithubDoc(components, env, image, beforeScript, extraScript, o
|
|
|
133
140
|
* for the pushed commit, so wave k reads what wave k-1 left, and a re-run of
|
|
134
141
|
* a wave that stopped at its gate reads what it left itself. Each keeps its
|
|
135
142
|
* report as `chant-pr-apply-wave-<k>`.
|
|
143
|
+
*
|
|
144
|
+
* The plan job holds no forge token while the pull request's code runs
|
|
145
|
+
* (#4295, `@intentius/chant/ci-handover`). It checks the change out with a
|
|
146
|
+
* script that gives the job's token to its fetches alone, starts every later
|
|
147
|
+
* step again without the token variables, and ends with the plan step, which
|
|
148
|
+
* hands its report on as the job's outputs. The `plan-note` job then checks
|
|
149
|
+
* out the target branch's commit, takes the report back, keeps it as the
|
|
150
|
+
* `chant-pr-plan` artifact, and posts the note and `chant/plan` with
|
|
151
|
+
* `chant components pr-post`. Only the note job may write to the pull
|
|
152
|
+
* request and its statuses.
|
|
153
|
+
*
|
|
154
|
+
* With `identity` (#4296, `@intentius/chant/ci-identity`) the plan, record
|
|
155
|
+
* and apply jobs get `id-token: write`, and each starts with a step that
|
|
156
|
+
* trades the job's OIDC token for its cloud identity before anything is
|
|
157
|
+
* checked out: the plan and record jobs for the plan identity, the apply
|
|
158
|
+
* jobs for the apply identity. The trade leaves the credentials in
|
|
159
|
+
* `GITHUB_ENV`. Every later step of the plan job starts without the
|
|
160
|
+
* variables a token is requested with, so the pull request's code holds no
|
|
161
|
+
* OIDC token and cannot request one, and the plan job names no apply
|
|
162
|
+
* identity.
|
|
136
163
|
*/
|
|
137
164
|
function prLoopGithubDoc(components, env, image, beforeScript, extraScript, options) {
|
|
138
165
|
const loop = options.prLoop;
|
|
@@ -158,8 +185,18 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
158
185
|
uses: "actions/cache/save@v4",
|
|
159
186
|
with: { path: recordPath, key: recordKey(wave) },
|
|
160
187
|
});
|
|
188
|
+
// #4296: the step that trades the job's OIDC token for its cloud identity, before the checkout.
|
|
189
|
+
const identity = loop.identity;
|
|
190
|
+
const tradeStep = (spec, which) => ({
|
|
191
|
+
name: `Trade the job's OIDC token for the ${which} identity`,
|
|
192
|
+
...(rooted ? {} : { "working-directory": "." }),
|
|
193
|
+
env: { [TRADE_SPEC_ENV]: JSON.stringify(spec) },
|
|
194
|
+
run: tradeLine("$GITHUB_ENV", "github-env"),
|
|
195
|
+
});
|
|
196
|
+
const withIdToken = (permissions) => (identity ? { ...permissions, "id-token": "write" } : permissions);
|
|
161
197
|
const reportName = (job) => `chant-pr-${job.wave ? prApplyWaveJobName(job.wave) : job.jobName}`;
|
|
162
198
|
const steps = (job, name) => [
|
|
199
|
+
...(identity ? [tradeStep(job.jobName === "record" ? identity.plan : identity.apply, job.jobName === "record" ? "plan" : "apply")] : []),
|
|
163
200
|
checkoutStep(true),
|
|
164
201
|
...runSteps(prLoopSetup(image)),
|
|
165
202
|
...runSteps(beforeScript),
|
|
@@ -190,12 +227,13 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
190
227
|
const onGreen = forgejo ? "startsWith(github.ref, 'refs/tags/ci/green/')" : "github.event_name == 'workflow_run' || github.event_name == 'workflow_dispatch'";
|
|
191
228
|
const onPush = loop.applyOnMerge && forgejo ? "github.event_name == 'push' && startsWith(github.ref, 'refs/heads/')" : "github.event_name == 'push'";
|
|
192
229
|
const applyJob = (job, needs, name) => ({
|
|
193
|
-
|
|
230
|
+
// #4291: a wave after the first runs after one that failed, and blocks only what depends on the failure.
|
|
231
|
+
if: job.jobName === "record" ? onPush : needs.length > 0 && job.wave ? afterEarlierWave(needs[0], onGreen) : onGreen,
|
|
194
232
|
"runs-on": GITHUB_RUNNER,
|
|
195
233
|
...(needs.length > 0 ? { needs } : {}),
|
|
196
234
|
container: image,
|
|
197
235
|
concurrency: concurrency(prApplyGroup(env, member?.name)),
|
|
198
|
-
permissions: { contents: "write", "pull-requests": "write", statuses: "write" },
|
|
236
|
+
permissions: withIdToken({ contents: "write", "pull-requests": "write", statuses: "write" }),
|
|
199
237
|
...(job.wave ? { outputs: { [WAVE_GREEN_OUTPUT]: `\${{ steps.${WAVE_STEP_ID}.outputs.${WAVE_GREEN_OUTPUT} }}` } } : {}),
|
|
200
238
|
steps: steps(job, name),
|
|
201
239
|
});
|
|
@@ -210,31 +248,84 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
210
248
|
? [["apply", applyJob(apply, [], "Apply the newest green commit under the merged changes' approvals")]]
|
|
211
249
|
: [];
|
|
212
250
|
const applyJobs = [recordJob, ...greenJobs];
|
|
251
|
+
// #4295: the plan job runs the pull request's code and holds no forge token while it does.
|
|
252
|
+
const forgeName = loop.forge ?? "github";
|
|
253
|
+
// #4296: once the plan job has traded its OIDC token, its later steps start without the variables a token is requested with.
|
|
254
|
+
const drop = dropForgeTokens("sh", identity ? OIDC_REQUEST_VARS : []);
|
|
255
|
+
const headStep = (line, name) => ({ ...(name ? { name } : {}), run: `${drop}\n${line}` });
|
|
256
|
+
const planSteps = [
|
|
257
|
+
// #4296: the trade runs first, before the change is checked out, with the plan identity alone.
|
|
258
|
+
...(identity ? [tradeStep(identity.plan, "plan")] : []),
|
|
259
|
+
// A script, not actions/checkout, whose post step runs at the job's end with the token as its input.
|
|
260
|
+
{
|
|
261
|
+
name: "Check out the change",
|
|
262
|
+
...(rooted ? {} : { "working-directory": "." }),
|
|
263
|
+
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN },
|
|
264
|
+
run: checkoutScript(true, true),
|
|
265
|
+
},
|
|
266
|
+
...[...prLoopSetup(image), ...beforeScript].map((line) => headStep(line)),
|
|
267
|
+
...(plan.build ? [headStep(plan.build[0], "Build the templates"), headStep(plan.build[1], "Lint the project")] : []),
|
|
268
|
+
// The job's last step: a step after it would start beside whatever the change's code left running.
|
|
269
|
+
{
|
|
270
|
+
name: "Plan the members this pull request reaches",
|
|
271
|
+
id: PLAN_STEP_ID,
|
|
272
|
+
env: plan.run.env,
|
|
273
|
+
run: [drop, ...handoverTrap(PR_LOOP_REPORT_DIR, forgeName), ...(plan.checkout ?? []), ...(plan.setup ?? []), plan.run.command, ...extraScript].join("\n"),
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
const post = prLoopPostJob(forgeName, env, member?.name);
|
|
277
|
+
const noteSteps = [
|
|
278
|
+
// The target branch's commit: the chant this job runs is the target branch's, and none of the change's code is here.
|
|
279
|
+
{ name: "Check out the target branch's commit", uses: actionRef("actions/checkout"), with: { ref: "${{ github.event.pull_request.base.sha }}" } },
|
|
280
|
+
...runSteps(beforeScript),
|
|
281
|
+
{ name: "Take the plan report from the plan job", run: takeOverScript("plan", PR_LOOP_REPORT_DIR, forgeName) },
|
|
282
|
+
{ name: "Keep the plan report", if: "always()", uses: UPLOAD_ARTIFACT, with: { name: reportName(plan), path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true } },
|
|
283
|
+
{ name: "Post the plan note and chant/plan", if: "always()", env: post.env, run: post.command },
|
|
284
|
+
];
|
|
285
|
+
// #4298: with a pull request environment the pull_request trigger fires on close too, and the plan skips that event.
|
|
286
|
+
const prEnv = loop.prEnvironment ? prEnvGithubJobs(loop.forge ?? "github", image, beforeScript, extraScript, loop, member) : undefined;
|
|
213
287
|
const jobsDoc = {
|
|
214
288
|
plan: {
|
|
215
|
-
if: "github.event_name == 'pull_request'",
|
|
289
|
+
if: prEnv ? "github.event_name == 'pull_request' && github.event.action != 'closed'" : "github.event_name == 'pull_request'",
|
|
290
|
+
"runs-on": GITHUB_RUNNER,
|
|
291
|
+
container: image,
|
|
292
|
+
permissions: withIdToken({ contents: "read" }),
|
|
293
|
+
outputs: handoverOutputs(PLAN_STEP_ID, forgeName),
|
|
294
|
+
steps: planSteps,
|
|
295
|
+
},
|
|
296
|
+
[PR_PLAN_NOTE_JOB]: {
|
|
297
|
+
needs: ["plan"],
|
|
298
|
+
if: "always() && github.event_name == 'pull_request' && (needs.plan.result == 'success' || needs.plan.result == 'failure')",
|
|
216
299
|
"runs-on": GITHUB_RUNNER,
|
|
217
300
|
container: image,
|
|
218
|
-
|
|
219
|
-
|
|
301
|
+
// #4289: `actions: read` lets pr-post ask when each scheduled watch or reconcile Op last ran.
|
|
302
|
+
permissions: { actions: "read", contents: "read", "pull-requests": "write", statuses: "write" },
|
|
303
|
+
steps: noteSteps,
|
|
220
304
|
},
|
|
221
305
|
...Object.fromEntries(applyJobs),
|
|
306
|
+
...(prEnv ? prEnv.jobs : {}),
|
|
222
307
|
};
|
|
223
308
|
const applyNames = applyJobs.map(([name]) => name);
|
|
309
|
+
const on = !loop.applyOnMerge
|
|
310
|
+
? { pull_request: { branches: [branch] }, push: { branches: [branch] } }
|
|
311
|
+
: forgejo
|
|
312
|
+
? { pull_request: { branches: [branch] }, push: { branches: [branch], tags: [GREEN_TAG_PATTERN] } }
|
|
313
|
+
: { pull_request: { branches: [branch] }, push: { branches: [branch] }, workflow_run: { workflows: [GREEN_TICK_WORKFLOW], types: ["completed"] }, workflow_dispatch: {} };
|
|
314
|
+
if (prEnv) {
|
|
315
|
+
on.pull_request = { branches: [branch], types: ["opened", "synchronize", "reopened", "closed"] };
|
|
316
|
+
on.schedule = [{ cron: PR_ENV_SWEEP_CRON }];
|
|
317
|
+
}
|
|
224
318
|
return {
|
|
225
319
|
name: member ? `chant-pr.${member.name}.${env}` : `chant-pr-${env}`,
|
|
226
320
|
environment: env,
|
|
227
|
-
on
|
|
228
|
-
? { pull_request: { branches: [branch] }, push: { branches: [branch] } }
|
|
229
|
-
: forgejo
|
|
230
|
-
? { pull_request: { branches: [branch] }, push: { branches: [branch], tags: [GREEN_TAG_PATTERN] } }
|
|
231
|
-
: { pull_request: { branches: [branch] }, push: { branches: [branch] }, workflow_run: { workflows: [GREEN_TICK_WORKFLOW], types: ["completed"] }, workflow_dispatch: {} },
|
|
321
|
+
on,
|
|
232
322
|
...(rooted ? {} : { defaults: { run: { "working-directory": member.dir } } }),
|
|
233
323
|
env: { ...options.variables, CHANT_ENV: env },
|
|
234
324
|
jobsDoc,
|
|
235
|
-
stages: ["plan", ...applyNames],
|
|
325
|
+
stages: ["plan", PR_PLAN_NOTE_JOB, ...applyNames, ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
|
|
236
326
|
jobs: [
|
|
237
327
|
{ jobName: "plan", component: "pull request plan", stage: "plan", needs: [] },
|
|
328
|
+
{ jobName: PR_PLAN_NOTE_JOB, component: "pull request plan note", stage: PR_PLAN_NOTE_JOB, needs: ["plan"] },
|
|
238
329
|
...applyNames.map((name, i) => ({
|
|
239
330
|
jobName: name,
|
|
240
331
|
component: name === "record" ? "merge record" : loop.waveGate ? `green apply, wave ${i}` : "green apply",
|
|
@@ -242,9 +333,109 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
242
333
|
// The record runs on a push and the apply on a green commit, so only the waves need each other.
|
|
243
334
|
needs: i > 1 ? [applyNames[i - 1]] : [],
|
|
244
335
|
})),
|
|
336
|
+
...(prEnv ? prEnv.listed : []),
|
|
337
|
+
],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The jobs of a pull request's environment (#4298). `env-up` runs on an
|
|
342
|
+
* opened, updated or reopened pull request and runs the change's code, so it
|
|
343
|
+
* follows the plan job's handover (#4295): a checkout script that alone sees
|
|
344
|
+
* the job's token, every later step started without the token variables,
|
|
345
|
+
* and the `pr-env up` step last, handing its report and record on as the
|
|
346
|
+
* job's outputs. `env-post` checks out the target branch's commit, takes
|
|
347
|
+
* them back, keeps the record on chant/lifecycle and posts the note.
|
|
348
|
+
* `env-down` runs when the pull request closes and `env-sweep` on the
|
|
349
|
+
* schedule, both from the base branch's code: a teardown reads the markers
|
|
350
|
+
* off the live resources and builds nothing of the change. `env-up` and
|
|
351
|
+
* `env-down` run one at a time per pull request, and a close waits for an
|
|
352
|
+
* apply still running rather than cancelling it.
|
|
353
|
+
*/
|
|
354
|
+
function prEnvGithubJobs(forge, image, beforeScript, extraScript, loop, member) {
|
|
355
|
+
const [up, post, down, sweep] = prEnvJobs(forge, loop, member?.name);
|
|
356
|
+
const rooted = !member || member.dir === "." || member.dir === "";
|
|
357
|
+
const reportPath = member ? memberRepoPath(member, PR_ENV_REPORT_DIR) : PR_ENV_REPORT_DIR;
|
|
358
|
+
const prNumber = "${{ github.event.pull_request.number }}";
|
|
359
|
+
const group = `chant-pr-env-${member ? `${member.name}-` : ""}${prNumber}`;
|
|
360
|
+
const writes = { contents: "write", "pull-requests": "write", statuses: "write" };
|
|
361
|
+
const keep = (name) => ({ name: `Keep the ${name} report`, if: "always()", uses: UPLOAD_ARTIFACT, with: { name: `chant-pr-${name}`, path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true } });
|
|
362
|
+
const opened = "github.event_name == 'pull_request' && github.event.action != 'closed'";
|
|
363
|
+
// #4295: env-up runs the change's code and holds no forge token while it does.
|
|
364
|
+
const drop = dropForgeTokens("sh");
|
|
365
|
+
const headStep = (line, name, env) => ({ ...(name ? { name } : {}), ...(env ? { env } : {}), run: `${drop}\n${line}` });
|
|
366
|
+
const upJob = {
|
|
367
|
+
if: opened,
|
|
368
|
+
"runs-on": GITHUB_RUNNER,
|
|
369
|
+
container: image,
|
|
370
|
+
concurrency: concurrency(group),
|
|
371
|
+
permissions: { contents: "read" },
|
|
372
|
+
outputs: handoverOutputs(PR_ENV_UP_STEP_ID, forge),
|
|
373
|
+
steps: [
|
|
374
|
+
{
|
|
375
|
+
name: "Check out the change",
|
|
376
|
+
...(rooted ? {} : { "working-directory": "." }),
|
|
377
|
+
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN },
|
|
378
|
+
run: checkoutScript(true, true),
|
|
379
|
+
},
|
|
380
|
+
...[...prLoopSetup(image), ...beforeScript].map((line) => headStep(line)),
|
|
381
|
+
...(up.build ? [headStep(up.build[0], "Build the templates", { CHANT_ENV: up.env.CHANT_ENV }), headStep(up.build[1], "Lint the project")] : []),
|
|
382
|
+
// The job's last step: a step after it would start beside whatever the change's code left running.
|
|
383
|
+
{
|
|
384
|
+
name: "Apply this pull request's environment once its plan is approved",
|
|
385
|
+
id: PR_ENV_UP_STEP_ID,
|
|
386
|
+
env: up.env,
|
|
387
|
+
run: [drop, ...handoverTrap(PR_ENV_REPORT_DIR, forge), up.command, ...extraScript].join("\n"),
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
const postJob = {
|
|
392
|
+
needs: ["env-up"],
|
|
393
|
+
if: `always() && ${opened} && (needs.env-up.result == 'success' || needs.env-up.result == 'failure')`,
|
|
394
|
+
"runs-on": GITHUB_RUNNER,
|
|
395
|
+
container: image,
|
|
396
|
+
concurrency: concurrency(`chant-pr-env-post-${member ? `${member.name}-` : ""}${prNumber}`),
|
|
397
|
+
permissions: writes,
|
|
398
|
+
steps: [
|
|
399
|
+
// The target branch's commit: the chant this job runs is the target branch's, and none of the change's code is here.
|
|
400
|
+
{ name: "Check out the target branch's commit", uses: actionRef("actions/checkout"), with: { ref: "${{ github.event.pull_request.base.sha }}", "fetch-depth": 0 } },
|
|
401
|
+
...runSteps(beforeScript),
|
|
402
|
+
{ name: "Take the environment's report from the env-up job", run: takeOverScript("env-up", PR_ENV_REPORT_DIR, forge) },
|
|
403
|
+
keep("env-up"),
|
|
404
|
+
{ name: "Keep the environment's record and post its note", if: "always()", env: post.env, run: post.command },
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
const baseJob = (j, condition, ref, name, groupName) => ({
|
|
408
|
+
if: condition,
|
|
409
|
+
"runs-on": GITHUB_RUNNER,
|
|
410
|
+
container: image,
|
|
411
|
+
concurrency: concurrency(groupName),
|
|
412
|
+
permissions: writes,
|
|
413
|
+
steps: [
|
|
414
|
+
{ uses: actionRef("actions/checkout"), with: { "fetch-depth": 0, ...(ref ? { ref } : {}) } },
|
|
415
|
+
...runSteps(prLoopSetup(image)),
|
|
416
|
+
...runSteps(beforeScript),
|
|
417
|
+
{ name, env: j.env, run: j.command },
|
|
418
|
+
...runSteps(extraScript),
|
|
419
|
+
keep(j.jobName),
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
jobs: {
|
|
424
|
+
"env-up": upJob,
|
|
425
|
+
[PR_ENV_POST_JOB]: postJob,
|
|
426
|
+
"env-down": baseJob(down, "github.event_name == 'pull_request' && github.event.action == 'closed'", "${{ github.event.pull_request.base.ref }}", "Tear down what this pull request's environment owns", group),
|
|
427
|
+
"env-sweep": baseJob(sweep, "github.event_name == 'schedule'", "", "Tear down the environments whose TTL passed or whose pull request closed", `chant-pr-env-sweep${member ? `-${member.name}` : ""}`),
|
|
428
|
+
},
|
|
429
|
+
listed: [
|
|
430
|
+
{ jobName: "env-up", component: "pull request environment", stage: "env-up", needs: [] },
|
|
431
|
+
{ jobName: PR_ENV_POST_JOB, component: "pull request environment note", stage: PR_ENV_POST_JOB, needs: ["env-up"] },
|
|
432
|
+
{ jobName: "env-down", component: "pull request environment teardown", stage: "env-down", needs: [] },
|
|
433
|
+
{ jobName: "env-sweep", component: "pull request environment sweep", stage: "env-sweep", needs: [] },
|
|
245
434
|
],
|
|
246
435
|
};
|
|
247
436
|
}
|
|
437
|
+
/** The id of the `pr-env up` step, whose outputs carry the report (#4295). */
|
|
438
|
+
const PR_ENV_UP_STEP_ID = "env-up";
|
|
248
439
|
/** The file a producer dumps its resolved outputs to, and a dependent seeds from — one per component, matching gitlab's naming so both generators produce interchangeable artifacts. */
|
|
249
440
|
function outputsFile(name) {
|
|
250
441
|
return `${name}.outputs.json`;
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "91be5cd758d335f713fe5eee9fdde779edfb2baf9ed1d38bc248487dfeb4301d",
|
|
5
5
|
"meta.json": "c50e0ef3845c370c3ad2210b8bba455980417c0f818aa6ed46e06d4fb8b1b9f9",
|
|
6
6
|
"types/index.d.ts": "0ead95536e947ddfb98a5acf0ddb88547913b5a53a342df67711c59266f84b3e",
|
|
7
7
|
"rules/deprecated-action-version.ts": "a9ba7cc924ff1c7055110416d8989273690415bfb5e8dcc32dc9431b9351f5ca",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"skills/chant-github-patterns.md": "bb3abef289a8fdfcf07d6bb2d7289dcb2f38bc0cb0321ea320b78b45a6f548c0",
|
|
79
79
|
"skills/chant-github-security.md": "aab111cb0871cad30281ce48d7da23663689619351029219e2be019a1a61e394"
|
|
80
80
|
},
|
|
81
|
-
"composite": "
|
|
81
|
+
"composite": "5c0703f8385fcd34f6d37731e92126fa3390127f40f3f829cb1eb2d33c011bee"
|
|
82
82
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.41",
|
|
4
4
|
"description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@intentius/chant": "^0.124.
|
|
64
|
+
"@intentius/chant": "^0.124.41"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -24,24 +24,82 @@ jobs:
|
|
|
24
24
|
container: node:22
|
|
25
25
|
permissions:
|
|
26
26
|
contents: read
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
outputs:
|
|
28
|
+
report_0: '${{ steps.plan.outputs.report_0 }}'
|
|
29
29
|
steps:
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
- name: Check out the change
|
|
31
|
+
env:
|
|
32
|
+
CHANT_CHECKOUT_TOKEN: '${{ github.token }}'
|
|
33
|
+
run: |
|
|
34
|
+
set -eu
|
|
35
|
+
git config --global --add safe.directory "$PWD" 2>/dev/null || true
|
|
36
|
+
[ -d .git ] || git init -q .
|
|
37
|
+
git remote get-url origin >/dev/null 2>&1 || git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
38
|
+
if [ -n "${CHANT_CHECKOUT_TOKEN:-}" ]; then
|
|
39
|
+
chant_auth="$(printf 'x-access-token:%s' "$CHANT_CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
40
|
+
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=http.extraheader GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $chant_auth"
|
|
41
|
+
unset chant_auth
|
|
42
|
+
fi
|
|
43
|
+
ref="$GITHUB_REF"
|
|
44
|
+
want="$GITHUB_SHA"
|
|
45
|
+
case "$ref" in
|
|
46
|
+
refs/heads/*) target="refs/remotes/origin/${ref#refs/heads/}" ;;
|
|
47
|
+
refs/tags/*) target="$ref" ;;
|
|
48
|
+
refs/*) target="refs/remotes/${ref#refs/}" ;;
|
|
49
|
+
*) target="refs/remotes/origin/chant-head" ;;
|
|
50
|
+
esac
|
|
51
|
+
if git rev-parse -q --verify HEAD >/dev/null; then git clean -ffdxq; fi
|
|
52
|
+
git -c protocol.version=2 fetch -q --prune --no-recurse-submodules origin '+refs/heads/*:refs/remotes/origin/*' '+refs/tags/*:refs/tags/*' "+$want:$target"
|
|
53
|
+
git -c protocol.version=2 fetch -q --no-tags origin '+refs/heads/chant/lifecycle:refs/heads/chant/lifecycle' 2>/dev/null || true
|
|
54
|
+
case "$ref" in
|
|
55
|
+
refs/heads/*) git checkout -q --force -B "${ref#refs/heads/}" "$target" ;;
|
|
56
|
+
*) git checkout -q --force --detach "$target" ;;
|
|
57
|
+
esac
|
|
58
|
+
- run: |
|
|
59
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
60
|
+
command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
34
61
|
- name: Build the templates
|
|
35
|
-
run:
|
|
62
|
+
run: |
|
|
63
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
64
|
+
chant build --env prod
|
|
36
65
|
- name: Lint the project
|
|
37
|
-
run:
|
|
66
|
+
run: |
|
|
67
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
68
|
+
chant lint
|
|
38
69
|
- name: Plan the members this pull request reaches
|
|
70
|
+
id: plan
|
|
39
71
|
env:
|
|
40
72
|
BASE_SHA: '${{ github.event.pull_request.base.sha }}'
|
|
41
73
|
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
42
|
-
GITHUB_TOKEN: '${{ github.token }}'
|
|
43
74
|
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
44
|
-
run:
|
|
75
|
+
run: |
|
|
76
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
77
|
+
chant_report="$PWD/.chant/pr"
|
|
78
|
+
chant_handover() { node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const [dir,out]=process.argv.slice(1);const CHUNK=450000,PARTS=1,NOTE=["pr-plan.json","pr-note.md"];const all={};const walk=(d,r)=>{let n=[];try{n=fs.readdirSync(d).sort()}catch{return}for(const x of n){const f=p.join(d,x),k=r?r+"/"+x:x;let s;try{s=fs.lstatSync(f)}catch{continue}if(s.isDirectory())walk(f,k);else if(s.isFile())all[k]=fs.readFileSync(f).toString("base64")}};walk(dir,"");const pack=(o)=>z.gzipSync(JSON.stringify(o)).toString("base64");let b=pack(all);if(b.length>CHUNK*PARTS){b=pack(Object.fromEntries(Object.entries(all).filter(([k])=>NOTE.includes(k))));if(b.length>CHUNK*PARTS){b=pack({});console.error("chant: the plan report is too large to hand to the note job, even cut to the report and the note; it hands on none of it")}else console.error("chant: the plan report is too large to hand to the note job whole; it hands on pr-plan.json and pr-note.md")}let o="";for(let i=0;i*CHUNK<b.length;i++)o+="report_"+i+"="+b.slice(i*CHUNK,(i+1)*CHUNK)+"\n";fs.appendFileSync(out,o);' "$chant_report" "${GITHUB_OUTPUT:-/dev/null}" || echo "chant: the plan report could not be handed to the note job" >&2; }
|
|
79
|
+
trap chant_handover EXIT
|
|
80
|
+
chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github
|
|
81
|
+
plan-note:
|
|
82
|
+
needs:
|
|
83
|
+
- plan
|
|
84
|
+
if: always() && github.event_name == 'pull_request' && (needs.plan.result == 'success' || needs.plan.result == 'failure')
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
container: node:22
|
|
87
|
+
permissions:
|
|
88
|
+
actions: read
|
|
89
|
+
contents: read
|
|
90
|
+
pull-requests: write
|
|
91
|
+
statuses: write
|
|
92
|
+
steps:
|
|
93
|
+
- name: Check out the target branch's commit
|
|
94
|
+
uses: actions/checkout@v7
|
|
95
|
+
with:
|
|
96
|
+
ref: '${{ github.event.pull_request.base.sha }}'
|
|
97
|
+
- name: Take the plan report from the plan job
|
|
98
|
+
run: |
|
|
99
|
+
set -u
|
|
100
|
+
node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const dir=process.argv[1];let b="";for(const l of fs.readFileSync(0,"utf8").split("\n")){if(!l.trim())continue;let s="";try{s=JSON.parse(l)}catch{}if(typeof s==="string")b+=s}if(!b){console.log("chant: the plan job handed on no report");process.exit(0)}let files;try{files=JSON.parse(z.gunzipSync(Buffer.from(b,"base64"),{maxOutputLength:268435456}).toString("utf8"))}catch(e){console.log("chant: the report the plan job handed on does not read: "+e.message);process.exit(0)}if(!files||typeof files!=="object"||Array.isArray(files)){console.log("chant: the report the plan job handed on is not a set of files");process.exit(0)}let n=0;for(const [k,v] of Object.entries(files)){const parts=k.split("/");if(typeof v!=="string"||k.length>1024||parts.some((x)=>!x||x==="."||x===".."||/[\\\x00-\x1f]/.test(x))){console.log("chant: left out "+JSON.stringify(k));continue}try{const f=p.join(dir,...parts);fs.mkdirSync(p.dirname(f),{recursive:true});fs.writeFileSync(f,Buffer.from(v,"base64"));n++}catch(e){console.log("chant: could not write "+JSON.stringify(k)+": "+e.message)}}console.log("chant: took "+n+" report files from the plan job")' .chant/pr <<'END_OF_REPORT'
|
|
101
|
+
${{ toJSON(needs.plan.outputs.report_0) }}
|
|
102
|
+
END_OF_REPORT
|
|
45
103
|
- name: Keep the plan report
|
|
46
104
|
if: always()
|
|
47
105
|
uses: actions/upload-artifact@v4
|
|
@@ -50,6 +108,14 @@ jobs:
|
|
|
50
108
|
path: .chant/pr
|
|
51
109
|
if-no-files-found: ignore
|
|
52
110
|
include-hidden-files: true
|
|
111
|
+
- name: Post the plan note and chant/plan
|
|
112
|
+
if: always()
|
|
113
|
+
env:
|
|
114
|
+
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
115
|
+
HEAD_SHA: '${{ github.sha }}'
|
|
116
|
+
PLAN_RESULT: '${{ needs.plan.result }}'
|
|
117
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
118
|
+
run: chant components pr-post --pr "$PR_NUMBER" --head "$HEAD_SHA" --env prod --output .chant/pr --forge github --result "$PLAN_RESULT"
|
|
53
119
|
record:
|
|
54
120
|
if: github.event_name == 'push'
|
|
55
121
|
runs-on: ubuntu-latest
|
|
@@ -18,20 +18,74 @@ jobs:
|
|
|
18
18
|
container: node:22
|
|
19
19
|
permissions:
|
|
20
20
|
contents: read
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
outputs:
|
|
22
|
+
report_0: '${{ steps.plan.outputs.report_0 }}'
|
|
23
23
|
steps:
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
- name: Check out the change
|
|
25
|
+
env:
|
|
26
|
+
CHANT_CHECKOUT_TOKEN: '${{ github.token }}'
|
|
27
|
+
run: |
|
|
28
|
+
set -eu
|
|
29
|
+
git config --global --add safe.directory "$PWD" 2>/dev/null || true
|
|
30
|
+
[ -d .git ] || git init -q .
|
|
31
|
+
git remote get-url origin >/dev/null 2>&1 || git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
32
|
+
if [ -n "${CHANT_CHECKOUT_TOKEN:-}" ]; then
|
|
33
|
+
chant_auth="$(printf 'x-access-token:%s' "$CHANT_CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
|
34
|
+
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=http.extraheader GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $chant_auth"
|
|
35
|
+
unset chant_auth
|
|
36
|
+
fi
|
|
37
|
+
ref="$GITHUB_REF"
|
|
38
|
+
want="$GITHUB_SHA"
|
|
39
|
+
case "$ref" in
|
|
40
|
+
refs/heads/*) target="refs/remotes/origin/${ref#refs/heads/}" ;;
|
|
41
|
+
refs/tags/*) target="$ref" ;;
|
|
42
|
+
refs/*) target="refs/remotes/${ref#refs/}" ;;
|
|
43
|
+
*) target="refs/remotes/origin/chant-head" ;;
|
|
44
|
+
esac
|
|
45
|
+
if git rev-parse -q --verify HEAD >/dev/null; then git clean -ffdxq; fi
|
|
46
|
+
git -c protocol.version=2 fetch -q --prune --no-recurse-submodules origin '+refs/heads/*:refs/remotes/origin/*' '+refs/tags/*:refs/tags/*' "+$want:$target"
|
|
47
|
+
git -c protocol.version=2 fetch -q --no-tags origin '+refs/heads/chant/lifecycle:refs/heads/chant/lifecycle' 2>/dev/null || true
|
|
48
|
+
case "$ref" in
|
|
49
|
+
refs/heads/*) git checkout -q --force -B "${ref#refs/heads/}" "$target" ;;
|
|
50
|
+
*) git checkout -q --force --detach "$target" ;;
|
|
51
|
+
esac
|
|
52
|
+
- run: |
|
|
53
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
54
|
+
command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
28
55
|
- name: Plan the members this pull request reaches
|
|
56
|
+
id: plan
|
|
29
57
|
env:
|
|
30
58
|
BASE_SHA: '${{ github.event.pull_request.base.sha }}'
|
|
31
59
|
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
32
|
-
GITHUB_TOKEN: '${{ github.token }}'
|
|
33
60
|
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
34
|
-
run:
|
|
61
|
+
run: |
|
|
62
|
+
if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
|
|
63
|
+
chant_report="$PWD/.chant/pr"
|
|
64
|
+
chant_handover() { node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const [dir,out]=process.argv.slice(1);const CHUNK=450000,PARTS=1,NOTE=["pr-plan.json","pr-note.md"];const all={};const walk=(d,r)=>{let n=[];try{n=fs.readdirSync(d).sort()}catch{return}for(const x of n){const f=p.join(d,x),k=r?r+"/"+x:x;let s;try{s=fs.lstatSync(f)}catch{continue}if(s.isDirectory())walk(f,k);else if(s.isFile())all[k]=fs.readFileSync(f).toString("base64")}};walk(dir,"");const pack=(o)=>z.gzipSync(JSON.stringify(o)).toString("base64");let b=pack(all);if(b.length>CHUNK*PARTS){b=pack(Object.fromEntries(Object.entries(all).filter(([k])=>NOTE.includes(k))));if(b.length>CHUNK*PARTS){b=pack({});console.error("chant: the plan report is too large to hand to the note job, even cut to the report and the note; it hands on none of it")}else console.error("chant: the plan report is too large to hand to the note job whole; it hands on pr-plan.json and pr-note.md")}let o="";for(let i=0;i*CHUNK<b.length;i++)o+="report_"+i+"="+b.slice(i*CHUNK,(i+1)*CHUNK)+"\n";fs.appendFileSync(out,o);' "$chant_report" "${GITHUB_OUTPUT:-/dev/null}" || echo "chant: the plan report could not be handed to the note job" >&2; }
|
|
65
|
+
trap chant_handover EXIT
|
|
66
|
+
chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github
|
|
67
|
+
plan-note:
|
|
68
|
+
needs:
|
|
69
|
+
- plan
|
|
70
|
+
if: always() && github.event_name == 'pull_request' && (needs.plan.result == 'success' || needs.plan.result == 'failure')
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
container: node:22
|
|
73
|
+
permissions:
|
|
74
|
+
actions: read
|
|
75
|
+
contents: read
|
|
76
|
+
pull-requests: write
|
|
77
|
+
statuses: write
|
|
78
|
+
steps:
|
|
79
|
+
- name: Check out the target branch's commit
|
|
80
|
+
uses: actions/checkout@v7
|
|
81
|
+
with:
|
|
82
|
+
ref: '${{ github.event.pull_request.base.sha }}'
|
|
83
|
+
- name: Take the plan report from the plan job
|
|
84
|
+
run: |
|
|
85
|
+
set -u
|
|
86
|
+
node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const dir=process.argv[1];let b="";for(const l of fs.readFileSync(0,"utf8").split("\n")){if(!l.trim())continue;let s="";try{s=JSON.parse(l)}catch{}if(typeof s==="string")b+=s}if(!b){console.log("chant: the plan job handed on no report");process.exit(0)}let files;try{files=JSON.parse(z.gunzipSync(Buffer.from(b,"base64"),{maxOutputLength:268435456}).toString("utf8"))}catch(e){console.log("chant: the report the plan job handed on does not read: "+e.message);process.exit(0)}if(!files||typeof files!=="object"||Array.isArray(files)){console.log("chant: the report the plan job handed on is not a set of files");process.exit(0)}let n=0;for(const [k,v] of Object.entries(files)){const parts=k.split("/");if(typeof v!=="string"||k.length>1024||parts.some((x)=>!x||x==="."||x===".."||/[\\\x00-\x1f]/.test(x))){console.log("chant: left out "+JSON.stringify(k));continue}try{const f=p.join(dir,...parts);fs.mkdirSync(p.dirname(f),{recursive:true});fs.writeFileSync(f,Buffer.from(v,"base64"));n++}catch(e){console.log("chant: could not write "+JSON.stringify(k)+": "+e.message)}}console.log("chant: took "+n+" report files from the plan job")' .chant/pr <<'END_OF_REPORT'
|
|
87
|
+
${{ toJSON(needs.plan.outputs.report_0) }}
|
|
88
|
+
END_OF_REPORT
|
|
35
89
|
- name: Keep the plan report
|
|
36
90
|
if: always()
|
|
37
91
|
uses: actions/upload-artifact@v4
|
|
@@ -40,6 +94,14 @@ jobs:
|
|
|
40
94
|
path: .chant/pr
|
|
41
95
|
if-no-files-found: ignore
|
|
42
96
|
include-hidden-files: true
|
|
97
|
+
- name: Post the plan note and chant/plan
|
|
98
|
+
if: always()
|
|
99
|
+
env:
|
|
100
|
+
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
101
|
+
HEAD_SHA: '${{ github.sha }}'
|
|
102
|
+
PLAN_RESULT: '${{ needs.plan.result }}'
|
|
103
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
104
|
+
run: chant components pr-post --pr "$PR_NUMBER" --head "$HEAD_SHA" --env prod --output .chant/pr --forge github --result "$PLAN_RESULT"
|
|
43
105
|
record:
|
|
44
106
|
if: github.event_name == 'push'
|
|
45
107
|
runs-on: ubuntu-latest
|