@intentius/chant-lexicon-gcp 0.124.35 → 0.124.37
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/api/read-client.d.ts +2 -1
- package/dist/api/read-client.d.ts.map +1 -1
- package/dist/api/read-client.js +2 -1
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/deep-observe.js +56 -11
- package/dist/describe-resources.d.ts +20 -6
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/describe-resources.js +40 -26
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +6 -9
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/op/activities/gcp-apply.d.ts +14 -1
- package/dist/op/activities/gcp-apply.d.ts.map +1 -1
- package/dist/op/activities/gcp-apply.js +49 -8
- package/dist/op/activities/gcp-compute.d.ts +10 -0
- package/dist/op/activities/gcp-compute.d.ts.map +1 -1
- package/dist/op/activities/gcp-compute.js +39 -3
- package/dist/op/activities/gcp-iam.d.ts.map +1 -1
- package/dist/op/activities/gcp-iam.js +2 -0
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +72 -16
- package/package.json +2 -2
- package/src/api/read-client.ts +2 -1
- package/src/deep-observe.test.ts +27 -1
- package/src/deep-observe.ts +52 -12
- package/src/describe-resources.test.ts +6 -5
- package/src/describe-resources.ts +43 -24
- package/src/health.ts +7 -10
- package/src/op/activities/gcp-apply.test.ts +48 -3
- package/src/op/activities/gcp-apply.ts +49 -9
- package/src/op/activities/gcp-compute.test.ts +94 -0
- package/src/op/activities/gcp-compute.ts +37 -2
- package/src/op/activities/gcp-iam.ts +2 -0
- package/src/project-config.test.ts +119 -0
- package/src/read-refs.test.ts +135 -0
- package/src/serializer.test.ts +26 -0
- package/src/serializer.ts +73 -15
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compute Engine mappers for the Cloud Run load balancer (#4233): ComputeAddress,
|
|
3
3
|
* ComputeRegionNetworkEndpointGroup (serverless NEGs), ComputeBackendService,
|
|
4
|
-
* ComputeURLMap, ComputeTargetHTTPProxy and ComputeForwardingRule
|
|
4
|
+
* ComputeURLMap, ComputeTargetHTTPProxy and ComputeForwardingRule; and for a
|
|
5
|
+
* declared network (#4248): ComputeNetwork (always global) and
|
|
6
|
+
* ComputeSubnetwork (regional, by its CNRM `region`).
|
|
5
7
|
*
|
|
6
8
|
* Each maps a CNRM spec onto the compute v1 insert body. A resource is global
|
|
7
9
|
* or regional by its CNRM `location` (`"global"` or a region; a NEG has
|
|
@@ -158,6 +160,38 @@ export function negBody(resource, ctx) {
|
|
|
158
160
|
body.subnetwork = refUrl("subnetworkRef", spec.subnetworkRef, ctx, scope);
|
|
159
161
|
return body;
|
|
160
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* ComputeNetwork → networks.insert body. A network is global. CNRM's
|
|
165
|
+
* `autoCreateSubnetworks` defaults to false, where compute's default is true,
|
|
166
|
+
* so it is always sent. Pure.
|
|
167
|
+
*/
|
|
168
|
+
export function networkBody(resource) {
|
|
169
|
+
const spec = (resource.spec ?? {});
|
|
170
|
+
const body = {
|
|
171
|
+
name: computeName(resource),
|
|
172
|
+
autoCreateSubnetworks: spec.autoCreateSubnetworks === true,
|
|
173
|
+
...pick(spec, ["description", "mtu", "networkFirewallPolicyEnforcementOrder"]),
|
|
174
|
+
};
|
|
175
|
+
if (typeof spec.routingMode === "string")
|
|
176
|
+
body.routingConfig = { routingMode: spec.routingMode };
|
|
177
|
+
return body;
|
|
178
|
+
}
|
|
179
|
+
/** ComputeSubnetwork → subnetworks.insert body. CNRM's `secondaryIpRange` and `logConfig` become compute's `secondaryIpRanges` and an enabled `logConfig`. Pure. */
|
|
180
|
+
export function subnetworkBody(resource, ctx) {
|
|
181
|
+
const spec = (resource.spec ?? {});
|
|
182
|
+
const scope = scopeOf(spec);
|
|
183
|
+
const body = {
|
|
184
|
+
name: computeName(resource),
|
|
185
|
+
...pick(spec, ["ipCidrRange", "description", "privateIpGoogleAccess", "purpose", "role", "stackType"]),
|
|
186
|
+
};
|
|
187
|
+
if (isObj(spec.networkRef))
|
|
188
|
+
body.network = refUrl("networkRef", spec.networkRef, ctx, scope);
|
|
189
|
+
if (Array.isArray(spec.secondaryIpRange))
|
|
190
|
+
body.secondaryIpRanges = spec.secondaryIpRange;
|
|
191
|
+
if (isObj(spec.logConfig))
|
|
192
|
+
body.logConfig = { enable: true, ...spec.logConfig };
|
|
193
|
+
return body;
|
|
194
|
+
}
|
|
161
195
|
/** ComputeBackendService → backendServices.insert body. Pure. */
|
|
162
196
|
export function backendServiceBody(resource, ctx) {
|
|
163
197
|
const spec = (resource.spec ?? {});
|
|
@@ -292,13 +326,13 @@ export const computeOperation = {
|
|
|
292
326
|
return errors?.length ? errors.map((e) => e.message ?? e.code ?? "error").join("; ") : undefined;
|
|
293
327
|
},
|
|
294
328
|
};
|
|
295
|
-
function computeMapper(kind, collection, build, regionalCollection = collection) {
|
|
329
|
+
function computeMapper(kind, collection, build, regionalCollection = collection, scopeFor = (spec) => scopeOf(spec)) {
|
|
296
330
|
return {
|
|
297
331
|
kind,
|
|
298
332
|
defaultHost: COMPUTE_HOST,
|
|
299
333
|
operation: computeOperation,
|
|
300
334
|
plan(resource, ctx) {
|
|
301
|
-
const scope =
|
|
335
|
+
const scope = scopeFor(resource.spec);
|
|
302
336
|
const coll = `${ctx.base}/compute/v1/${scopePath(ctx.project, scope)}/${scope === "global" ? collection : regionalCollection}`;
|
|
303
337
|
const name = computeName(resource);
|
|
304
338
|
return { getUrl: `${coll}/${enc(name)}`, create: { method: "POST", url: coll, body: build(resource, ctx) } };
|
|
@@ -311,3 +345,5 @@ export const backendServiceMapper = computeMapper("ComputeBackendService", "back
|
|
|
311
345
|
export const urlMapMapper = computeMapper("ComputeURLMap", "urlMaps", urlMapBody);
|
|
312
346
|
export const targetHttpProxyMapper = computeMapper("ComputeTargetHTTPProxy", "targetHttpProxies", targetHttpProxyBody);
|
|
313
347
|
export const forwardingRuleMapper = computeMapper("ComputeForwardingRule", "forwardingRules", forwardingRuleBody);
|
|
348
|
+
export const networkMapper = computeMapper("ComputeNetwork", "networks", networkBody, "networks", () => "global");
|
|
349
|
+
export const subnetworkMapper = computeMapper("ComputeSubnetwork", "subnetworks", subnetworkBody);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcp-iam.d.ts","sourceRoot":"","sources":["../../../src/op/activities/gcp-iam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAW,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAExE,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AAED,UAAU,MAAM;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/E,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK;QAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CAC/F;AAED,gHAAgH;AAChH,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAqB7D;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,SAAS,WAAW,EAAO,EACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,SAAS,GAAG,SAAS,CAgDvB;AAYD,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAElF;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAY5E;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAS/E;AAkED,eAAO,MAAM,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"gcp-iam.d.ts","sourceRoot":"","sources":["../../../src/op/activities/gcp-iam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAW,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAExE,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AAED,UAAU,MAAM;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/E,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK;QAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CAC/F;AAED,gHAAgH;AAChH,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAqB7D;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,SAAS,WAAW,EAAO,EACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,SAAS,GAAG,SAAS,CAgDvB;AAYD,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAElF;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAY5E;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAS/E;AAkED,eAAO,MAAM,qBAAqB,EAAE,cAgDnC,CAAC"}
|
|
@@ -202,6 +202,8 @@ export const iamPolicyMemberMapper = {
|
|
|
202
202
|
status: 200,
|
|
203
203
|
// `resourceRef` is the declared one: the policy read is that target's, so the binding is on it.
|
|
204
204
|
text: JSON.stringify({
|
|
205
|
+
// The binding has no id of its own; this one names it, so a read reports a physical id (#4251).
|
|
206
|
+
selfLink: `${target.path}#${spec.role}#${spec.member}`,
|
|
205
207
|
member: spec.member,
|
|
206
208
|
role: spec.role,
|
|
207
209
|
resource: target.path,
|
package/dist/serializer.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Converts Chant declarables to multi-document K8s YAML output
|
|
5
5
|
* for Config Connector resources with apiVersion, kind, metadata, and spec.
|
|
6
6
|
*/
|
|
7
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
7
8
|
import type { Serializer } from "@intentius/chant/serializer";
|
|
8
9
|
/**
|
|
9
10
|
* Resolve entityType to apiVersion and kind.
|
|
@@ -17,8 +18,25 @@ export declare function resolveGVK(entityType: string): {
|
|
|
17
18
|
apiVersion: string;
|
|
18
19
|
kind: string;
|
|
19
20
|
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* The metadata.name a resource is written with: a string `metadata.name` in its
|
|
23
|
+
* props, else its logical id in kebab case.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cnrmName(logical: string, entity: Declarable): string;
|
|
26
|
+
/** {@link cnrmName} from a resource's props, for callers holding props rather than the declarable. */
|
|
27
|
+
export declare function cnrmNameOf(logical: string, props: unknown): string;
|
|
28
|
+
/**
|
|
29
|
+
* Declared props with each reference to another resource replaced as the
|
|
30
|
+
* serializer writes it (#4251): an attribute reference (`neg.name`) becomes the
|
|
31
|
+
* target's metadata.name, and a resource placed whole becomes `{ name }`.
|
|
32
|
+
* `logicalOf` maps a target's props object to its logical id, for a reference
|
|
33
|
+
* that does not carry one. Other values, pseudo-parameters included, are kept.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveCnrmRefs(value: unknown, logicalOf: (props: unknown) => string | undefined): unknown;
|
|
20
36
|
/**
|
|
21
37
|
* GCP Config Connector YAML serializer implementation.
|
|
22
38
|
*/
|
|
23
39
|
export declare const gcpSerializer: Serializer;
|
|
40
|
+
/** The `gcp.project` a chant.config declares, if any (#4252). */
|
|
41
|
+
export declare function gcpConfigProject(config: Record<string, unknown> | undefined): string | undefined;
|
|
24
42
|
//# sourceMappingURL=serializer.d.ts.map
|
package/dist/serializer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAiClG;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAW1F;AA+CD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAEpE;AAED,sGAAsG;AACtG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAGlE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAkB1G;AA4BD;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAsG3B,CAAC;AAIF,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGhG"}
|
package/dist/serializer.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
9
|
-
import { isPropertyDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
|
|
9
|
+
import { isDeclarable, isPropertyDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
|
|
10
|
+
import { AttrRef } from "@intentius/chant/attrref";
|
|
10
11
|
import { ownershipEntries, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
|
|
11
12
|
import { walkValue } from "@intentius/chant/serializer-walker";
|
|
12
13
|
import { emitYAML } from "@intentius/chant/yaml";
|
|
@@ -62,15 +63,15 @@ function deriveGVKFromType(entityType) {
|
|
|
62
63
|
* GCP visitor for the generic serializer walker.
|
|
63
64
|
*/
|
|
64
65
|
function gcpVisitor(entityNames) {
|
|
66
|
+
// Config Connector resolves a `*Ref: { name }` by the target's metadata.name,
|
|
67
|
+
// which is its declared name or else the kebab-cased logical id (#4242).
|
|
68
|
+
const cnrmNames = new Map();
|
|
69
|
+
for (const [entity, logical] of entityNames)
|
|
70
|
+
cnrmNames.set(logical, cnrmName(logical, entity));
|
|
71
|
+
const resolve = (logical) => cnrmNames.get(logical) ?? logical;
|
|
65
72
|
return {
|
|
66
|
-
attrRef: (name, _attr) =>
|
|
67
|
-
|
|
68
|
-
return name;
|
|
69
|
-
},
|
|
70
|
-
resourceRef: (name) => {
|
|
71
|
-
// Config Connector uses resourceRef pattern: { name: "resource-name" }
|
|
72
|
-
return { name };
|
|
73
|
-
},
|
|
73
|
+
attrRef: (name, _attr) => resolve(name),
|
|
74
|
+
resourceRef: (name) => ({ name: resolve(name) }),
|
|
74
75
|
propertyDeclarable: (entity, walk) => {
|
|
75
76
|
if (!isResourceDeclarable(entity) || typeof entity.props !== "object" || entity.props === null) {
|
|
76
77
|
return undefined;
|
|
@@ -86,6 +87,51 @@ function gcpVisitor(entityNames) {
|
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The metadata.name a resource is written with: a string `metadata.name` in its
|
|
92
|
+
* props, else its logical id in kebab case.
|
|
93
|
+
*/
|
|
94
|
+
export function cnrmName(logical, entity) {
|
|
95
|
+
return cnrmNameOf(logical, isResourceDeclarable(entity) ? entity.props : undefined);
|
|
96
|
+
}
|
|
97
|
+
/** {@link cnrmName} from a resource's props, for callers holding props rather than the declarable. */
|
|
98
|
+
export function cnrmNameOf(logical, props) {
|
|
99
|
+
const declared = props?.metadata?.name;
|
|
100
|
+
return typeof declared === "string" && declared.length > 0 ? declared : kebabName(logical);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Declared props with each reference to another resource replaced as the
|
|
104
|
+
* serializer writes it (#4251): an attribute reference (`neg.name`) becomes the
|
|
105
|
+
* target's metadata.name, and a resource placed whole becomes `{ name }`.
|
|
106
|
+
* `logicalOf` maps a target's props object to its logical id, for a reference
|
|
107
|
+
* that does not carry one. Other values, pseudo-parameters included, are kept.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveCnrmRefs(value, logicalOf) {
|
|
110
|
+
if (value instanceof AttrRef) {
|
|
111
|
+
const parent = value.parent.deref();
|
|
112
|
+
const props = parent && isResourceDeclarable(parent) ? parent.props : undefined;
|
|
113
|
+
const logical = value.getLogicalName() ?? logicalOf(props);
|
|
114
|
+
return logical === undefined ? value : cnrmNameOf(logical, props);
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(value))
|
|
117
|
+
return value.map((v) => resolveCnrmRefs(v, logicalOf));
|
|
118
|
+
if (value === null || typeof value !== "object")
|
|
119
|
+
return value;
|
|
120
|
+
if (isDeclarable(value)) {
|
|
121
|
+
const props = isResourceDeclarable(value) ? value.props : undefined;
|
|
122
|
+
const logical = logicalOf(props);
|
|
123
|
+
return logical === undefined ? value : { name: cnrmNameOf(logical, props) };
|
|
124
|
+
}
|
|
125
|
+
if (INTRINSIC_MARKER in value)
|
|
126
|
+
return value;
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const [k, v] of Object.entries(value))
|
|
129
|
+
out[k] = resolveCnrmRefs(v, logicalOf);
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function kebabName(logical) {
|
|
133
|
+
return logical.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
134
|
+
}
|
|
89
135
|
/**
|
|
90
136
|
* Convert a value to YAML-compatible form using the walker.
|
|
91
137
|
*/
|
|
@@ -121,7 +167,11 @@ export const gcpSerializer = {
|
|
|
121
167
|
let defaultLabelEntries = context?.ownership
|
|
122
168
|
? { ...ownershipEntries(LABEL_OWNERSHIP_KEYS, context.ownership) }
|
|
123
169
|
: {};
|
|
124
|
-
|
|
170
|
+
// #4252: a member's project, once, in chant.config's `gcp.project`. It
|
|
171
|
+
// resolves GCP.ProjectId and is written as each resource's project-id
|
|
172
|
+
// annotation, which apply and the readers take ahead of the env vars.
|
|
173
|
+
const configProject = gcpConfigProject(context?.config);
|
|
174
|
+
let defaultAnnotationEntries = configProject ? { [PROJECT_ID_ANNOTATION]: configProject } : {};
|
|
125
175
|
for (const [, entity] of entities) {
|
|
126
176
|
if (isDefaultLabels(entity)) {
|
|
127
177
|
defaultLabelEntries = { ...defaultLabelEntries, ...entity.labels };
|
|
@@ -141,7 +191,7 @@ export const gcpSerializer = {
|
|
|
141
191
|
const gvk = resolveGVK(entityType);
|
|
142
192
|
if (!gvk)
|
|
143
193
|
continue;
|
|
144
|
-
const props = resolvePseudoParameters(toYAMLValue(isResourceDeclarable(entity) ? entity.props : undefined, entityNames), name, unresolved);
|
|
194
|
+
const props = resolvePseudoParameters(toYAMLValue(isResourceDeclarable(entity) ? entity.props : undefined, entityNames), name, unresolved, configProject);
|
|
145
195
|
if (!props)
|
|
146
196
|
continue;
|
|
147
197
|
// Build the Config Connector manifest structure
|
|
@@ -152,7 +202,7 @@ export const gcpSerializer = {
|
|
|
152
202
|
// Build metadata
|
|
153
203
|
const metadata = props.metadata ?? {};
|
|
154
204
|
if (!metadata.name) {
|
|
155
|
-
metadata.name = name
|
|
205
|
+
metadata.name = kebabName(name);
|
|
156
206
|
}
|
|
157
207
|
// Merge default labels
|
|
158
208
|
if (Object.keys(defaultLabelEntries).length > 0) {
|
|
@@ -189,6 +239,12 @@ export const gcpSerializer = {
|
|
|
189
239
|
return warnings.length > 0 ? { primary, warnings } : primary;
|
|
190
240
|
},
|
|
191
241
|
};
|
|
242
|
+
const PROJECT_ID_ANNOTATION = "cnrm.cloud.google.com/project-id";
|
|
243
|
+
/** The `gcp.project` a chant.config declares, if any (#4252). */
|
|
244
|
+
export function gcpConfigProject(config) {
|
|
245
|
+
const project = config?.gcp?.project;
|
|
246
|
+
return typeof project === "string" && project.length > 0 ? project : undefined;
|
|
247
|
+
}
|
|
192
248
|
/**
|
|
193
249
|
* Pseudo-parameter → environment variable mapping.
|
|
194
250
|
* Config Connector annotations must be plain strings, so PseudoParameters
|
|
@@ -206,16 +262,16 @@ const PSEUDO_ENV_MAP = {
|
|
|
206
262
|
* the API as an object. A variable that isn't set is recorded, and the
|
|
207
263
|
* build refuses with every one named.
|
|
208
264
|
*/
|
|
209
|
-
function resolvePseudoParameters(value, entity, unresolved) {
|
|
265
|
+
function resolvePseudoParameters(value, entity, unresolved, project) {
|
|
210
266
|
if (Array.isArray(value))
|
|
211
|
-
return value.map((v) => resolvePseudoParameters(v, entity, unresolved));
|
|
267
|
+
return value.map((v) => resolvePseudoParameters(v, entity, unresolved, project));
|
|
212
268
|
if (value === null || typeof value !== "object")
|
|
213
269
|
return value;
|
|
214
270
|
const obj = value;
|
|
215
271
|
const keys = Object.keys(obj);
|
|
216
272
|
if (keys.length === 1 && keys[0] === "Ref" && typeof obj.Ref === "string" && obj.Ref in PSEUDO_ENV_MAP) {
|
|
217
273
|
const { envVar } = PSEUDO_ENV_MAP[obj.Ref];
|
|
218
|
-
const resolved = process.env[envVar];
|
|
274
|
+
const resolved = (obj.Ref === "GCP::ProjectId" ? project : undefined) ?? process.env[envVar];
|
|
219
275
|
if (resolved)
|
|
220
276
|
return resolved;
|
|
221
277
|
unresolved.push({ entity, ref: obj.Ref, envVar });
|
|
@@ -223,7 +279,7 @@ function resolvePseudoParameters(value, entity, unresolved) {
|
|
|
223
279
|
}
|
|
224
280
|
const out = {};
|
|
225
281
|
for (const [k, v] of Object.entries(obj))
|
|
226
|
-
out[k] = resolvePseudoParameters(v, entity, unresolved);
|
|
282
|
+
out[k] = resolvePseudoParameters(v, entity, unresolved, project);
|
|
227
283
|
return out;
|
|
228
284
|
}
|
|
229
285
|
function unresolvedMessage(unresolved) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gcp",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.37",
|
|
4
4
|
"description": "Google Cloud lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -76,6 +76,6 @@
|
|
|
76
76
|
"typescript": "^5.9.3"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
|
-
"@intentius/chant": "^0.124.
|
|
79
|
+
"@intentius/chant": "^0.124.37"
|
|
80
80
|
}
|
|
81
81
|
}
|
package/src/api/read-client.ts
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* PubSubTopic, PubSubSubscription, SecretManagerSecret, IAMServiceAccount,
|
|
25
25
|
* RunService, IAMPolicyMember and the Cloud Run load balancer kinds
|
|
26
26
|
* (ComputeAddress, ComputeRegionNetworkEndpointGroup, ComputeBackendService,
|
|
27
|
-
* ComputeURLMap, ComputeTargetHTTPProxy, ComputeForwardingRule; #4233)
|
|
27
|
+
* ComputeURLMap, ComputeTargetHTTPProxy, ComputeForwardingRule; #4233), and
|
|
28
|
+
* ComputeNetwork and ComputeSubnetwork (#4248). An
|
|
28
29
|
* IAMPolicyMember is read through its target's policy and is present when the
|
|
29
30
|
* policy grants its member the role. The kubectl path could fetch any CNRM kind the cluster knew
|
|
30
31
|
* about, so this is narrower on paper. It is not narrower in practice for
|
package/src/deep-observe.test.ts
CHANGED
|
@@ -139,6 +139,32 @@ describe("toCnrmTree — each REST shape in the declared vocabulary (#1210)", ()
|
|
|
139
139
|
expect(other.template).toEqual({ serviceAccountRef: { external: "x@other.iam.gserviceaccount.com" } });
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
test("run service: vpcAccess network and subnetwork paths map back to refs (#4249)", () => {
|
|
143
|
+
const tree = toCnrmTree(
|
|
144
|
+
"RunService",
|
|
145
|
+
{
|
|
146
|
+
name: "projects/my-project/locations/europe-west1/services/web",
|
|
147
|
+
template: {
|
|
148
|
+
vpcAccess: {
|
|
149
|
+
egress: "PRIVATE_RANGES_ONLY",
|
|
150
|
+
networkInterfaces: [
|
|
151
|
+
{ network: "projects/my-project/global/networks/vpc", subnetwork: "projects/my-project/regions/europe-west1/subnetworks/apps" },
|
|
152
|
+
{ network: "projects/other/global/networks/shared", subnetwork: "apps" },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
ctx,
|
|
158
|
+
) as { template: { vpcAccess: unknown } };
|
|
159
|
+
expect(tree.template.vpcAccess).toEqual({
|
|
160
|
+
egress: "PRIVATE_RANGES_ONLY",
|
|
161
|
+
networkInterfaces: [
|
|
162
|
+
{ networkRef: { name: "vpc" }, subnetworkRef: { name: "apps" } },
|
|
163
|
+
{ networkRef: { external: "projects/other/global/networks/shared" }, subnetworkRef: { name: "apps" } },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
142
168
|
test("bucket: GCS's iamConfiguration and lifecycle map onto the CNRM field names", () => {
|
|
143
169
|
const tree = toCnrmTree(
|
|
144
170
|
"StorageBucket",
|
|
@@ -250,7 +276,7 @@ describe("observeResourcesDeepGcp — over REST (#1209)", () => {
|
|
|
250
276
|
});
|
|
251
277
|
|
|
252
278
|
test("a kind the applier cannot write is unsupported-kind, and is never queried", async () => {
|
|
253
|
-
const out = await observe(entities([["e", "GCP::Compute::
|
|
279
|
+
const out = await observe(entities([["e", "GCP::Compute::Firewall", { metadata: { name: "n" } }]]));
|
|
254
280
|
expect(out.unobserved?.e?.reason).toBe("unsupported-kind");
|
|
255
281
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
256
282
|
});
|
package/src/deep-observe.ts
CHANGED
|
@@ -61,7 +61,7 @@ import type {
|
|
|
61
61
|
} from "@intentius/chant/lexicon";
|
|
62
62
|
import { deepObservation, normalizeDeepProperties } from "@intentius/chant/deep-observation";
|
|
63
63
|
import { hasOwnershipMarker, type ChannelKeys } from "@intentius/chant/ownership";
|
|
64
|
-
import { deriveGVK, manifestProjectAnnotations, resolveReadProject } from "./describe-resources";
|
|
64
|
+
import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources";
|
|
65
65
|
import { getResource, mapperForKind, GcpReadError, isNotFound, type GcpReadClientOptions } from "./api/read-client";
|
|
66
66
|
import { gcpDeepNormalizationHooks } from "./deep-observe-hooks";
|
|
67
67
|
|
|
@@ -191,6 +191,24 @@ const CNRM_REFINERS: Record<string, (tree: CnrmTree, ctx: RefineContext) => void
|
|
|
191
191
|
template.serviceAccountRef = email.endsWith(suffix) ? { name: email.slice(0, -suffix.length) } : { external: email };
|
|
192
192
|
delete template.serviceAccount;
|
|
193
193
|
}
|
|
194
|
+
// #4249: Direct VPC egress paths back to CNRM's refs, `name` for the
|
|
195
|
+
// project's own network and subnetwork, `external` otherwise.
|
|
196
|
+
const vpcAccess = template?.vpcAccess as { networkInterfaces?: unknown } | undefined;
|
|
197
|
+
if (vpcAccess && Array.isArray(vpcAccess.networkInterfaces)) {
|
|
198
|
+
const own = (value: string, pattern: RegExp): { name: string } | { external: string } => {
|
|
199
|
+
if (!value.includes("/")) return { name: value };
|
|
200
|
+
const hit = pattern.exec(value);
|
|
201
|
+
return hit && hit[1] === ctx.project ? { name: hit[2] } : { external: value };
|
|
202
|
+
};
|
|
203
|
+
vpcAccess.networkInterfaces = (vpcAccess.networkInterfaces as Array<Record<string, unknown>>).map((i) => {
|
|
204
|
+
const { network, subnetwork, ...rest } = i;
|
|
205
|
+
return {
|
|
206
|
+
...rest,
|
|
207
|
+
...(typeof network === "string" ? { networkRef: own(network, /^projects\/([^/]+)\/global\/networks\/([^/]+)$/) } : {}),
|
|
208
|
+
...(typeof subnetwork === "string" ? { subnetworkRef: own(subnetwork, /^projects\/([^/]+)\/regions\/[^/]+\/subnetworks\/([^/]+)$/) } : {}),
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
194
212
|
},
|
|
195
213
|
// #4233: the inverse of gcp-compute.ts's body builders. Compute returns refs
|
|
196
214
|
// as full URLs and a region as a URL; CNRM writes `*Ref: { name }` and a
|
|
@@ -269,6 +287,35 @@ const CNRM_REFINERS: Record<string, (tree: CnrmTree, ctx: RefineContext) => void
|
|
|
269
287
|
delete tree.urlMap;
|
|
270
288
|
}
|
|
271
289
|
},
|
|
290
|
+
// #4248: a network is global and has no location; compute nests the routing
|
|
291
|
+
// mode and lists the subnetworks it holds, which CNRM declares separately.
|
|
292
|
+
ComputeNetwork(tree) {
|
|
293
|
+
const routing = tree.routingConfig as { routingMode?: unknown } | undefined;
|
|
294
|
+
if (routing) {
|
|
295
|
+
if (routing.routingMode !== undefined) tree.routingMode = routing.routingMode;
|
|
296
|
+
delete tree.routingConfig;
|
|
297
|
+
}
|
|
298
|
+
delete tree.subnetworks;
|
|
299
|
+
delete tree.gatewayIPv4;
|
|
300
|
+
},
|
|
301
|
+
ComputeSubnetwork(tree) {
|
|
302
|
+
if (typeof tree.region === "string") tree.region = lastSegment(tree.region);
|
|
303
|
+
if (typeof tree.network === "string") {
|
|
304
|
+
tree.networkRef = { name: lastSegment(tree.network) };
|
|
305
|
+
delete tree.network;
|
|
306
|
+
}
|
|
307
|
+
if (Array.isArray(tree.secondaryIpRanges)) {
|
|
308
|
+
tree.secondaryIpRange = tree.secondaryIpRanges;
|
|
309
|
+
delete tree.secondaryIpRanges;
|
|
310
|
+
}
|
|
311
|
+
const log = tree.logConfig as { enable?: unknown } | undefined;
|
|
312
|
+
if (log) {
|
|
313
|
+
if (log.enable === false) delete tree.logConfig;
|
|
314
|
+
else delete log.enable;
|
|
315
|
+
}
|
|
316
|
+
delete tree.gatewayAddress;
|
|
317
|
+
delete tree.fingerprint;
|
|
318
|
+
},
|
|
272
319
|
ComputeForwardingRule(tree) {
|
|
273
320
|
computeLocation(tree);
|
|
274
321
|
if (tree.IPProtocol !== undefined) {
|
|
@@ -340,8 +387,9 @@ export async function observeResourcesDeepGcp(options: GcpDeepObserveOptions): P
|
|
|
340
387
|
|
|
341
388
|
const endpoint = process.env.GCP_ENDPOINT_URL;
|
|
342
389
|
const manifestProjects = manifestProjectAnnotations(options.buildOutput);
|
|
390
|
+
const targets = readTargets(options.entities);
|
|
343
391
|
|
|
344
|
-
const reads = [...options.entities].map(async ([entityName, { entityType
|
|
392
|
+
const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
|
|
345
393
|
const gvk = deriveGVK(entityType);
|
|
346
394
|
if (!gvk || !mapperForKind(gvk.kind)) {
|
|
347
395
|
unobserved[entityName] = {
|
|
@@ -354,16 +402,8 @@ export async function observeResourcesDeepGcp(options: GcpDeepObserveOptions): P
|
|
|
354
402
|
return;
|
|
355
403
|
}
|
|
356
404
|
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
if (!name) {
|
|
360
|
-
unobserved[entityName] = {
|
|
361
|
-
type: entityType,
|
|
362
|
-
reason: "read-failed",
|
|
363
|
-
detail: "declared entity has no metadata.name to query by",
|
|
364
|
-
};
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
405
|
+
const { name, props } = targets.get(entityName)!;
|
|
406
|
+
const metadata = props.metadata as { annotations?: Record<string, string> } | undefined;
|
|
367
407
|
|
|
368
408
|
let client: GcpReadClientOptions;
|
|
369
409
|
try {
|
|
@@ -80,16 +80,17 @@ describe("gcp describeResources — direct REST (#1209)", () => {
|
|
|
80
80
|
|
|
81
81
|
test("a kind the applier has no mapper for is unsupported-kind, not absent", async () => {
|
|
82
82
|
const out = await read(
|
|
83
|
-
makeEntities([{ name: "e", entityType: "GCP::Compute::
|
|
83
|
+
makeEntities([{ name: "e", entityType: "GCP::Compute::Firewall", props: { metadata: { name: "net" } } }]),
|
|
84
84
|
);
|
|
85
85
|
expect(out.unobserved?.e?.reason).toBe("unsupported-kind");
|
|
86
86
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
test("an entity with no metadata.name
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect(
|
|
89
|
+
test("an entity with no metadata.name is read by the name the build gives it, the kebab-cased logical id (#4251)", async () => {
|
|
90
|
+
fetchMock.mockResolvedValue(reply(200, { name: "web-assets" }));
|
|
91
|
+
const out = await read(makeEntities([{ name: "webAssets", entityType: "GCP::Storage::Bucket", props: {} }]));
|
|
92
|
+
expect(out.resources.webAssets).toBeDefined();
|
|
93
|
+
expect(String(fetchMock.mock.calls[0][0])).toContain("/b/web-assets");
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
test("kinds resolve through the serializer's GVK map, not naive recasing", async () => {
|
|
@@ -40,8 +40,8 @@ import type { ObservationResult, ResourceMetadata, UnobservedEntity } from "@int
|
|
|
40
40
|
import { observation } from "@intentius/chant/observation";
|
|
41
41
|
import { hasOwnershipMarker, classifyOwnership, readOwnership, type ChannelKeys } from "@intentius/chant/ownership";
|
|
42
42
|
import { getResource, mapperForKind, GcpReadError, isNotFound, type GcpReadClientOptions } from "./api/read-client";
|
|
43
|
-
import {
|
|
44
|
-
import { resolveGVK } from "./serializer";
|
|
43
|
+
import { NO_PROJECT, parseManifest, projectRefProject } from "./op/activities/gcp-apply";
|
|
44
|
+
import { cnrmNameOf, resolveCnrmRefs, resolveGVK } from "./serializer";
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* chant's ownership marker as GCP labels.
|
|
@@ -119,11 +119,12 @@ export function manifestProjectAnnotations(buildOutput: string | undefined): Map
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
* The project a read should target: the entity's
|
|
123
|
-
* `
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
122
|
+
* The project a read should target, in apply's order (#4252): the entity's
|
|
123
|
+
* own literal `projectRef.external`, then the built manifest's project for it
|
|
124
|
+
* (its resolved projectRef, or the project-id annotation chant.config's
|
|
125
|
+
* `gcp.project` writes), then the entity's own annotation, then
|
|
126
|
+
* `GCP_PROJECT_ID`, then `GOOGLE_CLOUD_PROJECT`. Throws apply's own error when
|
|
127
|
+
* nothing resolves, so the `no-binding` detail names every knob.
|
|
127
128
|
*/
|
|
128
129
|
export function resolveReadProject(
|
|
129
130
|
kind: string,
|
|
@@ -131,14 +132,39 @@ export function resolveReadProject(
|
|
|
131
132
|
metadata: { annotations?: Record<string, string> } | undefined,
|
|
132
133
|
manifestProjects: Map<string, string>,
|
|
133
134
|
props?: Record<string, unknown>,
|
|
135
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
134
136
|
): string {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const project =
|
|
138
|
+
projectRefProject(props, {}) ||
|
|
139
|
+
manifestProjects.get(`${kind}/${name}`) ||
|
|
140
|
+
metadata?.annotations?.["cnrm.cloud.google.com/project-id"] ||
|
|
141
|
+
projectRefProject(props, env) ||
|
|
142
|
+
env.GCP_PROJECT_ID ||
|
|
143
|
+
env.GOOGLE_CLOUD_PROJECT;
|
|
144
|
+
if (!project) throw new Error(NO_PROJECT);
|
|
145
|
+
return project;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Each declared entity as a read sees it (#4251): the name the build writes
|
|
150
|
+
* (`metadata.name`, else the kebab-cased logical id) and its props with every
|
|
151
|
+
* reference to another resource resolved as the serializer resolves it, so a
|
|
152
|
+
* mapper building a read URL never meets an attribute reference.
|
|
153
|
+
*/
|
|
154
|
+
export function readTargets(
|
|
155
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>,
|
|
156
|
+
): Map<string, { name: string; props: Record<string, unknown> }> {
|
|
157
|
+
const logicalByProps = new Map<unknown, string>();
|
|
158
|
+
for (const [logical, { props }] of entities) logicalByProps.set(props, logical);
|
|
159
|
+
const logicalOf = (props: unknown) => logicalByProps.get(props);
|
|
160
|
+
const out = new Map<string, { name: string; props: Record<string, unknown> }>();
|
|
161
|
+
for (const [logical, { props }] of entities) {
|
|
162
|
+
out.set(logical, {
|
|
163
|
+
name: cnrmNameOf(logical, props),
|
|
164
|
+
props: resolveCnrmRefs(props, logicalOf) as Record<string, unknown>,
|
|
165
|
+
});
|
|
141
166
|
}
|
|
167
|
+
return out;
|
|
142
168
|
}
|
|
143
169
|
|
|
144
170
|
function pruneUndefined<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
|
|
@@ -196,8 +222,9 @@ export async function describeResources(options: {
|
|
|
196
222
|
let warnedOwnership = false;
|
|
197
223
|
|
|
198
224
|
const manifestProjects = manifestProjectAnnotations(options.buildOutput);
|
|
225
|
+
const targets = readTargets(options.entities);
|
|
199
226
|
|
|
200
|
-
const reads = [...options.entities].map(async ([entityName, { entityType
|
|
227
|
+
const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
|
|
201
228
|
const gvk = deriveGVK(entityType);
|
|
202
229
|
if (!gvk) {
|
|
203
230
|
unobserved[entityName] = {
|
|
@@ -218,16 +245,8 @@ export async function describeResources(options: {
|
|
|
218
245
|
return;
|
|
219
246
|
}
|
|
220
247
|
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
if (!name) {
|
|
224
|
-
unobserved[entityName] = {
|
|
225
|
-
type: entityType,
|
|
226
|
-
reason: "read-failed",
|
|
227
|
-
detail: "declared entity has no metadata.name to query by",
|
|
228
|
-
};
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
248
|
+
const { name, props } = targets.get(entityName)!;
|
|
249
|
+
const metadata = props.metadata as { annotations?: Record<string, string> } | undefined;
|
|
231
250
|
|
|
232
251
|
let client: GcpReadClientOptions;
|
|
233
252
|
try {
|
package/src/health.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { HealthEntry, HealthReadOptions, HealthReadResult, UnobservedEntity } from "@intentius/chant/lexicon";
|
|
13
13
|
import { getResource, GcpReadError, isNotFound, type GcpReadHttp } from "./api/read-client";
|
|
14
|
-
import { deriveGVK, manifestProjectAnnotations, resolveReadProject } from "./describe-resources";
|
|
14
|
+
import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources";
|
|
15
15
|
|
|
16
16
|
export const GCP_HEALTH_TYPES = ["GCP::Run::Service"] as const;
|
|
17
17
|
|
|
@@ -75,18 +75,15 @@ export async function observeHealthGcp(options: GcpHealthOptions): Promise<Healt
|
|
|
75
75
|
const entities: Record<string, HealthEntry> = {};
|
|
76
76
|
const unobserved: Record<string, UnobservedEntity> = {};
|
|
77
77
|
const absent: string[] = [];
|
|
78
|
+
const targets = readTargets(options.entities);
|
|
78
79
|
|
|
79
80
|
await Promise.all(
|
|
80
|
-
[...options.entities].map(async ([entityName, { entityType
|
|
81
|
+
[...options.entities].map(async ([entityName, { entityType }]) => {
|
|
81
82
|
const gvk = deriveGVK(entityType);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
if (!gvk
|
|
85
|
-
unobserved[entityName] = {
|
|
86
|
-
type: entityType,
|
|
87
|
-
reason: "read-failed",
|
|
88
|
-
detail: gvk ? "declared entity has no metadata.name to query by" : `cannot derive a GCP kind from ${entityType}`,
|
|
89
|
-
};
|
|
83
|
+
const { name, props } = targets.get(entityName)!;
|
|
84
|
+
const metadata = props.metadata as { annotations?: Record<string, string> } | undefined;
|
|
85
|
+
if (!gvk) {
|
|
86
|
+
unobserved[entityName] = { type: entityType, reason: "read-failed", detail: `cannot derive a GCP kind from ${entityType}` };
|
|
90
87
|
return;
|
|
91
88
|
}
|
|
92
89
|
let project: string;
|