@intentius/terragucci 0.4.33 → 0.4.35

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.
@@ -93,6 +93,47 @@
93
93
  ],
94
94
  "description": "What counts as a waiting wave's approval: the mode, or a map with the mode and require_signed."
95
95
  },
96
+ "approval_rules": {
97
+ "type": "array",
98
+ "minItems": 1,
99
+ "items": {
100
+ "type": "object",
101
+ "properties": {
102
+ "roots": {
103
+ "type": "array",
104
+ "minItems": 1,
105
+ "items": {
106
+ "type": "string",
107
+ "pattern": "^[^\\s,;=']+$"
108
+ },
109
+ "description": "Root globs: a wave with a root one matches takes the rule."
110
+ },
111
+ "count": {
112
+ "type": "integer",
113
+ "minimum": 1,
114
+ "description": "How many distinct people approve the wave's set digest. Default 1."
115
+ },
116
+ "roles": {
117
+ "type": "array",
118
+ "minItems": 1,
119
+ "items": {
120
+ "type": "string",
121
+ "pattern": "^[a-z][a-z0-9-]*$"
122
+ },
123
+ "description": "Each approver counted holds one of these roles in .chant/trust.json at base."
124
+ },
125
+ "not_author": {
126
+ "type": "boolean",
127
+ "description": "At least one approver counted is not the change's author."
128
+ }
129
+ },
130
+ "additionalProperties": false,
131
+ "required": [
132
+ "roots"
133
+ ]
134
+ },
135
+ "description": "More approvers, or approvers with a role, for the waves whose roots a glob names."
136
+ },
96
137
  "apply": {
97
138
  "type": "object",
98
139
  "properties": {
@@ -1034,8 +1075,69 @@
1034
1075
  "type": "array",
1035
1076
  "minItems": 1,
1036
1077
  "items": {
1037
- "type": "string",
1038
- "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
1078
+ "anyOf": [
1079
+ {
1080
+ "type": "string",
1081
+ "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
1082
+ },
1083
+ {
1084
+ "type": "object",
1085
+ "properties": {
1086
+ "roots": {
1087
+ "type": "array",
1088
+ "minItems": 1,
1089
+ "items": {
1090
+ "type": "string",
1091
+ "pattern": "^[^\\s,;=']+$"
1092
+ },
1093
+ "description": "Root globs: the entry covers a denial of a root one matches."
1094
+ },
1095
+ "who": {
1096
+ "type": "array",
1097
+ "minItems": 1,
1098
+ "items": {
1099
+ "type": "string",
1100
+ "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
1101
+ },
1102
+ "description": "The forge identities or signers who may override such a denial."
1103
+ },
1104
+ "roles": {
1105
+ "type": "array",
1106
+ "minItems": 1,
1107
+ "items": {
1108
+ "type": "string",
1109
+ "pattern": "^[a-z][a-z0-9-]*$"
1110
+ },
1111
+ "description": "Roles in .chant/trust.json at base whose holders may override such a denial (approval: sealed)."
1112
+ },
1113
+ "rules": {
1114
+ "type": "array",
1115
+ "minItems": 1,
1116
+ "items": {
1117
+ "type": "string",
1118
+ "pattern": "^\\S+$"
1119
+ },
1120
+ "description": "Only an override of these rule ids, every one of them."
1121
+ }
1122
+ },
1123
+ "additionalProperties": false,
1124
+ "required": [
1125
+ "roots"
1126
+ ]
1127
+ }
1128
+ ]
1129
+ }
1130
+ },
1131
+ "outcomes": {
1132
+ "type": "array",
1133
+ "minItems": 1,
1134
+ "uniqueItems": true,
1135
+ "items": {
1136
+ "enum": [
1137
+ "deny",
1138
+ "hold",
1139
+ "allow_unattended"
1140
+ ]
1039
1141
  }
1040
1142
  }
1041
1143
  },
@@ -1442,6 +1544,49 @@
1442
1544
  ],
1443
1545
  "description": "Roots each pull request gets a copy of."
1444
1546
  },
1547
+ "secrets": {
1548
+ "type": "array",
1549
+ "minItems": 1,
1550
+ "items": {
1551
+ "type": "object",
1552
+ "properties": {
1553
+ "roots": {
1554
+ "type": "array",
1555
+ "minItems": 1,
1556
+ "items": {
1557
+ "type": "string",
1558
+ "pattern": "\\S"
1559
+ }
1560
+ },
1561
+ "from": {
1562
+ "enum": [
1563
+ "aws",
1564
+ "gcp",
1565
+ "azure"
1566
+ ]
1567
+ },
1568
+ "values": {
1569
+ "type": "object",
1570
+ "minProperties": 1,
1571
+ "propertyNames": {
1572
+ "type": "string",
1573
+ "pattern": "^(?!(?:[Gg][Ii][Tt][Hh][Uu][Bb]_|[Gg][Ii][Tt][Ee][Aa]_|[Ff][Oo][Rr][Gg][Ee][Jj][Oo]_|[Tt][Gg]_|[Tt][Ee][Rr][Rr][Aa][Gg][Uu][Cc][Cc][Ii]_|[Aa][Ww][Ss]_|[Aa][Rr][Mm]_|[Aa][Zz][Uu][Rr][Ee]_|[Gg][Oo][Oo][Gg][Ll][Ee]_|[Cc][Ll][Oo][Uu][Dd][Ss][Dd][Kk]_))(?!(?:TF_IN_AUTOMATION|TF_INPUT)$)[A-Za-z_][A-Za-z0-9_]*$"
1574
+ },
1575
+ "additionalProperties": {
1576
+ "type": "string",
1577
+ "pattern": "^[^\\s]+$"
1578
+ }
1579
+ }
1580
+ },
1581
+ "additionalProperties": false,
1582
+ "required": [
1583
+ "roots",
1584
+ "from",
1585
+ "values"
1586
+ ]
1587
+ },
1588
+ "description": "Values a root's binary gets from a cloud's secret store, fetched at job time with the root's own identity."
1589
+ },
1445
1590
  "environments": {
1446
1591
  "type": "object",
1447
1592
  "minProperties": 1,
@@ -1776,6 +1921,47 @@
1776
1921
  ],
1777
1922
  "description": "What counts as a waiting wave's approval: the mode, or a map with the mode and require_signed."
1778
1923
  },
1924
+ "approval_rules": {
1925
+ "type": "array",
1926
+ "minItems": 1,
1927
+ "items": {
1928
+ "type": "object",
1929
+ "properties": {
1930
+ "roots": {
1931
+ "type": "array",
1932
+ "minItems": 1,
1933
+ "items": {
1934
+ "type": "string",
1935
+ "pattern": "^[^\\s,;=']+$"
1936
+ },
1937
+ "description": "Root globs: a wave with a root one matches takes the rule."
1938
+ },
1939
+ "count": {
1940
+ "type": "integer",
1941
+ "minimum": 1,
1942
+ "description": "How many distinct people approve the wave's set digest. Default 1."
1943
+ },
1944
+ "roles": {
1945
+ "type": "array",
1946
+ "minItems": 1,
1947
+ "items": {
1948
+ "type": "string",
1949
+ "pattern": "^[a-z][a-z0-9-]*$"
1950
+ },
1951
+ "description": "Each approver counted holds one of these roles in .chant/trust.json at base."
1952
+ },
1953
+ "not_author": {
1954
+ "type": "boolean",
1955
+ "description": "At least one approver counted is not the change's author."
1956
+ }
1957
+ },
1958
+ "additionalProperties": false,
1959
+ "required": [
1960
+ "roots"
1961
+ ]
1962
+ },
1963
+ "description": "More approvers, or approvers with a role, for the waves whose roots a glob names."
1964
+ },
1779
1965
  "apply": {
1780
1966
  "type": "object",
1781
1967
  "properties": {
@@ -2709,8 +2895,69 @@
2709
2895
  "type": "array",
2710
2896
  "minItems": 1,
2711
2897
  "items": {
2712
- "type": "string",
2713
- "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
2898
+ "anyOf": [
2899
+ {
2900
+ "type": "string",
2901
+ "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
2902
+ },
2903
+ {
2904
+ "type": "object",
2905
+ "properties": {
2906
+ "roots": {
2907
+ "type": "array",
2908
+ "minItems": 1,
2909
+ "items": {
2910
+ "type": "string",
2911
+ "pattern": "^[^\\s,;=']+$"
2912
+ },
2913
+ "description": "Root globs: the entry covers a denial of a root one matches."
2914
+ },
2915
+ "who": {
2916
+ "type": "array",
2917
+ "minItems": 1,
2918
+ "items": {
2919
+ "type": "string",
2920
+ "pattern": "^[^\\r\\n]*\\S[^\\r\\n]*$"
2921
+ },
2922
+ "description": "The forge identities or signers who may override such a denial."
2923
+ },
2924
+ "roles": {
2925
+ "type": "array",
2926
+ "minItems": 1,
2927
+ "items": {
2928
+ "type": "string",
2929
+ "pattern": "^[a-z][a-z0-9-]*$"
2930
+ },
2931
+ "description": "Roles in .chant/trust.json at base whose holders may override such a denial (approval: sealed)."
2932
+ },
2933
+ "rules": {
2934
+ "type": "array",
2935
+ "minItems": 1,
2936
+ "items": {
2937
+ "type": "string",
2938
+ "pattern": "^\\S+$"
2939
+ },
2940
+ "description": "Only an override of these rule ids, every one of them."
2941
+ }
2942
+ },
2943
+ "additionalProperties": false,
2944
+ "required": [
2945
+ "roots"
2946
+ ]
2947
+ }
2948
+ ]
2949
+ }
2950
+ },
2951
+ "outcomes": {
2952
+ "type": "array",
2953
+ "minItems": 1,
2954
+ "uniqueItems": true,
2955
+ "items": {
2956
+ "enum": [
2957
+ "deny",
2958
+ "hold",
2959
+ "allow_unattended"
2960
+ ]
2714
2961
  }
2715
2962
  }
2716
2963
  },
@@ -3117,6 +3364,49 @@
3117
3364
  ],
3118
3365
  "description": "Roots each pull request gets a copy of."
3119
3366
  },
3367
+ "secrets": {
3368
+ "type": "array",
3369
+ "minItems": 1,
3370
+ "items": {
3371
+ "type": "object",
3372
+ "properties": {
3373
+ "roots": {
3374
+ "type": "array",
3375
+ "minItems": 1,
3376
+ "items": {
3377
+ "type": "string",
3378
+ "pattern": "\\S"
3379
+ }
3380
+ },
3381
+ "from": {
3382
+ "enum": [
3383
+ "aws",
3384
+ "gcp",
3385
+ "azure"
3386
+ ]
3387
+ },
3388
+ "values": {
3389
+ "type": "object",
3390
+ "minProperties": 1,
3391
+ "propertyNames": {
3392
+ "type": "string",
3393
+ "pattern": "^(?!(?:[Gg][Ii][Tt][Hh][Uu][Bb]_|[Gg][Ii][Tt][Ee][Aa]_|[Ff][Oo][Rr][Gg][Ee][Jj][Oo]_|[Tt][Gg]_|[Tt][Ee][Rr][Rr][Aa][Gg][Uu][Cc][Cc][Ii]_|[Aa][Ww][Ss]_|[Aa][Rr][Mm]_|[Aa][Zz][Uu][Rr][Ee]_|[Gg][Oo][Oo][Gg][Ll][Ee]_|[Cc][Ll][Oo][Uu][Dd][Ss][Dd][Kk]_))(?!(?:TF_IN_AUTOMATION|TF_INPUT)$)[A-Za-z_][A-Za-z0-9_]*$"
3394
+ },
3395
+ "additionalProperties": {
3396
+ "type": "string",
3397
+ "pattern": "^[^\\s]+$"
3398
+ }
3399
+ }
3400
+ },
3401
+ "additionalProperties": false,
3402
+ "required": [
3403
+ "roots",
3404
+ "from",
3405
+ "values"
3406
+ ]
3407
+ },
3408
+ "description": "Values a root's binary gets from a cloud's secret store, fetched at job time with the root's own identity."
3409
+ },
3120
3410
  "environments": {
3121
3411
  "type": "object",
3122
3412
  "minProperties": 1,
package/dist/types.d.ts CHANGED
@@ -36,12 +36,49 @@ export interface ApprovalSettings {
36
36
  export declare function approvalModeOf(v: Approval | ApprovalSettings | undefined): Approval | undefined;
37
37
  /** Whether an `approval:` value refuses unsigned ledger approvals. */
38
38
  export declare function requireSignedOf(v: Approval | ApprovalSettings | undefined): boolean;
39
+ /** The keys of a scoped `policy.override` entry. */
40
+ export declare const OVERRIDE_ENTRY_KEYS: readonly ["roots", "who", "roles", "rules"];
41
+ /**
42
+ * A scoped `policy.override` entry (./override.ts): `who` (forge identities
43
+ * or signers) and the holders of `roles` in `.chant/trust.json` at base may
44
+ * override a denial of a root a glob of `roots` matches, for any rule, or
45
+ * with `rules` only when every rule the override names is one of them.
46
+ */
47
+ export interface OverrideEntry {
48
+ roots: string[];
49
+ who?: string[];
50
+ roles?: string[];
51
+ rules?: string[];
52
+ }
53
+ /** The keys of one `approval_rules` entry. */
54
+ export declare const APPROVAL_RULE_KEYS: readonly ["roots", "count", "roles", "not_author"];
55
+ /**
56
+ * One `approval_rules` entry (./approval-rules.ts): a wave with a root a glob
57
+ * of `roots` matches waits for `count` distinct approvers of its set digest
58
+ * (default 1), each holding one of `roles` in `.chant/trust.json` at base when
59
+ * `roles` is set, and with `not_author` at least one who is not the change's
60
+ * author. Read at base, like `approval:`.
61
+ */
62
+ export interface ApprovalRuleSettings {
63
+ roots: string[];
64
+ count?: number;
65
+ roles?: string[];
66
+ not_author?: boolean;
67
+ }
39
68
  /** Every stage runs on the forge's CI. */
40
69
  export declare const RUNTIMES: readonly ["forge"];
41
70
  export declare const DEPENDENTS: readonly ["follow", "plan"];
42
71
  /** What a pull request does with the dependents of a changed unit when terragrunt.dependents is unset. */
43
72
  export declare const DEFAULT_DEPENDENTS: Dependents;
44
73
  export declare const POLICY_ENGINES: readonly ["conftest", "opa"];
74
+ /**
75
+ * What a policy rule can do to a root's wave (./report/policy.ts): `deny`
76
+ * fails the root, `hold` makes its wave wait for an approval whatever `gate`
77
+ * says, `allow_unattended` lets a wave whose every changing root it allows
78
+ * apply without one.
79
+ */
80
+ export declare const POLICY_OUTCOMES: readonly ["deny", "hold", "allow_unattended"];
81
+ export type PolicyOutcome = (typeof POLICY_OUTCOMES)[number];
45
82
  export declare const POLICY_INPUTS: readonly ["plan", "hcp"];
46
83
  /**
47
84
  * When a pull request takes its root locks (`locks:`). `apply` (the default):
@@ -177,10 +214,19 @@ export interface PolicySettings {
177
214
  input?: PolicyInput;
178
215
  /**
179
216
  * Who may override a denial: the forge identities or signers whose recorded
180
- * override (`terragucci override`) lets `tf-apply` apply one denied plan.
181
- * Read at base, like `approval:`. Unset, no override counts.
217
+ * override (`terragucci override`) lets `tf-apply` apply one denied plan,
218
+ * each for every root and rule, or scoped entries (OverrideEntry) for the
219
+ * roots a glob names. Read at base, like `approval:`. Unset, no override
220
+ * counts.
182
221
  */
183
- override?: string[];
222
+ override?: (string | OverrideEntry)[];
223
+ /**
224
+ * The rule outcomes the policy has: `deny` always, and `hold` and
225
+ * `allow_unattended` when listed, read from `hold`, `hold_*`,
226
+ * `allow_unattended` and `allow_unattended_*` rules (engine `opa`). Read
227
+ * at base. Default `[deny]`.
228
+ */
229
+ outcomes?: PolicyOutcome[];
184
230
  }
185
231
  /**
186
232
  * The jobs' cloud identities over the forge's OIDC token. `plan_role` and
@@ -394,6 +440,8 @@ export interface ProjectSettings {
394
440
  gate?: GateName;
395
441
  /** What counts as a waiting wave's approval; see APPROVALS, or a map with the mode and `require_signed` (ApprovalSettings). Read from the config at base, never the applied commit's own. */
396
442
  approval?: Approval | ApprovalSettings;
443
+ /** More approvers, or approvers with a role, for the waves whose roots a glob names; see ApprovalRuleSettings. Read from the config at base. */
444
+ approval_rules?: ApprovalRuleSettings[];
397
445
  /** When a change applies; see ApplySettings. */
398
446
  apply?: ApplySettings;
399
447
  /** When a pull request takes its root locks; see LOCKS. */
@@ -507,6 +555,13 @@ export interface ProjectSettings {
507
555
  * every job its CI/CD variables already, so there the key changes nothing.
508
556
  */
509
557
  pass?: PassSettings;
558
+ /**
559
+ * Values fetched at job time from a cloud's secret store, by root glob,
560
+ * with the identity the root plans or applies with (./root-secrets.ts), and
561
+ * set in that root's binary environment alone. Read from the project's
562
+ * terragucci.yml by the stage.
563
+ */
564
+ secrets?: SecretSource[];
510
565
  /**
511
566
  * The secret holding `OTEL_EXPORTER_OTLP_HEADERS`, such as a collector's API
512
567
  * key, and `trace_url`: a link to a run's trace with `{trace_id}` in it
@@ -666,6 +721,43 @@ export interface PassSettings {
666
721
  secrets?: string[];
667
722
  vars?: string[];
668
723
  }
724
+ /** The clouds whose secret store `secrets` reads. */
725
+ export declare const SECRET_STORES: readonly ["aws", "gcp", "azure"];
726
+ export type SecretStore = (typeof SECRET_STORES)[number];
727
+ /** The keys of one `secrets` entry. */
728
+ export declare const SECRET_SOURCE_KEYS: string[];
729
+ /**
730
+ * One `secrets` entry: the roots its globs match get each of `values`, an
731
+ * environment variable name mapped to where the value is in `from`'s secret
732
+ * store, with `#<key>` to take one key of a JSON secret:
733
+ *
734
+ * aws a Secrets Manager secret's name or ARN: `prod/app/db#password`
735
+ * gcp a Secret Manager secret, its version `latest` unless named:
736
+ * `projects/<project>/secrets/<secret>[/versions/<version>]`
737
+ * azure a Key Vault secret, its current version unless named:
738
+ * `<vault>/<secret>[/<version>]`, the vault a name or a host
739
+ */
740
+ export interface SecretSource {
741
+ roots: string[];
742
+ from: SecretStore;
743
+ values: Record<string, string>;
744
+ }
745
+ /** Where a value is in each store, as `secrets` takes it. */
746
+ export declare const SECRET_PATH: Record<SecretStore, string>;
747
+ /** Names `secrets` refuses beside `pass`'s: the variables a root's binary takes its cloud identity from. */
748
+ export declare const SECRET_IDENTITY_PREFIXES: string[];
749
+ /** Why `secrets` is refused beside `waves.compose`: a composed run plans its roots in one binary environment. */
750
+ export declare const SECRETS_COMPOSE = "a composed run plans and applies its roots in one run, and secrets sets each value in its own root's binary environment alone; leave waves.compose unset";
751
+ /** Why `secrets` is refused beside `ephemeral`: a pull request's copies plan and apply without the store's values. */
752
+ export declare const SECRETS_EPHEMERAL = "an ephemeral copy plans and applies without the values secrets fetches; leave ephemeral unset, or keep the copies' values in pass.secrets";
753
+ /**
754
+ * The problems with `secrets`: a list of entries, each with root globs, a
755
+ * store and names mapped to paths in it. `whole` when `s` is a repo's own
756
+ * file: then a name `pass` or `env` sets too, a store whose cloud `oidc`
757
+ * does not configure, and the keys that plan or apply a root without its
758
+ * values are refused as well.
759
+ */
760
+ export declare function secretsProblems(v: unknown, where: string, s: ProjectSettings, whole: boolean): string[];
669
761
  /** Whether `runner` is a map of stage to runner, rather than one runner. */
670
762
  export declare function runnerByStage(v: RunnerSettings): v is Partial<Record<RunnerStage | "default", RunnerSpec>>;
671
763
  export declare const JOB_LABEL: RegExp;
@@ -705,7 +797,7 @@ export declare const BUILT_IN: ResolvedSettings;
705
797
  * own clone URL and `rollouts` belongs to a single repo, so `defaults`
706
798
  * refuses both.
707
799
  */
708
- export declare const PROJECT_FILE_KEYS: readonly ["policy", "sources", "reports", "approval", "gate", "roots", "waves", "parallelism", "synth", "steps", "drift", "cost", "tips", "runtime", "telemetry", "respond", "review", "decide", "audit_region", "modules", "terragrunt", "token_env", "generate", "ephemeral", "environments"];
800
+ export declare const PROJECT_FILE_KEYS: readonly ["policy", "sources", "reports", "approval", "approval_rules", "gate", "roots", "waves", "parallelism", "synth", "steps", "drift", "cost", "tips", "runtime", "telemetry", "respond", "review", "decide", "audit_region", "modules", "terragrunt", "token_env", "generate", "ephemeral", "environments", "secrets"];
709
801
  export declare class ConfigError extends Error {
710
802
  /** Every problem found, when the error is a validation failure. */
711
803
  readonly problems?: string[];
@@ -865,6 +957,12 @@ export declare const POINT_NAME: RegExp;
865
957
  export declare const APPLY_BRANCH: RegExp;
866
958
  /** Why `apply.branches` is refused with `apply.when: pull-request`. */
867
959
  export declare const BRANCHES_NOT_PR_APPLY = "apply.when: pull-request applies an open pull request into the default branch, and a push applies nothing, so no branch could apply its roots; leave apply.branches unset";
960
+ /** A role name, as `.chant/trust.json` names one under `roles`. */
961
+ export declare const TRUST_ROLE: RegExp;
962
+ /** Why `roles` and `not_author` need a mode that proves who approved. */
963
+ export declare const LEDGER_PROVES_NOBODY = "approval: ledger does not prove who wrote an approval, so it cannot tell a role or the author; set approval to sealed or pr-review";
964
+ /** Why an override's role needs `approval: sealed`. */
965
+ export declare const OVERRIDE_ROLE_NEEDS_SEALED = "an override names whoever wrote it, and only approval: sealed proves who that was, so a role cannot be checked; set approval to sealed, or name the people in who";
868
966
  /** Where a shared policy is fetched from: the git URL and the ref. */
869
967
  export interface PolicySource {
870
968
  url: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/terragucci",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "description": "The whole Terraform lifecycle, handled: one config file, generated pipelines for GitHub, GitLab and Forgejo.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",