@intentius/chant-lexicon-terraform 0.102.0 → 0.104.0
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/__fixtures__/terragrunt/affected-cases.d.ts +23 -0
- package/dist/__fixtures__/terragrunt/affected-cases.d.ts.map +1 -0
- package/dist/__fixtures__/terragrunt/record.d.ts +17 -0
- package/dist/__fixtures__/terragrunt/record.d.ts.map +1 -0
- package/dist/change-set.d.ts +67 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/changed-roots.d.ts +30 -0
- package/dist/changed-roots.d.ts.map +1 -0
- package/dist/commands.d.ts +26 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/components/capability-plugin.d.ts +54 -0
- package/dist/components/capability-plugin.d.ts.map +1 -0
- package/dist/composites/terraform-pin-rollout-op.d.ts +44 -0
- package/dist/composites/terraform-pin-rollout-op.d.ts.map +1 -0
- package/dist/hcl/descend.d.ts +1 -1
- package/dist/hcl/descend.d.ts.map +1 -1
- package/dist/hcl/edges.d.ts +2 -2
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/parse.d.ts +29 -1
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/roots.d.ts +1 -1
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +13 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/module-source.d.ts +27 -1
- package/dist/lint/post-synth/module-source.d.ts.map +1 -1
- package/dist/lint/post-synth/terragrunt.d.ts +37 -0
- package/dist/lint/post-synth/terragrunt.d.ts.map +1 -0
- package/dist/lint/post-synth/tf002.d.ts +3 -1
- package/dist/lint/post-synth/tf002.d.ts.map +1 -1
- package/dist/lint/post-synth/tf038.d.ts +22 -0
- package/dist/lint/post-synth/tf038.d.ts.map +1 -0
- package/dist/lint/post-synth/tf039.d.ts +23 -0
- package/dist/lint/post-synth/tf039.d.ts.map +1 -0
- package/dist/lint/post-synth/tf040.d.ts +30 -0
- package/dist/lint/post-synth/tf040.d.ts.map +1 -0
- package/dist/lint/post-synth/tf041.d.ts +20 -0
- package/dist/lint/post-synth/tf041.d.ts.map +1 -0
- package/dist/lint/post-synth/tf042.d.ts +18 -0
- package/dist/lint/post-synth/tf042.d.ts.map +1 -0
- package/dist/lint/post-synth/tf043.d.ts +17 -0
- package/dist/lint/post-synth/tf043.d.ts.map +1 -0
- package/dist/lint/post-synth/tf044.d.ts +17 -0
- package/dist/lint/post-synth/tf044.d.ts.map +1 -0
- package/dist/lint/post-synth/tf045.d.ts +22 -0
- package/dist/lint/post-synth/tf045.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +8 -0
- package/dist/okf/rules/TF038.md +11 -0
- package/dist/okf/rules/TF039.md +11 -0
- package/dist/okf/rules/TF040.md +11 -0
- package/dist/okf/rules/TF041.md +11 -0
- package/dist/okf/rules/TF042.md +11 -0
- package/dist/okf/rules/TF043.md +11 -0
- package/dist/okf/rules/TF044.md +11 -0
- package/dist/okf/rules/TF045.md +11 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.d.ts +46 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -0
- package/dist/op/activities/terraform.d.ts +24 -50
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +48 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/pin/edit-ts.d.ts +21 -0
- package/dist/pin/edit-ts.d.ts.map +1 -0
- package/dist/pin/edit.d.ts +52 -0
- package/dist/pin/edit.d.ts.map +1 -0
- package/dist/pin/forge.d.ts +39 -0
- package/dist/pin/forge.d.ts.map +1 -0
- package/dist/pin/index.d.ts +22 -0
- package/dist/pin/index.d.ts.map +1 -0
- package/dist/pin/rollout.d.ts +116 -0
- package/dist/pin/rollout.d.ts.map +1 -0
- package/dist/pin/scan.d.ts +54 -0
- package/dist/pin/scan.d.ts.map +1 -0
- package/dist/pin/source.d.ts +93 -0
- package/dist/pin/source.d.ts.map +1 -0
- package/dist/pin/waves.d.ts +66 -0
- package/dist/pin/waves.d.ts.map +1 -0
- package/dist/plan-digest.d.ts +59 -0
- package/dist/plan-digest.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/module-source.ts +56 -1
- package/dist/rules/terragrunt.ts +76 -0
- package/dist/rules/tf002.ts +1 -1
- package/dist/rules/tf038.ts +71 -0
- package/dist/rules/tf039.ts +64 -0
- package/dist/rules/tf040.ts +76 -0
- package/dist/rules/tf041.ts +56 -0
- package/dist/rules/tf042.ts +42 -0
- package/dist/rules/tf043.ts +49 -0
- package/dist/rules/tf044.ts +51 -0
- package/dist/rules/tf045.ts +47 -0
- package/dist/terragrunt/affected.d.ts +119 -0
- package/dist/terragrunt/affected.d.ts.map +1 -0
- package/dist/terragrunt/index.d.ts +11 -0
- package/dist/terragrunt/index.d.ts.map +1 -0
- package/dist/terragrunt/mocks.d.ts +147 -0
- package/dist/terragrunt/mocks.d.ts.map +1 -0
- package/dist/terragrunt/run.d.ts +130 -0
- package/dist/terragrunt/run.d.ts.map +1 -0
- package/dist/terragrunt/units.d.ts +103 -0
- package/dist/terragrunt/units.d.ts.map +1 -0
- package/dist/terragrunt/wave.d.ts +89 -0
- package/dist/terragrunt/wave.d.ts.map +1 -0
- package/package.json +12 -2
- package/src/__fixtures__/change-set/choudoufu-set-plan.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/choudoufu-set-plan.json +76 -0
- package/src/__fixtures__/change-set/choudoufu.part.golden.json +173 -0
- package/src/__fixtures__/change-set/choudoufu.plan.json +610 -0
- package/src/__fixtures__/change-set/terraform.import-forget.plan.json +81 -0
- package/src/__fixtures__/change-set/terraform.part.golden.json +173 -0
- package/src/__fixtures__/change-set/terraform.plan.json +589 -0
- package/src/__fixtures__/change-set/tofu.part.golden.json +173 -0
- package/src/__fixtures__/change-set/tofu.plan.json +588 -0
- package/src/__fixtures__/change-set/v1.tf.txt +21 -0
- package/src/__fixtures__/change-set/v2-module-app.tf.txt +4 -0
- package/src/__fixtures__/change-set/v2.tf.txt +21 -0
- package/src/__fixtures__/pin/choudoufu-live-waves-n5.json +83 -0
- package/src/__fixtures__/pin/largeset-oci-n5/apply-order.txt +5 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/main.tf +33 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/main.tf +45 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/main.tf +47 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/main.tf +31 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/main.tf +27 -0
- package/src/__fixtures__/pin/largeset-oci-n5/fixture.json +77 -0
- package/src/__fixtures__/plan-summary/largeset-n5-bump.json +2071 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.json +95 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.md +22 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.txt +12 -0
- package/src/__fixtures__/terragrunt/affected/catalog/units/web/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/gitignore +3 -0
- package/src/__fixtures__/terragrunt/affected/live/dev/app/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/dev/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/prod/app/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/stg/terragrunt.stack.hcl +5 -0
- package/src/__fixtures__/terragrunt/affected/modules/app/main.tf +6 -0
- package/src/__fixtures__/terragrunt/affected/modules/app/policy.json +1 -0
- package/src/__fixtures__/terragrunt/affected/modules/net/main.tf +2 -0
- package/src/__fixtures__/terragrunt/affected/modules/thing/main.tf +3 -0
- package/src/__fixtures__/terragrunt/affected/root.hcl +5 -0
- package/src/__fixtures__/terragrunt/affected-cases.ts +126 -0
- package/src/__fixtures__/terragrunt/five-units/catalog/units/thing/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/app/terragrunt.hcl +13 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/db/terragrunt.hcl +8 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/live/prod/app/terragrunt.hcl +8 -0
- package/src/__fixtures__/terragrunt/five-units/live/prod/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/modules/thing/main.tf +7 -0
- package/src/__fixtures__/terragrunt/five-units/root.hcl +5 -0
- package/src/__fixtures__/terragrunt/record.ts +56 -0
- package/src/__fixtures__/terragrunt/recorded/affected-find.json +50 -0
- package/src/__fixtures__/terragrunt/recorded/failed-plan-report.json +36 -0
- package/src/__fixtures__/terragrunt/recorded/find.json +67 -0
- package/src/__fixtures__/terragrunt/recorded/version.txt +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/apply-report.json +22 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/dev/vpc/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/prod/vpc/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/plan-report.json +22 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/dev/db/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/prod/app/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/plan-report.json +22 -0
- package/src/change-set-combined-run.test.ts +156 -0
- package/src/change-set.test.ts +151 -0
- package/src/change-set.ts +273 -0
- package/src/changed-roots.test.ts +58 -0
- package/src/changed-roots.ts +102 -0
- package/src/commands.ts +112 -0
- package/src/components/capability-plugin.test.ts +91 -0
- package/src/components/capability-plugin.ts +131 -0
- package/src/components/pr-loop.tofu.test.ts +244 -0
- package/src/composites/terraform-pin-rollout-op.test.ts +69 -0
- package/src/composites/terraform-pin-rollout-op.ts +60 -0
- package/src/hcl/descend.ts +1 -1
- package/src/hcl/edges.ts +2 -2
- package/src/hcl/parse.test.ts +25 -0
- package/src/hcl/parse.ts +91 -6
- package/src/hcl/roots.ts +1 -1
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +18 -0
- package/src/lint/audit-catalog.ts +82 -0
- package/src/lint/audit.test.ts +3 -1
- package/src/lint/post-synth/fixtures/TF038/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF039/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF041/negative/terragrunt.hcl +12 -0
- package/src/lint/post-synth/fixtures/TF041/positive/terragrunt.hcl +7 -0
- package/src/lint/post-synth/fixtures/TF041/positive-apply/terragrunt.hcl +8 -0
- package/src/lint/post-synth/fixtures/TF042/negative/terragrunt.hcl +13 -0
- package/src/lint/post-synth/fixtures/TF042/positive/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF043/negative/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/negative-other/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-git/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-oci/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-tfr/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF044/negative/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF044/positive/terragrunt.hcl +7 -0
- package/src/lint/post-synth/fixtures/TF044/positive-generate/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF045/negative/root.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF045/negative/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF045/positive/root.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF045/positive/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF045/unit-only/terragrunt.hcl +3 -0
- package/src/lint/post-synth/index.ts +16 -0
- package/src/lint/post-synth/module-source.test.ts +42 -1
- package/src/lint/post-synth/module-source.ts +56 -1
- package/src/lint/post-synth/terragrunt.ts +76 -0
- package/src/lint/post-synth/tf002.ts +1 -1
- package/src/lint/post-synth/tf038.test.ts +51 -0
- package/src/lint/post-synth/tf038.ts +71 -0
- package/src/lint/post-synth/tf039.test.ts +40 -0
- package/src/lint/post-synth/tf039.ts +64 -0
- package/src/lint/post-synth/tf040.test.ts +55 -0
- package/src/lint/post-synth/tf040.ts +76 -0
- package/src/lint/post-synth/tf041.test.ts +50 -0
- package/src/lint/post-synth/tf041.ts +56 -0
- package/src/lint/post-synth/tf042.test.ts +19 -0
- package/src/lint/post-synth/tf042.ts +42 -0
- package/src/lint/post-synth/tf043.test.ts +65 -0
- package/src/lint/post-synth/tf043.ts +49 -0
- package/src/lint/post-synth/tf044.test.ts +27 -0
- package/src/lint/post-synth/tf044.ts +51 -0
- package/src/lint/post-synth/tf045.test.ts +30 -0
- package/src/lint/post-synth/tf045.ts +47 -0
- package/src/op/activities/index.ts +4 -0
- package/src/op/activities/pin-rollout.ts +77 -0
- package/src/op/activities/terraform.test.ts +13 -0
- package/src/op/activities/terraform.ts +46 -86
- package/src/op/activity-contracts.test.ts +4 -1
- package/src/op/activity-contracts.ts +42 -0
- package/src/op/builders.ts +13 -0
- package/src/pin/bundle.test.ts +53 -0
- package/src/pin/edit-ts.test.ts +39 -0
- package/src/pin/edit-ts.ts +77 -0
- package/src/pin/edit.test.ts +199 -0
- package/src/pin/edit.ts +157 -0
- package/src/pin/forge.ts +85 -0
- package/src/pin/index.ts +22 -0
- package/src/pin/rollout.test.ts +232 -0
- package/src/pin/rollout.ts +451 -0
- package/src/pin/scan.ts +309 -0
- package/src/pin/source.ts +206 -0
- package/src/pin/waves.test.ts +101 -0
- package/src/pin/waves.ts +125 -0
- package/src/plan-digest.ts +99 -0
- package/src/plan-summary.test.ts +154 -0
- package/src/plugin.ts +25 -2
- package/src/terragrunt/affected.acceptance.test.ts +83 -0
- package/src/terragrunt/affected.test.ts +177 -0
- package/src/terragrunt/affected.ts +348 -0
- package/src/terragrunt/index.ts +11 -0
- package/src/terragrunt/mocks.acceptance.test.ts +105 -0
- package/src/terragrunt/mocks.test.ts +309 -0
- package/src/terragrunt/mocks.ts +324 -0
- package/src/terragrunt/run.ts +369 -0
- package/src/terragrunt/terragrunt.acceptance.test.ts +122 -0
- package/src/terragrunt/terragrunt.test.ts +332 -0
- package/src/terragrunt/units.ts +235 -0
- package/src/terragrunt/wave.ts +181 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Terragrunt runner (#3414): discovery and one wave, run against a real
|
|
3
|
+
* `terragrunt`. The pure halves are `./units.ts` and `./wave.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Every call takes an injectable `exec`, so tests stub the process and the
|
|
6
|
+
* acceptance test runs the real binary.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import type { ChangeSetPart, ChangeSetPlanner } from "@intentius/chant/change-set";
|
|
13
|
+
import { plannerForBinary } from "../change-set";
|
|
14
|
+
import {
|
|
15
|
+
parseTerragruntFind,
|
|
16
|
+
terragruntFindArgs,
|
|
17
|
+
terragruntVersionProblem,
|
|
18
|
+
type TerragruntFindOptions,
|
|
19
|
+
type TerragruntUnit,
|
|
20
|
+
} from "./units";
|
|
21
|
+
import {
|
|
22
|
+
describeMockRefusal,
|
|
23
|
+
failMockedParts,
|
|
24
|
+
markProvisional,
|
|
25
|
+
parseRenderedDependencies,
|
|
26
|
+
parseTerragruntOutputs,
|
|
27
|
+
PROVISIONAL_MARKER,
|
|
28
|
+
terragruntMockReads,
|
|
29
|
+
terragruntMockWarnings,
|
|
30
|
+
terragruntOutputArgs,
|
|
31
|
+
terragruntOutputUpstreams,
|
|
32
|
+
terragruntRenderArgs,
|
|
33
|
+
type RenderedDependency,
|
|
34
|
+
type TerragruntMockRead,
|
|
35
|
+
} from "./mocks";
|
|
36
|
+
import {
|
|
37
|
+
parseTerragruntReport,
|
|
38
|
+
terragruntEnv,
|
|
39
|
+
terragruntUnitResults,
|
|
40
|
+
terragruntWaveArgs,
|
|
41
|
+
terragruntWaveParts,
|
|
42
|
+
type TerragruntUnitResult,
|
|
43
|
+
} from "./wave";
|
|
44
|
+
|
|
45
|
+
export interface ExecResult {
|
|
46
|
+
/** The exit code; null when the process did not exit on its own. */
|
|
47
|
+
code: number | null;
|
|
48
|
+
stdout: string;
|
|
49
|
+
stderr: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Runs `file args` in `cwd` with `env` added to the process environment. Never throws on a non-zero exit. */
|
|
53
|
+
export type TerragruntExec = (file: string, args: readonly string[], options: { cwd: string; env: Record<string, string> }) => Promise<ExecResult>;
|
|
54
|
+
|
|
55
|
+
export const defaultTerragruntExec: TerragruntExec = (file, args, options) =>
|
|
56
|
+
new Promise((done) => {
|
|
57
|
+
execFile(
|
|
58
|
+
file,
|
|
59
|
+
[...args],
|
|
60
|
+
{ cwd: options.cwd, env: { ...process.env, ...options.env }, maxBuffer: 256 * 1024 * 1024 },
|
|
61
|
+
(err, stdout, stderr) => {
|
|
62
|
+
const code = err ? (typeof (err as { code?: unknown }).code === "number" ? ((err as { code: number }).code) : null) : 0;
|
|
63
|
+
done({ code, stdout: String(stdout), stderr: err && code === null ? `${String(stderr)}${err.message}` : String(stderr) });
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export interface TerragruntRunOptions {
|
|
69
|
+
/** The project directory Terragrunt runs in. Unit paths are relative to it. */
|
|
70
|
+
dir: string;
|
|
71
|
+
/** The `terragrunt` executable. Default `terragrunt` on PATH. */
|
|
72
|
+
terragrunt?: string;
|
|
73
|
+
/** The project's `binary`: what Terragrunt calls, via `TG_TF_PATH`. Unset leaves Terragrunt's own choice. */
|
|
74
|
+
binary?: string;
|
|
75
|
+
exec?: TerragruntExec;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Thrown when `terragrunt` is missing, too old or fails. */
|
|
79
|
+
export class TerragruntError extends Error {
|
|
80
|
+
constructor(message: string) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.name = "TerragruntError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Refuse a Terragrunt older than 1.1. Returns the version line. */
|
|
87
|
+
export async function checkTerragruntVersion(options: TerragruntRunOptions): Promise<string> {
|
|
88
|
+
const exec = options.exec ?? defaultTerragruntExec;
|
|
89
|
+
const tg = options.terragrunt ?? "terragrunt";
|
|
90
|
+
const r = await exec(tg, ["--version"], { cwd: options.dir, env: {} });
|
|
91
|
+
if (r.code !== 0) throw new TerragruntError(`\`${tg} --version\` failed (exit ${r.code}): ${(r.stderr || r.stdout).trim()}`);
|
|
92
|
+
const problem = terragruntVersionProblem(r.stdout);
|
|
93
|
+
if (problem) throw new TerragruntError(problem);
|
|
94
|
+
return r.stdout.trim();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TerragruntDiscovery {
|
|
98
|
+
units: TerragruntUnit[];
|
|
99
|
+
/** Units whose own configuration names a `terraform_binary` that `TG_TF_PATH` overrides. */
|
|
100
|
+
warnings: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The project's units, from `terragrunt find`, with catalog templates and the
|
|
105
|
+
* module cache left out and `.terragrunt-filters` honoured.
|
|
106
|
+
*/
|
|
107
|
+
export async function discoverTerragruntUnits(options: TerragruntRunOptions & TerragruntFindOptions): Promise<TerragruntDiscovery> {
|
|
108
|
+
const exec = options.exec ?? defaultTerragruntExec;
|
|
109
|
+
const tg = options.terragrunt ?? "terragrunt";
|
|
110
|
+
const args = terragruntFindArgs(options);
|
|
111
|
+
const r = await exec(tg, args, { cwd: options.dir, env: terragruntEnv(options.binary) });
|
|
112
|
+
if (r.code !== 0) throw new TerragruntError(`\`${tg} ${args.join(" ")}\` failed (exit ${r.code}): ${r.stderr.trim()}`);
|
|
113
|
+
let units: TerragruntUnit[];
|
|
114
|
+
try {
|
|
115
|
+
units = parseTerragruntFind(r.stdout);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
throw new TerragruntError(`could not read \`terragrunt find\` output: ${err instanceof Error ? err.message : String(err)}`);
|
|
118
|
+
}
|
|
119
|
+
return { units, warnings: options.binary ? terraformBinaryWarnings(options.dir, units, options.binary) : [] };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const TERRAFORM_BINARY = /^\s*terraform_binary\s*=\s*(.+?)\s*$/m;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A warning per unit whose `terragrunt.hcl`, or a file it includes, sets
|
|
126
|
+
* `terraform_binary` to something other than `binary`. `TG_TF_PATH` wins over
|
|
127
|
+
* it (observed on 1.1.6), so the unit runs `binary`, not what it names.
|
|
128
|
+
*/
|
|
129
|
+
export function terraformBinaryWarnings(dir: string, units: readonly TerragruntUnit[], binary: string): string[] {
|
|
130
|
+
const out: string[] = [];
|
|
131
|
+
for (const u of units) {
|
|
132
|
+
const files = [join(u.path, "terragrunt.hcl"), ...Object.values(u.include ?? {})];
|
|
133
|
+
for (const f of files) {
|
|
134
|
+
let text: string;
|
|
135
|
+
try {
|
|
136
|
+
text = readFileSync(resolve(dir, f), "utf8");
|
|
137
|
+
} catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const m = TERRAFORM_BINARY.exec(text);
|
|
141
|
+
if (!m) continue;
|
|
142
|
+
const literal = /^"([^"$]*)"$/.exec(m[1]!);
|
|
143
|
+
if (literal && literal[1] === binary) break;
|
|
144
|
+
out.push(
|
|
145
|
+
`${u.path}: ${f} sets terraform_binary = ${m[1]}, but the project's binary is ${binary}; ` +
|
|
146
|
+
`TG_TF_PATH=${binary} overrides it, so the unit runs ${binary}`,
|
|
147
|
+
);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The planner a wave's plans come from: the project's binary, else Terragrunt's default of tofu. */
|
|
155
|
+
export function terragruntPlanner(binary: string | undefined): ChangeSetPlanner {
|
|
156
|
+
return plannerForBinary(binary ?? "tofu");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface TerragruntWaveRunInput extends TerragruntRunOptions {
|
|
160
|
+
units: readonly string[];
|
|
161
|
+
/** A directory for this wave's plans, plan JSON and reports. Plan and apply of one wave share it. */
|
|
162
|
+
workDir: string;
|
|
163
|
+
destroy?: boolean;
|
|
164
|
+
parallelism?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Plan a preview of units whose upstream has not applied (`dependents:
|
|
167
|
+
* plan` at PR time, #3416). It skips the mock check, so it may read
|
|
168
|
+
* `mock_outputs`; its members are marked provisional, and its saved plans
|
|
169
|
+
* are never applied. Unset, a wave that would read a mock is refused.
|
|
170
|
+
*/
|
|
171
|
+
provisional?: boolean;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Thrown when a wave's plans would read `mock_outputs` (#3416). Nothing in
|
|
176
|
+
* the wave was planned, so there is nothing to gate. `reads` names each unit,
|
|
177
|
+
* dependency and upstream.
|
|
178
|
+
*/
|
|
179
|
+
export class TerragruntMockRefusal extends TerragruntError {
|
|
180
|
+
constructor(readonly reads: TerragruntMockRead[]) {
|
|
181
|
+
super(describeMockRefusal(reads));
|
|
182
|
+
this.name = "TerragruntMockRefusal";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Run `fn` over `items`, at most `limit` at a time, keeping their order. */
|
|
187
|
+
async function pool<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]> {
|
|
188
|
+
const out: R[] = new Array(items.length);
|
|
189
|
+
let next = 0;
|
|
190
|
+
const worker = async (): Promise<void> => {
|
|
191
|
+
while (next < items.length) {
|
|
192
|
+
const i = next++;
|
|
193
|
+
out[i] = await fn(items[i]!);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** `dir` and its real path, for reading the absolute paths Terragrunt prints. */
|
|
201
|
+
function dirsOf(dir: string): string[] {
|
|
202
|
+
const abs = resolve(dir);
|
|
203
|
+
try {
|
|
204
|
+
const real = realpathSync(abs);
|
|
205
|
+
return real === abs ? [abs] : [abs, real];
|
|
206
|
+
} catch {
|
|
207
|
+
return [abs];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface TerragruntMockCheckInput extends TerragruntRunOptions {
|
|
212
|
+
units: readonly string[];
|
|
213
|
+
/** How many `render` and `output` calls run at once. Default 8. */
|
|
214
|
+
parallelism?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Which dependencies of the wave's units would read `mock_outputs` at plan,
|
|
219
|
+
* asked of Terragrunt: `render --json` in each unit for its evaluated
|
|
220
|
+
* `dependency` blocks, then `output -json` in each upstream they read. Empty
|
|
221
|
+
* when the wave would plan on real outputs only. Throws when Terragrunt
|
|
222
|
+
* cannot render a unit or read an upstream's outputs, since then nobody
|
|
223
|
+
* knows what the plan would read. A unit with no `terragrunt.hcl` in `dir`
|
|
224
|
+
* (removed by the change, so planned as a destroy) is not rendered.
|
|
225
|
+
*/
|
|
226
|
+
export async function checkTerragruntWaveMocks(input: TerragruntMockCheckInput): Promise<TerragruntMockRead[]> {
|
|
227
|
+
const exec = input.exec ?? defaultTerragruntExec;
|
|
228
|
+
const tg = input.terragrunt ?? "terragrunt";
|
|
229
|
+
const env = terragruntEnv(input.binary);
|
|
230
|
+
const limit = input.parallelism ?? 8;
|
|
231
|
+
const dirs = dirsOf(input.dir);
|
|
232
|
+
// A unit this change removed has no terragrunt.hcl here: Terragrunt plans its destroy from the old checkout.
|
|
233
|
+
// It is not rendered; the plan log check still covers it.
|
|
234
|
+
const units = [...new Set(input.units)].sort().filter((u) => existsSync(join(resolve(input.dir), u, "terragrunt.hcl")));
|
|
235
|
+
const rendered = await pool(units, limit, async (unit): Promise<[string, RenderedDependency[]]> => {
|
|
236
|
+
const args = terragruntRenderArgs(unit);
|
|
237
|
+
const r = await exec(tg, args, { cwd: input.dir, env });
|
|
238
|
+
if (r.code !== 0) throw new TerragruntError(`\`${tg} ${args.join(" ")}\` failed (exit ${r.code}), so the wave's dependencies are unknown: ${r.stderr.trim()}`);
|
|
239
|
+
try {
|
|
240
|
+
return [unit, parseRenderedDependencies(unit, r.stdout, dirs)];
|
|
241
|
+
} catch (err) {
|
|
242
|
+
throw new TerragruntError(`could not read \`terragrunt render --json\` for ${unit}: ${err instanceof Error ? err.message : String(err)}`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const dependencies = new Map(rendered);
|
|
246
|
+
const upstreams = terragruntOutputUpstreams(dependencies);
|
|
247
|
+
const outputs = await pool(upstreams, limit, async (upstream): Promise<[string, Record<string, unknown>]> => {
|
|
248
|
+
const args = terragruntOutputArgs(upstream);
|
|
249
|
+
const r = await exec(tg, args, { cwd: input.dir, env });
|
|
250
|
+
const parsed = r.code === 0 ? parseTerragruntOutputs(r.stdout) : undefined;
|
|
251
|
+
if (!parsed) {
|
|
252
|
+
throw new TerragruntError(
|
|
253
|
+
`could not read the outputs of ${upstream}, which the wave reads (\`${tg} ${args.join(" ")}\` exit ${r.code}): ${(r.stderr || r.stdout).trim().slice(-2000)}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return [upstream, parsed];
|
|
257
|
+
});
|
|
258
|
+
return terragruntMockReads({ dependencies, outputs: new Map(outputs) });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface TerragruntWavePlan {
|
|
262
|
+
/** The change-set parts, one per unit. Marked provisional for a provisional plan. */
|
|
263
|
+
parts: ChangeSetPart[];
|
|
264
|
+
/** Whether this was a provisional plan. */
|
|
265
|
+
provisional: boolean;
|
|
266
|
+
results: TerragruntUnitResult[];
|
|
267
|
+
/** Terragrunt's exit code. */
|
|
268
|
+
code: number | null;
|
|
269
|
+
/** Terragrunt's stderr, for the job log. */
|
|
270
|
+
log: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Plan one wave with one `terragrunt run --all`, and read each unit's plan
|
|
275
|
+
* JSON and run-report row.
|
|
276
|
+
*
|
|
277
|
+
* Unless the plan is provisional, the wave is first checked for mock reads
|
|
278
|
+
* ({@link checkTerragruntWaveMocks}) and refused with a
|
|
279
|
+
* {@link TerragruntMockRefusal} when any dependency would read
|
|
280
|
+
* `mock_outputs`; nothing is planned then. A unit the plan log still shows
|
|
281
|
+
* reading a mock (its upstream's state went away in between) becomes a
|
|
282
|
+
* failed member.
|
|
283
|
+
*/
|
|
284
|
+
export async function planTerragruntWave(input: TerragruntWaveRunInput): Promise<TerragruntWavePlan> {
|
|
285
|
+
const exec = input.exec ?? defaultTerragruntExec;
|
|
286
|
+
const tg = input.terragrunt ?? "terragrunt";
|
|
287
|
+
const work = resolve(input.dir, input.workDir);
|
|
288
|
+
const outDir = join(work, "plans");
|
|
289
|
+
const jsonOutDir = join(work, "json");
|
|
290
|
+
const reportFile = join(work, "plan-report.json");
|
|
291
|
+
const marker = join(work, PROVISIONAL_MARKER);
|
|
292
|
+
// A plan or report left by an earlier attempt must not stand in for a unit that did not plan this time.
|
|
293
|
+
for (const stale of [outDir, jsonOutDir, reportFile, marker]) rmSync(stale, { recursive: true, force: true });
|
|
294
|
+
if (!input.provisional) {
|
|
295
|
+
const reads = await checkTerragruntWaveMocks({ ...input, exec });
|
|
296
|
+
if (reads.length > 0) throw new TerragruntMockRefusal(reads);
|
|
297
|
+
}
|
|
298
|
+
mkdirSync(work, { recursive: true });
|
|
299
|
+
// Written before planning, so a plan cut short still never applies.
|
|
300
|
+
if (input.provisional) writeFileSync(marker, "planned before its upstream applied; never applied\n");
|
|
301
|
+
const args = terragruntWaveArgs({
|
|
302
|
+
units: input.units,
|
|
303
|
+
command: "plan",
|
|
304
|
+
outDir,
|
|
305
|
+
jsonOutDir,
|
|
306
|
+
reportFile,
|
|
307
|
+
...(input.destroy ? { destroy: true } : {}),
|
|
308
|
+
...(input.parallelism !== undefined ? { parallelism: input.parallelism } : {}),
|
|
309
|
+
});
|
|
310
|
+
const r = await exec(tg, args, { cwd: input.dir, env: terragruntEnv(input.binary) });
|
|
311
|
+
const report = existsSync(reportFile) ? parseTerragruntReport(readFileSync(reportFile, "utf8")) : new Map();
|
|
312
|
+
const planFor = (unit: string): unknown => {
|
|
313
|
+
const f = join(jsonOutDir, unit, "tfplan.json");
|
|
314
|
+
return existsSync(f) ? (JSON.parse(readFileSync(f, "utf8")) as unknown) : undefined;
|
|
315
|
+
};
|
|
316
|
+
const parts = terragruntWaveParts({ units: input.units, report, planFor, planner: terragruntPlanner(input.binary) });
|
|
317
|
+
const inWave = new Set(input.units);
|
|
318
|
+
const mocked = terragruntMockWarnings(`${r.stderr}\n${r.stdout}`, dirsOf(input.dir)).filter((m) => inWave.has(m.unit));
|
|
319
|
+
return {
|
|
320
|
+
parts: input.provisional ? markProvisional(parts) : failMockedParts(parts, mocked),
|
|
321
|
+
provisional: input.provisional === true,
|
|
322
|
+
results: terragruntUnitResults(input.units, report),
|
|
323
|
+
code: r.code,
|
|
324
|
+
log: r.stderr,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface TerragruntWaveApply {
|
|
329
|
+
results: TerragruntUnitResult[];
|
|
330
|
+
code: number | null;
|
|
331
|
+
log: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Apply one planned wave from its saved plans, with the same filters. Refuses
|
|
336
|
+
* before running anything when a unit of the wave has no saved plan, since
|
|
337
|
+
* Terragrunt would plan it afresh and apply a plan nobody saw, and when the
|
|
338
|
+
* plans are provisional.
|
|
339
|
+
*/
|
|
340
|
+
export async function applyTerragruntWave(input: TerragruntWaveRunInput): Promise<TerragruntWaveApply> {
|
|
341
|
+
const exec = input.exec ?? defaultTerragruntExec;
|
|
342
|
+
const tg = input.terragrunt ?? "terragrunt";
|
|
343
|
+
const work = resolve(input.dir, input.workDir);
|
|
344
|
+
const outDir = join(work, "plans");
|
|
345
|
+
if (input.provisional || existsSync(join(work, PROVISIONAL_MARKER))) {
|
|
346
|
+
throw new TerragruntError(
|
|
347
|
+
`the plans under ${work} are provisional: planned before their upstream applied, possibly on mock_outputs. ` +
|
|
348
|
+
`They are never applied; plan the wave again once its upstream applied`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const missing = [...new Set(input.units)].sort().filter((u) => !existsSync(join(outDir, u, "tfplan.tfplan")));
|
|
352
|
+
if (missing.length > 0) {
|
|
353
|
+
throw new TerragruntError(`no saved plan for ${missing.join(", ")} under ${outDir}; plan the wave before applying it, so apply runs the plan that was reviewed`);
|
|
354
|
+
}
|
|
355
|
+
const reportFile = join(work, "apply-report.json");
|
|
356
|
+
rmSync(reportFile, { force: true });
|
|
357
|
+
mkdirSync(dirname(reportFile), { recursive: true });
|
|
358
|
+
const args = terragruntWaveArgs({
|
|
359
|
+
units: input.units,
|
|
360
|
+
command: "apply",
|
|
361
|
+
outDir,
|
|
362
|
+
reportFile,
|
|
363
|
+
...(input.destroy ? { destroy: true } : {}),
|
|
364
|
+
...(input.parallelism !== undefined ? { parallelism: input.parallelism } : {}),
|
|
365
|
+
});
|
|
366
|
+
const r = await exec(tg, args, { cwd: input.dir, env: terragruntEnv(input.binary) });
|
|
367
|
+
const report = existsSync(reportFile) ? parseTerragruntReport(readFileSync(reportFile, "utf8")) : new Map();
|
|
368
|
+
return { results: terragruntUnitResults(input.units, report), code: r.code, log: r.stderr };
|
|
369
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terragrunt units as roots (#3414), against a real `terragrunt` 1.1+ and
|
|
3
|
+
* `tofu`: on the five-unit fixture (implicit stacks), discovery matches
|
|
4
|
+
* `terragrunt find`, each wave plans and applies exactly its units, and the
|
|
5
|
+
* run report's per-unit results reach the change set.
|
|
6
|
+
*
|
|
7
|
+
* The fixture's units hold `terraform_data` only, with local state under the
|
|
8
|
+
* copied fixture, so no provider is downloaded and no cloud is called.
|
|
9
|
+
*
|
|
10
|
+
* Skipped, with the reason in the title, when `terragrunt` or `tofu` is not
|
|
11
|
+
* on PATH. Release binaries: https://github.com/gruntwork-io/terragrunt/releases.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
20
|
+
import { composeChangeSet } from "@intentius/chant/change-set";
|
|
21
|
+
import {
|
|
22
|
+
applyTerragruntWave,
|
|
23
|
+
checkTerragruntVersion,
|
|
24
|
+
defaultTerragruntExec,
|
|
25
|
+
discoverTerragruntUnits,
|
|
26
|
+
planTerragruntWave,
|
|
27
|
+
terragruntWaves,
|
|
28
|
+
} from "./index";
|
|
29
|
+
|
|
30
|
+
function onPath(cmd: string): boolean {
|
|
31
|
+
try {
|
|
32
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const skipReason = !onPath("terragrunt") ? "no terragrunt on PATH" : !onPath("tofu") ? "no tofu on PATH" : "";
|
|
40
|
+
const fixture = join(dirname(fileURLToPath(import.meta.url)), "../__fixtures__/terragrunt/five-units");
|
|
41
|
+
const tmp = mkdtempSync(join(tmpdir(), "chant-tg-accept-"));
|
|
42
|
+
afterAll(() => rmSync(tmp, { recursive: true, force: true }));
|
|
43
|
+
|
|
44
|
+
/** Units with state, by walking `.state/` as the fixture's root.hcl lays it out. */
|
|
45
|
+
function unitsWithState(dir: string): string[] {
|
|
46
|
+
const out: string[] = [];
|
|
47
|
+
const walk = (rel: string): void => {
|
|
48
|
+
const abs = join(dir, ".state", rel);
|
|
49
|
+
if (!existsSync(abs)) return;
|
|
50
|
+
for (const e of readdirSync(abs, { withFileTypes: true })) {
|
|
51
|
+
if (e.isFile() && e.name === "terraform.tfstate") out.push(rel);
|
|
52
|
+
else if (e.isDirectory()) walk(rel ? `${rel}/${e.name}` : e.name);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
walk("");
|
|
56
|
+
return out.sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe.skipIf(skipReason !== "")(`Terragrunt units against a real terragrunt${skipReason ? ` (skipped: ${skipReason})` : ""}`, () => {
|
|
60
|
+
const dir = join(tmp, "repo");
|
|
61
|
+
cpSync(fixture, dir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
it("runs on 1.1 or later", async () => {
|
|
64
|
+
expect(await checkTerragruntVersion({ dir })).toMatch(/terragrunt version v\d/);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("discovers what terragrunt find lists, less the catalog template", async () => {
|
|
68
|
+
const plain = await defaultTerragruntExec("terragrunt", ["find", "--json", "--no-color"], { cwd: dir, env: {} });
|
|
69
|
+
const listed = (JSON.parse(plain.stdout) as Array<{ path: string }>).map((u) => u.path).sort();
|
|
70
|
+
expect(listed).toContain("catalog/units/thing");
|
|
71
|
+
const { units, warnings } = await discoverTerragruntUnits({ dir, binary: "tofu" });
|
|
72
|
+
expect(units.map((u) => u.path).sort()).toEqual(listed.filter((p) => !p.startsWith("catalog/")));
|
|
73
|
+
expect(warnings).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it(
|
|
77
|
+
"plans and applies each wave's units and no others, and the run report reaches the change set",
|
|
78
|
+
async () => {
|
|
79
|
+
const { units } = await discoverTerragruntUnits({ dir, binary: "tofu" });
|
|
80
|
+
const waves = terragruntWaves(units);
|
|
81
|
+
expect(waves).toEqual([["live/dev/vpc", "live/prod/vpc"], ["live/dev/db", "live/prod/app"], ["live/dev/app"]]);
|
|
82
|
+
const applied: string[] = [];
|
|
83
|
+
for (const [i, wave] of waves.entries()) {
|
|
84
|
+
const workDir = join(tmp, `wave-${i + 1}`);
|
|
85
|
+
const plan = await planTerragruntWave({ dir, units: wave, workDir, binary: "tofu", parallelism: 4 });
|
|
86
|
+
expect(plan.code, plan.log).toBe(0);
|
|
87
|
+
expect(plan.results.map((r) => [r.unit, r.result])).toEqual(wave.map((u) => [u, "succeeded"]));
|
|
88
|
+
expect(readdirSync(join(workDir, "json"), { recursive: true }).filter((f) => String(f).endsWith("tfplan.json")).map((f) => dirname(String(f))).sort()).toEqual(wave);
|
|
89
|
+
|
|
90
|
+
const doc = composeChangeSet(plan.parts);
|
|
91
|
+
expect(doc.members.map((m) => [m.member, m.status])).toEqual(wave.map((u) => [u, "planned"]));
|
|
92
|
+
expect(doc.summary.actions.create).toBe(wave.length);
|
|
93
|
+
// Each wave planned after its upstream applied, so no input carries a mock.
|
|
94
|
+
expect(JSON.stringify(doc.entries)).not.toContain('"mock"');
|
|
95
|
+
|
|
96
|
+
expect(unitsWithState(dir)).toEqual([...applied].sort());
|
|
97
|
+
const apply = await applyTerragruntWave({ dir, units: wave, workDir, binary: "tofu" });
|
|
98
|
+
expect(apply.code, apply.log).toBe(0);
|
|
99
|
+
expect(apply.results.map((r) => [r.unit, r.result])).toEqual(wave.map((u) => [u, "succeeded"]));
|
|
100
|
+
applied.push(...wave);
|
|
101
|
+
expect(unitsWithState(dir)).toEqual([...applied].sort());
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
120_000,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
it("keeps a wave to its units when .terragrunt-filters selects more", async () => {
|
|
108
|
+
const other = join(tmp, "with-filters-file");
|
|
109
|
+
cpSync(fixture, other, { recursive: true });
|
|
110
|
+
const { writeFileSync } = await import("node:fs");
|
|
111
|
+
writeFileSync(join(other, ".terragrunt-filters"), "./live/prod/**\n");
|
|
112
|
+
const { units } = await discoverTerragruntUnits({ dir: other });
|
|
113
|
+
expect(units.map((u) => u.path).sort()).toEqual(["live/prod/app", "live/prod/vpc"]);
|
|
114
|
+
// Terragrunt unions the file's filters with the command line's, so a wave run must not read it.
|
|
115
|
+
const plan = await planTerragruntWave({ dir: other, units: ["live/dev/vpc"], workDir: join(tmp, "filters-wave") });
|
|
116
|
+
expect(plan.code, plan.log).toBe(0);
|
|
117
|
+
expect(plan.results.map((r) => [r.unit, r.result])).toEqual([["live/dev/vpc", "succeeded"]]);
|
|
118
|
+
expect(readdirSync(join(tmp, "filters-wave", "json"), { recursive: true }).filter((f) => String(f).endsWith("tfplan.json"))).toEqual([
|
|
119
|
+
join("live", "dev", "vpc", "tfplan.json"),
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
});
|