@intentius/chant-lexicon-terraform 0.124.31 → 0.124.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__fixtures__/terragrunt/affected-cases.js +111 -0
- package/dist/__fixtures__/terragrunt/record.js +57 -0
- package/dist/behaviour/index.js +6 -0
- package/dist/behaviour/kinds.js +194 -0
- package/dist/behaviour/predict.js +297 -0
- package/dist/behaviour/reference-catalog.js +178 -0
- package/dist/behaviour/request.js +482 -0
- package/dist/change-set.js +234 -0
- package/dist/changed-roots.js +97 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +44 -0
- package/dist/codegen/generate-cli.js +9 -0
- package/dist/codegen/generate.js +59 -0
- package/dist/codegen/package.js +43 -0
- package/dist/commands.js +106 -0
- package/dist/components/capability-plugin.d.ts +38 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +260 -0
- package/dist/composites/terraform-adopt-op.d.ts +2 -0
- package/dist/composites/terraform-adopt-op.d.ts.map +1 -1
- package/dist/composites/terraform-adopt-op.js +153 -0
- package/dist/composites/terraform-apply-op.d.ts +2 -0
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-apply-op.js +184 -0
- package/dist/composites/terraform-pin-rollout-op.js +45 -0
- package/dist/composites/terraform-watch-op.js +219 -0
- package/dist/config.js +118 -0
- package/dist/describe-resources.js +984 -0
- package/dist/generated/index.js +1 -0
- package/dist/hcl/descend.js +208 -0
- package/dist/hcl/edges.js +287 -0
- package/dist/hcl/jsonencode.js +442 -0
- package/dist/hcl/marker.js +212 -0
- package/dist/hcl/parse.js +372 -0
- package/dist/hcl/references.js +156 -0
- package/dist/hcl/roots.js +98 -0
- package/dist/hcl/suppressions.js +189 -0
- package/dist/hcl/value.js +72 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +501 -0
- package/dist/lint/audit-lineage.js +442 -0
- package/dist/lint/post-synth/blocks.js +87 -0
- package/dist/lint/post-synth/fixtures/load.js +94 -0
- package/dist/lint/post-synth/flat-attributes.js +135 -0
- package/dist/lint/post-synth/index.js +90 -0
- package/dist/lint/post-synth/module-source.js +153 -0
- package/dist/lint/post-synth/scope.js +104 -0
- package/dist/lint/post-synth/terragrunt.js +63 -0
- package/dist/lint/post-synth/tf001.js +138 -0
- package/dist/lint/post-synth/tf002.js +143 -0
- package/dist/lint/post-synth/tf003.js +60 -0
- package/dist/lint/post-synth/tf004.js +54 -0
- package/dist/lint/post-synth/tf005.js +68 -0
- package/dist/lint/post-synth/tf006.js +43 -0
- package/dist/lint/post-synth/tf007.js +66 -0
- package/dist/lint/post-synth/tf008.js +64 -0
- package/dist/lint/post-synth/tf009.js +49 -0
- package/dist/lint/post-synth/tf010.js +40 -0
- package/dist/lint/post-synth/tf011.js +38 -0
- package/dist/lint/post-synth/tf012.js +36 -0
- package/dist/lint/post-synth/tf013.js +53 -0
- package/dist/lint/post-synth/tf014.js +61 -0
- package/dist/lint/post-synth/tf015.js +62 -0
- package/dist/lint/post-synth/tf016.js +66 -0
- package/dist/lint/post-synth/tf017.js +39 -0
- package/dist/lint/post-synth/tf018.js +63 -0
- package/dist/lint/post-synth/tf019.js +60 -0
- package/dist/lint/post-synth/tf020.js +103 -0
- package/dist/lint/post-synth/tf021.js +117 -0
- package/dist/lint/post-synth/tf022.js +51 -0
- package/dist/lint/post-synth/tf024.js +62 -0
- package/dist/lint/post-synth/tf025.js +83 -0
- package/dist/lint/post-synth/tf026.js +74 -0
- package/dist/lint/post-synth/tf027.js +66 -0
- package/dist/lint/post-synth/tf028.js +83 -0
- package/dist/lint/post-synth/tf029.js +73 -0
- package/dist/lint/post-synth/tf030.js +253 -0
- package/dist/lint/post-synth/tf031.js +332 -0
- package/dist/lint/post-synth/tf032.js +201 -0
- package/dist/lint/post-synth/tf033.js +126 -0
- package/dist/lint/post-synth/tf034.js +45 -0
- package/dist/lint/post-synth/tf035.js +63 -0
- package/dist/lint/post-synth/tf036.js +97 -0
- package/dist/lint/post-synth/tf037.js +80 -0
- package/dist/lint/post-synth/tf038.js +63 -0
- package/dist/lint/post-synth/tf039.js +55 -0
- package/dist/lint/post-synth/tf040.js +67 -0
- package/dist/lint/post-synth/tf041.js +53 -0
- package/dist/lint/post-synth/tf042.js +38 -0
- package/dist/lint/post-synth/tf043.js +45 -0
- package/dist/lint/post-synth/tf044.js +45 -0
- package/dist/lint/post-synth/tf045.js +44 -0
- package/dist/lint/rules/fixtures/load.js +50 -0
- package/dist/lint/rules/index.js +4 -0
- package/dist/lint/rules/plan-before-apply.js +136 -0
- package/dist/lint/secret-shape.js +149 -0
- package/dist/live-ownership.js +57 -0
- package/dist/lsp/completions.js +95 -0
- package/dist/lsp/context.js +358 -0
- package/dist/lsp/hover.js +45 -0
- package/dist/lsp/option-keys.js +93 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/index.js +30 -0
- package/dist/op/activities/live-detect.js +143 -0
- package/dist/op/activities/pin-rollout.js +45 -0
- package/dist/op/activities/terraform.js +1270 -0
- package/dist/op/activity-contracts.js +162 -0
- package/dist/op/adoption.js +272 -0
- package/dist/op/builders.js +125 -0
- package/dist/op/resolve-root-mode.js +77 -0
- package/dist/package-cli.js +16 -0
- package/dist/pin/edit-ts.js +76 -0
- package/dist/pin/edit.js +118 -0
- package/dist/pin/forge.js +65 -0
- package/dist/pin/index.js +16 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +382 -0
- package/dist/pin/scan.js +287 -0
- package/dist/pin/source.js +171 -0
- package/dist/pin/waves.js +109 -0
- package/dist/plan-digest.js +67 -0
- package/dist/plugin.js +323 -0
- package/dist/serializer.js +23 -0
- package/dist/state-ownership.js +29 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +290 -0
- package/dist/terragrunt/index.js +10 -0
- package/dist/terragrunt/mocks.js +270 -0
- package/dist/terragrunt/run.js +264 -0
- package/dist/terragrunt/units.js +195 -0
- package/dist/terragrunt/wave.js +125 -0
- package/dist/validate-cli.js +4 -0
- package/dist/validate.js +31 -0
- package/package.json +10 -10
- package/src/codegen/package.ts +2 -2
- package/src/components/capability-plugin.test.ts +97 -2
- package/src/components/capability-plugin.ts +117 -12
- package/src/components/pr-loop.tofu.test.ts +51 -11
- package/src/composites/terraform-adopt-op.test.ts +6 -0
- package/src/composites/terraform-adopt-op.ts +7 -2
- package/src/composites/terraform-apply-op.test.ts +6 -0
- package/src/composites/terraform-apply-op.ts +7 -2
- package/src/index.ts +1 -0
- package/src/pin/rollout.ts +2 -1
- package/src/terragrunt/affected.test.ts +11 -0
- package/src/terragrunt/affected.ts +2 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP hover for the terraform lexicon (#2088). See `./completions.ts`'s
|
|
3
|
+
* module doc for why this reads `chant.config.*` as text rather than a
|
|
4
|
+
* generated index, and completes/documents the same two things: a
|
|
5
|
+
* `terraform.roots` name (when the word under the cursor is quoted next to
|
|
6
|
+
* it, so an ordinary identifier never gets mistaken for a root) and one of
|
|
7
|
+
* the composite/builder option keys (`./option-keys.ts`), the latter needing
|
|
8
|
+
* no reachable config at all.
|
|
9
|
+
*/
|
|
10
|
+
import { resolveRootsForUri } from "./context.js";
|
|
11
|
+
import { ALL_OPTION_KEYS } from "./option-keys.js";
|
|
12
|
+
/** `"app"` or `'app'` — the word is only a root reference if it sits inside a quote pair on the line. */
|
|
13
|
+
function isQuoted(lineText, word) {
|
|
14
|
+
if (!word)
|
|
15
|
+
return false;
|
|
16
|
+
return new RegExp(`["']${word}["']`).test(lineText);
|
|
17
|
+
}
|
|
18
|
+
function rootHover(uri, word, lineText) {
|
|
19
|
+
if (!isQuoted(lineText, word))
|
|
20
|
+
return undefined;
|
|
21
|
+
const roots = resolveRootsForUri(uri);
|
|
22
|
+
const root = roots?.[word];
|
|
23
|
+
if (!root)
|
|
24
|
+
return undefined;
|
|
25
|
+
const lines = [`**${word}**`, "", "terraform root (`terraform.roots." + word + "`)"];
|
|
26
|
+
if (root.dir)
|
|
27
|
+
lines.push("", `\`dir\`: \`${root.dir}\``);
|
|
28
|
+
if (root.workspace)
|
|
29
|
+
lines.push("", `\`workspace\`: \`${root.workspace}\``);
|
|
30
|
+
if (root.varFiles?.length)
|
|
31
|
+
lines.push("", `\`varFiles\`: ${root.varFiles.map((f) => `\`${f}\``).join(", ")}`);
|
|
32
|
+
return { contents: lines.join("\n") };
|
|
33
|
+
}
|
|
34
|
+
function optionKeyHover(word) {
|
|
35
|
+
const detail = ALL_OPTION_KEYS.get(word);
|
|
36
|
+
if (!detail)
|
|
37
|
+
return undefined;
|
|
38
|
+
return { contents: `**${word}**\n\n${detail}` };
|
|
39
|
+
}
|
|
40
|
+
export function hover(ctx) {
|
|
41
|
+
const word = ctx.word ?? "";
|
|
42
|
+
if (!word)
|
|
43
|
+
return undefined;
|
|
44
|
+
return rootHover(ctx.uri, word, ctx.lineText ?? "") ?? optionKeyHover(word);
|
|
45
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The option keys a `*.op.ts` or `chant.config.ts` author types against this
|
|
3
|
+
* lexicon's config schema (`../config.ts`), its composite
|
|
4
|
+
* (`../composites/terraform-apply-op.ts`) and its four builders
|
|
5
|
+
* (`../op/builders.ts`, `../op/activities/terraform.ts`). A fixed table, not
|
|
6
|
+
* read from any generated registry — there is no upstream schema here (see
|
|
7
|
+
* `plugin.ts`'s module doc), so this is the same set of fields those files
|
|
8
|
+
* already document in their own TSDoc, restated once for the editor.
|
|
9
|
+
*/
|
|
10
|
+
/** `TerraformConfig`'s own top-level keys (`terraform: { <here> }`). */
|
|
11
|
+
export const CONFIG_NAMESPACE_KEYS = [
|
|
12
|
+
{ key: "binary", detail: '"terraform" | "tofu" | "choudoufu" — which CLI drives the roots. Default: "terraform".' },
|
|
13
|
+
{ key: "roots", detail: "Named root modules. The name is the entity-key prefix." },
|
|
14
|
+
];
|
|
15
|
+
/** `TerraformRootConfig`'s keys — one entry of `terraform.roots.<name>: { <here> }`. */
|
|
16
|
+
export const ROOT_ENTRY_KEYS = [
|
|
17
|
+
{ key: "dir", detail: "Root module directory, relative to the project root. Required." },
|
|
18
|
+
{ key: "workspace", detail: "Terraform workspace to select for this root. Omitted means `default`." },
|
|
19
|
+
{ key: "varFiles", detail: "`-var-file` arguments, in order, relative to `dir`." },
|
|
20
|
+
{ key: "backendConfig", detail: "`-backend-config` key/value pairs handed to `init`." },
|
|
21
|
+
{ key: "delete", detail: '"never" | "owned-only" | "gated" — this root\'s delete mode, mapped onto choudoufu\'s policy block on a live root.' },
|
|
22
|
+
];
|
|
23
|
+
/** `TerraformApplyOpConfig`'s keys (`TerraformApplyOp({ <here> })`). */
|
|
24
|
+
export const APPLY_OP_KEYS = [
|
|
25
|
+
{ key: "name", detail: "Op name (kebab-case). `gateName` defaults to `approve-<name>`." },
|
|
26
|
+
{ key: "root", detail: "Key into the project's `terraform.roots`." },
|
|
27
|
+
{ key: "planFile", detail: "Plan file written by Plan and consumed by Apply. Default: `chant.tfplan`." },
|
|
28
|
+
{ key: "gate", detail: '"on-destroy" | "always" | "never" — when to emit the approval gate. Default: "on-destroy".' },
|
|
29
|
+
{ key: "gateName", detail: "The gate's name. Default: `approve-<name>`." },
|
|
30
|
+
{ key: "signalName", detail: "Deprecated spelling of `gateName` (#2202). Accepted through 0.59.0, removed in 0.60.0." },
|
|
31
|
+
{ key: "gateTimeout", detail: "How long a recorded pending gate stays valid, as a duration string. Default: core's own (48h)." },
|
|
32
|
+
{ key: "gateDescription", detail: "Override the gate description shown to the approver." },
|
|
33
|
+
{ key: "upgrade", detail: "`-upgrade` on the Init step: re-resolve provider and module versions." },
|
|
34
|
+
{ key: "cwd", detail: "Directory each step starts the `chant.config.*` search from." },
|
|
35
|
+
{ key: "compensate", detail: "Saga-style rollback on a failed apply: `true` with no command throws; supply `{ command }`." },
|
|
36
|
+
];
|
|
37
|
+
/** `compensate`'s own shape when it is an object: `compensate: { <here> }`. */
|
|
38
|
+
export const COMPENSATE_KEYS = [
|
|
39
|
+
{ key: "command", detail: "Shell command that undoes a failed apply — terraform has no automatic rollback." },
|
|
40
|
+
];
|
|
41
|
+
/** Extra opts every builder wrapper accepts alongside its activity's own fields. */
|
|
42
|
+
const STEP_OPTS = [
|
|
43
|
+
{ key: "profile", detail: "Activity profile override (defaults are set per builder)." },
|
|
44
|
+
{ key: "id", detail: 'Step id — required for `.out` references, e.g. `plan.out.planFile`.' },
|
|
45
|
+
];
|
|
46
|
+
/** `terraformInit(root, { <here> })`'s opts — `TerraformInitArgs` minus `root`, plus step opts. */
|
|
47
|
+
export const INIT_OPTS_KEYS = [
|
|
48
|
+
{ key: "upgrade", detail: "`-upgrade`: re-resolve provider and module versions within constraints." },
|
|
49
|
+
{ key: "reconfigure", detail: "`-reconfigure`: ignore any existing backend state and configure afresh." },
|
|
50
|
+
{ key: "cwd", detail: "Directory to start the `chant.config.*` search from." },
|
|
51
|
+
...STEP_OPTS,
|
|
52
|
+
];
|
|
53
|
+
/** `terraformPlan(root, { <here> })`'s opts — `TerraformPlanArgs` minus `root`, plus step opts. */
|
|
54
|
+
export const PLAN_OPTS_KEYS = [
|
|
55
|
+
{ key: "planFile", detail: "Plan file to write, relative to the root directory. Default: `chant.tfplan`." },
|
|
56
|
+
{ key: "destroy", detail: "`-destroy`: plan the removal of everything the root manages." },
|
|
57
|
+
{ key: "cwd", detail: "Directory to start the `chant.config.*` search from." },
|
|
58
|
+
...STEP_OPTS,
|
|
59
|
+
];
|
|
60
|
+
/** `terraformApply(root, { <here> })`'s opts — `TerraformApplyArgs` minus `root`, plus step opts. */
|
|
61
|
+
export const APPLY_OPTS_KEYS = [
|
|
62
|
+
{
|
|
63
|
+
key: "planFile",
|
|
64
|
+
detail: "The saved plan file to apply. Required on either kind of root; on a live root it is the approval " +
|
|
65
|
+
"artifact the apply re-plans against and refuses on a mismatch (choudoufu #878).",
|
|
66
|
+
},
|
|
67
|
+
{ key: "cwd", detail: "Directory to start the `chant.config.*` search from." },
|
|
68
|
+
...STEP_OPTS,
|
|
69
|
+
];
|
|
70
|
+
/** `terraformShow(root, { <here> })`'s opts — `TerraformShowArgs` minus `root`, plus step opts. */
|
|
71
|
+
export const SHOW_OPTS_KEYS = [
|
|
72
|
+
{ key: "planFile", detail: "Show this saved plan file. Omitted, the activity shows current state." },
|
|
73
|
+
{ key: "cwd", detail: "Directory to start the `chant.config.*` search from." },
|
|
74
|
+
...STEP_OPTS,
|
|
75
|
+
];
|
|
76
|
+
/** Every builder call name mapped to its opts table, for completion inside `(root, { <here> })`. */
|
|
77
|
+
export const BUILDER_OPTS_BY_CALL = {
|
|
78
|
+
terraformInit: INIT_OPTS_KEYS,
|
|
79
|
+
terraformPlan: PLAN_OPTS_KEYS,
|
|
80
|
+
terraformApply: APPLY_OPTS_KEYS,
|
|
81
|
+
terraformShow: SHOW_OPTS_KEYS,
|
|
82
|
+
};
|
|
83
|
+
/** Every option key across every table, keyed by name, for hover lookup. Last writer wins on a name shared across tables (`cwd`, `planFile`) — the detail text is the same fact restated per activity, so any one of them is a correct hover. */
|
|
84
|
+
export const ALL_OPTION_KEYS = new Map([
|
|
85
|
+
...CONFIG_NAMESPACE_KEYS,
|
|
86
|
+
...ROOT_ENTRY_KEYS,
|
|
87
|
+
...APPLY_OP_KEYS,
|
|
88
|
+
...COMPENSATE_KEYS,
|
|
89
|
+
...INIT_OPTS_KEYS,
|
|
90
|
+
...PLAN_OPTS_KEYS,
|
|
91
|
+
...APPLY_OPTS_KEYS,
|
|
92
|
+
...SHOW_OPTS_KEYS,
|
|
93
|
+
].map((o) => [o.key, o.detail]));
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terraform Op activities — resolved by the core activity registry when a
|
|
3
|
+
* project's `chant.config.ts` lists the `terraform` lexicon. `loadActivities`
|
|
4
|
+
* (`packages/core/src/op/activity-registry.ts`) imports
|
|
5
|
+
* `@intentius/chant-lexicon-terraform/op/activities` and keys every exported
|
|
6
|
+
* function by its name, so there is no plugin member to register:
|
|
7
|
+
*
|
|
8
|
+
* - terraformInit — `init` with the root's `backendConfig`.
|
|
9
|
+
* - terraformPlan — `plan -detailed-exitcode -out=<planFile>`, plus both
|
|
10
|
+
* `show` renders of the written plan.
|
|
11
|
+
* - terraformApply: `apply <planFile>` on a stock root, a saved plan only;
|
|
12
|
+
* `apply -auto-approve` with no plan file on a live root (#2103).
|
|
13
|
+
* - terraformShow — state, or a saved plan, as JSON and as text.
|
|
14
|
+
* - choudoufuLivePlan: `live-plan -detailed-exitcode -json -estate=<estate>`,
|
|
15
|
+
* plus a second `live-plan` for the human render (#2103).
|
|
16
|
+
* - choudoufuLiveLs: `live-ls -estate=<estate> -json [-consistent]` (#2103).
|
|
17
|
+
* - choudoufuLiveCheck: `live-check -json`, no cloud calls (#2103).
|
|
18
|
+
* - choudoufuAdopt: the tag writes that claim an adoption ledger's matches (#2105).
|
|
19
|
+
* - terraformPinRollout: one run of a pin-bump rollout, opening at most one
|
|
20
|
+
* wave's pull request (#3189). It loads the HCL parser on first call.
|
|
21
|
+
*
|
|
22
|
+
* The module is dependency-light on purpose: it shells out to the configured
|
|
23
|
+
* binary and reads the `terraform` config namespace, and never touches the
|
|
24
|
+
* lexicon's HCL parse or serializer, so `loadActivities` imports it cheaply at
|
|
25
|
+
* run time. The pure command and environment builders are exported alongside
|
|
26
|
+
* the activities because they are what the tests assert on.
|
|
27
|
+
*/
|
|
28
|
+
export { terraformInit, terraformPlan, terraformApply, terraformShow, choudoufuLivePlan, choudoufuLiveLs, choudoufuLiveCheck, choudoufuAdopt, terraformInitCommand, terraformPlanCommand, terraformApplyCommand, terraformShowCommand, choudoufuLivePlanCommand, choudoufuLiveLsCommand, choudoufuLiveCheckCommand, terraformEnvironment, terraformBinary, countPlanChanges, countLivePlanUnowned, isOlderVersion, parseChoudoufuVersion, quoteArg, DEFAULT_PLAN_FILE, DEFAULT_TERRAFORM_BINARY, DEFAULT_LIVE_PLAN_DOCUMENT_FILE, MIN_CHOUDOUFU_VERSION, classifyApprovalRefusal, CHOUDOUFU_LIVE_PLAN_OUT_REFUSAL, CHOUDOUFU_APPROVAL_EXIT_CODE, CHOUDOUFU_APPROVAL_MISMATCH_REFUSAL, CHOUDOUFU_WRONG_ESTATE_REFUSAL, } from "./terraform.js";
|
|
29
|
+
export { detectLiveEstate } from "./live-detect.js";
|
|
30
|
+
export { terraformPinRollout } from "./pin-rollout.js";
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fast, regex-based read of whether a root module directory declares a
|
|
3
|
+
* live estate: an `estate.chdf.hcl` sidecar, or a `live { ... }` block nested
|
|
4
|
+
* inside a `terraform { ... }` block of some `.tf` file (#2103).
|
|
5
|
+
*
|
|
6
|
+
* Deliberately separate from `../../hcl/parse.ts`'s AST-accurate detection
|
|
7
|
+
* (`blocksToEntities`'s `liveBlocksIn`), which the build and lint paths use.
|
|
8
|
+
* `op/activities/terraform.ts` is imported by core's activity registry at run
|
|
9
|
+
* time and is dependency-light on purpose, since it shells out to the
|
|
10
|
+
* configured binary and never touches the lexicon's HCL parse or serializer,
|
|
11
|
+
* so it never pulls in
|
|
12
|
+
* the ~1.8 MB `@cdktn/hcl2json` wasm parser just to decide whether an apply
|
|
13
|
+
* should run `apply -auto-approve` with no plan file. Plain text and a
|
|
14
|
+
* hand-rolled brace counter are enough for that narrower question, and the
|
|
15
|
+
* literal sidecar filename below is kept in sync with `LIVE_SIDECAR_FILENAME`
|
|
16
|
+
* in `../../hcl/parse.ts` by hand rather than by importing it, for the same
|
|
17
|
+
* reason.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
/** Kept in sync with `LIVE_SIDECAR_FILENAME` in `../../hcl/parse.ts`. */
|
|
22
|
+
const LIVE_SIDECAR_FILENAME = "estate.chdf.hcl";
|
|
23
|
+
/**
|
|
24
|
+
* Find `keyword {` and return the matching `{ ... }` body's inner text, via
|
|
25
|
+
* brace counting rather than a regex (HCL bodies nest, and a lone regex
|
|
26
|
+
* cannot match balanced braces).
|
|
27
|
+
*/
|
|
28
|
+
function extractBalancedBlock(source, keyword) {
|
|
29
|
+
const re = new RegExp(`\\b${keyword}\\s*\\{`);
|
|
30
|
+
const opening = re.exec(source);
|
|
31
|
+
if (!opening)
|
|
32
|
+
return undefined;
|
|
33
|
+
let depth = 0;
|
|
34
|
+
let start = -1;
|
|
35
|
+
for (let i = opening.index; i < source.length; i++) {
|
|
36
|
+
const ch = source[i];
|
|
37
|
+
if (ch === "{") {
|
|
38
|
+
if (depth === 0)
|
|
39
|
+
start = i + 1;
|
|
40
|
+
depth++;
|
|
41
|
+
}
|
|
42
|
+
else if (ch === "}") {
|
|
43
|
+
depth--;
|
|
44
|
+
if (depth === 0)
|
|
45
|
+
return source.slice(start, i);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined; // unbalanced, malformed HCL, not this function's problem to diagnose
|
|
49
|
+
}
|
|
50
|
+
/** Pull a quoted `estate = "..."` attribute's value out of a block's inner text. */
|
|
51
|
+
function extractEstateAttr(body) {
|
|
52
|
+
const match = /estate\s*=\s*"([^"]*)"/.exec(body);
|
|
53
|
+
return match && match[1] !== "" ? match[1] : undefined;
|
|
54
|
+
}
|
|
55
|
+
/** Pull a quoted `<key> = "..."` attribute's value out of a block's inner text. */
|
|
56
|
+
function extractQuotedAttr(body, key) {
|
|
57
|
+
const match = new RegExp(`\\b${key}\\s*=\\s*"([^"]*)"`).exec(body);
|
|
58
|
+
return match && match[1] !== "" ? match[1] : undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The declared estate name for the root module directory `dir`, or
|
|
62
|
+
* `undefined` when none is declared. Checks the `estate.chdf.hcl` sidecar
|
|
63
|
+
* first (cheaper: one file, no brace matching), then every `.tf` file in
|
|
64
|
+
* directory order for a `live { }` block nested in a `terraform { }` block.
|
|
65
|
+
* Returns the first estate name found; a directory with no `.tf` files and
|
|
66
|
+
* no sidecar (or one this function cannot read) yields `undefined` rather
|
|
67
|
+
* than throwing, since this is a best-effort check, not the authoritative parse.
|
|
68
|
+
*/
|
|
69
|
+
export function detectLiveEstate(dir) {
|
|
70
|
+
const sidecarPath = join(dir, LIVE_SIDECAR_FILENAME);
|
|
71
|
+
if (existsSync(sidecarPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const estate = extractEstateAttr(readFileSync(sidecarPath, "utf-8"));
|
|
74
|
+
if (estate)
|
|
75
|
+
return estate;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// fall through to the in-block form
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let names;
|
|
82
|
+
try {
|
|
83
|
+
names = readdirSync(dir)
|
|
84
|
+
.filter((f) => f.endsWith(".tf"))
|
|
85
|
+
.sort();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
let source;
|
|
92
|
+
try {
|
|
93
|
+
source = readFileSync(join(dir, name), "utf-8");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const terraformBlock = extractBalancedBlock(source, "terraform");
|
|
99
|
+
if (terraformBlock === undefined)
|
|
100
|
+
continue;
|
|
101
|
+
const liveBlock = extractBalancedBlock(terraformBlock, "live");
|
|
102
|
+
if (liveBlock === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
const estate = extractEstateAttr(liveBlock);
|
|
105
|
+
if (estate)
|
|
106
|
+
return estate;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
export function detectLivePolicyVerbs(dir) {
|
|
111
|
+
let names;
|
|
112
|
+
try {
|
|
113
|
+
names = readdirSync(dir)
|
|
114
|
+
.filter((f) => f.endsWith(".tf"))
|
|
115
|
+
.sort();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
for (const name of names) {
|
|
121
|
+
let source;
|
|
122
|
+
try {
|
|
123
|
+
source = readFileSync(join(dir, name), "utf-8");
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const terraformBlock = extractBalancedBlock(source, "terraform");
|
|
129
|
+
if (terraformBlock === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
const liveBlock = extractBalancedBlock(terraformBlock, "live");
|
|
132
|
+
if (liveBlock === undefined)
|
|
133
|
+
continue;
|
|
134
|
+
const policyBlock = extractBalancedBlock(liveBlock, "policy");
|
|
135
|
+
if (policyBlock === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
return {
|
|
138
|
+
undeclaredTagged: extractQuotedAttr(policyBlock, "undeclared_tagged"),
|
|
139
|
+
undeclaredUntagged: extractQuotedAttr(policyBlock, "undeclared_untagged"),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `terraformPinRollout`: one run of a pin-bump rollout (#3189), as an Op
|
|
3
|
+
* activity. `chant terraform pin-rollout` calls the same function.
|
|
4
|
+
*
|
|
5
|
+
* Each run reads where the rollout stands on the forge and opens at most one
|
|
6
|
+
* wave's pull request (`../../pin/rollout.ts`). Run it again, by hand or on
|
|
7
|
+
* an Op `schedule`, to move on once a wave's PR has merged and its roots have
|
|
8
|
+
* applied. A run that finds a failed root or a PR closed without merging
|
|
9
|
+
* throws, naming it, so the run fails rather than reporting success.
|
|
10
|
+
*
|
|
11
|
+
* The HCL parser, the pin code and the TypeScript editor load on first call,
|
|
12
|
+
* so importing the activities loads none of them.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { isAbsolute, join } from "node:path";
|
|
16
|
+
/** Run one step of a pin-bump rollout. Throws when the rollout has stopped. */
|
|
17
|
+
export async function terraformPinRollout(args) {
|
|
18
|
+
const result = await readPinRollout(args);
|
|
19
|
+
if (result.status === "stopped")
|
|
20
|
+
throw new Error(`pin rollout stopped: ${result.stop}\n${result.summary}`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
/** The same run, returning a stopped rollout instead of throwing. `chant terraform pin-rollout` calls this. */
|
|
24
|
+
export async function readPinRollout(args) {
|
|
25
|
+
const { loadHcl2json } = await import("@intentius/chant/terraform/hcl2json");
|
|
26
|
+
const { runPinRollout, wavesFromChoudoufu } = await import("../../pin/index.js");
|
|
27
|
+
const { editPinsInTs } = await import("../../pin/edit-ts.js");
|
|
28
|
+
const cwd = args.cwd ?? process.cwd();
|
|
29
|
+
const waves = args.wavesFrom ? wavesFromChoudoufu(JSON.parse(readFileSync(isAbsolute(args.wavesFrom) ? args.wavesFrom : join(cwd, args.wavesFrom), "utf-8")), args.wavesPrefix) : undefined;
|
|
30
|
+
return runPinRollout({
|
|
31
|
+
module: args.module,
|
|
32
|
+
from: args.from,
|
|
33
|
+
to: args.to,
|
|
34
|
+
cwd,
|
|
35
|
+
parser: await loadHcl2json(),
|
|
36
|
+
editTs: editPinsInTs,
|
|
37
|
+
...(args.roots ? { roots: args.roots } : {}),
|
|
38
|
+
...(args.canaries ? { canaries: args.canaries } : {}),
|
|
39
|
+
...(waves ? { waves } : {}),
|
|
40
|
+
...(args.mode ? { mode: args.mode } : {}),
|
|
41
|
+
...(args.base ? { base: args.base } : {}),
|
|
42
|
+
...(args.remote ? { remote: args.remote } : {}),
|
|
43
|
+
...(args.appliedCheck ? { appliedCheck: args.appliedCheck } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|