@intentius/chant-lexicon-terraform 0.124.52 → 0.124.54
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 -6
- package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
- package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
- package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
- package/dist/__fixtures__/report/generate.d.ts +26 -0
- package/dist/__fixtures__/report/generate.d.ts.map +1 -0
- package/dist/__fixtures__/report/generate.js +58 -0
- package/dist/applied-state.d.ts +27 -0
- package/dist/applied-state.d.ts.map +1 -0
- package/dist/applied-state.js +44 -0
- package/dist/backend-config.d.ts +53 -0
- package/dist/backend-config.d.ts.map +1 -0
- package/dist/backend-config.js +136 -0
- package/dist/behaviour/kinds.d.ts.map +1 -1
- package/dist/behaviour/kinds.js +4 -0
- package/dist/behaviour/predict.d.ts.map +1 -1
- package/dist/behaviour/predict.js +6 -2
- package/dist/behaviour/reference-catalog.d.ts +16 -5
- package/dist/behaviour/reference-catalog.d.ts.map +1 -1
- package/dist/behaviour/reference-catalog.js +16 -5
- package/dist/behaviour/request.d.ts.map +1 -1
- package/dist/behaviour/request.js +3 -2
- package/dist/changed-roots.d.ts +6 -4
- package/dist/changed-roots.d.ts.map +1 -1
- package/dist/changed-roots.js +14 -18
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/components/capability-plugin.d.ts +65 -3
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +197 -13
- package/dist/components/compose-group.d.ts +145 -0
- package/dist/components/compose-group.d.ts.map +1 -0
- package/dist/components/compose-group.js +407 -0
- package/dist/components/placeholder-preview.d.ts +78 -0
- package/dist/components/placeholder-preview.d.ts.map +1 -0
- package/dist/components/placeholder-preview.js +254 -0
- package/dist/compose/apply-events.d.ts +46 -0
- package/dist/compose/apply-events.d.ts.map +1 -0
- package/dist/compose/apply-events.js +77 -0
- package/dist/compose/estate.d.ts +194 -0
- package/dist/compose/estate.d.ts.map +1 -0
- package/dist/compose/estate.js +363 -0
- package/dist/compose/hcl.d.ts +99 -0
- package/dist/compose/hcl.d.ts.map +1 -0
- package/dist/compose/hcl.js +302 -0
- package/dist/compose/module-address.d.ts +24 -0
- package/dist/compose/module-address.d.ts.map +1 -0
- package/dist/compose/module-address.js +43 -0
- package/dist/compose/root.d.ts +127 -0
- package/dist/compose/root.d.ts.map +1 -0
- package/dist/compose/root.js +586 -0
- package/dist/compose/run.d.ts +183 -0
- package/dist/compose/run.d.ts.map +1 -0
- package/dist/compose/run.js +447 -0
- package/dist/compose/slice.d.ts +102 -0
- package/dist/compose/slice.d.ts.map +1 -0
- package/dist/compose/slice.js +249 -0
- package/dist/compose/state-address.d.ts +28 -0
- package/dist/compose/state-address.d.ts.map +1 -0
- package/dist/compose/state-address.js +154 -0
- package/dist/compose/state-merge.d.ts +52 -0
- package/dist/compose/state-merge.d.ts.map +1 -0
- package/dist/compose/state-merge.js +135 -0
- package/dist/compose.d.ts +179 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +482 -0
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-apply-op.js +7 -3
- package/dist/config.d.ts +59 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -9
- package/dist/describe-resources.d.ts +74 -12
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/describe-resources.js +374 -66
- package/dist/drift-read.d.ts +33 -0
- package/dist/drift-read.d.ts.map +1 -0
- package/dist/drift-read.js +243 -0
- package/dist/drift-record.d.ts +147 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +383 -0
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/edges.js +24 -1
- package/dist/hcl/files.d.ts +61 -0
- package/dist/hcl/files.d.ts.map +1 -0
- package/dist/hcl/files.js +221 -0
- package/dist/hcl/instances.d.ts +82 -0
- package/dist/hcl/instances.d.ts.map +1 -0
- package/dist/hcl/instances.js +390 -0
- package/dist/hcl/parse.d.ts +81 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/parse.js +114 -16
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/roots.js +5 -0
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/hcl/suppressions.js +8 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/integrity.json +3 -3
- package/dist/live-plan-reasons.d.ts +21 -0
- package/dist/live-plan-reasons.d.ts.map +1 -0
- package/dist/live-plan-reasons.js +47 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/lsp/option-keys.js +2 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/index.d.ts +6 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +5 -1
- package/dist/op/activities/live-detect.d.ts +4 -3
- package/dist/op/activities/live-detect.d.ts.map +1 -1
- package/dist/op/activities/live-detect.js +57 -32
- package/dist/op/activities/pin-rollout.d.ts +2 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.js +1 -0
- package/dist/op/activities/terraform.d.ts +153 -5
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +205 -20
- package/dist/op/activity-contracts.d.ts +27 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +19 -1
- package/dist/op/builders.d.ts +7 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -0
- package/dist/pin/edit.d.ts.map +1 -1
- package/dist/pin/edit.js +35 -0
- package/dist/pin/rollout.d.ts +2 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +14 -11
- package/dist/plan-shape.d.ts +30 -0
- package/dist/plan-shape.d.ts.map +1 -0
- package/dist/plan-shape.js +125 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +32 -18
- package/dist/report.d.ts +15 -6
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +28 -55
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/state/azure-blob.d.ts +68 -0
- package/dist/state/azure-blob.d.ts.map +1 -0
- package/dist/state/azure-blob.js +201 -0
- package/dist/state/backend.d.ts +158 -0
- package/dist/state/backend.d.ts.map +1 -0
- package/dist/state/backend.js +406 -0
- package/dist/state/exec.d.ts +39 -0
- package/dist/state/exec.d.ts.map +1 -0
- package/dist/state/exec.js +80 -0
- package/dist/state/gcs.d.ts +62 -0
- package/dist/state/gcs.d.ts.map +1 -0
- package/dist/state/gcs.js +224 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +14 -0
- package/dist/state/locks.d.ts +105 -0
- package/dist/state/locks.d.ts.map +1 -0
- package/dist/state/locks.js +130 -0
- package/dist/state/object-store.d.ts +37 -0
- package/dist/state/object-store.d.ts.map +1 -0
- package/dist/state/object-store.js +20 -0
- package/dist/state/root-states.d.ts +131 -0
- package/dist/state/root-states.d.ts.map +1 -0
- package/dist/state/root-states.js +241 -0
- package/dist/state/s3.d.ts +68 -0
- package/dist/state/s3.d.ts.map +1 -0
- package/dist/state/s3.js +170 -0
- package/dist/state/store.d.ts +51 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +185 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +2 -1
- package/package.json +7 -3
- package/src/__fixtures__/ACCEPTANCE.md +69 -7
- package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
- package/src/__fixtures__/compose/state-app.json +66 -0
- package/src/__fixtures__/compose/state-edge.json +40 -0
- package/src/__fixtures__/compose/state-net.json +58 -0
- package/src/__fixtures__/compose/three-app.plan.json +184 -0
- package/src/__fixtures__/compose/three-composed.plan.json +509 -0
- package/src/__fixtures__/compose/three-edge.plan.json +144 -0
- package/src/__fixtures__/compose/three-net.plan.json +174 -0
- package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
- package/src/__fixtures__/compose/working-after-apply.json +145 -0
- package/src/__fixtures__/config-blocks/app/main.tf +61 -0
- package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
- package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
- package/src/__fixtures__/config-files/shadow/main.tf +3 -0
- package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
- package/src/__fixtures__/config-files/shadow/other.tf +3 -0
- package/src/__fixtures__/config-files/tf/main.tf +11 -0
- package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
- package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
- package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
- package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
- package/src/__fixtures__/drift-record/README.md +11 -0
- package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
- package/src/__fixtures__/instance-forms/app/main.tf +110 -0
- package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/main.tf +52 -0
- package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
- package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
- package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
- package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
- package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
- package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
- package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
- package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
- package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
- package/src/__fixtures__/pr-loop/README.md +50 -0
- package/src/__fixtures__/pr-loop/chant.config.json +13 -0
- package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
- package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
- package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
- package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
- package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
- package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
- package/src/__fixtures__/report/applied.show.json +51 -0
- package/src/__fixtures__/report/generate.ts +63 -0
- package/src/__fixtures__/report/plan.show.json +200 -0
- package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
- package/src/__fixtures__/report/tf-apply/report.json +338 -0
- package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
- package/src/__fixtures__/report/tf-plan/report.json +320 -0
- package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
- package/src/applied-state.ts +52 -0
- package/src/backend-config.test.ts +105 -0
- package/src/backend-config.ts +133 -0
- package/src/behaviour/kinds.ts +4 -0
- package/src/behaviour/predict.ts +5 -2
- package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
- package/src/behaviour/reference-catalog.ts +16 -5
- package/src/behaviour/request.ts +3 -2
- package/src/change-set-instances.test.ts +98 -0
- package/src/changed-roots.ts +14 -16
- package/src/commands.ts +4 -3
- package/src/components/capability-plugin.test.ts +172 -5
- package/src/components/capability-plugin.ts +212 -11
- package/src/components/change-set-graph.e2e.test.ts +11 -5
- package/src/components/compose-group.test.ts +486 -0
- package/src/components/compose-group.ts +489 -0
- package/src/components/demo-composed-roots.e2e.test.ts +440 -0
- package/src/components/member-outputs.test.ts +76 -0
- package/src/components/placeholder-preview.test.ts +147 -0
- package/src/components/placeholder-preview.ts +257 -0
- package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
- package/src/components/pr-loop.tofu.test.ts +112 -56
- package/src/compose/apply-events.test.ts +62 -0
- package/src/compose/apply-events.ts +100 -0
- package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
- package/src/compose/estate.test.ts +421 -0
- package/src/compose/estate.ts +443 -0
- package/src/compose/hcl.ts +311 -0
- package/src/compose/module-address.ts +49 -0
- package/src/compose/root.test.ts +416 -0
- package/src/compose/root.ts +592 -0
- package/src/compose/run.test.ts +440 -0
- package/src/compose/run.tofu.test.ts +176 -0
- package/src/compose/run.ts +527 -0
- package/src/compose/slice.test.ts +184 -0
- package/src/compose/slice.ts +290 -0
- package/src/compose/state-address.ts +157 -0
- package/src/compose/state-merge.test.ts +131 -0
- package/src/compose/state-merge.ts +148 -0
- package/src/compose.test.ts +248 -0
- package/src/compose.ts +627 -0
- package/src/composites/terraform-apply-op.test.ts +9 -0
- package/src/composites/terraform-apply-op.ts +7 -3
- package/src/config-file-listers.test.ts +265 -0
- package/src/config.ts +44 -9
- package/src/describe-resources.engine-facts.test.ts +412 -0
- package/src/describe-resources.live-module-keyed.test.ts +290 -0
- package/src/describe-resources.ts +416 -70
- package/src/drift-read.ts +284 -0
- package/src/drift-record.test.ts +223 -0
- package/src/drift-record.ts +455 -0
- package/src/hcl/config-blocks.test.ts +174 -0
- package/src/hcl/edges.ts +21 -1
- package/src/hcl/files.ts +234 -0
- package/src/hcl/instance-forms.test.ts +94 -0
- package/src/hcl/instances.test.ts +137 -0
- package/src/hcl/instances.ts +422 -0
- package/src/hcl/parse.test.ts +139 -1
- package/src/hcl/parse.ts +149 -16
- package/src/hcl/roots.ts +6 -0
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +76 -0
- package/src/live-plan-reasons.ts +54 -0
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/option-keys.ts +2 -0
- package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/live-detect.ts +55 -26
- package/src/op/activities/pin-rollout.ts +3 -0
- package/src/op/activities/registry.test.ts +2 -1
- package/src/op/activities/terraform.test.ts +55 -0
- package/src/op/activities/terraform.ts +266 -22
- package/src/op/activity-contracts.test.ts +1 -0
- package/src/op/activity-contracts.ts +24 -1
- package/src/op/builders.ts +14 -0
- package/src/pin/bundle.test.ts +2 -0
- package/src/pin/edit.ts +30 -0
- package/src/pin/rollout.ts +16 -11
- package/src/plan-shape.test.ts +148 -0
- package/src/plan-shape.ts +128 -0
- package/src/plugin.ts +33 -19
- package/src/report.test.ts +82 -1
- package/src/report.ts +31 -54
- package/src/signal-only-root.test.ts +72 -0
- package/src/skills/chant-terraform.md +1 -1
- package/src/state/azure-blob.ts +223 -0
- package/src/state/backend-verdict.test.ts +187 -0
- package/src/state/backend.test.ts +127 -0
- package/src/state/backend.ts +488 -0
- package/src/state/exec.ts +111 -0
- package/src/state/gcs.ts +254 -0
- package/src/state/index.ts +15 -0
- package/src/state/locks.test.ts +84 -0
- package/src/state/locks.ts +201 -0
- package/src/state/object-store.ts +35 -0
- package/src/state/root-states-floci.e2e.test.ts +66 -0
- package/src/state/root-states.test.ts +462 -0
- package/src/state/root-states.ts +309 -0
- package/src/state/s3.ts +193 -0
- package/src/state/store.ts +225 -0
- package/src/terragrunt/affected.ts +2 -1
- package/src/workspace-kinds.test.ts +4 -17
- package/workspace-kinds.json +1 -24
- package/dist/workspace-member.d.ts +0 -30
- package/dist/workspace-member.d.ts.map +0 -1
- package/dist/workspace-member.js +0 -91
- package/schemas/pulumi.schema.json +0 -34
- package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
- package/src/testdata/pulumi-member/stack-export.json +0 -53
- package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
- package/src/workspace-member.test.ts +0 -89
- package/src/workspace-member.ts +0 -126
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "The azure member (a choudoufu estate) of behold origin/integration/consolidation-graph example-three-cloud, read with chant on integration/consolidation-graph (#4535): chant workspace graph --member azure, the read behold composes. The source attribute (the file text) dropped.",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "azure/azure/azurerm_container_app_environment.site",
|
|
6
|
+
"kind": "Terraform::Resource",
|
|
7
|
+
"lexicon": "terraform",
|
|
8
|
+
"attrs": {
|
|
9
|
+
"address": "azurerm_container_app_environment.site",
|
|
10
|
+
"type": "azurerm_container_app_environment",
|
|
11
|
+
"name": "site",
|
|
12
|
+
"file": "site.tf",
|
|
13
|
+
"root": "azure",
|
|
14
|
+
"line": 51,
|
|
15
|
+
"mode": "live",
|
|
16
|
+
"estate": "three-cloud-azure",
|
|
17
|
+
"lockFile": false
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "azure/azure/azurerm_container_app.web",
|
|
22
|
+
"kind": "Terraform::Resource",
|
|
23
|
+
"lexicon": "terraform",
|
|
24
|
+
"attrs": {
|
|
25
|
+
"address": "azurerm_container_app.web",
|
|
26
|
+
"type": "azurerm_container_app",
|
|
27
|
+
"name": "web",
|
|
28
|
+
"file": "site.tf",
|
|
29
|
+
"root": "azure",
|
|
30
|
+
"line": 65,
|
|
31
|
+
"mode": "live",
|
|
32
|
+
"estate": "three-cloud-azure",
|
|
33
|
+
"lockFile": false
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "azure/azure/azurerm_log_analytics_workspace.logs",
|
|
38
|
+
"kind": "Terraform::Resource",
|
|
39
|
+
"lexicon": "terraform",
|
|
40
|
+
"attrs": {
|
|
41
|
+
"address": "azurerm_log_analytics_workspace.logs",
|
|
42
|
+
"type": "azurerm_log_analytics_workspace",
|
|
43
|
+
"name": "logs",
|
|
44
|
+
"file": "site.tf",
|
|
45
|
+
"root": "azure",
|
|
46
|
+
"line": 37,
|
|
47
|
+
"mode": "live",
|
|
48
|
+
"estate": "three-cloud-azure",
|
|
49
|
+
"lockFile": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "azure/azure/azurerm_resource_group.site",
|
|
54
|
+
"kind": "Terraform::Resource",
|
|
55
|
+
"lexicon": "terraform",
|
|
56
|
+
"attrs": {
|
|
57
|
+
"address": "azurerm_resource_group.site",
|
|
58
|
+
"type": "azurerm_resource_group",
|
|
59
|
+
"name": "site",
|
|
60
|
+
"file": "site.tf",
|
|
61
|
+
"root": "azure",
|
|
62
|
+
"line": 7,
|
|
63
|
+
"mode": "live",
|
|
64
|
+
"estate": "three-cloud-azure",
|
|
65
|
+
"lockFile": false
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "azure/azure/azurerm_subnet.apps",
|
|
70
|
+
"kind": "Terraform::Resource",
|
|
71
|
+
"lexicon": "terraform",
|
|
72
|
+
"attrs": {
|
|
73
|
+
"address": "azurerm_subnet.apps",
|
|
74
|
+
"type": "azurerm_subnet",
|
|
75
|
+
"name": "apps",
|
|
76
|
+
"file": "site.tf",
|
|
77
|
+
"root": "azure",
|
|
78
|
+
"line": 20,
|
|
79
|
+
"mode": "live",
|
|
80
|
+
"estate": "three-cloud-azure",
|
|
81
|
+
"lockFile": false
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "azure/azure/azurerm_user_assigned_identity.web",
|
|
86
|
+
"kind": "Terraform::Resource",
|
|
87
|
+
"lexicon": "terraform",
|
|
88
|
+
"attrs": {
|
|
89
|
+
"address": "azurerm_user_assigned_identity.web",
|
|
90
|
+
"type": "azurerm_user_assigned_identity",
|
|
91
|
+
"name": "web",
|
|
92
|
+
"file": "site.tf",
|
|
93
|
+
"root": "azure",
|
|
94
|
+
"line": 45,
|
|
95
|
+
"mode": "live",
|
|
96
|
+
"estate": "three-cloud-azure",
|
|
97
|
+
"lockFile": false
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "azure/azure/azurerm_virtual_network.network",
|
|
102
|
+
"kind": "Terraform::Resource",
|
|
103
|
+
"lexicon": "terraform",
|
|
104
|
+
"attrs": {
|
|
105
|
+
"address": "azurerm_virtual_network.network",
|
|
106
|
+
"type": "azurerm_virtual_network",
|
|
107
|
+
"name": "network",
|
|
108
|
+
"file": "site.tf",
|
|
109
|
+
"root": "azure",
|
|
110
|
+
"line": 13,
|
|
111
|
+
"mode": "live",
|
|
112
|
+
"estate": "three-cloud-azure",
|
|
113
|
+
"lockFile": false
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "azure/azure/live",
|
|
118
|
+
"kind": "Terraform::Live",
|
|
119
|
+
"lexicon": "terraform",
|
|
120
|
+
"attrs": {
|
|
121
|
+
"address": "live",
|
|
122
|
+
"file": "main.tf",
|
|
123
|
+
"root": "azure",
|
|
124
|
+
"mode": "live",
|
|
125
|
+
"estate": "three-cloud-azure",
|
|
126
|
+
"lockFile": false
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "azure/azure/output.azureEndpoint",
|
|
131
|
+
"kind": "Terraform::Output",
|
|
132
|
+
"lexicon": "terraform",
|
|
133
|
+
"attrs": {
|
|
134
|
+
"address": "output.azureEndpoint",
|
|
135
|
+
"file": "site.tf",
|
|
136
|
+
"root": "azure",
|
|
137
|
+
"line": 102,
|
|
138
|
+
"mode": "live",
|
|
139
|
+
"estate": "three-cloud-azure",
|
|
140
|
+
"lockFile": false
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "azure/azure/provider.azurerm",
|
|
145
|
+
"kind": "Terraform::Provider",
|
|
146
|
+
"lexicon": "terraform",
|
|
147
|
+
"attrs": {
|
|
148
|
+
"address": "provider.azurerm",
|
|
149
|
+
"file": "main.tf",
|
|
150
|
+
"root": "azure",
|
|
151
|
+
"line": 35,
|
|
152
|
+
"mode": "live",
|
|
153
|
+
"estate": "three-cloud-azure",
|
|
154
|
+
"lockFile": false
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "azure/azure/terraform",
|
|
159
|
+
"kind": "Terraform::Terraform",
|
|
160
|
+
"lexicon": "terraform",
|
|
161
|
+
"attrs": {
|
|
162
|
+
"address": "terraform",
|
|
163
|
+
"file": "main.tf",
|
|
164
|
+
"root": "azure",
|
|
165
|
+
"line": 13,
|
|
166
|
+
"mode": "live",
|
|
167
|
+
"estate": "three-cloud-azure",
|
|
168
|
+
"lockFile": false
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "azure/azure/var.floci_az_host",
|
|
173
|
+
"kind": "Terraform::Variable",
|
|
174
|
+
"lexicon": "terraform",
|
|
175
|
+
"attrs": {
|
|
176
|
+
"address": "var.floci_az_host",
|
|
177
|
+
"file": "main.tf",
|
|
178
|
+
"root": "azure",
|
|
179
|
+
"line": 25,
|
|
180
|
+
"mode": "live",
|
|
181
|
+
"estate": "three-cloud-azure",
|
|
182
|
+
"lockFile": false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"edges": [
|
|
187
|
+
{
|
|
188
|
+
"from": "azure/azure/azurerm_container_app_environment.site",
|
|
189
|
+
"to": "azure/azure/azurerm_log_analytics_workspace.logs",
|
|
190
|
+
"kind": "ref",
|
|
191
|
+
"viaAttr": "log_analytics_workspace_id"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"from": "azure/azure/azurerm_container_app_environment.site",
|
|
195
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
196
|
+
"kind": "ref",
|
|
197
|
+
"viaAttr": "location"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"from": "azure/azure/azurerm_container_app_environment.site",
|
|
201
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
202
|
+
"kind": "ref",
|
|
203
|
+
"viaAttr": "resource_group_name"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"from": "azure/azure/azurerm_container_app_environment.site",
|
|
207
|
+
"to": "azure/azure/azurerm_subnet.apps",
|
|
208
|
+
"kind": "ref",
|
|
209
|
+
"viaAttr": "infrastructure_subnet_id"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"from": "azure/azure/azurerm_container_app.web",
|
|
213
|
+
"to": "azure/azure/azurerm_container_app_environment.site",
|
|
214
|
+
"kind": "ref",
|
|
215
|
+
"viaAttr": "container_app_environment_id"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"from": "azure/azure/azurerm_container_app.web",
|
|
219
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
220
|
+
"kind": "ref",
|
|
221
|
+
"viaAttr": "resource_group_name"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"from": "azure/azure/azurerm_container_app.web",
|
|
225
|
+
"to": "azure/azure/azurerm_user_assigned_identity.web",
|
|
226
|
+
"kind": "ref",
|
|
227
|
+
"viaAttr": "identity"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"from": "azure/azure/azurerm_log_analytics_workspace.logs",
|
|
231
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
232
|
+
"kind": "ref",
|
|
233
|
+
"viaAttr": "location"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"from": "azure/azure/azurerm_log_analytics_workspace.logs",
|
|
237
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
238
|
+
"kind": "ref",
|
|
239
|
+
"viaAttr": "resource_group_name"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"from": "azure/azure/azurerm_subnet.apps",
|
|
243
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
244
|
+
"kind": "ref",
|
|
245
|
+
"viaAttr": "resource_group_name"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"from": "azure/azure/azurerm_subnet.apps",
|
|
249
|
+
"to": "azure/azure/azurerm_virtual_network.network",
|
|
250
|
+
"kind": "ref",
|
|
251
|
+
"viaAttr": "virtual_network_name"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"from": "azure/azure/azurerm_user_assigned_identity.web",
|
|
255
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
256
|
+
"kind": "ref",
|
|
257
|
+
"viaAttr": "location"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"from": "azure/azure/azurerm_user_assigned_identity.web",
|
|
261
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
262
|
+
"kind": "ref",
|
|
263
|
+
"viaAttr": "resource_group_name"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"from": "azure/azure/azurerm_virtual_network.network",
|
|
267
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
268
|
+
"kind": "ref",
|
|
269
|
+
"viaAttr": "location"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"from": "azure/azure/azurerm_virtual_network.network",
|
|
273
|
+
"to": "azure/azure/azurerm_resource_group.site",
|
|
274
|
+
"kind": "ref",
|
|
275
|
+
"viaAttr": "resource_group_name"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"from": "azure/azure/output.azureEndpoint",
|
|
279
|
+
"to": "azure/azure/azurerm_container_app.web",
|
|
280
|
+
"kind": "ref",
|
|
281
|
+
"viaAttr": "value"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"from": "azure/azure/provider.azurerm",
|
|
285
|
+
"to": "azure/azure/var.floci_az_host",
|
|
286
|
+
"kind": "ref",
|
|
287
|
+
"viaAttr": "metadata_host"
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What applied, read from `show -json` of a plan made after the apply: its
|
|
3
|
+
* `prior_state` is the state the apply left (chant#4445). choudoufu refuses a
|
|
4
|
+
* bare `show` under a `live` block, since there is no state file to show, so
|
|
5
|
+
* a post-apply plan is the one place its applied values can be read.
|
|
6
|
+
*
|
|
7
|
+
* `chant terraform report` (`./report.ts`) and the composed estate run
|
|
8
|
+
* (`./compose/estate.ts`) both read through {@link priorStateValues}.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Json = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
const isObject = (v: unknown): v is Json => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `values` of the state a `show -json` holds: a plan's `prior_state`, or
|
|
17
|
+
* a state's own `values` when it is a `show -json` of state. Undefined when
|
|
18
|
+
* it holds neither.
|
|
19
|
+
*/
|
|
20
|
+
export function priorStateValues(show: unknown): Json | undefined {
|
|
21
|
+
const root = isObject(show) ? show : {};
|
|
22
|
+
const state = isObject(root.prior_state) ? root.prior_state : root;
|
|
23
|
+
return isObject(state.values) ? state.values : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One resource of a state: its values and its sensitivity map. */
|
|
27
|
+
export interface StateResourceValues {
|
|
28
|
+
values: Json;
|
|
29
|
+
sensitive: Json;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Every resource of the state a `show -json` holds, by address. A deposed object is left out. */
|
|
33
|
+
export function stateResources(show: unknown): Map<string, StateResourceValues> {
|
|
34
|
+
const out = new Map<string, StateResourceValues>();
|
|
35
|
+
const walk = (mod: unknown): void => {
|
|
36
|
+
if (!isObject(mod)) return;
|
|
37
|
+
for (const r of Array.isArray(mod.resources) ? mod.resources : []) {
|
|
38
|
+
if (!isObject(r) || typeof r.address !== "string" || r.deposed_key !== undefined) continue;
|
|
39
|
+
out.set(r.address, { values: isObject(r.values) ? r.values : {}, sensitive: isObject(r.sensitive_values) ? r.sensitive_values : {} });
|
|
40
|
+
}
|
|
41
|
+
for (const c of Array.isArray(mod.child_modules) ? mod.child_modules : []) walk(c);
|
|
42
|
+
};
|
|
43
|
+
walk(priorStateValues(show)?.root_module);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The outputs of the state a `show -json` holds, by name, each its value. */
|
|
48
|
+
export function stateOutputs(show: unknown): Record<string, unknown> {
|
|
49
|
+
const outputs = priorStateValues(show)?.outputs;
|
|
50
|
+
if (!isObject(outputs)) return {};
|
|
51
|
+
return Object.fromEntries(Object.entries(outputs).map(([k, v]) => [k, isObject(v) && "value" in v ? v.value : v]));
|
|
52
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A root can name backend-config files (chant#4459): the config key, the
|
|
3
|
+
* flags init gets, where a relative path resolves, and the keys the
|
|
4
|
+
* composition reads from a file before init.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
10
|
+
import { terraformRootSchema } from "./config";
|
|
11
|
+
import { ROOT_ENTRY_KEYS } from "./lsp/option-keys";
|
|
12
|
+
import { backendConfigArgs, backendConfigFileKeys, backendConfigFilePath, readBackendConfigFile } from "./backend-config";
|
|
13
|
+
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
14
|
+
import { compositionFor } from "./compose";
|
|
15
|
+
import { rootState } from "./compose/hcl";
|
|
16
|
+
import { declaredBackend, lockRefusal, readBackend } from "./state/backend";
|
|
17
|
+
|
|
18
|
+
const made: string[] = [];
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
for (const d of made) rmSync(d, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function tree(files: Record<string, string>): string {
|
|
24
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-4459-"));
|
|
25
|
+
made.push(dir);
|
|
26
|
+
for (const [path, text] of Object.entries(files)) {
|
|
27
|
+
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
28
|
+
writeFileSync(join(dir, path), text);
|
|
29
|
+
}
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const S3_NO_LOCK = 'terraform {\n backend "s3" {\n bucket = "b"\n }\n}\n';
|
|
34
|
+
|
|
35
|
+
describe("terraform.roots.<name>.backendConfigFiles", () => {
|
|
36
|
+
test("the config schema takes a list of paths beside backendConfig, and refuses anything else", () => {
|
|
37
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: ["backend.hcl"], backendConfig: { key: "k" } }).success).toBe(true);
|
|
38
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: "backend.hcl" }).success).toBe(false);
|
|
39
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: [1] }).success).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("the LSP offers the key on a root entry", () => {
|
|
43
|
+
expect(ROOT_ENTRY_KEYS.map((k) => k.key)).toContain("backendConfigFiles");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("init's flags: files in order, then the pairs", () => {
|
|
47
|
+
expect(backendConfigArgs({ backendConfigFiles: ["a.hcl", "b.hcl"], backendConfig: { key: "k" } })).toEqual(["-backend-config=a.hcl", "-backend-config=b.hcl", "-backend-config=key=k"]);
|
|
48
|
+
expect(backendConfigArgs({})).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("a backend-config file's keys", () => {
|
|
53
|
+
test("a relative path resolves against the root's directory, as varFiles does; an absolute one stays", () => {
|
|
54
|
+
expect(backendConfigFilePath("/repo/roots/app", "backend.hcl")).toBe("/repo/roots/app/backend.hcl");
|
|
55
|
+
expect(backendConfigFilePath("/repo/roots/app", "../shared/backend.hcl")).toBe("/repo/roots/shared/backend.hcl");
|
|
56
|
+
expect(backendConfigFilePath("/repo/roots/app", "/etc/backend.hcl")).toBe("/etc/backend.hcl");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("a file's key = value lines, typed, comments and interpolations left out", () => {
|
|
60
|
+
const dir = tree({ "backend.hcl": '# shared\nbucket = "acme-state" // the bucket\nuse_lockfile = true\nmax_retries = 5\nkey = "${var.no}"\n' });
|
|
61
|
+
expect(readBackendConfigFile(join(dir, "backend.hcl"))).toEqual({ bucket: "acme-state", use_lockfile: true, max_retries: 5 });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("later files over earlier ones, read from the root's directory, and a missing file named", () => {
|
|
65
|
+
const dir = tree({ "roots/app/one.hcl": 'bucket = "one"\nkey = "k"\n', "roots/shared/two.hcl": 'bucket = "two"\n' });
|
|
66
|
+
expect(backendConfigFileKeys(join(dir, "roots/app"), ["one.hcl", "../shared/two.hcl", "gone.hcl"])).toEqual({ config: { bucket: "two", key: "k" }, unread: ["gone.hcl"] });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("the composition reads the file's keys before init (ruled: yes)", () => {
|
|
71
|
+
test("an s3 root whose use_lockfile = true is only in the file is lockable", () => {
|
|
72
|
+
const dir = tree({ "main.tf": S3_NO_LOCK, "backend.hcl": 'key = "app.tfstate"\nuse_lockfile = true\n' });
|
|
73
|
+
expect(lockRefusal(readBackend(dir, { recorded: null }))).toMatchObject({ refused: "no-lockfile" });
|
|
74
|
+
expect(lockRefusal(readBackend(dir, { recorded: null, backendConfigFiles: ["backend.hcl"] }))).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("a pair overrides the file, and the file overrides the code", () => {
|
|
78
|
+
const dir = tree({ "main.tf": 'terraform {\n backend "s3" {\n bucket = "code"\n key = "code"\n }\n}\n', "backend.hcl": 'bucket = "file"\nkey = "file"\n' });
|
|
79
|
+
expect(declaredBackend(dir, { backendConfigFiles: ["backend.hcl"], backendConfig: { key: "pair" } })).toEqual({ type: "s3", config: { bucket: "file", key: "pair" } });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("a -backend-config file in TF_CLI_ARGS_init is read too", () => {
|
|
83
|
+
const dir = tree({ "main.tf": S3_NO_LOCK, "ci.hcl": "use_lockfile = true\n" });
|
|
84
|
+
expect(lockRefusal(readBackend(dir, { recorded: null, env: { TF_CLI_ARGS_init: "-backend-config=ci.hcl" } }))).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("rootState finds the state's address from a key only the file gives", () => {
|
|
88
|
+
const dir = tree({ "main.tf": S3_NO_LOCK });
|
|
89
|
+
expect(rootState(dir, "app", { env: {}, recorded: null, backendConfig: { key: "from-file.tfstate" } }).own).toMatchObject({ key: "from-file.tfstate" });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("a composition lines a read up with a member whose state key is only in its file", () => {
|
|
93
|
+
const tf = (name: string): DriverComponent => ({ name, dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: name }] }] });
|
|
94
|
+
const reader = 'data "terraform_remote_state" "a" {\n backend = "s3"\n config = {\n bucket = "b"\n key = "a.tfstate"\n }\n}\n\nresource "terraform_data" "x" {\n input = data.terraform_remote_state.a.outputs.id\n}\n';
|
|
95
|
+
const lockedS3 = (key?: string): string => `terraform {\n backend "s3" {\n bucket = "b"\n${key ? ` key = "${key}"\n` : ""} use_lockfile = true\n }\n}\n`;
|
|
96
|
+
const dir = tree({ "roots/a/main.tf": lockedS3(), "roots/a/backend.hcl": 'key = "a.tfstate"\n', "roots/b/main.tf": lockedS3("b.tfstate") + reader });
|
|
97
|
+
const c = compositionFor({
|
|
98
|
+
projectRoot: dir,
|
|
99
|
+
env: {},
|
|
100
|
+
components: [tf("a"), tf("b")],
|
|
101
|
+
terraform: { roots: { a: { dir: "roots/a", backendConfigFiles: ["backend.hcl"] }, b: { dir: "roots/b" } }, compose: { enabled: true } },
|
|
102
|
+
});
|
|
103
|
+
expect(c.reads.get("b")).toEqual([{ via: "remote-state", upstream: "a", name: "a" }]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A root's `-backend-config` (chant#4459): the files it names in
|
|
3
|
+
* `terraform.roots.<name>.backendConfigFiles` and the pairs in
|
|
4
|
+
* `backendConfig`, as the flags every init the lexicon runs passes, and the
|
|
5
|
+
* keys a file holds, as the readers that find a root's state before init see
|
|
6
|
+
* them.
|
|
7
|
+
*
|
|
8
|
+
* Files go first and pairs after, because init applies later flags over
|
|
9
|
+
* earlier ones and a pair is the more specific of the two. A file's path is
|
|
10
|
+
* passed as written: init runs in the root's directory, so a relative path
|
|
11
|
+
* resolves against it, as `varFiles` does. A reader of the file resolves it
|
|
12
|
+
* the same way.
|
|
13
|
+
*
|
|
14
|
+
* Plain node: `op/activities/terraform.ts` imports this, and it stays out of
|
|
15
|
+
* the HCL parser's dependency graph.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { isAbsolute, resolve } from "node:path";
|
|
20
|
+
|
|
21
|
+
/** The two keys of a root that say what init's `-backend-config` flags are. */
|
|
22
|
+
export interface BackendConfigFlags {
|
|
23
|
+
backendConfigFiles?: readonly string[];
|
|
24
|
+
backendConfig?: Readonly<Record<string, string>>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The `-backend-config` values in the order init takes them: each file's path, then each `key=value` pair. Unquoted, one per flag. */
|
|
28
|
+
export function backendConfigValues(root: BackendConfigFlags): string[] {
|
|
29
|
+
return [...(root.backendConfigFiles ?? []), ...Object.entries(root.backendConfig ?? {}).map(([k, v]) => `${k}=${v}`)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** {@link backendConfigValues} as argv: `-backend-config=<value>` each. */
|
|
33
|
+
export function backendConfigArgs(root: BackendConfigFlags): string[] {
|
|
34
|
+
return backendConfigValues(root).map((v) => `-backend-config=${v}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A backend-config file's path as init in `dir` reads it: an absolute path as it is, a relative one against `dir`. */
|
|
38
|
+
export function backendConfigFilePath(dir: string, file: string): string {
|
|
39
|
+
return isAbsolute(file) ? file : resolve(dir, file);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The text of `body` with everything nested deeper than `level` braces
|
|
44
|
+
* blanked, newlines kept, so only the attributes at that level are left to
|
|
45
|
+
* read. Strings are read through, so a brace in one does not count.
|
|
46
|
+
*/
|
|
47
|
+
export function blankBelow(body: string, level: number): string {
|
|
48
|
+
const out = body.split("");
|
|
49
|
+
let depth = 0;
|
|
50
|
+
let inString = false;
|
|
51
|
+
for (let i = 0; i < body.length; i++) {
|
|
52
|
+
const c = body[i]!;
|
|
53
|
+
if (depth > level && c !== "\n") out[i] = " ";
|
|
54
|
+
if (inString) {
|
|
55
|
+
if (c === "\\") {
|
|
56
|
+
if (depth > level && body[i + 1] !== undefined && body[i + 1] !== "\n") out[i + 1] = " ";
|
|
57
|
+
i++;
|
|
58
|
+
} else if (c === '"') inString = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === '"') inString = true;
|
|
62
|
+
else if (c === "{") depth++;
|
|
63
|
+
else if (c === "}") depth--;
|
|
64
|
+
}
|
|
65
|
+
return out.join("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The literal attributes of HCL text at brace depth `level` (0 for a file of
|
|
70
|
+
* bare attributes, 1 for the body of a block given with its header): plain
|
|
71
|
+
* strings, booleans and numbers, typed as init records them. A string with an
|
|
72
|
+
* interpolation, and anything that is not one line of `name = literal`, is
|
|
73
|
+
* left out. Comments are left to the caller to blank.
|
|
74
|
+
*/
|
|
75
|
+
export function literalAttributes(text: string, level: number): Record<string, unknown> {
|
|
76
|
+
const out: Record<string, unknown> = {};
|
|
77
|
+
for (const m of blankBelow(text, level).matchAll(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*("(?:[^"\\]|\\.)*"|true|false|-?\d+(?:\.\d+)?)\s*$/gm)) {
|
|
78
|
+
const raw = m[2]!;
|
|
79
|
+
if (raw.startsWith('"')) {
|
|
80
|
+
const s = raw.slice(1, -1);
|
|
81
|
+
if (/\$\{|%\{/.test(s)) continue;
|
|
82
|
+
out[m[1]!] = s.replace(/\\(.)/g, "$1");
|
|
83
|
+
} else out[m[1]!] = raw === "true" ? true : raw === "false" ? false : Number(raw);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** HCL text with its `#`, `//` and `/* *\/` comments blanked, strings read through. */
|
|
89
|
+
function withoutComments(text: string): string {
|
|
90
|
+
const out = text.split("");
|
|
91
|
+
let inString = false;
|
|
92
|
+
for (let i = 0; i < text.length; i++) {
|
|
93
|
+
const c = text[i]!;
|
|
94
|
+
if (inString) {
|
|
95
|
+
if (c === "\\") i++;
|
|
96
|
+
else if (c === '"') inString = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (c === '"') inString = true;
|
|
100
|
+
else if (c === "#" || (c === "/" && text[i + 1] === "/")) {
|
|
101
|
+
for (; i < text.length && text[i] !== "\n"; i++) out[i] = " ";
|
|
102
|
+
} else if (c === "/" && text[i + 1] === "*") {
|
|
103
|
+
const end = text.indexOf("*/", i + 2);
|
|
104
|
+
const stop = end < 0 ? text.length : end + 2;
|
|
105
|
+
for (; i < stop; i++) if (text[i] !== "\n") out[i] = " ";
|
|
106
|
+
i--;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out.join("");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The keys one backend-config file sets: its top-level `key = "value"` lines. Throws when the file cannot be read. */
|
|
113
|
+
export function readBackendConfigFile(path: string): Record<string, unknown> {
|
|
114
|
+
return literalAttributes(withoutComments(readFileSync(path, "utf-8")), 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The keys a root's backend-config files set, later files over earlier ones,
|
|
119
|
+
* each path resolved against `dir`; and the files that could not be read,
|
|
120
|
+
* which init itself will refuse.
|
|
121
|
+
*/
|
|
122
|
+
export function backendConfigFileKeys(dir: string, files: readonly string[] = []): { config: Record<string, unknown>; unread: string[] } {
|
|
123
|
+
const config: Record<string, unknown> = {};
|
|
124
|
+
const unread: string[] = [];
|
|
125
|
+
for (const f of files) {
|
|
126
|
+
try {
|
|
127
|
+
Object.assign(config, readBackendConfigFile(backendConfigFilePath(dir, f)));
|
|
128
|
+
} catch {
|
|
129
|
+
unread.push(f);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { config, unread };
|
|
133
|
+
}
|
package/src/behaviour/kinds.ts
CHANGED
|
@@ -138,6 +138,10 @@ const BLOCKS: Readonly<Record<string, string>> = {
|
|
|
138
138
|
"Terraform::Module": "a call to a child module; the blocks the call expands to are entities of their own and carry the figures",
|
|
139
139
|
"Terraform::Data": "a read of something that exists outside this root; whatever it reads is priced where it is declared",
|
|
140
140
|
"Terraform::Live": "choudoufu's estate declaration, which names the ownership marker every resource of the root carries and holds nothing itself",
|
|
141
|
+
"Terraform::Moved": "an instruction to rename an address in state; the resource it names carries the figures",
|
|
142
|
+
"Terraform::Import": "an instruction to adopt an existing object into state; the resource it names carries the figures",
|
|
143
|
+
"Terraform::Removed": "an instruction to drop an address from state; nothing is created by it",
|
|
144
|
+
"Terraform::Check": "an assertion terraform evaluates after a plan or apply, which holds nothing in any account",
|
|
141
145
|
};
|
|
142
146
|
|
|
143
147
|
/**
|
package/src/behaviour/predict.ts
CHANGED
|
@@ -163,10 +163,13 @@ export const readTerraformDeclaration: TerraformDeclarationReader = async (cwd)
|
|
|
163
163
|
const { config, configPath } = await loadChantConfigUpward(start);
|
|
164
164
|
const namespace = (config as { terraform?: TerraformConfig }).terraform;
|
|
165
165
|
const entities = new Map<string, TerraformBehaviourEntity>();
|
|
166
|
-
|
|
166
|
+
// A `signalOnly` root (#4484) builds nothing, as in `buildRoots`.
|
|
167
|
+
if (!namespace) return entities;
|
|
168
|
+
const roots = Object.fromEntries(Object.entries(namespace.roots ?? {}).filter(([, r]) => r.signalOnly !== true));
|
|
169
|
+
if (Object.keys(roots).length === 0) return entities;
|
|
167
170
|
const rendered = await renderTerraformRoots({
|
|
168
171
|
projectRoot: configPath ? dirname(configPath) : start,
|
|
169
|
-
roots
|
|
172
|
+
roots,
|
|
170
173
|
binary: namespace.binary,
|
|
171
174
|
callModuleType: namespace.callModuleType,
|
|
172
175
|
moduleRoot: namespace.moduleRoot,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4535: terraform's catalog is on the plugin, and what it reads there.
|
|
3
|
+
*
|
|
4
|
+
* A reader that asks each lexicon of a graph for its catalog (behold#1030)
|
|
5
|
+
* now finds terraform's. On a `chant graph` node (`Terraform::Resource`, no
|
|
6
|
+
* arguments in its attrs) no rule applies, so a declared terraform graph
|
|
7
|
+
* keeps exactly the edges its HCL references give it: on example-three-cloud's
|
|
8
|
+
* azure member that is also what behold's matcher added (nothing). On a node
|
|
9
|
+
* whose kind is the provider type and whose attrs are the block's arguments,
|
|
10
|
+
* each rule draws its edge.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import type { IRNode } from "@intentius/chant/graph-ir";
|
|
16
|
+
import { reconstructIndexedEdges, refRuleId } from "@intentius/chant/graph-refs";
|
|
17
|
+
import { TERRAFORM_REFERENCE_CATALOG } from "./reference-catalog";
|
|
18
|
+
import { terraformPlugin } from "../plugin";
|
|
19
|
+
|
|
20
|
+
const LEXICON = "terraform";
|
|
21
|
+
|
|
22
|
+
function atPath(path: string, value: string): Record<string, unknown> {
|
|
23
|
+
const parts = path.split(".");
|
|
24
|
+
const build = (i: number): unknown => {
|
|
25
|
+
if (i === parts.length) return value;
|
|
26
|
+
const part = parts[i];
|
|
27
|
+
const isArr = part.endsWith("[]");
|
|
28
|
+
const inner = build(i + 1);
|
|
29
|
+
return { [isArr ? part.slice(0, -2) : part]: isArr ? [inner] : inner };
|
|
30
|
+
};
|
|
31
|
+
return build(0) as Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("terraform reference catalog on the plugin", () => {
|
|
35
|
+
it("is the plugin's referenceCatalog", () => {
|
|
36
|
+
expect(terraformPlugin.referenceCatalog).toBe(TERRAFORM_REFERENCE_CATALOG);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("each targeted rule draws its edge on provider-typed nodes", () => {
|
|
40
|
+
for (const [i, rule] of TERRAFORM_REFERENCE_CATALOG.refs.entries()) {
|
|
41
|
+
if (!rule.targetKind) continue;
|
|
42
|
+
it(`${rule.from} ${rule.path} -> ${rule.targetKind}`, () => {
|
|
43
|
+
const value = `id-${i}`;
|
|
44
|
+
const nodes: IRNode[] = [
|
|
45
|
+
{ id: `holder-${i}`, kind: rule.from, lexicon: LEXICON, attrs: atPath(rule.path, value) },
|
|
46
|
+
{ id: `target-${i}`, kind: rule.targetKind!, lexicon: LEXICON, attrs: {}, physicalId: value },
|
|
47
|
+
];
|
|
48
|
+
const out = reconstructIndexedEdges(nodes, [{ lexicon: LEXICON, catalog: TERRAFORM_REFERENCE_CATALOG }], () => ({ account: "declared" }));
|
|
49
|
+
expect(out.edges.find((e) => e.from === `holder-${i}` && e.to === `target-${i}`)?.rule).toBe(refRuleId(LEXICON, rule.from, rule.path));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("example-three-cloud's azure member (parity with behold's matcher)", () => {
|
|
55
|
+
const graph = JSON.parse(readFileSync(join(import.meta.dirname, "..", "__fixtures__", "three-cloud-azure.graph.json"), "utf-8")) as { nodes: IRNode[] };
|
|
56
|
+
const out = reconstructIndexedEdges(graph.nodes, [{ lexicon: LEXICON, catalog: TERRAFORM_REFERENCE_CATALOG }], () => ({ account: "declared" }));
|
|
57
|
+
|
|
58
|
+
it("adds no edge to a declared block graph, as the matcher added none", () => {
|
|
59
|
+
expect(graph.nodes.some((n) => n.kind === "Terraform::Resource")).toBe(true);
|
|
60
|
+
expect(out.edges).toEqual([]);
|
|
61
|
+
expect(out.ambiguous).toEqual([]);
|
|
62
|
+
expect(out.dangling).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|