@intentius/chant 0.124.44 → 0.124.46
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/capabilities/aws.paths +148 -0
- package/dist/capabilities/base-mappings.json +122 -0
- package/dist/capabilities/catalog.d.ts +89 -0
- package/dist/capabilities/catalog.d.ts.map +1 -0
- package/dist/capabilities/catalog.js +204 -0
- package/dist/capabilities/language.d.ts +103 -0
- package/dist/capabilities/language.d.ts.map +1 -0
- package/dist/capabilities/language.js +335 -0
- package/dist/capabilities/matcher.d.ts +65 -0
- package/dist/capabilities/matcher.d.ts.map +1 -0
- package/dist/capabilities/matcher.js +299 -0
- package/dist/capabilities/view.d.ts +207 -0
- package/dist/capabilities/view.d.ts.map +1 -0
- package/dist/capabilities/view.js +484 -0
- package/dist/ci-identity.d.ts +183 -12
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +362 -53
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +17 -1
- package/dist/cli/handlers/declared-graph.d.ts +44 -0
- package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
- package/dist/cli/handlers/declared-graph.js +87 -0
- package/dist/cli/handlers/graph.d.ts +25 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +93 -87
- package/dist/cli/handlers/pr-env.d.ts +8 -2
- package/dist/cli/handlers/pr-env.d.ts.map +1 -1
- package/dist/cli/handlers/pr-env.js +127 -225
- package/dist/cli/handlers/pr.d.ts +3 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +1 -0
- package/dist/cli/handlers/search-count.d.ts +89 -0
- package/dist/cli/handlers/search-count.d.ts.map +1 -0
- package/dist/cli/handlers/search-count.js +132 -0
- package/dist/cli/handlers/search.d.ts +358 -6
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +1065 -155
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +23 -3
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
- package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
- package/dist/cli/mcp/tools/capabilities.js +27 -0
- package/dist/cli/mcp/workspace-tools.d.ts +3 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.js +47 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +64 -17
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +2 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +4 -2
- package/dist/components/fan-out-support.d.ts +3 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +1 -1
- package/dist/components/pr-env-ports.d.ts +29 -0
- package/dist/components/pr-env-ports.d.ts.map +1 -0
- package/dist/components/pr-env-ports.js +77 -0
- package/dist/components/pr-env-run.d.ts +254 -0
- package/dist/components/pr-env-run.d.ts.map +1 -0
- package/dist/components/pr-env-run.js +376 -0
- package/dist/components/pr-environment.d.ts +91 -15
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +95 -9
- package/dist/components/pr-pipeline.d.ts +10 -1
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/config-environments.d.ts +92 -0
- package/dist/config-environments.d.ts.map +1 -0
- package/dist/config-environments.js +102 -0
- package/dist/config.d.ts +24 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -96
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +15 -3
- package/dist/graph-effective.d.ts +33 -2
- package/dist/graph-effective.d.ts.map +1 -1
- package/dist/graph-effective.js +215 -68
- package/dist/graph-ir.d.ts +23 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +130 -0
- package/dist/graph-sub-template.d.ts +11 -0
- package/dist/graph-sub-template.d.ts.map +1 -0
- package/dist/graph-sub-template.js +19 -0
- package/dist/lexicon.d.ts +9 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +28 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +23 -1
- package/dist/lint/rule.d.ts +6 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +1 -1
- package/dist/observation.d.ts +48 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +21 -5
- package/dist/op/activities/activity-contracts.d.ts +4 -4
- package/dist/op/op-waves.d.ts +17 -0
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +23 -1
- package/dist/pr-forge.d.ts +15 -1
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +28 -16
- package/dist/pr-requires.d.ts +5 -1
- package/dist/pr-requires.d.ts.map +1 -1
- package/dist/wave-runtime.mjs +251 -250
- package/dist/workspace/capabilities-contract.d.ts +66 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -0
- package/dist/workspace/capabilities-contract.js +107 -0
- package/dist/workspace/capabilities.schema.json +229 -0
- package/dist/workspace/ci-cloud-identity.d.ts +3 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +16 -1
- package/dist/workspace/ci-run.d.ts +35 -8
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +95 -21
- package/dist/workspace/ci-secrets.d.ts.map +1 -1
- package/dist/workspace/ci-secrets.js +4 -3
- package/dist/workspace/compose-graph.d.ts +9 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +3 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +39 -2
- package/dist/workspace/declaration.d.ts +10 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +17 -1
- package/dist/workspace/declaration.schema.json +36 -1
- package/dist/workspace/graph-cache.d.ts +8 -0
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +27 -3
- package/dist/workspace/graph-cli.d.ts +39 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +66 -24
- package/dist/workspace/graph.schema.json +148 -0
- package/dist/workspace/kind-readers.d.ts +3 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +7 -1
- package/dist/workspace/member-commands.d.ts +10 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +17 -1
- package/dist/workspace/nested-graph.d.ts +2 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +32 -1
- package/dist/workspace/reason-codes.d.ts +10 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +12 -0
- package/dist/workspace/search-cli.d.ts +204 -0
- package/dist/workspace/search-cli.d.ts.map +1 -0
- package/dist/workspace/search-cli.js +466 -0
- package/dist/workspace/search-contract.d.ts +56 -0
- package/dist/workspace/search-contract.d.ts.map +1 -0
- package/dist/workspace/search-contract.js +70 -0
- package/dist/workspace/search.schema.json +822 -0
- package/package.json +1 -1
- package/src/__snapshots__/okf.test.ts.snap +10 -0
- package/src/capabilities/aws.paths +148 -0
- package/src/capabilities/base-mappings.json +122 -0
- package/src/capabilities/catalog.ts +230 -0
- package/src/capabilities/language.test.ts +191 -0
- package/src/capabilities/language.ts +373 -0
- package/src/capabilities/matcher.ts +315 -0
- package/src/capabilities/view-compose.test.ts +76 -0
- package/src/capabilities/view.ts +631 -0
- package/src/ci-identity-targets.test.ts +275 -0
- package/src/ci-identity.ts +394 -61
- package/src/cli/commands/lint.ts +1 -0
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/declared-graph.ts +99 -0
- package/src/cli/handlers/graph.test.ts +94 -3
- package/src/cli/handlers/graph.ts +123 -96
- package/src/cli/handlers/pr-env.ts +135 -227
- package/src/cli/handlers/pr.ts +4 -0
- package/src/cli/handlers/search-build-roots.test.ts +144 -0
- package/src/cli/handlers/search-count.test.ts +191 -0
- package/src/cli/handlers/search-count.ts +165 -0
- package/src/cli/handlers/search-json.test.ts +333 -0
- package/src/cli/handlers/search-live.test.ts +387 -0
- package/src/cli/handlers/search-paths.test.ts +235 -0
- package/src/cli/handlers/search.ts +1312 -166
- package/src/cli/main.test.ts +28 -3
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/server.test.ts +3 -3
- package/src/cli/mcp/server.ts +3 -0
- package/src/cli/mcp/tools/capabilities.ts +32 -0
- package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
- package/src/cli/mcp/workspace-tools.ts +47 -2
- package/src/cli/parse-args.ts +52 -13
- package/src/cli/registry.ts +22 -0
- package/src/components/cli-support.ts +6 -1
- package/src/components/fan-out-support.ts +4 -1
- package/src/components/pr-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.test.ts +290 -0
- package/src/components/pr-env-run.ts +563 -0
- package/src/components/pr-environment.ts +161 -13
- package/src/components/pr-pipeline.ts +10 -1
- package/src/config-environments.ts +133 -0
- package/src/config.ts +37 -124
- package/src/graph-detail.ts +15 -3
- package/src/graph-effective.ts +226 -75
- package/src/graph-ir.test.ts +163 -0
- package/src/graph-ir.ts +147 -0
- package/src/graph-sub-template.ts +22 -0
- package/src/lexicon.ts +10 -0
- package/src/lifecycle/observe.test.ts +27 -1
- package/src/lifecycle/observe.ts +43 -1
- package/src/lint/rule.ts +6 -0
- package/src/live-endpoint.ts +1 -1
- package/src/observation.test.ts +25 -0
- package/src/observation.ts +69 -5
- package/src/op/op-waves.ts +38 -1
- package/src/pr-forge-bundle.test.ts +1 -1
- package/src/pr-forge.ts +44 -17
- package/src/pr-requires.test.ts +43 -6
- package/src/pr-requires.ts +5 -1
- package/src/workspace/capabilities-contract.test.ts +88 -0
- package/src/workspace/capabilities-contract.ts +132 -0
- package/src/workspace/capabilities.schema.json +229 -0
- package/src/workspace/ci-cloud-identity.test.ts +20 -0
- package/src/workspace/ci-cloud-identity.ts +14 -1
- package/src/workspace/ci-run.test.ts +74 -1
- package/src/workspace/ci-run.ts +122 -29
- package/src/workspace/ci-secrets.test.ts +1 -1
- package/src/workspace/ci-secrets.ts +4 -3
- package/src/workspace/compose-graph.ts +10 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
- package/src/workspace/conformance/index.ts +31 -2
- package/src/workspace/declaration.schema.json +36 -1
- package/src/workspace/declaration.test.ts +7 -0
- package/src/workspace/declaration.ts +24 -2
- package/src/workspace/graph-cache.ts +27 -3
- package/src/workspace/graph-cli.ts +96 -24
- package/src/workspace/graph.schema.json +148 -0
- package/src/workspace/kind-readers.e2e.test.ts +28 -0
- package/src/workspace/kind-readers.test.ts +1 -1
- package/src/workspace/kind-readers.ts +7 -1
- package/src/workspace/member-commands.ts +22 -1
- package/src/workspace/nested-graph.ts +32 -1
- package/src/workspace/reason-codes.test.ts +7 -0
- package/src/workspace/reason-codes.ts +12 -0
- package/src/workspace/search-cli.e2e.test.ts +305 -0
- package/src/workspace/search-cli.ts +591 -0
- package/src/workspace/search-contract.ts +95 -0
- package/src/workspace/search.schema.json +822 -0
package/dist/ci-identity.js
CHANGED
|
@@ -21,6 +21,17 @@
|
|
|
21
21
|
* identity. The record job plans the merged commit with the plan identity,
|
|
22
22
|
* and the apply jobs trade for the apply identity.
|
|
23
23
|
*
|
|
24
|
+
* Per target (#4332): a rule with `targets` covers only the targets those
|
|
25
|
+
* globs match, the Terraform roots or Op wave runs inside one member, so
|
|
26
|
+
* one member's targets can plan and apply as different identities. A
|
|
27
|
+
* target takes the first rule that covers its environment, member and
|
|
28
|
+
* target ({@link resolveTargetCloudIdentity}). The plan job then trades for
|
|
29
|
+
* every plan identity the member's targets take, keeps the credentials of
|
|
30
|
+
* each in a file named by {@link CLOUD_TARGETS_ENV}, and a product hands
|
|
31
|
+
* each target's tool its own with {@link targetCloudEnv}. An apply splits
|
|
32
|
+
* into one job per apply identity ({@link cloudIdentityGroups},
|
|
33
|
+
* {@link cloudIdentityWave}), so no job holds two targets' apply tokens.
|
|
34
|
+
*
|
|
24
35
|
* The trade is terragucci's `oidc-trade.ts` (INTENTIUS/terragucci e6cb431,
|
|
25
36
|
* packages/terragucci/src/oidc-trade.ts) with these clients:
|
|
26
37
|
*
|
|
@@ -38,65 +49,289 @@
|
|
|
38
49
|
* another client could be traded for; a tool that needs the OIDC token
|
|
39
50
|
* itself cannot plan under this block.
|
|
40
51
|
*/
|
|
41
|
-
|
|
42
|
-
import picomatch from "picomatch";
|
|
52
|
+
import { readFileSync } from "node:fs";
|
|
43
53
|
/** The clouds `ci.identity.cloud` names. */
|
|
44
54
|
export const CLOUDS = ["aws", "gcp", "azure"];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
/** The key a cloud's trade names its identity under. */
|
|
56
|
+
export const IDENTITY_KEY = { aws: "role", gcp: "serviceAccount", azure: "clientId" };
|
|
57
|
+
/**
|
|
58
|
+
* A glob over a name (#4296): `*` and `?` within the name, `[...]` classes,
|
|
59
|
+
* `{a,b}` alternatives, a leading `!` negates. The grammar `ciMemberGlobs`
|
|
60
|
+
* documents, with no dependency, so this module bundles small (#4332).
|
|
61
|
+
*/
|
|
62
|
+
export function nameGlobMatch(glob, name) {
|
|
63
|
+
if (glob.startsWith("!"))
|
|
64
|
+
return !nameGlobMatch(glob.slice(1), name);
|
|
65
|
+
return expandBraces(glob).some((g) => new RegExp(`^${globBody(g, false)}$`).test(name));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A glob over a target (#4332): `*` within one `/` segment, `**` across
|
|
69
|
+
* segments (and `a/**\/b` across none), `?` one character. The reading of
|
|
70
|
+
* `targetGlobMatch` in ./op/gate-quorum.ts and the terraform lexicon's unit
|
|
71
|
+
* globs, so a root's glob means one thing in each.
|
|
72
|
+
*/
|
|
73
|
+
export function targetGlobMatch(glob, target) {
|
|
74
|
+
const trim = (v) => v.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
75
|
+
return new RegExp(`^${globBody(trim(glob), true)}$`).test(trim(target));
|
|
76
|
+
}
|
|
77
|
+
/** `{a,b}` alternatives, nested too, expanded into plain globs. */
|
|
78
|
+
function expandBraces(glob) {
|
|
79
|
+
const open = glob.indexOf("{");
|
|
80
|
+
if (open < 0)
|
|
81
|
+
return [glob];
|
|
82
|
+
let depth = 0;
|
|
83
|
+
const cuts = [];
|
|
84
|
+
for (let i = open; i < glob.length; i++) {
|
|
85
|
+
const c = glob[i];
|
|
86
|
+
if (c === "{")
|
|
87
|
+
depth++;
|
|
88
|
+
else if (c === "}" && --depth === 0) {
|
|
89
|
+
const parts = cuts.length === 0 ? [glob.slice(open + 1, i)] : [open, ...cuts].map((at, k, all) => glob.slice(at + 1, k + 1 < all.length ? all[k + 1] : i));
|
|
90
|
+
const rest = expandBraces(glob.slice(i + 1));
|
|
91
|
+
return parts.flatMap((p) => expandBraces(glob.slice(0, open) + p)).flatMap((head) => rest.map((tail) => head + tail));
|
|
92
|
+
}
|
|
93
|
+
else if (c === "," && depth === 1)
|
|
94
|
+
cuts.push(i);
|
|
95
|
+
}
|
|
96
|
+
return [glob];
|
|
97
|
+
}
|
|
98
|
+
/** A glob's regular expression body. `paths`: `**` crosses `/`, and classes and braces are literal. */
|
|
99
|
+
function globBody(g, paths) {
|
|
100
|
+
let re = "";
|
|
101
|
+
for (let i = 0; i < g.length; i++) {
|
|
102
|
+
const c = g[i];
|
|
103
|
+
if (c === "*" && g[i + 1] === "*" && paths) {
|
|
104
|
+
if (g[i + 2] === "/") {
|
|
105
|
+
re += "(?:.*/)?";
|
|
106
|
+
i += 2;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
re += ".*";
|
|
110
|
+
i += 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (c === "*") {
|
|
114
|
+
re += "[^/]*";
|
|
115
|
+
while (g[i + 1] === "*")
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
else if (c === "?")
|
|
119
|
+
re += "[^/]";
|
|
120
|
+
else if (c === "[" && !paths && g.indexOf("]", i + 2) > i) {
|
|
121
|
+
const end = g.indexOf("]", i + 2);
|
|
122
|
+
const body = g.slice(i + 1, end);
|
|
123
|
+
re += `[${body.startsWith("!") ? `^${body.slice(1)}` : body}]`.replace(/\\/g, "\\\\");
|
|
124
|
+
i = end;
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
128
|
+
}
|
|
129
|
+
return re;
|
|
130
|
+
}
|
|
131
|
+
const matches = (globs, name) => globs === undefined || (name !== undefined && globs.some((g) => nameGlobMatch(g, name)));
|
|
132
|
+
const coversTarget = (r, target) => r.targets === undefined || (target !== undefined && r.targets.some((g) => targetGlobMatch(g, target)));
|
|
133
|
+
/**
|
|
134
|
+
* The first rule that covers `env`, `member` and `target`, or undefined.
|
|
135
|
+
* With no `target`, the first rule that names no targets: the pipeline's own.
|
|
136
|
+
*/
|
|
137
|
+
export function ruleFor(rules, env, member, target) {
|
|
138
|
+
return rules.find((r) => matches(r.environments, env) && matches(r.members, member) && coversTarget(r, target));
|
|
49
139
|
}
|
|
50
140
|
/** The provider's resource name without the `//iam.googleapis.com/` prefix. */
|
|
51
141
|
const providerPath = (p) => p.replace(/^(https:)?\/\/iam\.googleapis\.com\//, "");
|
|
142
|
+
/** Where each cloud trades, the same for every identity of the cloud. */
|
|
143
|
+
function cloudPlace(decl, cloud) {
|
|
144
|
+
if (cloud === "aws") {
|
|
145
|
+
const a = decl.aws;
|
|
146
|
+
return { region: a.region ?? "us-east-1", audience: a.audience ?? "sts.amazonaws.com", ...(a.endpoint ? { endpoint: a.endpoint } : {}) };
|
|
147
|
+
}
|
|
148
|
+
if (cloud === "gcp") {
|
|
149
|
+
const g = decl.gcp;
|
|
150
|
+
const provider = providerPath(g.provider);
|
|
151
|
+
return {
|
|
152
|
+
provider,
|
|
153
|
+
audience: g.audience ?? `https://iam.googleapis.com/${provider}`,
|
|
154
|
+
...(g.stsEndpoint ? { stsEndpoint: g.stsEndpoint } : {}),
|
|
155
|
+
...(g.iamEndpoint ? { iamEndpoint: g.iamEndpoint } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const z = decl.azure;
|
|
159
|
+
return {
|
|
160
|
+
tenantId: z.tenantId,
|
|
161
|
+
audience: z.audience ?? "api://AzureADTokenExchange",
|
|
162
|
+
...(z.subscriptionId ? { subscriptionId: z.subscriptionId } : {}),
|
|
163
|
+
...(z.authority ? { authority: z.authority } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** One cloud's trade: its own identity, the targets that take another, and where it trades. */
|
|
167
|
+
function cloudTrade(decl, cloud, own, targets) {
|
|
168
|
+
const key = IDENTITY_KEY[cloud];
|
|
169
|
+
return {
|
|
170
|
+
...(own !== undefined ? { [key]: own } : {}),
|
|
171
|
+
...(targets.length > 0 ? { targets: targets.map((t) => ({ targets: [...t.targets], [key]: t.id })) } : {}),
|
|
172
|
+
...cloudPlace(decl, cloud),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
52
175
|
/**
|
|
53
176
|
* The plan and apply trades of the pipeline for `env` and `member`, or
|
|
54
177
|
* undefined when no rule of any cloud covers them. A cloud no rule covers
|
|
55
|
-
* is left out of both.
|
|
178
|
+
* is left out of both. Each cloud's own identity is the first rule with no
|
|
179
|
+
* `targets` that covers the pipeline; the rules with `targets` before it
|
|
180
|
+
* are the cloud's `targets` (#4332), so the plan trade holds every plan
|
|
181
|
+
* identity a target of the member takes, and the apply trade every apply
|
|
182
|
+
* identity.
|
|
56
183
|
*/
|
|
57
184
|
export function resolveCloudIdentity(decl, env, member) {
|
|
58
185
|
if (!decl)
|
|
59
186
|
return undefined;
|
|
60
187
|
const plan = {};
|
|
61
188
|
const apply = {};
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
189
|
+
for (const cloud of CLOUDS) {
|
|
190
|
+
const rules = decl[cloud]?.rules;
|
|
191
|
+
if (!rules)
|
|
192
|
+
continue;
|
|
193
|
+
const covering = rules.filter((r) => matches(r.environments, env) && matches(r.members, member));
|
|
194
|
+
const cut = covering.findIndex((r) => r.targets === undefined);
|
|
195
|
+
const own = cut < 0 ? undefined : covering[cut];
|
|
196
|
+
const byTarget = (cut < 0 ? covering : covering.slice(0, cut)).filter((r) => r.targets !== undefined);
|
|
197
|
+
if (!own && byTarget.length === 0)
|
|
198
|
+
continue;
|
|
199
|
+
plan[cloud] = cloudTrade(decl, cloud, own?.plan, byTarget.map((r) => ({ targets: r.targets, id: r.plan })));
|
|
200
|
+
apply[cloud] = cloudTrade(decl, cloud, own?.apply, byTarget.map((r) => ({ targets: r.targets, id: r.apply })));
|
|
67
201
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
202
|
+
return Object.keys(plan).length > 0 ? { plan: plan, apply: apply } : undefined;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The plan and apply trades of one target (#4332): per cloud, the first
|
|
206
|
+
* rule that covers `env`, `member` and `target`. Undefined when no rule of
|
|
207
|
+
* any cloud covers it. Each trade names one identity per cloud and no
|
|
208
|
+
* `targets`.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveTargetCloudIdentity(decl, env, member, target) {
|
|
211
|
+
if (!decl)
|
|
212
|
+
return undefined;
|
|
213
|
+
const plan = {};
|
|
214
|
+
const apply = {};
|
|
215
|
+
for (const cloud of CLOUDS) {
|
|
216
|
+
const rule = decl[cloud] && ruleFor(decl[cloud].rules, env, member, target);
|
|
217
|
+
if (!rule)
|
|
218
|
+
continue;
|
|
219
|
+
plan[cloud] = cloudTrade(decl, cloud, rule.plan, []);
|
|
220
|
+
apply[cloud] = cloudTrade(decl, cloud, rule.apply, []);
|
|
79
221
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
222
|
+
return Object.keys(plan).length > 0 ? { plan: plan, apply: apply } : undefined;
|
|
223
|
+
}
|
|
224
|
+
/** The name a pull request environment's rule is matched against (#4330): any pull request's, as `pr-*` covers it. */
|
|
225
|
+
export const PR_ENV_IDENTITY_PROBE = "pr-1";
|
|
226
|
+
/**
|
|
227
|
+
* The trade a pull request environment's jobs take (#4330): per cloud, the
|
|
228
|
+
* apply identity of the rules {@link resolveCloudIdentity} gives `pr-1` and
|
|
229
|
+
* `member`, from the rules that name `environments` only. env-up runs the
|
|
230
|
+
* change's code with that identity, so a rule with no `environments`, which
|
|
231
|
+
* covers every environment, is never one it gets: give pull request
|
|
232
|
+
* environments a rule of their own, such as `environments: ["pr-*"]`, whose
|
|
233
|
+
* identity reaches only what they may touch. Undefined when no such rule
|
|
234
|
+
* covers them.
|
|
235
|
+
*/
|
|
236
|
+
export function resolvePrEnvCloudIdentity(decl, member) {
|
|
237
|
+
if (!decl)
|
|
238
|
+
return undefined;
|
|
239
|
+
const named = {};
|
|
240
|
+
for (const cloud of CLOUDS) {
|
|
241
|
+
const c = decl[cloud];
|
|
242
|
+
if (c)
|
|
243
|
+
named[cloud] = { ...c, rules: c.rules.filter((r) => r.environments !== undefined) };
|
|
244
|
+
}
|
|
245
|
+
return resolveCloudIdentity(named, PR_ENV_IDENTITY_PROBE, member)?.apply;
|
|
246
|
+
}
|
|
247
|
+
/** The group name a glob gives: `envs/prod/**` is `envs-prod`. terragucci's `nameFrom` (groups.ts at INTENTIUS/terragucci ca05bd9). */
|
|
248
|
+
const nameFrom = (glob) => glob.toLowerCase().replace(/\*+/g, "").replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
249
|
+
/** The name of the apply job group for {@link CloudIdentityGroup}s with no rule of their own. */
|
|
250
|
+
export const DEFAULT_IDENTITY_GROUP = "default";
|
|
251
|
+
/**
|
|
252
|
+
* `targets` grouped by the identities they take (#4332), so each group's
|
|
253
|
+
* apply runs in a job of its own with its group's identities alone.
|
|
254
|
+
* Targets that take the same plan and apply identity in every cloud are one
|
|
255
|
+
* group. The groups come in the order their first target comes, with
|
|
256
|
+
* `default` (the targets no rule with `targets` took) last. Names are
|
|
257
|
+
* unique: a second group of one name gets `-2`.
|
|
258
|
+
*/
|
|
259
|
+
export function cloudIdentityGroups(decl, env, member, targets) {
|
|
260
|
+
const byKey = new Map();
|
|
261
|
+
for (const target of targets) {
|
|
262
|
+
const identity = resolveTargetCloudIdentity(decl, env, member, target);
|
|
263
|
+
const key = JSON.stringify(identity ? CLOUDS.map((c) => [identity.plan[c] ? tradeIdentities({ [c]: identity.plan[c] }) : [], identity.apply[c] ? tradeIdentities({ [c]: identity.apply[c] }) : []]) : null);
|
|
264
|
+
const at = byKey.get(key);
|
|
265
|
+
if (at) {
|
|
266
|
+
at.group.targets.push(target);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const rule = CLOUDS.map((c) => decl?.[c] && ruleFor(decl[c].rules, env, member, target)).find((r) => r?.targets !== undefined);
|
|
270
|
+
const named = rule ? (rule.name ?? (nameFrom(rule.targets[0]) || "group")) : DEFAULT_IDENTITY_GROUP;
|
|
271
|
+
// An Op wave's own jobs are `decide` and `share-<i>` (./op/op-waves.ts), so a group takes neither name.
|
|
272
|
+
const base = named === "decide" || /^share-\d+$/.test(named) ? `${named}-targets` : named;
|
|
273
|
+
byKey.set(key, { base, group: { name: base, targets: [target], ...(identity ? { identity } : {}) } });
|
|
90
274
|
}
|
|
91
|
-
|
|
275
|
+
const all = [...byKey.values()];
|
|
276
|
+
const ordered = [...all.filter((g) => g.base !== DEFAULT_IDENTITY_GROUP), ...all.filter((g) => g.base === DEFAULT_IDENTITY_GROUP)];
|
|
277
|
+
const taken = new Set();
|
|
278
|
+
return ordered.map(({ base, group }) => {
|
|
279
|
+
let name = base;
|
|
280
|
+
for (let n = 2; taken.has(name); n++)
|
|
281
|
+
name = `${base}-${n}`;
|
|
282
|
+
taken.add(name);
|
|
283
|
+
return { ...group, name };
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* An Op wave's cloud identities over its run targets (#4332), as
|
|
288
|
+
* `OpWave.cloud` and `OpWave.jobs` (./op/op-waves.ts) take them. With two
|
|
289
|
+
* or more {@link cloudIdentityGroups}, the deciding job trades for every
|
|
290
|
+
* plan identity of the targets and each group's job for its group's apply
|
|
291
|
+
* identities alone. With one group, the wave's one job trades for that
|
|
292
|
+
* group's apply identities, and `jobs` is absent. Neither, when no rule
|
|
293
|
+
* covers a target.
|
|
294
|
+
*/
|
|
295
|
+
export function cloudIdentityWave(decl, env, member, targets) {
|
|
296
|
+
const groups = cloudIdentityGroups(decl, env, member, targets);
|
|
297
|
+
if (groups.length === 0)
|
|
298
|
+
return {};
|
|
299
|
+
if (groups.length === 1)
|
|
300
|
+
return groups[0].identity ? { cloud: groups[0].identity.apply } : {};
|
|
301
|
+
const plan = resolveCloudIdentity(decl, env, member)?.plan;
|
|
302
|
+
return {
|
|
303
|
+
...(plan ? { cloud: plan } : {}),
|
|
304
|
+
jobs: groups.map((g) => ({ name: g.name, runs: [...g.targets], ...(g.identity ? { cloud: g.identity.apply } : {}) })),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Why one job may not apply under `spec` (#4332), or undefined: a cloud
|
|
309
|
+
* whose trade names more than one identity would hand one job the apply
|
|
310
|
+
* tokens of targets that apply as different identities.
|
|
311
|
+
*/
|
|
312
|
+
export function oneApplyIdentityProblem(spec) {
|
|
313
|
+
for (const cloud of tradedClouds(spec)) {
|
|
314
|
+
const ids = tradeIdentities({ [cloud]: spec[cloud] });
|
|
315
|
+
if (ids.length > 1)
|
|
316
|
+
return `ci.identity.cloud gives the targets of one apply job ${ids.length} ${cloud} apply identities (${ids.join(", ")}); a job may hold one, so apply them in a job per identity (cloudIdentityGroups)`;
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
92
319
|
}
|
|
93
320
|
/** The clouds a trade covers, in {@link CLOUDS} order. */
|
|
94
321
|
export function tradedClouds(spec) {
|
|
95
322
|
return CLOUDS.filter((c) => spec[c] !== undefined);
|
|
96
323
|
}
|
|
97
|
-
/** Every identity a trade names: the
|
|
324
|
+
/** Every identity a trade names, once each: the roles, the service accounts, the client ids, its targets' too (#4332). */
|
|
98
325
|
export function tradeIdentities(spec) {
|
|
99
|
-
|
|
326
|
+
const out = [];
|
|
327
|
+
for (const cloud of tradedClouds(spec)) {
|
|
328
|
+
const c = spec[cloud];
|
|
329
|
+
const key = IDENTITY_KEY[cloud];
|
|
330
|
+
for (const id of [c[key], ...(c.targets ?? []).map((t) => t[key])])
|
|
331
|
+
if (id !== undefined && !out.includes(id))
|
|
332
|
+
out.push(id);
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
100
335
|
}
|
|
101
336
|
/**
|
|
102
337
|
* The problems of a declared `ci.identity.cloud`, each with its JSON
|
|
@@ -137,15 +372,25 @@ export const TRADED_VARS = {
|
|
|
137
372
|
gcp: ["GOOGLE_OAUTH_ACCESS_TOKEN", "CLOUDSDK_AUTH_ACCESS_TOKEN"],
|
|
138
373
|
azure: ["AZURE_ACCESS_TOKEN", "AZURE_CLIENT_ID", "AZURE_TENANT_ID", "ARM_CLIENT_ID", "ARM_TENANT_ID", "AZURE_SUBSCRIPTION_ID", "ARM_SUBSCRIPTION_ID"],
|
|
139
374
|
};
|
|
375
|
+
/**
|
|
376
|
+
* The variable the trade sets when a cloud names `targets` (#4332): the
|
|
377
|
+
* path of a JSON file, mode 0600 and outside the checkout, with each
|
|
378
|
+
* identity's credentials and which targets take it. {@link targetCloudEnv}
|
|
379
|
+
* reads it.
|
|
380
|
+
*/
|
|
381
|
+
export const CLOUD_TARGETS_ENV = "CHANT_CLOUD_TARGETS";
|
|
140
382
|
/**
|
|
141
383
|
* The trade, a script for `node -e` with no dependency, so it runs before
|
|
142
384
|
* the checkout and reads none of the change's files. argv 1 is the file it
|
|
143
385
|
* writes and argv 2 its {@link TradeFormat}; {@link TRADE_SPEC_ENV} is the
|
|
144
386
|
* spec. The token comes from a cloud's {@link idTokenVar} when the job has
|
|
145
387
|
* it (GitLab's `id_tokens`), else from the forge's request URL with the
|
|
146
|
-
* cloud's audience
|
|
147
|
-
*
|
|
148
|
-
*
|
|
388
|
+
* cloud's audience, once per cloud. Each identity of a cloud (its own, and
|
|
389
|
+
* its targets', #4332) is traded with that one token. The job's own
|
|
390
|
+
* identity's variables go in the file; with targets, every identity's go in
|
|
391
|
+
* the {@link CLOUD_TARGETS_ENV} file too. A value that holds anything but
|
|
392
|
+
* printable ASCII with no space is refused, so no answer can add a line of
|
|
393
|
+
* its own to the file. The script never prints a token.
|
|
149
394
|
*/
|
|
150
395
|
export const TRADE_JS = [
|
|
151
396
|
'const fs=require("fs");',
|
|
@@ -164,27 +409,40 @@ export const TRADE_JS = [
|
|
|
164
409
|
'const r=await fetch(u+(u.includes("?")?"&":"?")+"audience="+encodeURIComponent(aud),{headers:{authorization:"bearer "+k,accept:"application/json"}});',
|
|
165
410
|
'if(!r.ok)throw new Error("the OIDC token request answered "+r.status);',
|
|
166
411
|
'const v=json(await r.text(),"the OIDC token request").value;if(!clean(v))throw new Error("the OIDC token request gave no token");return v};',
|
|
167
|
-
|
|
412
|
+
`const KEY=${JSON.stringify(IDENTITY_KEY)};`,
|
|
413
|
+
// #4332: a cloud's identities, its own first, then its targets', once each.
|
|
414
|
+
'const ids=(c)=>{const s=spec[c],l=[];if(s[KEY[c]])l.push(s[KEY[c]]);for(const t of s.targets||[])if(!l.includes(t[KEY[c]]))l.push(t[KEY[c]]);if(!l.length)throw new Error(c+" names no identity");return l};',
|
|
415
|
+
"const vars={},secrets=[],said=[],by={};",
|
|
168
416
|
"const run=async()=>{",
|
|
169
|
-
|
|
417
|
+
'if(spec.aws){const a=spec.aws;const tok=await idToken("aws",a.audience);by.aws={};',
|
|
170
418
|
'const ep=trim(a.endpoint||process.env.AWS_ENDPOINT_URL_STS||("https://sts."+a.region+".amazonaws.com"+(a.region.startsWith("cn-")?".cn":"")));',
|
|
171
|
-
'const
|
|
419
|
+
'for(const role of ids("aws")){',
|
|
420
|
+
'const t=await post(ep+"/",{"content-type":"application/x-www-form-urlencoded",accept:"application/xml"},new URLSearchParams({Action:"AssumeRoleWithWebIdentity",Version:"2011-06-15",RoleArn:role,RoleSessionName:"chant-"+(process.env.GITHUB_RUN_ID||process.env.CI_JOB_ID||"job"),WebIdentityToken:tok}).toString());',
|
|
172
421
|
'const g=(k)=>{const m=new RegExp("<"+k+">([^<]+)</"+k+">").exec(t);return m?m[1].trim():undefined};',
|
|
173
|
-
'const id=g("AccessKeyId"),key=g("SecretAccessKey"),st=g("SessionToken");if(!clean(id)||!clean(key)||!clean(st))throw new Error("STS gave no credentials for "+
|
|
174
|
-
|
|
175
|
-
"if(
|
|
422
|
+
'const id=g("AccessKeyId"),key=g("SecretAccessKey"),st=g("SessionToken");if(!clean(id)||!clean(key)||!clean(st))throw new Error("STS gave no credentials for "+role);',
|
|
423
|
+
"by.aws[role]={AWS_ACCESS_KEY_ID:id,AWS_SECRET_ACCESS_KEY:key,AWS_SESSION_TOKEN:st,AWS_REGION:a.region,AWS_DEFAULT_REGION:a.region};secrets.push(key,st);said.push(role)}",
|
|
424
|
+
"if(a.role)Object.assign(vars,by.aws[a.role])}",
|
|
425
|
+
'if(spec.gcp){const c=spec.gcp;const tok=await idToken("gcp",c.audience);by.gcp={};',
|
|
176
426
|
'const sts=trim(c.stsEndpoint||"https://sts.googleapis.com"),iam=trim(c.iamEndpoint||"https://iamcredentials.googleapis.com");',
|
|
177
427
|
'const fed=json(await post(sts+"/v1/token",{"content-type":"application/json"},JSON.stringify({grantType:"urn:ietf:params:oauth:grant-type:token-exchange",audience:"//iam.googleapis.com/"+c.provider,scope:"https://www.googleapis.com/auth/cloud-platform",requestedTokenType:"urn:ietf:params:oauth:token-type:access_token",subjectToken:tok,subjectTokenType:"urn:ietf:params:oauth:token-type:jwt"})),"Google STS").access_token;',
|
|
178
428
|
'if(!clean(fed))throw new Error("Google STS gave no token");',
|
|
179
|
-
'const
|
|
180
|
-
'
|
|
181
|
-
|
|
182
|
-
"
|
|
429
|
+
'for(const sa of ids("gcp")){',
|
|
430
|
+
'const at=json(await post(iam+"/v1/projects/-/serviceAccounts/"+encodeURIComponent(sa)+":generateAccessToken",{"content-type":"application/json",authorization:"Bearer "+fed},JSON.stringify({scope:["https://www.googleapis.com/auth/cloud-platform"]})),"IAM credentials").accessToken;',
|
|
431
|
+
'if(!clean(at))throw new Error("IAM credentials gave no token for "+sa);',
|
|
432
|
+
"by.gcp[sa]={GOOGLE_OAUTH_ACCESS_TOKEN:at,CLOUDSDK_AUTH_ACCESS_TOKEN:at};secrets.push(at);said.push(sa)}",
|
|
433
|
+
"if(c.serviceAccount)Object.assign(vars,by.gcp[c.serviceAccount])}",
|
|
434
|
+
'if(spec.azure){const z=spec.azure;const tok=await idToken("azure",z.audience);by.azure={};',
|
|
183
435
|
'const host=trim(z.authority||"https://login.microsoftonline.com");',
|
|
184
|
-
'const
|
|
185
|
-
'
|
|
186
|
-
|
|
187
|
-
"
|
|
436
|
+
'for(const cid of ids("azure")){',
|
|
437
|
+
'const at=json(await post(host+"/"+encodeURIComponent(z.tenantId)+"/oauth2/v2.0/token",{"content-type":"application/x-www-form-urlencoded"},new URLSearchParams({client_id:cid,scope:"https://management.azure.com/.default",grant_type:"client_credentials",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:tok}).toString()),"Entra").access_token;',
|
|
438
|
+
'if(!clean(at))throw new Error("Entra gave no token for "+cid);',
|
|
439
|
+
"by.azure[cid]={AZURE_ACCESS_TOKEN:at,AZURE_CLIENT_ID:cid,AZURE_TENANT_ID:z.tenantId,ARM_CLIENT_ID:cid,ARM_TENANT_ID:z.tenantId,...(z.subscriptionId?{AZURE_SUBSCRIPTION_ID:z.subscriptionId,ARM_SUBSCRIPTION_ID:z.subscriptionId}:{})};secrets.push(at);said.push(cid)}",
|
|
440
|
+
"if(z.clientId)Object.assign(vars,by.azure[z.clientId])}",
|
|
441
|
+
// #4332: with targets, every identity's variables go in a file of their own, outside the checkout.
|
|
442
|
+
'const clouds=Object.keys(by);if(clouds.some((c)=>(spec[c].targets||[]).length>0)){',
|
|
443
|
+
'const doc={version:1,clouds:{}};for(const c of clouds){const s=spec[c];doc.clouds[c]={...(s[KEY[c]]?{default:s[KEY[c]]}:{}),targets:(s.targets||[]).map((t)=>({targets:t.targets,identity:t[KEY[c]]})),vars:by[c]}}',
|
|
444
|
+
'const tf=trim(process.env.RUNNER_TEMP||process.env.TMPDIR||"/tmp")+"/chant-cloud-targets-"+require("crypto").randomBytes(8).toString("hex")+".json";',
|
|
445
|
+
`fs.writeFileSync(tf,JSON.stringify(doc),{mode:0o600,flag:"wx"});vars.${CLOUD_TARGETS_ENV}=tf}`,
|
|
188
446
|
'for(const [k,v] of Object.entries(vars))if(!clean(v))throw new Error(k+" is not a value the job can hold");',
|
|
189
447
|
'if(format==="github-env"){for(const s of secrets)console.log("::add-mask::"+s);fs.appendFileSync(out,Object.entries(vars).map(([k,v])=>k+"="+v+"\\n").join(""))}',
|
|
190
448
|
'else fs.writeFileSync(out,Object.entries(vars).map(([k,v])=>"export "+k+"="+Q+v.split(Q).join(Q+"\\\\"+Q+Q)+Q+"\\n").join(""),{mode:0o600});',
|
|
@@ -217,3 +475,54 @@ export function gitlabTradeLines(extraUnset = []) {
|
|
|
217
475
|
export function gitlabTradeIdTokens(spec) {
|
|
218
476
|
return Object.fromEntries(tradedClouds(spec).map((c) => [idTokenVar(c), { aud: spec[c].audience }]));
|
|
219
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* `env` for one target's tool (#4332): the job's environment with each
|
|
480
|
+
* cloud's credentials replaced by those of the identity `target` takes, as
|
|
481
|
+
* the trade left them in the {@link CLOUD_TARGETS_ENV} file. A target that
|
|
482
|
+
* no rule with `targets` covers keeps the job's own identity. Without the
|
|
483
|
+
* file (no cloud names targets), `env` as it is. terragucci's `rootRoleEnv`
|
|
484
|
+
* (roles.ts at INTENTIUS/terragucci ca05bd9).
|
|
485
|
+
*/
|
|
486
|
+
export function targetCloudEnv(env, target, read = (p) => readFileSync(p, "utf-8")) {
|
|
487
|
+
const out = { ...env };
|
|
488
|
+
const path = env[CLOUD_TARGETS_ENV];
|
|
489
|
+
if (!path)
|
|
490
|
+
return out;
|
|
491
|
+
const file = readTargetsFile(path, read);
|
|
492
|
+
for (const cloud of CLOUDS) {
|
|
493
|
+
const c = file.clouds[cloud];
|
|
494
|
+
if (!c)
|
|
495
|
+
continue;
|
|
496
|
+
const id = targetIdentityIn(c, target);
|
|
497
|
+
if (id === undefined || id === c.default)
|
|
498
|
+
continue;
|
|
499
|
+
const vars = c.vars[id];
|
|
500
|
+
if (!vars)
|
|
501
|
+
throw new Error(`${CLOUD_TARGETS_ENV} (${path}) holds no ${cloud} credentials for ${id}`);
|
|
502
|
+
for (const k of TRADED_VARS[cloud])
|
|
503
|
+
delete out[k];
|
|
504
|
+
Object.assign(out, vars);
|
|
505
|
+
}
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* The identity `target` takes in one cloud of a {@link CloudTargetsFile}:
|
|
510
|
+
* the first entry whose globs match, else the job's own. Undefined when
|
|
511
|
+
* the job has none for that cloud.
|
|
512
|
+
*/
|
|
513
|
+
export function targetIdentityIn(cloud, target) {
|
|
514
|
+
return cloud.targets.find((t) => t.targets.some((g) => targetGlobMatch(g, target)))?.identity ?? cloud.default;
|
|
515
|
+
}
|
|
516
|
+
/** The {@link CloudTargetsFile} at `path`, refused when it is not one. */
|
|
517
|
+
export function readTargetsFile(path, read = (p) => readFileSync(p, "utf-8")) {
|
|
518
|
+
let file;
|
|
519
|
+
try {
|
|
520
|
+
file = JSON.parse(read(path));
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
throw new Error(`${CLOUD_TARGETS_ENV} names ${path}, which cannot be read as the trade's targets file: ${err instanceof Error ? err.message : String(err)}`);
|
|
524
|
+
}
|
|
525
|
+
if (!file || file.version !== 1 || typeof file.clouds !== "object" || file.clouds === null)
|
|
526
|
+
throw new Error(`${CLOUD_TARGETS_ENV} names ${path}, which is not a version 1 targets file`);
|
|
527
|
+
return file;
|
|
528
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAyCzE;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAoBhC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,WAAW,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AA2I9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,qCAAqC;IACrC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAyCzE;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAoBhC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,WAAW,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AA2I9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,qCAAqC;IACrC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAgfD;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA+N3E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAIxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACtC,MAAM,IAAI,CAiCZ"}
|
|
@@ -653,6 +653,7 @@ async function runLexiconPostSynthDiagnostics(infraPath, plugins, buildParams, s
|
|
|
653
653
|
ruleId: d.checkId,
|
|
654
654
|
severity: d.severity,
|
|
655
655
|
message: `${d.entity ? `[${d.entity}] ` : ""}${d.message}`,
|
|
656
|
+
...(d.entity ? { entity: d.entity } : {}),
|
|
656
657
|
});
|
|
657
658
|
for (const { plugin, diagnostics: raw } of runLexiconPostSynthChecks(withChecks, result, env, { activityContracts, telemetry, projectDir: configDir, ...(loadedConfig ? { projectConfig: loadedConfig } : {}) })) {
|
|
658
659
|
const presetIds = resolvePresetIds(plugin.lintPresets?.(), lintConfig.presets?.[plugin.name] ?? "recommended");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/build.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/build.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqPlD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsJnE"}
|
|
@@ -146,6 +146,22 @@ async function runGenerateComponents(ctx) {
|
|
|
146
146
|
if (found && args.prLoop) {
|
|
147
147
|
try {
|
|
148
148
|
identity = await (await import("../../workspace/ci-cloud-identity.js")).declaredCloudIdentity(resolve(args.path), args.env ?? "production", plan?.member.name);
|
|
149
|
+
// #4332: the pull-request pipeline applies a member in one job, which may hold one apply identity per cloud.
|
|
150
|
+
const applies = args.applyOnMerge || args.applyBeforeMerge;
|
|
151
|
+
const problem = identity && applies ? (await import("../../ci-identity.js")).oneApplyIdentityProblem(identity.apply) : undefined;
|
|
152
|
+
if (problem)
|
|
153
|
+
throw new Error(`${problem}; the pull-request pipeline applies ${plan?.member.name ?? "the project"} in one job, so give its targets one apply identity, or apply them through Op waves with a job group per identity (cloudIdentityWave)`);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// #4330: the identity a pull request environment's jobs trade for, from a rule that names those environments.
|
|
161
|
+
let prEnvIdentity;
|
|
162
|
+
if (found && args.prLoop && args.prEnv) {
|
|
163
|
+
try {
|
|
164
|
+
prEnvIdentity = await (await import("../../workspace/ci-cloud-identity.js")).declaredPrEnvCloudIdentity(resolve(args.path), plan?.member.name);
|
|
149
165
|
}
|
|
150
166
|
catch (err) {
|
|
151
167
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
@@ -170,7 +186,7 @@ async function runGenerateComponents(ctx) {
|
|
|
170
186
|
: {}),
|
|
171
187
|
// #3183: a plan job per pull request and an apply job per merge.
|
|
172
188
|
// #4292: on the branch ci.branches routes the member to, when it routes it.
|
|
173
|
-
...(args.prLoop ? { prLoop: { ...prLoopOption(args, reportUrl, reviewRules, isolatedRunners), ...(plan?.branch ? { branch: plan.branch } : {}), ...(identity ? { identity } : {}) } } : {}),
|
|
189
|
+
...(args.prLoop ? { prLoop: { ...prLoopOption(args, reportUrl, reviewRules, isolatedRunners), ...(plan?.branch ? { branch: plan.branch } : {}), ...(identity ? { identity } : {}), ...(prEnvIdentity ? { prEnvIdentity } : {}) } } : {}),
|
|
174
190
|
// #4002: the job image and the lines that install what it lacks, chant itself included.
|
|
175
191
|
...(args.image ? { image: args.image } : {}),
|
|
176
192
|
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Declarable } from "../../declarable.js";
|
|
2
|
+
import { type GraphIR } from "../../graph-ir.js";
|
|
3
|
+
import type { BuildRootContributor, LexiconPlugin } from "../../lexicon.js";
|
|
4
|
+
import type { CommandContext } from "../registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* The pieces of the declared graph that `chant graph` and `chant search` both
|
|
7
|
+
* read (#1626, #4365, #4368). They live here, once, so the two commands answer
|
|
8
|
+
* over the same entity set and the same edges: search drifted from graph once
|
|
9
|
+
* already, answering `(no matches)` over a `terraform.roots` project graph drew
|
|
10
|
+
* in full, because it never ran the build-root contributors.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The build-root contributors this invocation's project configures (#1626):
|
|
14
|
+
* a `terraform.roots` root's blocks, a `k8s.kustomize.roots` root's rendered
|
|
15
|
+
* objects. The same closures `chant build` and the lifecycle handlers hand to
|
|
16
|
+
* `BuildOptions.buildRoots`, bound here for the discover-based paths, which
|
|
17
|
+
* never run `build()` and so never ran the hook.
|
|
18
|
+
*
|
|
19
|
+
* Neither command is `requiresPlugins` (Op/source-graph modes must work
|
|
20
|
+
* without a lexicon), so plugins are loaded here when `ctx.plugins` is empty.
|
|
21
|
+
* Rooted at the config's own directory, like `cli/commands/build.ts`: a
|
|
22
|
+
* relative root resolves against where it was declared, not against a
|
|
23
|
+
* sourceDir-scoped path. A project that fails to load plugins degrades to no
|
|
24
|
+
* contributors with a warning rather than failing modes that never needed
|
|
25
|
+
* plugins at all.
|
|
26
|
+
*/
|
|
27
|
+
export declare function declaredBuildRootContributors(ctx: CommandContext, projectPath: string): Promise<BuildRootContributor[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Merge build-root contributions into a discovered entity set: the exact
|
|
30
|
+
* merge `build()` performs (one implementation: `mergeBuildRootEntities`,
|
|
31
|
+
* ../../build.ts), with its warnings and errors printed. Returns false when a
|
|
32
|
+
* contributor failed or a name collided; the caller stops, because a graph
|
|
33
|
+
* missing the entities the build would refuse over must not be answered from
|
|
34
|
+
* as if complete.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mergeDeclaredBuildRoots(entities: Map<string, Declarable>, contributors: BuildRootContributor[]): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Add the edges the lexicons' `graphEdges` hooks resolve (#3910): CI
|
|
39
|
+
* dependencies written as strings (`needs`, `uses:`, `trigger:`, `include:`,
|
|
40
|
+
* `extends`), which the AttrRef walk in `buildGraphIr` cannot see. A hook that
|
|
41
|
+
* throws costs its own edges and a warning, not the graph.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withLexiconEdges(ir: GraphIR, plugins: readonly LexiconPlugin[], entities: Map<string, Declarable>): GraphIR;
|
|
44
|
+
//# sourceMappingURL=declared-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declared-graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/declared-graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAA0B,KAAK,OAAO,EAA0B,MAAM,gBAAgB,CAAC;AAC9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAoBjC;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,YAAY,EAAE,oBAAoB,EAAE,GACnC,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,OAAO,CAY3H"}
|