@intentius/chant-lexicon-gcp 0.124.37 → 0.124.39

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.
Files changed (58) hide show
  1. package/dist/api/read-client.d.ts +14 -1
  2. package/dist/api/read-client.d.ts.map +1 -1
  3. package/dist/api/read-client.js +25 -10
  4. package/dist/composites/catalog.d.ts.map +1 -1
  5. package/dist/composites/catalog.js +187 -0
  6. package/dist/composites/index.d.ts +4 -0
  7. package/dist/composites/index.d.ts.map +1 -1
  8. package/dist/composites/index.js +2 -0
  9. package/dist/composites/private-egress-network.d.ts +42 -0
  10. package/dist/composites/private-egress-network.d.ts.map +1 -0
  11. package/dist/composites/private-egress-network.js +48 -0
  12. package/dist/composites/public-cloud-run.d.ts +85 -0
  13. package/dist/composites/public-cloud-run.d.ts.map +1 -0
  14. package/dist/composites/public-cloud-run.js +144 -0
  15. package/dist/deep-observe.d.ts +5 -0
  16. package/dist/deep-observe.d.ts.map +1 -1
  17. package/dist/deep-observe.js +37 -3
  18. package/dist/describe-resources.d.ts +13 -0
  19. package/dist/describe-resources.d.ts.map +1 -1
  20. package/dist/describe-resources.js +19 -0
  21. package/dist/health.d.ts +26 -2
  22. package/dist/health.d.ts.map +1 -1
  23. package/dist/health.js +85 -6
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/integrity.json +3 -3
  28. package/dist/manifest.json +1 -1
  29. package/dist/op/activities/gcp-apply.d.ts +19 -0
  30. package/dist/op/activities/gcp-apply.d.ts.map +1 -1
  31. package/dist/op/activities/gcp-apply.js +16 -0
  32. package/dist/op/activities/gcp-compute.d.ts +9 -0
  33. package/dist/op/activities/gcp-compute.d.ts.map +1 -1
  34. package/dist/op/activities/gcp-compute.js +107 -11
  35. package/dist/plugin.d.ts.map +1 -1
  36. package/dist/plugin.js +6 -2
  37. package/dist/skills/chant-gcp.md +2 -0
  38. package/package.json +2 -2
  39. package/src/api/read-client.ts +30 -9
  40. package/src/composites/catalog.ts +187 -0
  41. package/src/composites/cloud-run-lb.test.ts +93 -0
  42. package/src/composites/index.ts +4 -0
  43. package/src/composites/private-egress-network.ts +82 -0
  44. package/src/composites/public-cloud-run.ts +245 -0
  45. package/src/deep-observe.test.ts +46 -0
  46. package/src/deep-observe.ts +41 -3
  47. package/src/describe-resources.ts +23 -1
  48. package/src/health.test.ts +65 -1
  49. package/src/health.ts +88 -11
  50. package/src/index.ts +2 -1
  51. package/src/op/activities/gcp-apply.ts +31 -1
  52. package/src/op/activities/gcp-compute-reconcile.test.ts +150 -0
  53. package/src/op/activities/gcp-compute.test.ts +4 -2
  54. package/src/op/activities/gcp-compute.ts +129 -11
  55. package/src/plugin.ts +6 -2
  56. package/src/project-config.test.ts +50 -3
  57. package/src/read-refs.test.ts +60 -0
  58. package/src/skills/chant-gcp.md +2 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * PublicCloudRun composite: a Cloud Run service behind a global external
3
+ * Application Load Balancer, running as its own service account with Direct
4
+ * VPC egress into a subnet.
5
+ */
6
+ import { Composite, mergeDefaults } from "@intentius/chant";
7
+ import { BackendService, ComputeAddress, ForwardingRule, GCPServiceAccount, ManagedSSLCertificate, RegionNetworkEndpointGroup, TargetHTTPProxy, TargetHTTPSProxy, URLMap, } from "../generated/index.js";
8
+ import { GCP } from "../pseudo.js";
9
+ import { CloudRunServiceComposite } from "./cloud-run-service.js";
10
+ /**
11
+ * Create a PublicCloudRun composite.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { PrivateEgressNetwork, PublicCloudRun } from "@intentius/chant-lexicon-gcp";
16
+ *
17
+ * const network = PrivateEgressNetwork({ name: "web", region: "us-central1", subnetCidr: "10.10.0.0/24" });
18
+ * const web = PublicCloudRun({
19
+ * name: "web",
20
+ * projectId: "my-project",
21
+ * region: "us-central1",
22
+ * vpc: network.vpc,
23
+ * subnet: network.subnet,
24
+ * image: "us-docker.pkg.dev/my-project/web/site:1",
25
+ * });
26
+ * ```
27
+ */
28
+ export const PublicCloudRun = Composite((props) => {
29
+ const { name, image, region, projectId, vpc, subnet, vpcEgress = "PRIVATE_RANGES_ONLY", port = 8080, frontendPort = 80, minInstanceCount = 0, maxInstanceCount = 100, env, serviceAccount, domains, securityPolicy, labels: extraLabels = {}, namespace, defaults: defs, } = props;
30
+ const commonLabels = {
31
+ "app.kubernetes.io/name": name,
32
+ "app.kubernetes.io/managed-by": "chant",
33
+ ...extraLabels,
34
+ };
35
+ const meta = (suffix, component) => ({
36
+ name: `${name}-${suffix}`,
37
+ ...(namespace && { namespace }),
38
+ labels: { ...commonLabels, "app.kubernetes.io/component": component },
39
+ });
40
+ const result = {};
41
+ if (!serviceAccount) {
42
+ result.runtime = new GCPServiceAccount(mergeDefaults({
43
+ metadata: meta("run", "service-account"),
44
+ displayName: `${name} Cloud Run runtime`,
45
+ }, defs?.runtime));
46
+ }
47
+ const run = CloudRunServiceComposite({
48
+ name: `${name}-site`,
49
+ image,
50
+ projectId,
51
+ location: region,
52
+ port,
53
+ minInstanceCount,
54
+ maxInstanceCount,
55
+ env,
56
+ publicAccess: true,
57
+ serviceAccount: serviceAccount ?? `${name}-run`,
58
+ labels: extraLabels,
59
+ namespace,
60
+ defaults: {
61
+ service: mergeDefaults({
62
+ template: {
63
+ vpcAccess: {
64
+ egress: vpcEgress,
65
+ networkInterfaces: [{ networkRef: { name: vpc.name }, subnetworkRef: { name: subnet.name } }],
66
+ },
67
+ },
68
+ }, defs?.service),
69
+ publicIam: defs?.publicAccess,
70
+ },
71
+ });
72
+ result.service = run.service;
73
+ result.publicAccess = run.publicIam;
74
+ const neg = new RegionNetworkEndpointGroup(mergeDefaults({
75
+ metadata: meta("neg", "neg"),
76
+ region,
77
+ networkEndpointType: "SERVERLESS",
78
+ cloudRun: { serviceRef: { name: run.service.name } },
79
+ }, defs?.neg));
80
+ // A backend service over a serverless NEG takes no health check: Cloud Run
81
+ // reports its own readiness.
82
+ const backend = new BackendService(mergeDefaults({
83
+ metadata: meta("backend", "backend"),
84
+ location: "global",
85
+ loadBalancingScheme: "EXTERNAL_MANAGED",
86
+ protocol: "HTTP",
87
+ backend: [{ group: { networkEndpointGroupRef: { name: neg.name } } }],
88
+ ...(securityPolicy && { securityPolicyRef: { name: securityPolicy } }),
89
+ }, defs?.backend));
90
+ const urlMap = new URLMap(mergeDefaults({
91
+ metadata: meta("map", "url-map"),
92
+ location: "global",
93
+ defaultService: { backendServiceRef: { name: backend.name } },
94
+ }, defs?.urlMap));
95
+ const address = new ComputeAddress(mergeDefaults({
96
+ metadata: meta("ip", "address"),
97
+ location: "global",
98
+ }, defs?.address));
99
+ Object.assign(result, { neg, backend, urlMap, address });
100
+ // With domains, HTTPS serves the URL map and HTTP serves a redirect to it.
101
+ let httpMapName = urlMap.name;
102
+ if (domains && domains.length > 0) {
103
+ const certificate = new ManagedSSLCertificate(mergeDefaults({
104
+ metadata: meta("cert", "certificate"),
105
+ projectRef: { external: projectId ?? GCP.ProjectId },
106
+ managed: { domains },
107
+ }, defs?.certificate));
108
+ const httpsProxy = new TargetHTTPSProxy(mergeDefaults({
109
+ metadata: meta("https-proxy", "proxy"),
110
+ location: "global",
111
+ urlMapRef: { name: urlMap.name },
112
+ sslCertificates: [{ name: certificate.name }],
113
+ }, defs?.httpsProxy));
114
+ const httpsRule = new ForwardingRule(mergeDefaults({
115
+ metadata: meta("https-rule", "forwarding-rule"),
116
+ location: "global",
117
+ loadBalancingScheme: "EXTERNAL_MANAGED",
118
+ portRange: "443",
119
+ ipAddress: { addressRef: { name: address.name } },
120
+ target: { targetHTTPSProxyRef: { name: httpsProxy.name } },
121
+ }, defs?.httpsRule));
122
+ const redirectMap = new URLMap(mergeDefaults({
123
+ metadata: meta("redirect", "url-map"),
124
+ location: "global",
125
+ defaultUrlRedirect: { httpsRedirect: true, stripQuery: false },
126
+ }, defs?.redirectMap));
127
+ httpMapName = redirectMap.name;
128
+ Object.assign(result, { certificate, httpsProxy, httpsRule, redirectMap });
129
+ }
130
+ result.proxy = new TargetHTTPProxy(mergeDefaults({
131
+ metadata: meta("proxy", "proxy"),
132
+ location: "global",
133
+ urlMapRef: { name: httpMapName },
134
+ }, defs?.proxy));
135
+ result.rule = new ForwardingRule(mergeDefaults({
136
+ metadata: meta("rule", "forwarding-rule"),
137
+ location: "global",
138
+ loadBalancingScheme: "EXTERNAL_MANAGED",
139
+ portRange: String(frontendPort),
140
+ ipAddress: { addressRef: { name: address.name } },
141
+ target: { targetHTTPProxyRef: { name: result.proxy.name } },
142
+ }, defs?.rule));
143
+ return result;
144
+ }, "PublicCloudRun");
@@ -82,6 +82,11 @@ export declare function restToCnrmShape(body: Record<string, unknown>): Record<s
82
82
  interface RefineContext {
83
83
  project: string;
84
84
  restName?: string;
85
+ /** A forwarding rule's declared `ipAddress.addressRef` and that address's live IP (#4238). */
86
+ address?: {
87
+ ip: string;
88
+ ref: unknown;
89
+ };
85
90
  }
86
91
  /**
87
92
  * A REST payload as a CNRM-shaped property tree, in the declared side's
@@ -1 +1 @@
1
- {"version":3,"file":"deep-observe.d.ts","sourceRoot":"","sources":["../src/deep-observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAajE,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWtF;AAUD;6EAC6E;AAC7E,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0OD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnH;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAuF5G"}
1
+ {"version":3,"file":"deep-observe.d.ts","sourceRoot":"","sources":["../src/deep-observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAajE,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWtF;AAUD;6EAC6E;AAC7E,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;CACxC;AAgPD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnH;AA6BD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA0F5G"}
@@ -55,7 +55,7 @@
55
55
  */
56
56
  import { deepObservation, normalizeDeepProperties } from "@intentius/chant/deep-observation";
57
57
  import { hasOwnershipMarker } from "@intentius/chant/ownership";
58
- import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources.js";
58
+ import { deriveGVK, manifestProjectAnnotations, readManifest, readTargets, resolveReadProject } from "./describe-resources.js";
59
59
  import { getResource, mapperForKind, GcpReadError, isNotFound } from "./api/read-client.js";
60
60
  import { gcpDeepNormalizationHooks } from "./deep-observe-hooks.js";
61
61
  /** The labels the applier stamps — see describe-resources.ts. */
@@ -299,8 +299,14 @@ const CNRM_REFINERS = {
299
299
  delete tree.gatewayAddress;
300
300
  delete tree.fingerprint;
301
301
  },
302
- ComputeForwardingRule(tree) {
302
+ ComputeForwardingRule(tree, ctx) {
303
303
  computeLocation(tree);
304
+ // Compute returns the IP; CNRM writes `ipAddress: { addressRef }` or `{ ip }`.
305
+ // The IP is the declared address's own when the read found it so (#4238).
306
+ if (typeof tree.IPAddress === "string") {
307
+ tree.ipAddress = ctx.address && ctx.address.ip === tree.IPAddress ? { addressRef: ctx.address.ref } : { ip: tree.IPAddress };
308
+ delete tree.IPAddress;
309
+ }
304
310
  if (tree.IPProtocol !== undefined) {
305
311
  tree.ipProtocol = tree.IPProtocol;
306
312
  delete tree.IPProtocol;
@@ -352,6 +358,31 @@ export function toCnrmTree(kind, body, ctx) {
352
358
  CNRM_REFINERS[kind]?.(tree, { ...ctx, restName });
353
359
  return tree;
354
360
  }
361
+ /**
362
+ * The live IP of the address a forwarding rule declares in
363
+ * `ipAddress.addressRef`, read with the address's own declaration when the
364
+ * manifest holds it, so the rule's `IPAddress` can be matched to the ref
365
+ * rather than read as drift (#4238). Undefined when there is no ref or the
366
+ * read fails; the rule then shows the IP as it is.
367
+ */
368
+ async function declaredAddressIp(client, props, manifest) {
369
+ const spec = props.spec ?? props;
370
+ const ref = spec.ipAddress?.addressRef;
371
+ if (!ref)
372
+ return undefined;
373
+ const raw = typeof ref.name === "string" ? ref.name : typeof ref.external === "string" ? ref.external : undefined;
374
+ if (!raw)
375
+ return undefined;
376
+ const name = raw.slice(raw.lastIndexOf("/") + 1);
377
+ const declared = manifest.find((r) => r.kind === "ComputeAddress" && r.metadata?.name === name);
378
+ try {
379
+ const body = await getResource(client, "ComputeAddress", name, declared?.spec ?? { location: spec.location });
380
+ return typeof body.address === "string" ? { ip: body.address, ref } : undefined;
381
+ }
382
+ catch {
383
+ return undefined;
384
+ }
385
+ }
355
386
  /**
356
387
  * Read the live property tree for each declared entity over the applier's
357
388
  * REST transport, normalized into the declared CNRM shape (see the module
@@ -365,6 +396,7 @@ export async function observeResourcesDeepGcp(options) {
365
396
  const endpoint = process.env.GCP_ENDPOINT_URL;
366
397
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
367
398
  const targets = readTargets(options.entities);
399
+ const manifest = readManifest(options.entities, targets);
368
400
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
369
401
  const gvk = deriveGVK(entityType);
370
402
  if (!gvk || !mapperForKind(gvk.kind)) {
@@ -384,6 +416,7 @@ export async function observeResourcesDeepGcp(options) {
384
416
  client = {
385
417
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
386
418
  ...(endpoint ? { endpoint } : {}),
419
+ manifest,
387
420
  };
388
421
  }
389
422
  catch (err) {
@@ -397,6 +430,7 @@ export async function observeResourcesDeepGcp(options) {
397
430
  try {
398
431
  const obj = await getResource(client, gvk.kind, name, props);
399
432
  const labels = obj.labels;
433
+ const address = gvk.kind === "ComputeForwardingRule" ? await declaredAddressIp(client, props, manifest) : undefined;
400
434
  // owned filter: withhold what does not carry chant's marker. Withheld is
401
435
  // not absent (#1089). Where the payload has no labels at all there is
402
436
  // nothing to filter on, so the resource passes through — the same
@@ -412,7 +446,7 @@ export async function observeResourcesDeepGcp(options) {
412
446
  resources[entityName] = {
413
447
  type: entityType,
414
448
  physicalId: obj.id ?? obj.selfLink ?? obj.name,
415
- properties: normalizeDeepProperties(toCnrmTree(gvk.kind, obj, { project: client.project }), {
449
+ properties: normalizeDeepProperties(toCnrmTree(gvk.kind, obj, { project: client.project, ...(address ? { address } : {}) }), {
416
450
  entityType,
417
451
  side: "live",
418
452
  // The static table is the whole prune here — there is no per-resource
@@ -36,6 +36,7 @@
36
36
  * dropped.
37
37
  */
38
38
  import type { ObservationResult } from "@intentius/chant/lexicon";
39
+ import { type GcpResource } from "./op/activities/gcp-apply.js";
39
40
  /** The parts of a GCP REST payload this reader looks at. Every field is
40
41
  * optional because they vary by kind — a bucket has `id` and `labels`, a
41
42
  * Pub/Sub topic has neither. */
@@ -105,6 +106,18 @@ export declare function readTargets(entities: Map<string, {
105
106
  name: string;
106
107
  props: Record<string, unknown>;
107
108
  }>;
109
+ /**
110
+ * The declared resources as the applier's manifest holds them (kind, CNRM
111
+ * name, spec), for a read that needs another declared resource: an
112
+ * IAMPolicyMember on a RunService takes the service's region from it (#4251).
113
+ */
114
+ export declare function readManifest(entities: Map<string, {
115
+ entityType: string;
116
+ props: Record<string, unknown>;
117
+ }>, targets: Map<string, {
118
+ name: string;
119
+ props: Record<string, unknown>;
120
+ }>): GcpResource[];
108
121
  /**
109
122
  * Status from a REST payload.
110
123
  *
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,MAAM,0BAA0B,CAAC;AAsBtG;;gCAEgC;AAChC,UAAU,eAAe;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACxF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgB/F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,SAAS,EAC9D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAUR;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5E,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAY/D;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAY3D;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiH7B"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"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"}
@@ -137,6 +137,23 @@ export function readTargets(entities) {
137
137
  }
138
138
  return out;
139
139
  }
140
+ /**
141
+ * The declared resources as the applier's manifest holds them (kind, CNRM
142
+ * name, spec), for a read that needs another declared resource: an
143
+ * IAMPolicyMember on a RunService takes the service's region from it (#4251).
144
+ */
145
+ export function readManifest(entities, targets) {
146
+ const out = [];
147
+ for (const [logical, { entityType }] of entities) {
148
+ const gvk = deriveGVK(entityType);
149
+ const target = targets.get(logical);
150
+ if (!gvk || !target)
151
+ continue;
152
+ const spec = target.props.spec ?? target.props;
153
+ out.push({ kind: gvk.kind, metadata: { name: target.name }, spec });
154
+ }
155
+ return out;
156
+ }
140
157
  function pruneUndefined(obj) {
141
158
  const out = {};
142
159
  for (const [k, v] of Object.entries(obj)) {
@@ -185,6 +202,7 @@ export async function describeResources(options) {
185
202
  let warnedOwnership = false;
186
203
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
187
204
  const targets = readTargets(options.entities);
205
+ const manifest = readManifest(options.entities, targets);
188
206
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
189
207
  const gvk = deriveGVK(entityType);
190
208
  if (!gvk) {
@@ -212,6 +230,7 @@ export async function describeResources(options) {
212
230
  client = {
213
231
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
214
232
  ...(endpoint ? { endpoint } : {}),
233
+ manifest,
215
234
  };
216
235
  }
217
236
  catch (err) {
package/dist/health.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Runtime health for GCP (#4177): Cloud Run services.
2
+ * Runtime health for GCP (#4177): Cloud Run services; and (#4239) backend
3
+ * services, from `getHealth` on each backend group, and IAM policy members,
4
+ * healthy when the member holds the role in the target's policy.
3
5
  *
4
6
  * A Cloud Run v2 service carries its `Ready` condition as `terminalCondition`
5
7
  * (`CONDITION_SUCCEEDED`, `CONDITION_FAILED`, pending or reconciling). The v1
@@ -10,7 +12,7 @@
10
12
  */
11
13
  import type { HealthEntry, HealthReadOptions, HealthReadResult } from "@intentius/chant/lexicon";
12
14
  import { type GcpReadHttp } from "./api/read-client.js";
13
- export declare const GCP_HEALTH_TYPES: readonly ["GCP::Run::Service"];
15
+ export declare const GCP_HEALTH_TYPES: readonly ["GCP::Run::Service", "GCP::Compute::BackendService", "GCP::Iam::PolicyMember"];
14
16
  export interface GcpHealthOptions extends HealthReadOptions {
15
17
  http?: GcpReadHttp;
16
18
  now?: () => Date;
@@ -37,6 +39,28 @@ export interface CloudRunServiceState {
37
39
  }
38
40
  /** The `Ready` condition's verdict. No condition at all is `unknown`. */
39
41
  export declare function cloudRunEntry(body: CloudRunServiceState, observedAt: string): HealthEntry;
42
+ /** One backend group's `getHealth` answer: `healthStatus[].healthState` per endpoint. */
43
+ export interface BackendGroupHealth {
44
+ group: string;
45
+ healthStatus?: Array<{
46
+ healthState?: string;
47
+ }>;
48
+ /** Set when the call failed; the group then counts as unknown. */
49
+ error?: string;
50
+ }
51
+ /**
52
+ * A backend service's verdict from its groups: any endpoint `UNHEALTHY` is
53
+ * unhealthy; every endpoint `HEALTHY`, with at least one, is healthy; anything
54
+ * else (no backends, a group that reports no status, as a serverless NEG
55
+ * does, a failed call) is unknown.
56
+ */
57
+ export declare function backendServiceEntry(groups: readonly BackendGroupHealth[], observedAt: string): HealthEntry;
58
+ /** A binding the read found in its target's policy. A missing one is `absent`, never here. */
59
+ export declare function policyMemberEntry(binding: {
60
+ role?: string;
61
+ member?: string;
62
+ resource?: string;
63
+ }, observedAt: string): HealthEntry;
40
64
  export declare function observeHealthGcp(options: GcpHealthOptions): Promise<HealthReadResult>;
41
65
  export {};
42
66
  //# sourceMappingURL=health.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AACnH,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG5F,eAAO,MAAM,gBAAgB,YAAI,mBAAmB,CAAU,CAAC;AAE/D,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAOD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAqBzF;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiD3F"}
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AACnH,OAAO,EAAkD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrG,eAAO,MAAM,gBAAgB,YAAI,mBAAmB,EAAE,8BAA8B,EAAE,wBAAwB,CAAU,CAAC;AAEzH,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAOD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAqBzF;AAED,yFAAyF;AACzF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAkB1G;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOjI;AA6BD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqD3F"}
package/dist/health.js CHANGED
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Runtime health for GCP (#4177): Cloud Run services.
2
+ * Runtime health for GCP (#4177): Cloud Run services; and (#4239) backend
3
+ * services, from `getHealth` on each backend group, and IAM policy members,
4
+ * healthy when the member holds the role in the target's policy.
3
5
  *
4
6
  * A Cloud Run v2 service carries its `Ready` condition as `terminalCondition`
5
7
  * (`CONDITION_SUCCEEDED`, `CONDITION_FAILED`, pending or reconciling). The v1
@@ -8,9 +10,9 @@
8
10
  * project resolution and endpoint rule as `describe-resources.ts`, so
9
11
  * `GCP_ENDPOINT_URL` (floci-gcp) answers.
10
12
  */
11
- import { getResource, GcpReadError, isNotFound } from "./api/read-client.js";
12
- import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources.js";
13
- export const GCP_HEALTH_TYPES = ["GCP::Run::Service"];
13
+ import { getResource, GcpReadError, isNotFound, readUrl } from "./api/read-client.js";
14
+ import { deriveGVK, manifestProjectAnnotations, readManifest, readTargets, resolveReadProject } from "./describe-resources.js";
15
+ export const GCP_HEALTH_TYPES = ["GCP::Run::Service", "GCP::Compute::BackendService", "GCP::Iam::PolicyMember"];
14
16
  function readyCondition(body) {
15
17
  if (body.terminalCondition && (body.terminalCondition.type ?? "Ready") === "Ready")
16
18
  return body.terminalCondition;
@@ -39,6 +41,72 @@ export function cloudRunEntry(body, observedAt) {
39
41
  ...(ready.lastTransitionTime ? { checkedAt: ready.lastTransitionTime } : {}),
40
42
  };
41
43
  }
44
+ /**
45
+ * A backend service's verdict from its groups: any endpoint `UNHEALTHY` is
46
+ * unhealthy; every endpoint `HEALTHY`, with at least one, is healthy; anything
47
+ * else (no backends, a group that reports no status, as a serverless NEG
48
+ * does, a failed call) is unknown.
49
+ */
50
+ export function backendServiceEntry(groups, observedAt) {
51
+ const type = "GCP::Compute::BackendService";
52
+ if (groups.length === 0)
53
+ return { type, state: "unknown", detail: "no backends", observedAt };
54
+ let healthy = 0;
55
+ let unhealthy = 0;
56
+ let unknown = 0;
57
+ for (const g of groups) {
58
+ const states = g.error ? [] : (g.healthStatus ?? []).map((h) => (h.healthState ?? "").toUpperCase());
59
+ if (states.length === 0)
60
+ unknown++;
61
+ else if (states.some((st) => st === "UNHEALTHY"))
62
+ unhealthy++;
63
+ else if (states.every((st) => st === "HEALTHY"))
64
+ healthy++;
65
+ else
66
+ unknown++;
67
+ }
68
+ const state = unhealthy > 0 ? "unhealthy" : healthy === groups.length ? "healthy" : "unknown";
69
+ const parts = [`${healthy} of ${groups.length} backend${groups.length === 1 ? "" : "s"} healthy`];
70
+ if (unhealthy)
71
+ parts.push(`${unhealthy} unhealthy`);
72
+ if (unknown)
73
+ parts.push(`${unknown} with no health status`);
74
+ return { type, state, detail: parts.join(", "), observedAt };
75
+ }
76
+ /** A binding the read found in its target's policy. A missing one is `absent`, never here. */
77
+ export function policyMemberEntry(binding, observedAt) {
78
+ return {
79
+ type: "GCP::Iam::PolicyMember",
80
+ state: "healthy",
81
+ detail: `${binding.member ?? "member"} holds ${binding.role ?? "the role"}${binding.resource ? ` on ${binding.resource}` : ""}`,
82
+ observedAt,
83
+ };
84
+ }
85
+ const defaultReadHttp = async (method, url, body) => {
86
+ const res = await fetch(url, {
87
+ method,
88
+ ...(body === undefined ? {} : { headers: { "content-type": "application/json" }, body: JSON.stringify(body) }),
89
+ });
90
+ return { status: res.status, text: await res.text() };
91
+ };
92
+ /** `getHealth` for each of a backend service's groups, at the service's own read URL. */
93
+ async function backendGroupsHealth(url, body, http) {
94
+ const backends = Array.isArray(body.backends) ? body.backends : [];
95
+ return Promise.all(backends
96
+ .map((b) => b.group)
97
+ .filter((g) => typeof g === "string")
98
+ .map(async (group) => {
99
+ try {
100
+ const res = await http("POST", `${url}/getHealth`, { group });
101
+ if (res.status < 200 || res.status >= 300)
102
+ return { group, error: `getHealth ${res.status}` };
103
+ return { group, ...JSON.parse(res.text) };
104
+ }
105
+ catch (err) {
106
+ return { group, error: err instanceof Error ? err.message : String(err) };
107
+ }
108
+ }));
109
+ }
42
110
  export async function observeHealthGcp(options) {
43
111
  const observedAt = (options.now?.() ?? new Date()).toISOString();
44
112
  const endpoint = process.env.GCP_ENDPOINT_URL;
@@ -47,6 +115,7 @@ export async function observeHealthGcp(options) {
47
115
  const unobserved = {};
48
116
  const absent = [];
49
117
  const targets = readTargets(options.entities);
118
+ const manifest = readManifest(options.entities, targets);
50
119
  await Promise.all([...options.entities].map(async ([entityName, { entityType }]) => {
51
120
  const gvk = deriveGVK(entityType);
52
121
  const { name, props } = targets.get(entityName);
@@ -64,8 +133,18 @@ export async function observeHealthGcp(options) {
64
133
  return;
65
134
  }
66
135
  try {
67
- const body = await getResource({ project, ...(endpoint ? { endpoint } : {}), ...(options.http ? { http: options.http } : {}) }, gvk.kind, name, props);
68
- entities[entityName] = cloudRunEntry(body, observedAt);
136
+ const client = { project, ...(endpoint ? { endpoint } : {}), ...(options.http ? { http: options.http } : {}), manifest };
137
+ const body = await getResource(client, gvk.kind, name, props);
138
+ if (gvk.kind === "ComputeBackendService") {
139
+ const groups = await backendGroupsHealth(readUrl(client, gvk.kind, name, props), body, options.http ?? defaultReadHttp);
140
+ entities[entityName] = backendServiceEntry(groups, observedAt);
141
+ }
142
+ else if (gvk.kind === "IAMPolicyMember") {
143
+ entities[entityName] = policyMemberEntry(body, observedAt);
144
+ }
145
+ else {
146
+ entities[entityName] = cloudRunEntry(body, observedAt);
147
+ }
69
148
  }
70
149
  catch (err) {
71
150
  if (isNotFound(err)) {
package/dist/index.d.ts CHANGED
@@ -7,8 +7,8 @@ export { GCP, ProjectId, Region, Zone } from "./pseudo.js";
7
7
  export { GcpAnnotations } from "./variables.js";
8
8
  export { LexiconOutput, output, isLexiconOutput } from "@intentius/chant/lexicon-output";
9
9
  export * from "./generated/index.js";
10
- export { GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork, PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject, MemorystoreRedis, MultiRegionVpc, GkeCrdbRegion, GpuNodePool, GovernanceFoundation, LocationRestriction, OrganizationAuditConfig, } from "./composites/index.js";
11
- export type { GkeClusterProps, CloudRunServiceProps, CloudSqlInstanceProps, GcsBucketProps, VpcNetworkProps, VpcSubnet, PubSubPipelineProps, CloudFunctionWithTriggerProps, PrivateServiceProps, ManagedCertificateProps, SecureProjectProps, MemorystoreRedisProps, MultiRegionVpcConfig, MultiRegionVpcRegion, GkeCrdbRegionConfig, GkeCrdbRegionNodeConfig, GpuNodePoolProps, GpuNodePoolTaint, GovernanceFoundationProps, LocationRestrictionProps, OrganizationAuditConfigProps, } from "./composites/index.js";
10
+ export { GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork, PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject, MemorystoreRedis, MultiRegionVpc, GkeCrdbRegion, GpuNodePool, PrivateEgressNetwork, PublicCloudRun, GovernanceFoundation, LocationRestriction, OrganizationAuditConfig, } from "./composites/index.js";
11
+ export type { GkeClusterProps, CloudRunServiceProps, CloudSqlInstanceProps, GcsBucketProps, VpcNetworkProps, VpcSubnet, PubSubPipelineProps, CloudFunctionWithTriggerProps, PrivateServiceProps, ManagedCertificateProps, SecureProjectProps, MemorystoreRedisProps, MultiRegionVpcConfig, MultiRegionVpcRegion, GkeCrdbRegionConfig, GkeCrdbRegionNodeConfig, GpuNodePoolProps, GpuNodePoolTaint, PrivateEgressNetworkProps, PublicCloudRunProps, GovernanceFoundationProps, LocationRestrictionProps, OrganizationAuditConfigProps, } from "./composites/index.js";
12
12
  export { landingZoneConfig, resourceLocationRestriction, DISABLE_SA_KEY_CREATION, SKIP_DEFAULT_NETWORK, AUDIT_ALL_SERVICES, FOUNDATION_FOLDERS, } from "./governance.js";
13
13
  export type { GcpGovernanceConfig, LandingZoneConfigProps, FolderConfig, OrgPolicyConfig, ProjectConfig } from "./governance.js";
14
14
  export { StorageRoles, ComputeRoles, ContainerRoles, IAMRoles, SQLRoles, RunRoles, PubSubRoles } from "./actions/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAGrF,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIzF,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EACL,UAAU,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAC7E,cAAc,EAAE,wBAAwB,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAC3F,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAC5D,oBAAoB,EAAE,mBAAmB,EAAE,uBAAuB,GACnE,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,eAAe,EAAE,SAAS,EAC1B,mBAAmB,EACnB,6BAA6B,EAC7B,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EAAE,oBAAoB,EAC1C,mBAAmB,EAAE,uBAAuB,EAC5C,gBAAgB,EAAE,gBAAgB,EAClC,yBAAyB,EAAE,wBAAwB,EAAE,4BAA4B,GAClF,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,iBAAiB,EAAE,2BAA2B,EAC9C,uBAAuB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,GACtF,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG9H,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGxH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrI,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAGrF,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIzF,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EACL,UAAU,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAC7E,cAAc,EAAE,wBAAwB,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAC3F,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,cAAc,EAClG,oBAAoB,EAAE,mBAAmB,EAAE,uBAAuB,GACnE,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,eAAe,EAAE,SAAS,EAC1B,mBAAmB,EACnB,6BAA6B,EAC7B,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EAAE,oBAAoB,EAC1C,mBAAmB,EAAE,uBAAuB,EAC5C,gBAAgB,EAAE,gBAAgB,EAClC,yBAAyB,EAAE,mBAAmB,EAC9C,yBAAyB,EAAE,wBAAwB,EAAE,4BAA4B,GAClF,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,iBAAiB,EAAE,2BAA2B,EAC9C,uBAAuB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,GACtF,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG9H,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGxH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrI,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ export { LexiconOutput, output, isLexiconOutput } from "@intentius/chant/lexicon
21
21
  // After running `npm run generate`, this re-exports all Config Connector resource classes
22
22
  export * from "./generated/index.js";
23
23
  // Composites
24
- export { GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork, PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject, MemorystoreRedis, MultiRegionVpc, GkeCrdbRegion, GpuNodePool, GovernanceFoundation, LocationRestriction, OrganizationAuditConfig, } from "./composites/index.js";
24
+ export { GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork, PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject, MemorystoreRedis, MultiRegionVpc, GkeCrdbRegion, GpuNodePool, PrivateEgressNetwork, PublicCloudRun, GovernanceFoundation, LocationRestriction, OrganizationAuditConfig, } from "./composites/index.js";
25
25
  // GCP governance authoring: the desired-state config an external governance
26
26
  // reconciler applies, and the typed landing-zone authoring layer.
27
27
  export { landingZoneConfig, resourceLocationRestriction, DISABLE_SA_KEY_CREATION, SKIP_DEFAULT_NETWORK, AUDIT_ALL_SERVICES, FOUNDATION_FOLDERS, } from "./governance.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "648e7fb96c2a44b72459dc70e777c22836de776eb5f5b8017f8c6057135062a3",
4
+ "manifest.json": "f3591792043dc8b5c86d1c30c694b09225546ef08cd7a2c9856718ea094d6259",
5
5
  "meta.json": "75643660010c044c6f465bef2688a864ab30cc96fd9b68111969749748ac64ca",
6
6
  "types/index.d.ts": "da1ebc6bcf83e1a018a773b59714194aeeb74ae1d5c1252b737b7e730ca98bd5",
7
7
  "rules/hardcoded-project.ts": "6d5ae9efa74d73800eebe1a0a0d43c619ee0fdebe3347195df93fcbe63b34f0d",
@@ -35,10 +35,10 @@
35
35
  "rules/wgc501.ts": "bf99d8dc97537687fdfe9a8d5eb420172bf3279a4147311037f9c1d1f5fd4c85",
36
36
  "rules/wgc502.ts": "6460a028f73ef31a66653954e67142079130112b70fa01a79f80464968a1b9f6",
37
37
  "rules/wgc503.ts": "44dc51efbf9f9384b61a4e19b173b4c182557143cb59fbaf4da087e70a38f916",
38
- "skills/chant-gcp.md": "46a5e711ac59086d9f59ef645085aa54e8b1953afb76a0d93158b7288edf362e",
38
+ "skills/chant-gcp.md": "da90cf5982451652bd0f0ad51563c248379824a77bd49c9ecbadbb37a82a29e1",
39
39
  "skills/chant-gcp-security.md": "39a1c97b7425ed43b081257132449d11ce9d12f2d4755fc30d8b14bbc38f60bd",
40
40
  "skills/chant-gcp-patterns.md": "4c6809fc7c37fa1879379ad4c0e2d114dec9497bc6eb9d1bca4e79fddea4f509",
41
41
  "skills/chant-gcp-gke.md": "be277019da9a722c851e47cd2dfb9c9536668948c3535fb20db7697e934c4e2b"
42
42
  },
43
- "composite": "12781f2fbe08546cbfed0496ef97a0cfc6f301c5576bfafad2cbd4bf3cac39f3"
43
+ "composite": "73e7ca62da8ef9beafa48e58b549248bd5b24abd37f1a4bb180de513b7319acc"
44
44
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcp",
3
- "version": "0.124.37",
3
+ "version": "0.124.39",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GCP",
6
6
  "intrinsics": [],
@@ -86,7 +86,25 @@ export interface ApplyPlan {
86
86
  url: string;
87
87
  body: unknown;
88
88
  };
89
+ /**
90
+ * In place of `update`, for a resource whose update depends on the live body
91
+ * (#4238): compute updates carry the live `fingerprint`, and some fields only
92
+ * change through a setter (`setTarget`, `setLabels`). Returns the requests to
93
+ * send, none when the live resource already matches, or `replace` naming what
94
+ * differs on a field that can't change in place.
95
+ */
96
+ reconcile?(live: unknown, http: GcpHttp, signal?: AbortSignal): Promise<ReconcileStep>;
89
97
  }
98
+ /** What {@link ApplyPlan.reconcile} decided for an existing resource. */
99
+ export type ReconcileStep = {
100
+ requests: Array<{
101
+ method: "POST" | "PATCH" | "PUT";
102
+ url: string;
103
+ body: unknown;
104
+ }>;
105
+ } | {
106
+ replace: string;
107
+ };
90
108
  /**
91
109
  * Handles an async create whose response is a long-running operation. Present
92
110
  * only on mappers for resources that provision asynchronously (Cloud Run, GKE,
@@ -280,6 +298,7 @@ export declare function applyResource(mapper: ResourceMapper, resource: GcpResou
280
298
  name: string;
281
299
  created: boolean;
282
300
  updated: boolean;
301
+ replaceRequired?: string;
283
302
  }>;
284
303
  /**
285
304
  * Poll a long-running operation until it reports done (or errors / times out).