@intentius/chant-lexicon-terraform 0.124.54 → 0.124.56
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/README.md +7 -4
- package/dist/ci-setup.d.ts +11 -0
- package/dist/ci-setup.d.ts.map +1 -0
- package/dist/ci-setup.js +41 -0
- package/dist/components/capability-plugin.d.ts +40 -6
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +174 -30
- package/dist/components/compose-group.d.ts +1 -1
- package/dist/components/compose-group.d.ts.map +1 -1
- package/dist/components/compose-group.js +1 -1
- package/dist/components/release-inputs.d.ts +29 -0
- package/dist/components/release-inputs.d.ts.map +1 -0
- package/dist/components/release-inputs.js +57 -0
- package/dist/components/trace-facts.d.ts +16 -0
- package/dist/components/trace-facts.d.ts.map +1 -0
- package/dist/components/trace-facts.js +19 -0
- package/dist/cone.d.ts +53 -0
- package/dist/cone.d.ts.map +1 -0
- package/dist/cone.js +119 -0
- package/dist/cost-hold.d.ts +27 -0
- package/dist/cost-hold.d.ts.map +1 -0
- package/dist/cost-hold.js +21 -0
- package/dist/drift-rows.d.ts +61 -0
- package/dist/drift-rows.d.ts.map +1 -0
- package/dist/drift-rows.js +108 -0
- package/dist/exit-codes.d.ts +55 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/integrity.json +7 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-catalog.js +27 -3
- package/dist/lint/audit-lineage.d.ts +14 -2
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +4 -0
- package/dist/lint/post-synth/record-store.d.ts +54 -0
- package/dist/lint/post-synth/record-store.d.ts.map +1 -0
- package/dist/lint/post-synth/record-store.js +104 -0
- package/dist/lint/post-synth/tf001.d.ts +2 -2
- package/dist/lint/post-synth/tf001.js +2 -2
- package/dist/lint/post-synth/tf024.d.ts +35 -11
- package/dist/lint/post-synth/tf024.d.ts.map +1 -1
- package/dist/lint/post-synth/tf024.js +92 -20
- package/dist/lint/post-synth/tf046.d.ts +64 -0
- package/dist/lint/post-synth/tf046.d.ts.map +1 -0
- package/dist/lint/post-synth/tf046.js +175 -0
- package/dist/lint/post-synth/tf047.d.ts +36 -0
- package/dist/lint/post-synth/tf047.d.ts.map +1 -0
- package/dist/lint/post-synth/tf047.js +94 -0
- package/dist/lock-rows.d.ts +36 -0
- package/dist/lock-rows.d.ts.map +1 -0
- package/dist/lock-rows.js +60 -0
- package/dist/lock-timeout.d.ts +22 -0
- package/dist/lock-timeout.d.ts.map +1 -0
- package/dist/lock-timeout.js +32 -0
- package/dist/manifest.json +1 -1
- package/dist/module-catalog/index.d.ts +69 -0
- package/dist/module-catalog/index.d.ts.map +1 -0
- package/dist/module-catalog/index.js +110 -0
- package/dist/module-catalog/refresh-cli.d.ts +4 -0
- package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
- package/dist/module-catalog/refresh-cli.js +64 -0
- package/dist/module-catalog/static-tier.d.ts +6 -0
- package/dist/module-catalog/static-tier.d.ts.map +1 -0
- package/dist/module-catalog/static-tier.js +1650 -0
- package/dist/module-catalog/types.d.ts +65 -0
- package/dist/module-catalog/types.d.ts.map +1 -0
- package/dist/module-catalog/types.js +33 -0
- package/dist/okf/index.md +3 -1
- package/dist/okf/rules/TF024.md +2 -2
- package/dist/okf/rules/TF046.md +11 -0
- package/dist/okf/rules/TF047.md +11 -0
- package/dist/op/activities/terraform.d.ts +29 -0
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +19 -4
- package/dist/pin/consumers.d.ts +35 -0
- package/dist/pin/consumers.d.ts.map +1 -0
- package/dist/pin/consumers.js +70 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +5 -0
- package/dist/registry/address.d.ts +63 -0
- package/dist/registry/address.d.ts.map +1 -0
- package/dist/registry/address.js +65 -0
- package/dist/registry/catalog-read.d.ts +14 -0
- package/dist/registry/catalog-read.d.ts.map +1 -0
- package/dist/registry/catalog-read.js +58 -0
- package/dist/registry/deprecated.d.ts +37 -0
- package/dist/registry/deprecated.d.ts.map +1 -0
- package/dist/registry/deprecated.js +43 -0
- package/dist/registry/docs.d.ts +96 -0
- package/dist/registry/docs.d.ts.map +1 -0
- package/dist/registry/docs.js +210 -0
- package/dist/registry/edge/edge.d.ts +46 -0
- package/dist/registry/edge/edge.d.ts.map +1 -0
- package/dist/registry/edge/edge.js +194 -0
- package/dist/registry/edge/index.d.ts +11 -0
- package/dist/registry/edge/index.d.ts.map +1 -0
- package/dist/registry/edge/index.js +10 -0
- package/dist/registry/edge/lambda-edge.d.ts +76 -0
- package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
- package/dist/registry/edge/lambda-edge.js +95 -0
- package/dist/registry/edge/sigv4-web.d.ts +33 -0
- package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
- package/dist/registry/edge/sigv4-web.js +56 -0
- package/dist/registry/edge/token-source.d.ts +73 -0
- package/dist/registry/edge/token-source.d.ts.map +1 -0
- package/dist/registry/edge/token-source.js +115 -0
- package/dist/registry/edge/worker.d.ts +45 -0
- package/dist/registry/edge/worker.d.ts.map +1 -0
- package/dist/registry/edge/worker.js +26 -0
- package/dist/registry/handler.d.ts +25 -0
- package/dist/registry/handler.d.ts.map +1 -0
- package/dist/registry/handler.js +75 -0
- package/dist/registry/headers-file.d.ts +21 -0
- package/dist/registry/headers-file.d.ts.map +1 -0
- package/dist/registry/headers-file.js +57 -0
- package/dist/registry/index.d.ts +31 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +30 -0
- package/dist/registry/mirror-paths.d.ts +9 -0
- package/dist/registry/mirror-paths.d.ts.map +1 -0
- package/dist/registry/mirror-paths.js +8 -0
- package/dist/registry/mirror.d.ts +130 -0
- package/dist/registry/mirror.d.ts.map +1 -0
- package/dist/registry/mirror.js +319 -0
- package/dist/registry/proxies.d.ts +73 -0
- package/dist/registry/proxies.d.ts.map +1 -0
- package/dist/registry/proxies.js +111 -0
- package/dist/registry/reader.d.ts +57 -0
- package/dist/registry/reader.d.ts.map +1 -0
- package/dist/registry/reader.js +198 -0
- package/dist/registry/semver.d.ts +16 -0
- package/dist/registry/semver.d.ts.map +1 -0
- package/dist/registry/semver.js +24 -0
- package/dist/registry/serve.d.ts +24 -0
- package/dist/registry/serve.d.ts.map +1 -0
- package/dist/registry/serve.js +64 -0
- package/dist/registry/source-forms.d.ts +79 -0
- package/dist/registry/source-forms.d.ts.map +1 -0
- package/dist/registry/source-forms.js +202 -0
- package/dist/registry/targets/artifactory.d.ts +28 -0
- package/dist/registry/targets/artifactory.d.ts.map +1 -0
- package/dist/registry/targets/artifactory.js +206 -0
- package/dist/registry/targets/fake-artifactory.d.ts +30 -0
- package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
- package/dist/registry/targets/fake-artifactory.js +142 -0
- package/dist/registry/targets/fake-gitlab.d.ts +39 -0
- package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
- package/dist/registry/targets/fake-gitlab.js +136 -0
- package/dist/registry/targets/fake-hcp.d.ts +50 -0
- package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
- package/dist/registry/targets/fake-hcp.js +202 -0
- package/dist/registry/targets/fake-http.d.ts +31 -0
- package/dist/registry/targets/fake-http.d.ts.map +1 -0
- package/dist/registry/targets/fake-http.js +87 -0
- package/dist/registry/targets/fake-nexus.d.ts +27 -0
- package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
- package/dist/registry/targets/fake-nexus.js +137 -0
- package/dist/registry/targets/gitlab.d.ts +53 -0
- package/dist/registry/targets/gitlab.d.ts.map +1 -0
- package/dist/registry/targets/gitlab.js +187 -0
- package/dist/registry/targets/hcp.d.ts +29 -0
- package/dist/registry/targets/hcp.d.ts.map +1 -0
- package/dist/registry/targets/hcp.js +310 -0
- package/dist/registry/targets/hosted.d.ts +62 -0
- package/dist/registry/targets/hosted.d.ts.map +1 -0
- package/dist/registry/targets/hosted.js +74 -0
- package/dist/registry/targets/index.d.ts +18 -0
- package/dist/registry/targets/index.d.ts.map +1 -0
- package/dist/registry/targets/index.js +17 -0
- package/dist/registry/targets/module-target.d.ts +68 -0
- package/dist/registry/targets/module-target.d.ts.map +1 -0
- package/dist/registry/targets/module-target.js +63 -0
- package/dist/registry/targets/nexus.d.ts +43 -0
- package/dist/registry/targets/nexus.d.ts.map +1 -0
- package/dist/registry/targets/nexus.js +250 -0
- package/dist/registry/targets/zip.d.ts +55 -0
- package/dist/registry/targets/zip.d.ts.map +1 -0
- package/dist/registry/targets/zip.js +278 -0
- package/dist/registry/writer.d.ts +101 -0
- package/dist/registry/writer.d.ts.map +1 -0
- package/dist/registry/writer.js +109 -0
- package/dist/rules/record-store.ts +122 -0
- package/dist/rules/tf001.ts +2 -2
- package/dist/rules/tf024.ts +105 -22
- package/dist/rules/tf046.ts +204 -0
- package/dist/rules/tf047.ts +114 -0
- package/dist/state/azure-blob.d.ts +8 -67
- package/dist/state/azure-blob.d.ts.map +1 -1
- package/dist/state/azure-blob.js +6 -199
- package/dist/state/gcs.d.ts +7 -61
- package/dist/state/gcs.d.ts.map +1 -1
- package/dist/state/gcs.js +5 -222
- package/dist/state/object-store.d.ts +4 -33
- package/dist/state/object-store.d.ts.map +1 -1
- package/dist/state/object-store.js +4 -17
- package/dist/state/s3.d.ts +11 -58
- package/dist/state/s3.d.ts.map +1 -1
- package/dist/state/s3.js +10 -161
- package/dist/state/store.js +2 -2
- package/dist/state-ops.d.ts +255 -0
- package/dist/state-ops.d.ts.map +1 -0
- package/dist/state-ops.js +305 -0
- package/dist/state-reads.d.ts +57 -0
- package/dist/state-reads.d.ts.map +1 -0
- package/dist/state-reads.js +104 -0
- package/dist/used-sources.d.ts +54 -0
- package/dist/used-sources.d.ts.map +1 -0
- package/dist/used-sources.js +157 -0
- package/package.json +19 -3
- package/src/__fixtures__/drift-record/README.md +5 -0
- package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
- package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
- package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
- package/src/__fixtures__/used-sources/root/main.tf +31 -0
- package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
- package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
- package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
- package/src/ci-setup.ts +42 -0
- package/src/components/capability-plugin.test.ts +26 -14
- package/src/components/capability-plugin.ts +185 -31
- package/src/components/compose-group.ts +1 -1
- package/src/components/pr-loop-compose.tofu.e2e.test.ts +1 -1
- package/src/components/pr-loop.tofu.test.ts +1 -1
- package/src/components/release-inputs.ts +69 -0
- package/src/components/trace-facts.test.ts +21 -0
- package/src/components/trace-facts.ts +27 -0
- package/src/cone.ts +138 -0
- package/src/cost-hold.ts +29 -0
- package/src/describe-resources.engine-facts.test.ts +2 -3
- package/src/drift-rows.test.ts +135 -0
- package/src/drift-rows.ts +137 -0
- package/src/exit-codes.test.ts +23 -0
- package/src/exit-codes.ts +59 -0
- package/src/index.ts +3 -0
- package/src/lint/audit-catalog.ts +29 -3
- package/src/lint/audit-lineage.ts +14 -2
- package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
- package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
- package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/record-store.ts +122 -0
- package/src/lint/post-synth/tf001.ts +2 -2
- package/src/lint/post-synth/tf024.test.ts +26 -5
- package/src/lint/post-synth/tf024.ts +105 -22
- package/src/lint/post-synth/tf046.test.ts +137 -0
- package/src/lint/post-synth/tf046.ts +204 -0
- package/src/lint/post-synth/tf047.test.ts +56 -0
- package/src/lint/post-synth/tf047.ts +114 -0
- package/src/lock-rows.test.ts +70 -0
- package/src/lock-rows.ts +82 -0
- package/src/lock-timeout.ts +34 -0
- package/src/module-catalog/index.test.ts +93 -0
- package/src/module-catalog/index.ts +159 -0
- package/src/module-catalog/refresh-cli.ts +68 -0
- package/src/module-catalog/static-tier.ts +1659 -0
- package/src/module-catalog/types.ts +88 -0
- package/src/op/activities/terraform.ts +46 -5
- package/src/op-wave-step.test.ts +109 -0
- package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
- package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
- package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
- package/src/pin/consumers.test.ts +57 -0
- package/src/pin/consumers.ts +95 -0
- package/src/plugin.ts +6 -0
- package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
- package/src/registry/__fixtures__/docs/documented/README.md +1 -0
- package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
- package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
- package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
- package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
- package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
- package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
- package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
- package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
- package/src/registry/address.ts +100 -0
- package/src/registry/catalog-read.ts +56 -0
- package/src/registry/deprecated.test.ts +139 -0
- package/src/registry/deprecated.ts +76 -0
- package/src/registry/docs.test.ts +140 -0
- package/src/registry/docs.ts +299 -0
- package/src/registry/edge/bundle.test.ts +50 -0
- package/src/registry/edge/edge.test.ts +176 -0
- package/src/registry/edge/edge.ts +211 -0
- package/src/registry/edge/index.ts +10 -0
- package/src/registry/edge/lambda-edge.ts +127 -0
- package/src/registry/edge/sigv4-web.ts +79 -0
- package/src/registry/edge/token-source.test.ts +258 -0
- package/src/registry/edge/token-source.ts +169 -0
- package/src/registry/edge/worker.ts +61 -0
- package/src/registry/handler.ts +89 -0
- package/src/registry/headers-file.ts +61 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/mirror-paths.ts +10 -0
- package/src/registry/mirror.test.ts +198 -0
- package/src/registry/mirror.ts +395 -0
- package/src/registry/proxies.test.ts +153 -0
- package/src/registry/proxies.ts +132 -0
- package/src/registry/reader.ts +225 -0
- package/src/registry/registry.test.ts +262 -0
- package/src/registry/semver.ts +34 -0
- package/src/registry/serve.ts +82 -0
- package/src/registry/source-forms.test.ts +87 -0
- package/src/registry/source-forms.ts +216 -0
- package/src/registry/targets/artifactory.test.ts +248 -0
- package/src/registry/targets/artifactory.ts +257 -0
- package/src/registry/targets/fake-artifactory.ts +158 -0
- package/src/registry/targets/fake-gitlab.ts +161 -0
- package/src/registry/targets/fake-hcp.ts +233 -0
- package/src/registry/targets/fake-http.ts +97 -0
- package/src/registry/targets/fake-nexus.ts +152 -0
- package/src/registry/targets/gitlab.test.ts +208 -0
- package/src/registry/targets/gitlab.ts +223 -0
- package/src/registry/targets/hcp.test.ts +230 -0
- package/src/registry/targets/hcp.ts +360 -0
- package/src/registry/targets/hosted.ts +117 -0
- package/src/registry/targets/index.ts +17 -0
- package/src/registry/targets/module-target.ts +131 -0
- package/src/registry/targets/nexus.test.ts +220 -0
- package/src/registry/targets/nexus.ts +314 -0
- package/src/registry/targets/zip.test.ts +65 -0
- package/src/registry/targets/zip.ts +287 -0
- package/src/registry/writer.ts +155 -0
- package/src/state/azure-blob.ts +6 -221
- package/src/state/gcs.ts +5 -252
- package/src/state/object-store.ts +4 -32
- package/src/state/s3.ts +10 -182
- package/src/state/store.ts +2 -2
- package/src/state-ops.test.ts +182 -0
- package/src/state-ops.ts +462 -0
- package/src/state-reads.ts +116 -0
- package/src/used-sources.test.ts +75 -0
- package/src/used-sources.ts +204 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of choudoufu's `live/module-compat.json` (choudoufu#2292,
|
|
3
|
+
* `tools/modcompat/modcompat.go`), as far as the known-module catalog reads
|
|
4
|
+
* it. Only the static tier is typed here; see `./refresh-cli.ts`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** One refusal on a static row: a choudoufu refusal ID and where it is documented. */
|
|
8
|
+
export interface StaticRefusal {
|
|
9
|
+
id: string;
|
|
10
|
+
layer: string;
|
|
11
|
+
sites: number;
|
|
12
|
+
docs_ref: string;
|
|
13
|
+
docs_url: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One terraform-aws-modules example measured offline in the onboarded form. */
|
|
17
|
+
export interface StaticRow {
|
|
18
|
+
tier: "static";
|
|
19
|
+
/** Registry address, `terraform-aws-modules/vpc/aws`. */
|
|
20
|
+
module: string;
|
|
21
|
+
/** The module repository's tag, `v6.6.1`. */
|
|
22
|
+
version: string;
|
|
23
|
+
/** The example directory inside the module repository. */
|
|
24
|
+
example: string;
|
|
25
|
+
date: string;
|
|
26
|
+
commit: string;
|
|
27
|
+
aws_provider: string;
|
|
28
|
+
verdict: "clear" | "refused" | "unmeasured";
|
|
29
|
+
/** Eligible data-source reads. These never refuse and are not refusals. */
|
|
30
|
+
reads: number;
|
|
31
|
+
refusals: StaticRefusal[];
|
|
32
|
+
reason?: string;
|
|
33
|
+
partial_schemas?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface StaticSource {
|
|
37
|
+
module: string;
|
|
38
|
+
repo: string;
|
|
39
|
+
version: string;
|
|
40
|
+
commit: string;
|
|
41
|
+
glob: string;
|
|
42
|
+
examples: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** How the static tier was measured. */
|
|
46
|
+
export interface StaticMeasurement {
|
|
47
|
+
date: string;
|
|
48
|
+
commit: string;
|
|
49
|
+
instrument: string;
|
|
50
|
+
form: string;
|
|
51
|
+
schemas: boolean;
|
|
52
|
+
second_pass: boolean;
|
|
53
|
+
aws_provider: string;
|
|
54
|
+
sources: StaticSource[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The parts of the file the catalog reads. */
|
|
58
|
+
export interface ModuleCompatFile {
|
|
59
|
+
schema: number;
|
|
60
|
+
static_measurement: StaticMeasurement;
|
|
61
|
+
static: StaticRow[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const VERDICTS = new Set(["clear", "refused", "unmeasured"]);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check a parsed `live/module-compat.json` against the shape above and return
|
|
68
|
+
* it. Throws on a schema other than 1, a row outside the static tier, or a
|
|
69
|
+
* row whose verdict disagrees with its refusals, so a refresh never vendors a
|
|
70
|
+
* table the catalog would misread.
|
|
71
|
+
*/
|
|
72
|
+
export function parseModuleCompat(raw: unknown): ModuleCompatFile {
|
|
73
|
+
const file = raw as Partial<ModuleCompatFile>;
|
|
74
|
+
if (file?.schema !== 1) throw new Error(`module-compat.json: schema ${String(file?.schema)}, expected 1`);
|
|
75
|
+
if (!file.static_measurement || !Array.isArray(file.static)) {
|
|
76
|
+
throw new Error("module-compat.json: missing static_measurement or static");
|
|
77
|
+
}
|
|
78
|
+
for (const row of file.static) {
|
|
79
|
+
const where = `${row.module} ${row.version} ${row.example}`;
|
|
80
|
+
if (row.tier !== "static") throw new Error(`module-compat.json: ${where} is in the static array with tier ${row.tier}`);
|
|
81
|
+
if (!VERDICTS.has(row.verdict)) throw new Error(`module-compat.json: ${where} has verdict ${row.verdict}`);
|
|
82
|
+
if (!Array.isArray(row.refusals)) throw new Error(`module-compat.json: ${where} has no refusals list`);
|
|
83
|
+
if ((row.verdict === "refused") !== (row.refusals.length > 0)) {
|
|
84
|
+
throw new Error(`module-compat.json: ${where} is ${row.verdict} with ${row.refusals.length} refusals`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { schema: file.schema, static_measurement: file.static_measurement, static: file.static };
|
|
88
|
+
}
|
|
@@ -53,6 +53,8 @@ import type { ChangeSetPart } from "@intentius/chant/change-set";
|
|
|
53
53
|
import type { TerraformConfig, TerraformRootConfig } from "../../config";
|
|
54
54
|
import { detectLiveEstate } from "./live-detect";
|
|
55
55
|
import { backendConfigValues } from "../../backend-config";
|
|
56
|
+
import { lockTimeoutArgs } from "../../lock-timeout";
|
|
57
|
+
import { coneArgs, readPlanCone, type PlanCone } from "../../cone";
|
|
56
58
|
import {
|
|
57
59
|
parseAdoptionCommands,
|
|
58
60
|
readAdoptionLedger,
|
|
@@ -195,6 +197,14 @@ export interface TerraformRootArgs {
|
|
|
195
197
|
* `process.cwd()`, which is the project root under `chant run`.
|
|
196
198
|
*/
|
|
197
199
|
cwd?: string;
|
|
200
|
+
/**
|
|
201
|
+
* The directory the root's files are read from in place of the entry's
|
|
202
|
+
* `dir`, relative to the project root (chant#4542). A promote or rollback
|
|
203
|
+
* under a release sets it to the release's recorded copy of the root; the
|
|
204
|
+
* entry's workspace, var files, backend config and estate still apply, and
|
|
205
|
+
* its var files and backend-config files resolve against this directory.
|
|
206
|
+
*/
|
|
207
|
+
dir?: string;
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
export interface TerraformInitArgs extends TerraformRootArgs {
|
|
@@ -231,6 +241,19 @@ export interface TerraformPlanArgs extends TerraformRootArgs {
|
|
|
231
241
|
* the text, such as the `terraform-apply` capability. Default: `true`.
|
|
232
242
|
*/
|
|
233
243
|
text?: boolean;
|
|
244
|
+
/**
|
|
245
|
+
* How long the plan waits for another run's state lock (#4545), passed as
|
|
246
|
+
* `-lock-timeout` unless `TF_CLI_ARGS` or `TF_CLI_ARGS_plan` names one.
|
|
247
|
+
* Default: no flag, terraform's own (no wait).
|
|
248
|
+
*/
|
|
249
|
+
lockTimeout?: string;
|
|
250
|
+
/**
|
|
251
|
+
* choudoufu only (#4545): plan the cone of the change since this revision
|
|
252
|
+
* (`-cone=<base>`) on a root under a `live` block that is not an estate
|
|
253
|
+
* file. Any other root, binary, or a plan with `targets` or `destroy`,
|
|
254
|
+
* plans in full. {@link TerraformPlanResult.cone} says which it was.
|
|
255
|
+
*/
|
|
256
|
+
cone?: string;
|
|
234
257
|
}
|
|
235
258
|
|
|
236
259
|
export interface TerraformApplyArgs extends TerraformRootArgs {
|
|
@@ -251,6 +274,8 @@ export interface TerraformApplyArgs extends TerraformRootArgs {
|
|
|
251
274
|
* separate to pass.
|
|
252
275
|
*/
|
|
253
276
|
planFile?: string;
|
|
277
|
+
/** How long the apply waits for another run's state lock (#4545), as {@link TerraformPlanArgs.lockTimeout}. */
|
|
278
|
+
lockTimeout?: string;
|
|
254
279
|
}
|
|
255
280
|
|
|
256
281
|
export interface TerraformShowArgs extends TerraformRootArgs {
|
|
@@ -367,6 +392,8 @@ export interface TerraformPlanResult extends PlanChangeCounts {
|
|
|
367
392
|
* `composeChangeSet` and bind the run's gate to the document's digest.
|
|
368
393
|
*/
|
|
369
394
|
changeSet: ChangeSetPart;
|
|
395
|
+
/** Set when the plan was asked for a cone (#4545): the cone it planned, or why it planned the whole estate. */
|
|
396
|
+
cone?: PlanCone;
|
|
370
397
|
}
|
|
371
398
|
|
|
372
399
|
/**
|
|
@@ -617,9 +644,12 @@ export function terraformPlanCommand(opts: {
|
|
|
617
644
|
vars?: Record<string, unknown>;
|
|
618
645
|
destroy?: boolean;
|
|
619
646
|
targets?: string[];
|
|
647
|
+
/** Extra flags after the fixed ones (#4545): `-lock-timeout`, `-cone`. */
|
|
648
|
+
extra?: readonly string[];
|
|
620
649
|
}): string {
|
|
621
650
|
const parts = [opts.binary, "plan", "-input=false", "-detailed-exitcode"];
|
|
622
651
|
if (opts.destroy) parts.push("-destroy");
|
|
652
|
+
for (const flag of opts.extra ?? []) parts.push(quoteArg(flag));
|
|
623
653
|
for (const target of [...new Set(opts.targets ?? [])].sort()) parts.push(`-target=${quoteArg(target)}`);
|
|
624
654
|
for (const varFile of opts.varFiles ?? []) parts.push(`-var-file=${quoteArg(varFile)}`);
|
|
625
655
|
for (const [name, value] of Object.entries(opts.vars ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
@@ -636,8 +666,9 @@ export function terraformPlanCommand(opts: {
|
|
|
636
666
|
* block, and a matched one applies without re-prompting, exactly as stock's
|
|
637
667
|
* own `apply <planfile>` does, so no `-auto-approve` is needed or passed.
|
|
638
668
|
*/
|
|
639
|
-
export function terraformApplyCommand(opts: { binary: string; planFile: string }): string {
|
|
640
|
-
|
|
669
|
+
export function terraformApplyCommand(opts: { binary: string; planFile: string; extra?: readonly string[] }): string {
|
|
670
|
+
const extra = (opts.extra ?? []).map((f) => ` ${quoteArg(f)}`).join("");
|
|
671
|
+
return `${opts.binary} apply -input=false${extra} ${quoteArg(opts.planFile)}`;
|
|
641
672
|
}
|
|
642
673
|
|
|
643
674
|
/**
|
|
@@ -937,7 +968,7 @@ async function resolveRoot(args: TerraformRootArgs, signal?: AbortSignal): Promi
|
|
|
937
968
|
);
|
|
938
969
|
}
|
|
939
970
|
const binary = terraformBinary(namespace);
|
|
940
|
-
const dir = resolve(projectRoot, root.dir);
|
|
971
|
+
const dir = resolve(projectRoot, args.dir ?? root.dir);
|
|
941
972
|
await ensureChoudoufuVersion(binary, signal);
|
|
942
973
|
const estate = binary === "choudoufu" ? detectLiveEstate(dir) : undefined;
|
|
943
974
|
// `estate` stays what it has always been: the estate the root's own HCL
|
|
@@ -1075,9 +1106,13 @@ export async function terraformPlan(
|
|
|
1075
1106
|
args: TerraformPlanArgs,
|
|
1076
1107
|
signal?: AbortSignal,
|
|
1077
1108
|
): Promise<TerraformPlanResult> {
|
|
1078
|
-
const { binary, root, dir } = await resolveRoot(args, signal);
|
|
1109
|
+
const { binary, root, dir, live } = await resolveRoot(args, signal);
|
|
1079
1110
|
const planFile = args.planFile ?? DEFAULT_PLAN_FILE;
|
|
1080
1111
|
const env = terraformEnvironment(root);
|
|
1112
|
+
const lock = args.lockTimeout !== undefined ? lockTimeoutArgs("plan", args.lockTimeout) : [];
|
|
1113
|
+
// #4545: a cone only on a live root that is not an estate file; -target and -destroy plan in full.
|
|
1114
|
+
const fixed = [...(args.destroy ? ["-destroy"] : []), ...((args.targets ?? []).length > 0 ? ["-target"] : [])];
|
|
1115
|
+
const cone = args.cone !== undefined ? coneArgs(binary, args.cone, fixed, live && !existsSync(join(dir, "estate.chdf.hcl"))) : [];
|
|
1081
1116
|
const cmd = terraformPlanCommand({
|
|
1082
1117
|
binary,
|
|
1083
1118
|
planFile,
|
|
@@ -1085,12 +1120,15 @@ export async function terraformPlan(
|
|
|
1085
1120
|
...(args.vars ? { vars: args.vars } : {}),
|
|
1086
1121
|
...(args.destroy ? { destroy: true } : {}),
|
|
1087
1122
|
...(args.targets && args.targets.length > 0 ? { targets: args.targets } : {}),
|
|
1123
|
+
...(cone.length + lock.length > 0 ? { extra: [...cone, ...lock] } : {}),
|
|
1088
1124
|
});
|
|
1089
1125
|
|
|
1090
1126
|
let changed: boolean;
|
|
1127
|
+
let output = "";
|
|
1091
1128
|
try {
|
|
1092
1129
|
const { stdout, stderr } = await run(cmd, dir, env, signal);
|
|
1093
1130
|
report(stdout, stderr);
|
|
1131
|
+
output = `${stdout}\n${stderr}`;
|
|
1094
1132
|
changed = false;
|
|
1095
1133
|
} catch (err) {
|
|
1096
1134
|
// An abort or a spawn failure carries no numeric exit code. That is not
|
|
@@ -1104,6 +1142,7 @@ export async function terraformPlan(
|
|
|
1104
1142
|
);
|
|
1105
1143
|
}
|
|
1106
1144
|
report(failure.stdout ?? "", failure.stderr ?? "");
|
|
1145
|
+
output = `${failure.stdout ?? ""}\n${failure.stderr ?? ""}`;
|
|
1107
1146
|
changed = true;
|
|
1108
1147
|
}
|
|
1109
1148
|
|
|
@@ -1125,6 +1164,7 @@ export async function terraformPlan(
|
|
|
1125
1164
|
...((root.estate ?? root.workspace) ? { scope: root.estate ?? root.workspace } : {}),
|
|
1126
1165
|
}),
|
|
1127
1166
|
...countPlanChanges(json),
|
|
1167
|
+
...(cone.length > 0 ? { cone: readPlanCone(output, join(dir, planFile), args.cone!) } : {}),
|
|
1128
1168
|
};
|
|
1129
1169
|
}
|
|
1130
1170
|
|
|
@@ -1176,7 +1216,8 @@ export async function terraformApply(
|
|
|
1176
1216
|
}
|
|
1177
1217
|
|
|
1178
1218
|
const planFile = args.planFile!;
|
|
1179
|
-
const
|
|
1219
|
+
const lock = args.lockTimeout !== undefined ? lockTimeoutArgs("apply", args.lockTimeout) : [];
|
|
1220
|
+
const cmd = terraformApplyCommand({ binary, planFile, ...(lock.length > 0 ? { extra: lock } : {}) });
|
|
1180
1221
|
|
|
1181
1222
|
try {
|
|
1182
1223
|
const { stdout, stderr } = await run(cmd, dir, terraformEnvironment(root), signal);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terraform side of op waves through capabilities (#4545): lock
|
|
3
|
+
* timeouts, choudoufu cone plans, reads from another root's state and the
|
|
4
|
+
* cost hold preset, moved here from terragucci's wave callbacks.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { DEFAULT_LOCK_TIMEOUT, isLockTimeout, lockTimeoutArgs } from "./lock-timeout";
|
|
11
|
+
import { coneArgs, conePlanDigest, coneRecordPath, coneText, readPlanCone } from "./cone";
|
|
12
|
+
import { atmosStateReads, fillStateReads, readOutputValue, stateReadsProblems, waitingReadsReason } from "./state-reads";
|
|
13
|
+
import { INFRACOST_ARGV, terraformCostHold } from "./cost-hold";
|
|
14
|
+
import { terraformApplyCommand, terraformPlanCommand } from "./op/activities/terraform";
|
|
15
|
+
|
|
16
|
+
describe("lock timeout (#4545)", () => {
|
|
17
|
+
test("adds -lock-timeout unless the job sets one", () => {
|
|
18
|
+
expect(lockTimeoutArgs("plan", DEFAULT_LOCK_TIMEOUT, {})).toEqual(["-lock-timeout=5m"]);
|
|
19
|
+
expect(lockTimeoutArgs("apply", "30s", { TF_CLI_ARGS: "-no-color" })).toEqual(["-lock-timeout=30s"]);
|
|
20
|
+
expect(lockTimeoutArgs("plan", "5m", { TF_CLI_ARGS_plan: "-lock-timeout=1m" })).toEqual([]);
|
|
21
|
+
expect(lockTimeoutArgs("apply", "5m", { TF_CLI_ARGS: "--lock-timeout 2m" })).toEqual([]);
|
|
22
|
+
expect(lockTimeoutArgs("plan", false, {})).toEqual([]);
|
|
23
|
+
expect(() => lockTimeoutArgs("plan", "five", {})).toThrow(/not a duration/);
|
|
24
|
+
expect(isLockTimeout("1h30m")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("the plan and apply commands carry the extra flags", () => {
|
|
28
|
+
expect(terraformPlanCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-cone=origin/main", "-lock-timeout=5m"] })).toBe(
|
|
29
|
+
"tofu plan -input=false -detailed-exitcode -cone=origin/main -lock-timeout=5m -out=chant.tfplan",
|
|
30
|
+
);
|
|
31
|
+
expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-lock-timeout=5m"] })).toBe("tofu apply -input=false -lock-timeout=5m chant.tfplan");
|
|
32
|
+
expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan" })).toBe("tofu apply -input=false chant.tfplan");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("cone plans (#4545)", () => {
|
|
37
|
+
test("only choudoufu on a live root, and never beside -target or -destroy", () => {
|
|
38
|
+
expect(coneArgs("choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
|
|
39
|
+
expect(coneArgs("/usr/local/bin/choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
|
|
40
|
+
expect(coneArgs("tofu", "origin/main", [], true)).toEqual([]);
|
|
41
|
+
expect(coneArgs("choudoufu", "origin/main", [], false)).toEqual([]);
|
|
42
|
+
expect(coneArgs("choudoufu", "origin/main", ["-target"], true)).toEqual([]);
|
|
43
|
+
expect(coneArgs("choudoufu", undefined, [], true)).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("reads the record beside the plan, or the guard's fallback", () => {
|
|
47
|
+
const dir = mkdtempSync(join(tmpdir(), "cone-"));
|
|
48
|
+
const plan = join(dir, "chant.tfplan");
|
|
49
|
+
writeFileSync(coneRecordPath(plan), JSON.stringify({ base: "origin/main", base_sha: "abc", targets: ["b.x", "a.y"] }));
|
|
50
|
+
const cone = readPlanCone("Warning: Cone plan\n\nThis plan covers the cone: 2 of the estate's 40 blocks\n\nPlan: 1 to add", plan, "origin/main");
|
|
51
|
+
expect(cone).toEqual({ base: "origin/main", blocks: 2, of: 40, record: { base: "origin/main", base_sha: "abc", targets: ["a.y", "b.x"] } });
|
|
52
|
+
expect(coneText(cone)).toBe("the cone of the change since origin/main, 2 of the estate's 40 blocks");
|
|
53
|
+
const fell = readPlanCone("Warning: Cone plan fell back to a full plan\n\nThis plan covers the whole estate (guard lockfile): the lock file changed.\n", plan, "origin/main");
|
|
54
|
+
expect(fell).toEqual({ base: "origin/main", fallback: { kind: "lockfile", reason: "the lock file changed" } });
|
|
55
|
+
rmSync(dir, { recursive: true, force: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("the cone's targets join the digest; a full plan keeps its own", () => {
|
|
59
|
+
const d = `sha256:${"1".repeat(64)}`;
|
|
60
|
+
expect(conePlanDigest(d, undefined)).toBe(d);
|
|
61
|
+
expect(conePlanDigest(d, { base: "origin/main", fallback: { kind: "x", reason: "y" } })).toBe(d);
|
|
62
|
+
const a = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["a"] } });
|
|
63
|
+
const b = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["b"] } });
|
|
64
|
+
expect(a).not.toBe(d);
|
|
65
|
+
expect(a).not.toBe(b);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("reads from another root's state (#4545)", () => {
|
|
70
|
+
test("fills each read once per upstream, and waits on a missing output", async () => {
|
|
71
|
+
let reads = 0;
|
|
72
|
+
const filled = await fillStateReads(
|
|
73
|
+
[
|
|
74
|
+
{ var: "vpc_id", root: "network", output: ["vpc", "id"] },
|
|
75
|
+
{ var: "subnets", root: "network", output: ["subnets"] },
|
|
76
|
+
{ var: "db", root: "data", output: ["endpoint"] },
|
|
77
|
+
],
|
|
78
|
+
async (root) => {
|
|
79
|
+
reads++;
|
|
80
|
+
return root === "network" ? { vpc: { id: "vpc-1" }, subnets: ["a", "b"] } : {};
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
expect(reads).toBe(2);
|
|
84
|
+
expect(filled.vars).toEqual({ vpc_id: "vpc-1", subnets: ["a", "b"] });
|
|
85
|
+
expect(filled.waiting.map((w) => w.why)).toEqual(["has no state yet"]);
|
|
86
|
+
expect(waitingReadsReason(filled.waiting)).toBe("not planned: it reads db from data, which has no state yet; apply data first");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a state that cannot be read is an error, not a wait", async () => {
|
|
90
|
+
const filled = await fillStateReads([{ var: "x", root: "up", output: ["x"] }], async () => {
|
|
91
|
+
throw new Error("init failed");
|
|
92
|
+
});
|
|
93
|
+
expect(filled.errors).toEqual(["it reads x from up, whose state could not be read: init failed"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("checks the reads, reads a nested output, and maps Atmos edges", () => {
|
|
97
|
+
expect(stateReadsProblems([{ var: "a", root: "r", output: ["o"] }, { var: "a", root: "", output: [] }])).toHaveLength(3);
|
|
98
|
+
expect(readOutputValue({ o: { k: 1 } }, ["o", "k"])).toEqual({ value: 1 });
|
|
99
|
+
expect(readOutputValue({ o: { k: 1 } }, ["o", "j"])).toBeUndefined();
|
|
100
|
+
expect(atmosStateReads({ reads: [{ var: "v", upstream: "dev/vpc", output: ["id"], function: "!terraform.state vpc id" }] }, (u) => u.replace("/", "-"))).toEqual([{ var: "v", root: "dev-vpc", output: ["id"] }]);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("cost hold preset (#4545)", () => {
|
|
105
|
+
test("prices each root's plan document with infracost", () => {
|
|
106
|
+
expect(terraformCostHold({ approveAbove: 100 })).toEqual({ command: [...INFRACOST_ARGV], approveAbove: 100, format: "infracost" });
|
|
107
|
+
expect(() => terraformCostHold({ approveAbove: 1, command: ["infracost"] })).toThrow(/\{planJson\}/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module "vpc" {
|
|
2
|
+
source = "registry.acme.dev/acme/vpc/aws"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
module "eks" {
|
|
7
|
+
source = "acme/eks/aws"
|
|
8
|
+
version = "~> 2.0"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module "net" {
|
|
12
|
+
source = "git::https://git.acme.dev/acme/net.git?ref=v1.2.0"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module "cache" {
|
|
16
|
+
source = "oci://registry.acme.dev/modules/cache?tag=3.1.0"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module "local" {
|
|
20
|
+
source = "./modules/local"
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consumer scan (chant#4599): registry, git ref and OCI tag pins, a
|
|
3
|
+
* constraint left unpinned, a Terragrunt unit's `terraform { source }`, and
|
|
4
|
+
* local calls left out, all read from source files.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { isLocalSource, moduleConsumers, normalizeModuleSource } from "./consumers";
|
|
9
|
+
|
|
10
|
+
const REPO = join(import.meta.dirname, "__fixtures__", "consumers");
|
|
11
|
+
|
|
12
|
+
describe("moduleConsumers", () => {
|
|
13
|
+
test("lists every published module call in the roots, sorted", async () => {
|
|
14
|
+
const calls = await moduleConsumers(REPO, ["live/prod", "live/dev", "units/vpc"]);
|
|
15
|
+
expect(calls.map((c) => [c.root, c.address, c.module, c.pin])).toEqual([
|
|
16
|
+
["live/dev", "module.vpc", "registry.acme.dev/acme/vpc/aws", "v2.0.0"],
|
|
17
|
+
["live/prod", "module.cache", "oci://registry.acme.dev/modules/cache", "3.1.0"],
|
|
18
|
+
["live/prod", "module.eks", "registry.terraform.io/acme/eks/aws", null],
|
|
19
|
+
["live/prod", "module.net", "git::https://git.acme.dev/acme/net.git", "v1.2.0"],
|
|
20
|
+
["live/prod", "module.vpc", "registry.acme.dev/acme/vpc/aws", "1.0.0"],
|
|
21
|
+
["units/vpc", "terragrunt.terraform", "registry.acme.dev/acme/vpc/aws", "1.1.0"],
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("keeps where each pin sits and why a call has none", async () => {
|
|
26
|
+
const calls = await moduleConsumers(REPO, ["live/prod", "units/vpc"]);
|
|
27
|
+
const by = (a: string, root = "live/prod") => calls.find((c) => c.address === a && c.root === root)!;
|
|
28
|
+
expect(by("module.vpc")).toMatchObject({ kind: "terraform", file: "live/prod/main.tf", at: "version", source: "registry.acme.dev/acme/vpc/aws" });
|
|
29
|
+
expect(by("module.net")).toMatchObject({ at: "source", param: "ref" });
|
|
30
|
+
expect(by("module.cache")).toMatchObject({ at: "source", param: "tag" });
|
|
31
|
+
expect(by("module.eks").unpinned).toContain("constraint");
|
|
32
|
+
expect(by("terragrunt.terraform", "units/vpc")).toMatchObject({ kind: "terragrunt", file: "units/vpc/terragrunt.hcl", at: "source", param: "version" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("leaves local calls out", async () => {
|
|
36
|
+
const calls = await moduleConsumers(REPO, ["live/prod"]);
|
|
37
|
+
expect(calls.find((c) => c.address === "module.local")).toBeUndefined();
|
|
38
|
+
expect(isLocalSource("./modules/local")).toBe(true);
|
|
39
|
+
expect(isLocalSource("../shared")).toBe(true);
|
|
40
|
+
expect(isLocalSource("acme/vpc/aws")).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("normalizeModuleSource", () => {
|
|
45
|
+
test.each([
|
|
46
|
+
["acme/vpc/aws", "registry.terraform.io/acme/vpc/aws"],
|
|
47
|
+
["acme/vpc/aws//modules/x", "registry.terraform.io/acme/vpc/aws//modules/x"],
|
|
48
|
+
["Registry.Acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
|
|
49
|
+
["tfr:///terraform-aws-modules/vpc/aws", "registry.terraform.io/terraform-aws-modules/vpc/aws"],
|
|
50
|
+
["tfr://registry.acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
|
|
51
|
+
["github.com/acme/vpc", "github.com/acme/vpc"],
|
|
52
|
+
["git::https://git.acme.dev/acme/net.git", "git::https://git.acme.dev/acme/net.git"],
|
|
53
|
+
["oci://registry.acme.dev/modules/cache", "oci://registry.acme.dev/modules/cache"],
|
|
54
|
+
])("%s is %s", (from, to) => {
|
|
55
|
+
expect(normalizeModuleSource(from)).toBe(to);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a repo calls its modules (chant#4599, terragucci#1289 bundle B): one
|
|
3
|
+
* entry per module call in the roots given, Terragrunt units included, read
|
|
4
|
+
* from the source files with chant's HCL parse and never from a plan, so the
|
|
5
|
+
* scan runs in a check job before anything is planned.
|
|
6
|
+
*
|
|
7
|
+
* Each call carries its root, file and block path, the source as written,
|
|
8
|
+
* the module it names with the pin taken out and normalised
|
|
9
|
+
* ({@link normalizeModuleSource}), and the pin `readModulePin` (./source.ts)
|
|
10
|
+
* reads. A local call (`./`, `../`) names no published module and is left
|
|
11
|
+
* out. The release catalog lists these as a version's consumers, and
|
|
12
|
+
* `deprecatedPins` (../registry/deprecated.ts) checks them against it.
|
|
13
|
+
*
|
|
14
|
+
* The `terragucci_module_pin` metric keeps reading the plan; this is the
|
|
15
|
+
* source-side view of the same calls. The normal form is chant#3924's
|
|
16
|
+
* (../registry/source-forms.ts), shared with `usedSources` (../sources.ts).
|
|
17
|
+
*/
|
|
18
|
+
import { join, relative } from "node:path";
|
|
19
|
+
import type { Hcl2Json } from "@intentius/chant/terraform/hcl2json";
|
|
20
|
+
import { MODULE_TYPE, TERRAGRUNT_SOURCE_TYPE, parseTerraformRootDir, type TerraformEntity } from "../hcl/parse";
|
|
21
|
+
import { normalizeModuleSource, PUBLIC_REGISTRY_HOST } from "../registry/source-forms";
|
|
22
|
+
import { readModulePin, type ModuleCallPin } from "./source";
|
|
23
|
+
|
|
24
|
+
export { normalizeModuleSource, PUBLIC_REGISTRY_HOST };
|
|
25
|
+
|
|
26
|
+
/** One module call in a consuming repo. */
|
|
27
|
+
export interface ModuleConsumer {
|
|
28
|
+
/** The root (or Terragrunt unit) directory, from the repo's top, `/`-separated. */
|
|
29
|
+
root: string;
|
|
30
|
+
/** The file holding the call, from the repo's top. */
|
|
31
|
+
file: string;
|
|
32
|
+
/** The block: `module.<name>`, or `terragrunt.terraform` for a unit's source. */
|
|
33
|
+
address: string;
|
|
34
|
+
/** `terraform` for a module block, `terragrunt` for a unit's `terraform { source }`. */
|
|
35
|
+
kind: "terraform" | "terragrunt";
|
|
36
|
+
/** The `source` as written. */
|
|
37
|
+
source: string;
|
|
38
|
+
/** The module the call names: the source with the pin taken out, normalised. */
|
|
39
|
+
module: string;
|
|
40
|
+
/** The pinned version, tag, digest or ref, or null when the call pins none. */
|
|
41
|
+
pin: string | null;
|
|
42
|
+
/** Where the pin is, as `readModulePin` reads it. */
|
|
43
|
+
at: ModuleCallPin["at"];
|
|
44
|
+
param?: ModuleCallPin["param"];
|
|
45
|
+
/** Why there is no pin, when `pin` is null. */
|
|
46
|
+
unpinned?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A source names a local directory, not a published module. */
|
|
50
|
+
export function isLocalSource(source: string): boolean {
|
|
51
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function str(v: unknown): string | undefined {
|
|
55
|
+
return typeof v === "string" ? v : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every module call in `roots` (directories from `repo`), sorted by root,
|
|
60
|
+
* file and address. A root whose files cannot be parsed throws, naming the
|
|
61
|
+
* root, so a check job fails loudly instead of listing fewer consumers.
|
|
62
|
+
*/
|
|
63
|
+
export async function moduleConsumers(repo: string, roots: readonly string[], parser?: Hcl2Json): Promise<ModuleConsumer[]> {
|
|
64
|
+
const out: ModuleConsumer[] = [];
|
|
65
|
+
for (const root of [...new Set(roots)].sort()) {
|
|
66
|
+
const dir = join(repo, root);
|
|
67
|
+
const rel = relative(repo, dir).split("\\").join("/") || ".";
|
|
68
|
+
let entities: TerraformEntity[];
|
|
69
|
+
try {
|
|
70
|
+
entities = [...(await parseTerraformRootDir(dir, "consumers", parser)).values()] as TerraformEntity[];
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throw new Error(`${rel}: ${(e as Error).message}`);
|
|
73
|
+
}
|
|
74
|
+
for (const e of entities) {
|
|
75
|
+
if (e.entityType !== MODULE_TYPE && e.entityType !== TERRAGRUNT_SOURCE_TYPE) continue;
|
|
76
|
+
const source = str(e.props.body.source);
|
|
77
|
+
if (source === undefined || isLocalSource(source)) continue;
|
|
78
|
+
const version = e.entityType === MODULE_TYPE ? str(e.props.body.version) : undefined;
|
|
79
|
+
const pin = readModulePin(source, version);
|
|
80
|
+
out.push({
|
|
81
|
+
root: rel,
|
|
82
|
+
file: rel === "." ? e.props.file : `${rel}/${e.props.file}`,
|
|
83
|
+
address: e.props.address,
|
|
84
|
+
kind: e.entityType === MODULE_TYPE ? "terraform" : "terragrunt",
|
|
85
|
+
source,
|
|
86
|
+
module: normalizeModuleSource(pin.module),
|
|
87
|
+
pin: pin.pin,
|
|
88
|
+
at: pin.at,
|
|
89
|
+
...(pin.param ? { param: pin.param } : {}),
|
|
90
|
+
...(pin.unpinned ? { unpinned: pin.unpinned } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out.sort((a, b) => a.root.localeCompare(b.root) || a.file.localeCompare(b.file) || a.address.localeCompare(b.address));
|
|
95
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuditEntitiesInput, LexiconPlugin } from "@intentius/chant/lexicon";
|
|
2
|
+
import { terraformCiSetup } from "./ci-setup";
|
|
2
3
|
import { terraformBehaviourKinds } from "./behaviour/kinds";
|
|
3
4
|
import type { CompletionContext, HoverContext } from "@intentius/chant/lsp/types";
|
|
4
5
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
@@ -84,6 +85,11 @@ export const terraformPlugin: LexiconPlugin = {
|
|
|
84
85
|
serializer: terraformSerializer,
|
|
85
86
|
configSchema: terraformConfigSchema,
|
|
86
87
|
|
|
88
|
+
// #4550: the engine install lines for the pull request pipeline's default image.
|
|
89
|
+
ciSetup(ctx: { dir: string }): string[] {
|
|
90
|
+
return terraformCiSetup(ctx);
|
|
91
|
+
},
|
|
92
|
+
|
|
87
93
|
// ── Required lifecycle methods ────────────────────────────────
|
|
88
94
|
|
|
89
95
|
async generate(options?: { verbose?: boolean }): Promise<void> {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# documented
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"terraform": {
|
|
3
|
+
"required_providers": {
|
|
4
|
+
"google": { "source": "hashicorp/google", "version": "~> 6.0" }
|
|
5
|
+
}
|
|
6
|
+
},
|
|
7
|
+
"variable": {
|
|
8
|
+
"project": { "type": "string", "description": "The project id." },
|
|
9
|
+
"region": { "type": "string", "default": "us-central1" }
|
|
10
|
+
},
|
|
11
|
+
"output": {
|
|
12
|
+
"name": { "value": "${var.project}", "description": "The project id, echoed." }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Simple
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
variable "cidr" {
|
|
2
|
+
type = string
|
|
3
|
+
description = "The VPC's IPv4 CIDR block."
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "tags" {
|
|
7
|
+
type = map(string)
|
|
8
|
+
default = {}
|
|
9
|
+
description = "Tags for every resource."
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "flow_log_key" {
|
|
13
|
+
type = string
|
|
14
|
+
default = "not-a-real-key"
|
|
15
|
+
sensitive = true
|
|
16
|
+
}
|