@intentius/chant 0.124.30 → 0.124.32
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/bin/chant +7 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +23 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +74 -12
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +65 -0
- package/dist/cli/registry.d.ts +29 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +2 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/local-executor.d.ts +12 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -3
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/redact.d.ts +34 -0
- package/dist/op/redact.d.ts.map +1 -0
- package/dist/op/redact.js +94 -0
- package/dist/op/step-console.d.ts +2 -0
- package/dist/op/step-console.d.ts.map +1 -1
- package/dist/op/step-console.js +5 -2
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +27 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +51 -6
- package/dist/pr-loop.d.ts +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +10 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +52 -6
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +208 -208
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +108 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -0
- package/dist/workspace/ci-run-index.js +164 -0
- package/dist/workspace/ci-run-index.schema.json +52 -0
- package/dist/workspace/ci-run.d.ts +159 -9
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +419 -50
- package/dist/workspace/ci-runner-key.d.ts +133 -0
- package/dist/workspace/ci-runner-key.d.ts.map +1 -0
- package/dist/workspace/ci-runner-key.js +237 -0
- package/dist/workspace/ci-secrets.d.ts +65 -0
- package/dist/workspace/ci-secrets.d.ts.map +1 -0
- package/dist/workspace/ci-secrets.js +211 -0
- package/dist/workspace/ci-serve.d.ts +67 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +240 -35
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +74 -7
- package/dist/workspace/reason-codes.d.ts +7 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +9 -4
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/trust/trust-write.d.ts +109 -0
- package/dist/workspace/trust/trust-write.d.ts.map +1 -0
- package/dist/workspace/trust/trust-write.js +320 -0
- package/dist/workspace/trust-write.schema.json +265 -0
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +69 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +19 -0
- package/src/cli/main.ts +73 -12
- package/src/cli/parse-args.ts +48 -0
- package/src/cli/registry.ts +35 -4
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/local-executor.ts +31 -3
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/redact.ts +93 -0
- package/src/op/step-console.ts +7 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +71 -0
- package/src/pr-forge.ts +70 -6
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +58 -6
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +52 -0
- package/src/workspace/ci-run-index.test.ts +392 -0
- package/src/workspace/ci-run-index.ts +206 -0
- package/src/workspace/ci-run.test.ts +520 -42
- package/src/workspace/ci-run.ts +483 -51
- package/src/workspace/ci-runner-key.test.ts +204 -0
- package/src/workspace/ci-runner-key.ts +275 -0
- package/src/workspace/ci-secrets.test.ts +106 -0
- package/src/workspace/ci-secrets.ts +213 -0
- package/src/workspace/ci-serve.test.ts +282 -39
- package/src/workspace/ci-serve.ts +231 -34
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +9 -4
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/trust/trust-write.test.ts +179 -0
- package/src/workspace/trust/trust-write.ts +377 -0
- package/src/workspace/trust-write.schema.json +265 -0
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
|
@@ -4,28 +4,33 @@
|
|
|
4
4
|
* real `tofu` roots is `lexicons/terraform/src/components/pr-loop.tofu.test.ts`.
|
|
5
5
|
*
|
|
6
6
|
* The capability stands in for `terraform-apply`: `plan` returns the root, a
|
|
7
|
-
* digest over what it would write (its `version` and every input)
|
|
8
|
-
* change-set part
|
|
7
|
+
* digest over what it would write (its `version` and every input), a
|
|
8
|
+
* change-set part and, where `predict` says, the outputs it will expose
|
|
9
|
+
* (#3999); `outputs` reads what it exposes now; `run` applies.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { describe, expect, test } from "vitest";
|
|
12
|
-
import { CapabilityRegistry, type DeployContext } from "./capability";
|
|
13
|
+
import { CapabilityRegistry, type DeployContext, type PlannedOutputs } from "./capability";
|
|
13
14
|
import { memoryGateLedgerPort } from "../op/gate";
|
|
14
15
|
import { planFanOut } from "./fan-out";
|
|
15
16
|
import {
|
|
16
17
|
applyPrSet,
|
|
17
18
|
approvalOf,
|
|
19
|
+
approversWhoAuthored,
|
|
18
20
|
approversWithoutReview,
|
|
19
21
|
decidePrGate,
|
|
20
22
|
planCoveredBy,
|
|
21
23
|
planPrSet,
|
|
22
24
|
prReport,
|
|
25
|
+
prWavePlan,
|
|
23
26
|
readOnlyLedger,
|
|
24
27
|
resumePrSet,
|
|
28
|
+
waveNotCovered,
|
|
25
29
|
type PrPlanSet,
|
|
26
30
|
} from "./pr-run";
|
|
27
31
|
import { changeSetDigest, changeSetDocumentDigest, composeChangeSet, type ChangeSetEntry, type ChangeSetPart } from "../change-set";
|
|
28
32
|
import type { PrApplyRecord } from "./fan-out-record";
|
|
33
|
+
import { renderPrNote } from "../pr-loop";
|
|
29
34
|
import type { FanOutPlan } from "./fan-out";
|
|
30
35
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
31
36
|
import type { DriverComponent } from "./driver";
|
|
@@ -65,7 +70,17 @@ function world() {
|
|
|
65
70
|
fromPlan: {} as Record<string, boolean>,
|
|
66
71
|
failing: new Set<string>(),
|
|
67
72
|
planFailing: new Set<string>(),
|
|
73
|
+
/**
|
|
74
|
+
* What each root's plan says it will expose (#3999). By default `net`
|
|
75
|
+
* keeps the cidr it has, and `a` will expose the id its version gives,
|
|
76
|
+
* as a terraform plan's `planned_values` would say.
|
|
77
|
+
*/
|
|
78
|
+
predict: {} as Record<string, (() => PlannedOutputs | undefined) | undefined>,
|
|
79
|
+
/** What `a` writes when it applies, when not what its version gives. */
|
|
80
|
+
appliesAs: undefined as string | undefined,
|
|
68
81
|
};
|
|
82
|
+
w.predict.net = () => ({ known: { ...(w.state.net ?? {}) }, unknown: [] });
|
|
83
|
+
w.predict.a = () => ({ known: { id: `a-${w.versions.a ?? "v1"}` }, unknown: [] });
|
|
69
84
|
const digestOf = (input: RootInput) => computePlanDigest("fake-root", { root: input.root, version: w.versions[input.root] ?? "v1", vars: input.vars ?? null });
|
|
70
85
|
w.registry.register({
|
|
71
86
|
kind: "fake-root",
|
|
@@ -74,10 +89,12 @@ function world() {
|
|
|
74
89
|
if (w.planFailing.has(input.root)) throw new Error(`${input.root}: no value for a required variable`);
|
|
75
90
|
const planDigest = digestOf(input);
|
|
76
91
|
const changes = (w.versions[input.root] ?? "v1") !== "v1";
|
|
92
|
+
const outputs = w.predict[input.root]?.();
|
|
77
93
|
return {
|
|
78
94
|
member: input.root,
|
|
79
95
|
planDigest,
|
|
80
96
|
artifact: { planDigest },
|
|
97
|
+
...(outputs ? { outputs } : {}),
|
|
81
98
|
changeSet: {
|
|
82
99
|
member: { member: input.root, lexicon: "terraform", planner: "tofu", status: "planned", planDigest, holes: [] },
|
|
83
100
|
entries: changes
|
|
@@ -105,7 +122,7 @@ function world() {
|
|
|
105
122
|
w.ranWith[input.root] = input.vars;
|
|
106
123
|
if (w.failing.has(input.root)) throw new Error(`${input.root} failed`);
|
|
107
124
|
w.applied.push(input.root);
|
|
108
|
-
if (input.root === "a") w.state.a = { id: `a-${w.versions.a ?? "v1"}` };
|
|
125
|
+
if (input.root === "a") w.state.a = { id: w.appliesAs ?? `a-${w.versions.a ?? "v1"}` };
|
|
109
126
|
return { outputs: { ...(w.state[input.root] ?? {}) } };
|
|
110
127
|
},
|
|
111
128
|
} as never);
|
|
@@ -126,19 +143,23 @@ const approval = (planDigest: string, resolvedBy = "github:alice") => ({
|
|
|
126
143
|
});
|
|
127
144
|
|
|
128
145
|
describe("planPrSet", () => {
|
|
129
|
-
test("plans the changed root and its dependents
|
|
146
|
+
test("plans the changed root and its dependents: a dependency outside the change as deployed, one in it as its plan says (#3999)", async () => {
|
|
130
147
|
const w = world();
|
|
131
148
|
w.versions.a = "v2";
|
|
132
149
|
const plan = planFanOut({ components: ESTATE, changed: ["a"] });
|
|
133
150
|
const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
|
|
134
151
|
|
|
135
152
|
expect(plan.order).toEqual(["a", "app"]);
|
|
153
|
+
// app reads the id a's plan says a will have, not the one a has now.
|
|
136
154
|
expect(w.planned).toEqual([
|
|
137
155
|
{ root: "a", vars: { cidr: "10.0.0.0/16" } },
|
|
138
|
-
{ root: "app", vars: { subnet: "a-
|
|
156
|
+
{ root: "app", vars: { subnet: "a-v2" } },
|
|
139
157
|
]);
|
|
140
|
-
// net is read
|
|
141
|
-
expect(w.outputsRead).toEqual(["net"
|
|
158
|
+
// net is read from the estate; a is not read at all.
|
|
159
|
+
expect(w.outputsRead).toEqual(["net"]);
|
|
160
|
+
expect(set.deployedOutputs).toEqual({ net: ["cidr"] });
|
|
161
|
+
expect(set.plannedOutputs).toEqual({ a: ["id"] });
|
|
162
|
+
expect(set.members.every((m) => m.unknownInputs === undefined)).toBe(true);
|
|
142
163
|
expect(set.members.map((m) => [m.member, m.status, m.counts.update])).toEqual([
|
|
143
164
|
["a", "planned", 1],
|
|
144
165
|
["app", "planned", 0],
|
|
@@ -281,7 +302,8 @@ describe("a dependency outside the selection (#4104)", () => {
|
|
|
281
302
|
const plan = planFanOut({ components: ESTATE, changed: ["a"] });
|
|
282
303
|
const { set, first, record } = await firstAttempt(w, plan, ["app"]);
|
|
283
304
|
expect(first.run.completed).toEqual(["a"]);
|
|
284
|
-
expect(set.deployedOutputs).toEqual({
|
|
305
|
+
expect(set.deployedOutputs).toEqual({ net: ["cidr"] });
|
|
306
|
+
expect(set.plannedOutputs).toEqual({ a: ["id"] });
|
|
285
307
|
|
|
286
308
|
w.outputsRead.length = 0;
|
|
287
309
|
w.state.net = { cidr: "10.9.0.0/16" };
|
|
@@ -318,22 +340,29 @@ describe("the gate", () => {
|
|
|
318
340
|
expect(approversWithoutReview(["github:alice", "GitHub:Bob"], ["github:alice", "github:bob"])).toEqual([]);
|
|
319
341
|
expect(approversWithoutReview(["github:mallory"], ["github:alice"])).toEqual(["github:mallory"]);
|
|
320
342
|
});
|
|
343
|
+
|
|
344
|
+
test("an approver who wrote the change is named, the forge's naming compared without case (#4000)", () => {
|
|
345
|
+
expect(approversWhoAuthored(["github:alice", "GitHub:Carol"], ["github:carol", "github:dave"])).toEqual(["GitHub:Carol"]);
|
|
346
|
+
expect(approversWhoAuthored(["github:alice"], ["github:carol"])).toEqual([]);
|
|
347
|
+
});
|
|
321
348
|
});
|
|
322
349
|
|
|
323
350
|
describe("applyPrSet", () => {
|
|
324
|
-
test("applies each member's approved plan in order
|
|
351
|
+
test("applies each member's approved plan in order; a dependent planned on the output its upstream applied as applies as approved (#3999)", async () => {
|
|
325
352
|
const w = world();
|
|
326
353
|
w.versions.a = "v2";
|
|
327
354
|
const plan = planFanOut({ components: ESTATE, changed: ["a"] });
|
|
328
355
|
const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
|
|
329
|
-
const { members, status } = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set });
|
|
356
|
+
const { members, status, rounds } = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set });
|
|
330
357
|
|
|
331
358
|
expect(status).toBe("applied");
|
|
332
359
|
expect(w.applied).toEqual(["a", "app"]);
|
|
333
360
|
expect(w.fromPlan).toEqual({ a: true, app: true });
|
|
334
|
-
expect(
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
expect(w.ranWith.app).toEqual({ subnet: "a-v2" });
|
|
362
|
+
expect(rounds).toBeUndefined();
|
|
363
|
+
expect(members.map((m) => [m.member, m.status, m.inputsMoved, m.round])).toEqual([
|
|
364
|
+
["a", "applied", undefined, undefined],
|
|
365
|
+
["app", "applied", undefined, undefined],
|
|
337
366
|
]);
|
|
338
367
|
});
|
|
339
368
|
|
|
@@ -355,6 +384,226 @@ describe("applyPrSet", () => {
|
|
|
355
384
|
});
|
|
356
385
|
});
|
|
357
386
|
|
|
387
|
+
describe("a dependent planned on its upstream's planned outputs (#3999)", () => {
|
|
388
|
+
/** `a` and `app` reading its `id`, the change touching `a`. */
|
|
389
|
+
const plan = () => planFanOut({ components: ESTATE, changed: ["a"] });
|
|
390
|
+
const approveAll = (digest: string, gate: string) =>
|
|
391
|
+
decidePrGate(memoryGateLedgerPort({ resolutions: [{ ...approval(digest), gate }] }), { op: "pr-12", gate, digest, now: NOW });
|
|
392
|
+
const decideWith = (approved: Set<string>) => async ({ gate, set }: { gate: string; set: PrPlanSet }) => {
|
|
393
|
+
const check = approved.has(set.doc.digest) ? await approveAll(set.doc.digest, gate) : await decidePrGate(readOnlyLedger(memoryGateLedgerPort()), { op: "pr-12", gate, digest: set.doc.digest, now: NOW });
|
|
394
|
+
return { satisfied: check.satisfied, approval: approvalOf(check) };
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
test("an output the change leaves as it is: the dependent plans on the planned value, which is the deployed one", async () => {
|
|
398
|
+
const w = world();
|
|
399
|
+
const set = await planPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod" });
|
|
400
|
+
expect(w.planned.find((p) => p.root === "app")?.vars).toEqual({ subnet: "a-v1" });
|
|
401
|
+
expect(set.plannedOutputs).toEqual({ a: ["id"] });
|
|
402
|
+
const applied = await applyPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", set });
|
|
403
|
+
expect(applied.status).toBe("applied");
|
|
404
|
+
expect(w.applied).toEqual(["a", "app"]);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("an output the change moves: the dependent plans on the moved value, not the one standing now", async () => {
|
|
408
|
+
const w = world();
|
|
409
|
+
w.versions.a = "v2";
|
|
410
|
+
expect(w.state.a).toEqual({ id: "a-v1" });
|
|
411
|
+
const set = await planPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod" });
|
|
412
|
+
expect(w.planned.find((p) => p.root === "app")?.vars).toEqual({ subnet: "a-v2" });
|
|
413
|
+
expect(w.outputsRead).not.toContain("a");
|
|
414
|
+
// The reviewer approves app's plan against a-v2, and that is what applies.
|
|
415
|
+
const applied = await applyPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", set });
|
|
416
|
+
expect(applied.status).toBe("applied");
|
|
417
|
+
expect(w.ranWith.app).toEqual({ subnet: "a-v2" });
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("an output known only after apply is listed on the dependent, which the apply plans again and gates once its upstream applied", async () => {
|
|
421
|
+
const w = world();
|
|
422
|
+
w.versions.a = "v2";
|
|
423
|
+
w.predict.a = () => ({ known: {}, unknown: ["id"] });
|
|
424
|
+
const set = await planPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod" });
|
|
425
|
+
expect(set.failed).toBe(false);
|
|
426
|
+
expect(w.planned.find((p) => p.root === "app")?.vars?.subnet).toBeUndefined();
|
|
427
|
+
expect(set.plannedOutputs).toEqual({ a: [] });
|
|
428
|
+
expect(set.members.map((m) => [m.member, m.unknownInputs])).toEqual([
|
|
429
|
+
["a", undefined],
|
|
430
|
+
["app", ["a.id"]],
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
// No approval of the plan made after a applied: a applies, app waits.
|
|
434
|
+
const waiting = await applyPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", set, decideRound: decideWith(new Set()) });
|
|
435
|
+
expect(waiting.status).toBe("waiting");
|
|
436
|
+
expect(w.applied).toEqual(["a"]);
|
|
437
|
+
expect(waiting.rounds).toHaveLength(1);
|
|
438
|
+
const round = waiting.rounds![0]!;
|
|
439
|
+
expect(round).toMatchObject({ round: 2, gate: "pr-apply-round-2", components: ["app"], status: "waiting", approval: { status: "pending" } });
|
|
440
|
+
// Round 2 planned app against the id a applied as.
|
|
441
|
+
expect(w.planned.at(-1)).toEqual({ root: "app", vars: { subnet: "a-v2" } });
|
|
442
|
+
const app = waiting.members.find((m) => m.member === "app")!;
|
|
443
|
+
expect(app).toMatchObject({ status: "waiting", round: 2, held: "its plan read a.id before that output applied as another value" });
|
|
444
|
+
expect(app.planDigest).toBe(round.changeSet.members[0]!.planDigest);
|
|
445
|
+
expect(app.planDigest).not.toBe(set.members.find((m) => m.member === "app")!.planDigest);
|
|
446
|
+
|
|
447
|
+
// With that plan approved, app applies against the value a applied as.
|
|
448
|
+
const w2 = world();
|
|
449
|
+
w2.versions.a = "v2";
|
|
450
|
+
w2.predict.a = () => ({ known: {}, unknown: ["id"] });
|
|
451
|
+
const set2 = await planPrSet({ components: ESTATE, plan: plan(), registry: w2.registry, env: "prod" });
|
|
452
|
+
const applied = await applyPrSet({ components: ESTATE, plan: plan(), registry: w2.registry, env: "prod", set: set2, decideRound: decideWith(new Set([round.digest])) });
|
|
453
|
+
expect(applied.status).toBe("applied");
|
|
454
|
+
expect(w2.applied).toEqual(["a", "app"]);
|
|
455
|
+
expect(w2.ranWith.app).toEqual({ subnet: "a-v2" });
|
|
456
|
+
expect(applied.rounds?.map((r) => [r.round, r.status, r.approval.status])).toEqual([[2, "applied", "approved"]]);
|
|
457
|
+
expect(applied.members.find((m) => m.member === "app")).toMatchObject({ status: "applied", round: 2 });
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("an upstream new in the same pull request plans, and so does its dependent", async () => {
|
|
461
|
+
const pair: DriverComponent[] = [root("vpc"), root("svc", ["vpc"], { vpc: { stackOutput: { stack: "vpc", name: "id" } } })];
|
|
462
|
+
const w = world();
|
|
463
|
+
w.state = {};
|
|
464
|
+
// A new root: its plan creates it, and its id is known only after apply.
|
|
465
|
+
w.versions.vpc = "v2";
|
|
466
|
+
w.predict.vpc = () => ({ known: {}, unknown: ["id"] });
|
|
467
|
+
const p = planFanOut({ components: pair, changed: ["vpc", "svc"] });
|
|
468
|
+
const set = await planPrSet({ components: pair, plan: p, registry: w.registry, env: "prod" });
|
|
469
|
+
expect(set.failed).toBe(false);
|
|
470
|
+
expect(set.members.map((m) => [m.member, m.status, m.unknownInputs])).toEqual([
|
|
471
|
+
["svc", "planned", ["vpc.id"]],
|
|
472
|
+
["vpc", "planned", undefined],
|
|
473
|
+
]);
|
|
474
|
+
expect(w.outputsRead).toEqual([]);
|
|
475
|
+
expect(set.deployedOutputs).toBeUndefined();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("an upstream that applies as another value than its plan said holds the dependent back, which does not apply its approved plan", async () => {
|
|
479
|
+
const w = world();
|
|
480
|
+
w.versions.a = "v2";
|
|
481
|
+
w.appliesAs = "a-v2-real";
|
|
482
|
+
const set = await planPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod" });
|
|
483
|
+
expect(w.planned.find((p) => p.root === "app")?.vars).toEqual({ subnet: "a-v2" });
|
|
484
|
+
|
|
485
|
+
// Without a way to decide a later round, app is left waiting.
|
|
486
|
+
const held = await applyPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", set });
|
|
487
|
+
expect(held.status).toBe("waiting");
|
|
488
|
+
expect(w.applied).toEqual(["a"]);
|
|
489
|
+
expect(held.members.find((m) => m.member === "app")).toMatchObject({ status: "waiting", held: "its plan read a.id before that output applied as another value" });
|
|
490
|
+
expect(held.run.held).toEqual([{ component: "app", reason: "its plan read a.id before that output applied as another value" }]);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("a resumed apply carries on from a waiting round once its plan is approved", async () => {
|
|
494
|
+
const w = world();
|
|
495
|
+
w.versions.a = "v2";
|
|
496
|
+
w.predict.a = () => ({ known: {}, unknown: ["id"] });
|
|
497
|
+
const set = await planPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod" });
|
|
498
|
+
const first = await applyPrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", set, decideRound: decideWith(new Set()) });
|
|
499
|
+
expect(first.status).toBe("waiting");
|
|
500
|
+
const roundDigest = first.rounds![0]!.digest;
|
|
501
|
+
|
|
502
|
+
const record = recordOf(set);
|
|
503
|
+
const resumed = await resumePrSet({ components: ESTATE, plan: plan(), registry: w.registry, env: "prod", record, completed: ["a"] });
|
|
504
|
+
if (!resumed.ok) throw new Error(resumed.reasons.join("; "));
|
|
505
|
+
expect(resumed.plan.order).toEqual(["app"]);
|
|
506
|
+
const second = await applyPrSet({
|
|
507
|
+
components: ESTATE,
|
|
508
|
+
plan: resumed.plan,
|
|
509
|
+
registry: w.registry,
|
|
510
|
+
env: "prod",
|
|
511
|
+
set: resumed.set,
|
|
512
|
+
resumed: { completed: ["a"], outputs: { a: first.run.componentOutputs.a! } },
|
|
513
|
+
decideRound: decideWith(new Set([roundDigest])),
|
|
514
|
+
});
|
|
515
|
+
expect(second.status).toBe("applied");
|
|
516
|
+
expect(second.rounds?.[0]?.digest).toBe(roundDigest);
|
|
517
|
+
expect(w.applied).toEqual(["a", "app"]);
|
|
518
|
+
expect(w.ranWith.app).toEqual({ subnet: "a-v2" });
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe("a digest a build in the same pull request publishes (#3999)", () => {
|
|
523
|
+
/** `web-image` builds and publishes an image; `web` deploys it, and with `withSubnet` also reads `a.id`. */
|
|
524
|
+
const estate = (withSubnet = false): DriverComponent[] => [
|
|
525
|
+
root("a"),
|
|
526
|
+
{ name: "web-image", dependsOn: [], deploy: [{ phase: "Publish", steps: [{ kind: "fake-build" }] }] },
|
|
527
|
+
root("web", withSubnet ? ["web-image", "a"] : ["web-image"], {
|
|
528
|
+
image: "@web-image.publish.digest",
|
|
529
|
+
...(withSubnet ? { subnet: { stackOutput: { stack: "a", name: "id" } } } : {}),
|
|
530
|
+
}),
|
|
531
|
+
];
|
|
532
|
+
function buildWorld() {
|
|
533
|
+
const w = world();
|
|
534
|
+
let n = 0;
|
|
535
|
+
w.registry.register({
|
|
536
|
+
kind: "fake-build",
|
|
537
|
+
async run() {
|
|
538
|
+
n++;
|
|
539
|
+
return { uri: "registry.example/web", digest: `sha256:built-${n}` };
|
|
540
|
+
},
|
|
541
|
+
} as never);
|
|
542
|
+
return w;
|
|
543
|
+
}
|
|
544
|
+
const neverApproved = async () => ({ satisfied: false, approval: { status: "pending" as const } });
|
|
545
|
+
|
|
546
|
+
test("image plus deploy in one pull request: the deploy plans with the digest unknown, lists it as the build's, and applies in one round", async () => {
|
|
547
|
+
const w = buildWorld();
|
|
548
|
+
const components = estate();
|
|
549
|
+
const plan = planFanOut({ components, changed: ["web-image", "web"] });
|
|
550
|
+
const set = await planPrSet({ components, plan, registry: w.registry, env: "prod" });
|
|
551
|
+
expect(set.failed).toBe(false);
|
|
552
|
+
const web = set.members.find((m) => m.member === "web")!;
|
|
553
|
+
expect(web.buildInputs).toEqual(["web-image.publish.digest"]);
|
|
554
|
+
expect(web.unknownInputs).toBeUndefined();
|
|
555
|
+
|
|
556
|
+
let asked = 0;
|
|
557
|
+
const applied = await applyPrSet({ components, plan, registry: w.registry, env: "prod", set, decideRound: async () => (asked++, neverApproved()) });
|
|
558
|
+
expect(applied.status).toBe("applied");
|
|
559
|
+
expect(asked).toBe(0);
|
|
560
|
+
expect(applied.rounds).toBeUndefined();
|
|
561
|
+
expect(w.ranWith.web).toEqual({ image: "sha256:built-1" });
|
|
562
|
+
expect(applied.members.find((m) => m.member === "web")).toMatchObject({ status: "applied" });
|
|
563
|
+
|
|
564
|
+
const note = renderPrNote(prReport({ stage: "plan", pr: 12, env: "prod", base: "1".repeat(40), head: "2".repeat(40), op: "pr-12", gate: "pr-apply", plan, signal: { changed: ["web-image", "web"], indeterminate: [], unclaimed: [] }, set, approval: { status: "pending" }, status: "planned" }));
|
|
565
|
+
expect(note).toContain("From this pull request's build: `web` reads `web-image.publish.digest`. The image digest comes from this pull request's build");
|
|
566
|
+
expect(note).not.toContain("Known only after apply");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("a deploy that also reads an output known only after apply still goes to round 2", async () => {
|
|
570
|
+
const w = buildWorld();
|
|
571
|
+
w.versions.a = "v2";
|
|
572
|
+
w.predict.a = () => ({ known: {}, unknown: ["id"] });
|
|
573
|
+
const components = estate(true);
|
|
574
|
+
const plan = planFanOut({ components, changed: ["a", "web-image", "web"] });
|
|
575
|
+
const set = await planPrSet({ components, plan, registry: w.registry, env: "prod" });
|
|
576
|
+
const web = set.members.find((m) => m.member === "web")!;
|
|
577
|
+
expect(web.buildInputs).toEqual(["web-image.publish.digest"]);
|
|
578
|
+
expect(web.unknownInputs).toEqual(["a.id"]);
|
|
579
|
+
|
|
580
|
+
const applied = await applyPrSet({ components, plan, registry: w.registry, env: "prod", set, decideRound: neverApproved });
|
|
581
|
+
expect(applied.status).toBe("waiting");
|
|
582
|
+
expect(w.ranWith.web).toBeUndefined();
|
|
583
|
+
// Held for a.id alone: the image digest is not a reason.
|
|
584
|
+
expect(applied.members.find((m) => m.member === "web")).toMatchObject({ status: "waiting", round: 2, held: "its plan read a.id before that output applied as another value" });
|
|
585
|
+
expect(applied.rounds?.map((r) => [r.gate, r.components])).toEqual([["pr-apply-round-2", ["web"]]]);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test("under --wave-gate, the approved set covers a wave whose member waited only on the build's digest", () => {
|
|
589
|
+
const hole = (member: string, digest: string): ChangeSetPart => ({
|
|
590
|
+
member: { member, lexicon: "aws", planner: "chant", status: "planned", planDigest: `jcs1-sha256:${digest.repeat(64)}`, holes: [{ address: member, reason: "inputs from web-image; re-checked on apply" }] },
|
|
591
|
+
entries: [],
|
|
592
|
+
});
|
|
593
|
+
const approvedDoc = composeChangeSet([hole("web", "1")]);
|
|
594
|
+
const fresh = composeChangeSet([
|
|
595
|
+
{
|
|
596
|
+
member: { member: "web", lexicon: "aws", planner: "chant", status: "planned", planDigest: `jcs1-sha256:${"2".repeat(64)}`, holes: [] },
|
|
597
|
+
entries: [{ member: "web", lexicon: "aws", planner: "chant", address: "Service", type: "AWS::ECS::Service", action: "update", attributes: [{ path: "Image", after: "sha256:built-1" }] }],
|
|
598
|
+
},
|
|
599
|
+
]);
|
|
600
|
+
const member = (extra: Record<string, unknown>) => [{ member: "web", component: "web", planDigest: null, status: "planned" as const, counts: { create: 0, update: 0, replace: 0, delete: 0 }, ...extra }];
|
|
601
|
+
expect(waveNotCovered(approvedDoc, fresh, member({ buildInputs: ["web-image.publish.digest"] }))).toEqual([]);
|
|
602
|
+
expect(waveNotCovered(approvedDoc, fresh, member({ buildInputs: ["web-image.publish.digest"], unknownInputs: ["a.id"] }))).toHaveLength(1);
|
|
603
|
+
expect(waveNotCovered(approvedDoc, fresh)).toHaveLength(1);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
358
607
|
/** The record a first attempt leaves (`pr-apply --resume`), from the set it applied. */
|
|
359
608
|
function recordOf(set: PrPlanSet, approvedBy = "github:alice"): PrApplyRecord {
|
|
360
609
|
return {
|
|
@@ -463,7 +712,7 @@ describe("resuming an apply that failed partway (#3464)", () => {
|
|
|
463
712
|
expect(report.resumed).toEqual({ applied: ["b", "dns", "net"] });
|
|
464
713
|
});
|
|
465
714
|
|
|
466
|
-
test("a dependent whose dependency applied in the earlier attempt applies its approved plan
|
|
715
|
+
test("a dependent whose dependency applied as planned in the earlier attempt applies its approved plan", async () => {
|
|
467
716
|
const w = world();
|
|
468
717
|
w.versions.a = "v2";
|
|
469
718
|
const plan = planFanOut({ components: ESTATE, changed: ["a"] });
|
|
@@ -475,8 +724,8 @@ describe("resuming an apply that failed partway (#3464)", () => {
|
|
|
475
724
|
w.planned.length = 0;
|
|
476
725
|
const resumed = await resumePrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", record, completed: ["a"] });
|
|
477
726
|
if (!resumed.ok) throw new Error(resumed.reasons.join("; "));
|
|
478
|
-
// app is planned against the value it was approved with
|
|
479
|
-
expect(w.planned).toEqual([{ root: "app", vars: { subnet: "a-
|
|
727
|
+
// app is planned against the value it was approved with: the one a's plan said, which a wrote.
|
|
728
|
+
expect(w.planned).toEqual([{ root: "app", vars: { subnet: "a-v2" } }]);
|
|
480
729
|
|
|
481
730
|
const second = await applyPrSet({
|
|
482
731
|
components: ESTATE,
|
|
@@ -486,9 +735,11 @@ describe("resuming an apply that failed partway (#3464)", () => {
|
|
|
486
735
|
set: resumed.set,
|
|
487
736
|
resumed: { completed: ["a"], outputs },
|
|
488
737
|
});
|
|
738
|
+
expect(second.status).toBe("applied");
|
|
739
|
+
expect(w.ranWith.app).toEqual({ subnet: "a-v2" });
|
|
489
740
|
expect(second.members.map((m) => [m.member, m.status, m.inputsMoved])).toEqual([
|
|
490
741
|
["a", "applied", undefined],
|
|
491
|
-
["app", "applied",
|
|
742
|
+
["app", "applied", undefined],
|
|
492
743
|
]);
|
|
493
744
|
});
|
|
494
745
|
|
|
@@ -637,3 +888,57 @@ describe("planCoveredBy", () => {
|
|
|
637
888
|
expect(planCoveredBy(approved, other, "edge")).toBe("edge was not in the approved plan");
|
|
638
889
|
});
|
|
639
890
|
});
|
|
891
|
+
|
|
892
|
+
describe("a gate per wave on merge (#4003)", () => {
|
|
893
|
+
const estate = [root("net"), root("a", ["net"]), root("b", ["net"]), root("app", ["a"])];
|
|
894
|
+
const plan = planFanOut({ components: estate, changed: ["net"] });
|
|
895
|
+
|
|
896
|
+
test("prWavePlan narrows the derivation to one wave, less what applied, with the earlier waves as seeds and the same digest", () => {
|
|
897
|
+
expect(plan.waves).toEqual([["net"], ["a", "b"], ["app"]]);
|
|
898
|
+
const two = prWavePlan(plan, estate, 2);
|
|
899
|
+
expect(two).toMatchObject({ order: ["a", "b"], waves: [["a", "b"]], seeds: ["net"], digest: plan.digest });
|
|
900
|
+
expect(prWavePlan(plan, estate, 2, ["a"])).toMatchObject({ order: ["b"], waves: [["b"]] });
|
|
901
|
+
expect(prWavePlan(plan, estate, 2, ["a", "b"])).toMatchObject({ order: [], waves: [] });
|
|
902
|
+
expect(prWavePlan(plan, estate, 4)).toMatchObject({ order: [], waves: [] });
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const member = (name: string, digest: string, planner: "tofu" | "chant" = "tofu") => ({
|
|
906
|
+
member: name,
|
|
907
|
+
lexicon: planner === "tofu" ? "terraform" : "chant",
|
|
908
|
+
planner,
|
|
909
|
+
status: "planned" as const,
|
|
910
|
+
planDigest: `jcs1-sha256:${digest.repeat(64)}`,
|
|
911
|
+
holes: [],
|
|
912
|
+
});
|
|
913
|
+
const write = (name: string, cidr: unknown): ChangeSetEntry => ({
|
|
914
|
+
member: name,
|
|
915
|
+
lexicon: "terraform",
|
|
916
|
+
planner: "tofu",
|
|
917
|
+
address: `${name}.main`,
|
|
918
|
+
type: "aws_subnet",
|
|
919
|
+
action: "update",
|
|
920
|
+
attributes: cidr === undefined ? [{ path: "cidr", unknown: true }] : [{ path: "cidr", after: cidr }],
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("waveNotCovered: the same plan, or one the approved plan knew only after apply, is covered; a moved value is not", () => {
|
|
924
|
+
const approved = composeChangeSet([
|
|
925
|
+
{ member: member("a", "1"), entries: [write("a", "10.0.0.0/16")] },
|
|
926
|
+
{ member: member("b", "2"), entries: [write("b", undefined)] },
|
|
927
|
+
]);
|
|
928
|
+
const same = composeChangeSet([{ member: member("a", "1"), entries: [write("a", "10.0.0.0/16")] }]);
|
|
929
|
+
expect(waveNotCovered(approved, same)).toEqual([]);
|
|
930
|
+
// b's cidr was known only after net applied: the value it has now is covered.
|
|
931
|
+
const known = composeChangeSet([{ member: member("b", "3"), entries: [write("b", "10.1.0.0/16")] }]);
|
|
932
|
+
expect(waveNotCovered(approved, known)).toEqual([]);
|
|
933
|
+
const moved = composeChangeSet([{ member: member("a", "4"), entries: [write("a", "10.1.0.0/16")] }]);
|
|
934
|
+
expect(waveNotCovered(approved, moved)).toEqual(["a now writes a.main.cidr to a different value than the approved plan"]);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test("waveNotCovered: a member whose planner describes no changes is covered only by the digest it was approved with", () => {
|
|
938
|
+
const approved = composeChangeSet([{ member: member("job", "1", "chant"), entries: [] }]);
|
|
939
|
+
expect(waveNotCovered(approved, composeChangeSet([{ member: member("job", "1", "chant"), entries: [] }]))).toEqual([]);
|
|
940
|
+
const moved = waveNotCovered(approved, composeChangeSet([{ member: member("job", "2", "chant"), entries: [] }]));
|
|
941
|
+
expect(moved).toHaveLength(1);
|
|
942
|
+
expect(moved[0]).toMatch(/^job now plans to jcs1-sha256:2+, not the approved jcs1-sha256:1+, and its planner describes no changes to compare$/);
|
|
943
|
+
});
|
|
944
|
+
});
|