@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
package/src/serializer.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { createRequire } from "module";
|
|
9
9
|
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
10
10
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
11
|
-
import { isPropertyDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
|
|
11
|
+
import { isDeclarable, isPropertyDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
|
|
12
|
+
import { AttrRef } from "@intentius/chant/attrref";
|
|
12
13
|
import type { Serializer, SerializerResult, SerializeContext } from "@intentius/chant/serializer";
|
|
13
14
|
import { ownershipEntries, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
|
|
14
15
|
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
@@ -84,15 +85,14 @@ function deriveGVKFromType(entityType: string): { apiVersion: string; kind: stri
|
|
|
84
85
|
* GCP visitor for the generic serializer walker.
|
|
85
86
|
*/
|
|
86
87
|
function gcpVisitor(entityNames: Map<Declarable, string>): SerializerVisitor {
|
|
88
|
+
// Config Connector resolves a `*Ref: { name }` by the target's metadata.name,
|
|
89
|
+
// which is its declared name or else the kebab-cased logical id (#4242).
|
|
90
|
+
const cnrmNames = new Map<string, string>();
|
|
91
|
+
for (const [entity, logical] of entityNames) cnrmNames.set(logical, cnrmName(logical, entity));
|
|
92
|
+
const resolve = (logical: string) => cnrmNames.get(logical) ?? logical;
|
|
87
93
|
return {
|
|
88
|
-
attrRef: (name, _attr) =>
|
|
89
|
-
|
|
90
|
-
return name;
|
|
91
|
-
},
|
|
92
|
-
resourceRef: (name) => {
|
|
93
|
-
// Config Connector uses resourceRef pattern: { name: "resource-name" }
|
|
94
|
-
return { name };
|
|
95
|
-
},
|
|
94
|
+
attrRef: (name, _attr) => resolve(name),
|
|
95
|
+
resourceRef: (name) => ({ name: resolve(name) }),
|
|
96
96
|
propertyDeclarable: (entity, walk) => {
|
|
97
97
|
if (!isResourceDeclarable(entity) || typeof entity.props !== "object" || entity.props === null) {
|
|
98
98
|
return undefined;
|
|
@@ -109,6 +109,51 @@ function gcpVisitor(entityNames: Map<Declarable, string>): SerializerVisitor {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The metadata.name a resource is written with: a string `metadata.name` in its
|
|
114
|
+
* props, else its logical id in kebab case.
|
|
115
|
+
*/
|
|
116
|
+
export function cnrmName(logical: string, entity: Declarable): string {
|
|
117
|
+
return cnrmNameOf(logical, isResourceDeclarable(entity) ? entity.props : undefined);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** {@link cnrmName} from a resource's props, for callers holding props rather than the declarable. */
|
|
121
|
+
export function cnrmNameOf(logical: string, props: unknown): string {
|
|
122
|
+
const declared = (props as { metadata?: { name?: unknown } } | undefined)?.metadata?.name;
|
|
123
|
+
return typeof declared === "string" && declared.length > 0 ? declared : kebabName(logical);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Declared props with each reference to another resource replaced as the
|
|
128
|
+
* serializer writes it (#4251): an attribute reference (`neg.name`) becomes the
|
|
129
|
+
* target's metadata.name, and a resource placed whole becomes `{ name }`.
|
|
130
|
+
* `logicalOf` maps a target's props object to its logical id, for a reference
|
|
131
|
+
* that does not carry one. Other values, pseudo-parameters included, are kept.
|
|
132
|
+
*/
|
|
133
|
+
export function resolveCnrmRefs(value: unknown, logicalOf: (props: unknown) => string | undefined): unknown {
|
|
134
|
+
if (value instanceof AttrRef) {
|
|
135
|
+
const parent = value.parent.deref() as Declarable | undefined;
|
|
136
|
+
const props = parent && isResourceDeclarable(parent) ? parent.props : undefined;
|
|
137
|
+
const logical = value.getLogicalName() ?? logicalOf(props);
|
|
138
|
+
return logical === undefined ? value : cnrmNameOf(logical, props);
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(value)) return value.map((v) => resolveCnrmRefs(v, logicalOf));
|
|
141
|
+
if (value === null || typeof value !== "object") return value;
|
|
142
|
+
if (isDeclarable(value)) {
|
|
143
|
+
const props = isResourceDeclarable(value) ? value.props : undefined;
|
|
144
|
+
const logical = logicalOf(props);
|
|
145
|
+
return logical === undefined ? value : { name: cnrmNameOf(logical, props) };
|
|
146
|
+
}
|
|
147
|
+
if (INTRINSIC_MARKER in (value as object)) return value;
|
|
148
|
+
const out: Record<string, unknown> = {};
|
|
149
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = resolveCnrmRefs(v, logicalOf);
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function kebabName(logical: string): string {
|
|
154
|
+
return logical.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
155
|
+
}
|
|
156
|
+
|
|
112
157
|
/**
|
|
113
158
|
* Convert a value to YAML-compatible form using the walker.
|
|
114
159
|
*/
|
|
@@ -150,7 +195,11 @@ export const gcpSerializer: Serializer = {
|
|
|
150
195
|
let defaultLabelEntries: Record<string, unknown> = context?.ownership
|
|
151
196
|
? { ...ownershipEntries(LABEL_OWNERSHIP_KEYS, context.ownership) }
|
|
152
197
|
: {};
|
|
153
|
-
|
|
198
|
+
// #4252: a member's project, once, in chant.config's `gcp.project`. It
|
|
199
|
+
// resolves GCP.ProjectId and is written as each resource's project-id
|
|
200
|
+
// annotation, which apply and the readers take ahead of the env vars.
|
|
201
|
+
const configProject = gcpConfigProject(context?.config);
|
|
202
|
+
let defaultAnnotationEntries: Record<string, unknown> = configProject ? { [PROJECT_ID_ANNOTATION]: configProject } : {};
|
|
154
203
|
|
|
155
204
|
for (const [, entity] of entities) {
|
|
156
205
|
if (isDefaultLabels(entity)) {
|
|
@@ -176,6 +225,7 @@ export const gcpSerializer: Serializer = {
|
|
|
176
225
|
toYAMLValue(isResourceDeclarable(entity) ? entity.props : undefined, entityNames),
|
|
177
226
|
name,
|
|
178
227
|
unresolved,
|
|
228
|
+
configProject,
|
|
179
229
|
) as Record<string, unknown> | undefined;
|
|
180
230
|
|
|
181
231
|
if (!props) continue;
|
|
@@ -189,7 +239,7 @@ export const gcpSerializer: Serializer = {
|
|
|
189
239
|
// Build metadata
|
|
190
240
|
const metadata: Record<string, unknown> = props.metadata as Record<string, unknown> ?? {};
|
|
191
241
|
if (!metadata.name) {
|
|
192
|
-
metadata.name = name
|
|
242
|
+
metadata.name = kebabName(name);
|
|
193
243
|
}
|
|
194
244
|
|
|
195
245
|
// Merge default labels
|
|
@@ -233,6 +283,14 @@ export const gcpSerializer: Serializer = {
|
|
|
233
283
|
},
|
|
234
284
|
};
|
|
235
285
|
|
|
286
|
+
const PROJECT_ID_ANNOTATION = "cnrm.cloud.google.com/project-id";
|
|
287
|
+
|
|
288
|
+
/** The `gcp.project` a chant.config declares, if any (#4252). */
|
|
289
|
+
export function gcpConfigProject(config: Record<string, unknown> | undefined): string | undefined {
|
|
290
|
+
const project = (config?.gcp as { project?: unknown } | undefined)?.project;
|
|
291
|
+
return typeof project === "string" && project.length > 0 ? project : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
236
294
|
/**
|
|
237
295
|
* Pseudo-parameter → environment variable mapping.
|
|
238
296
|
* Config Connector annotations must be plain strings, so PseudoParameters
|
|
@@ -257,20 +315,20 @@ interface UnresolvedPseudo {
|
|
|
257
315
|
* the API as an object. A variable that isn't set is recorded, and the
|
|
258
316
|
* build refuses with every one named.
|
|
259
317
|
*/
|
|
260
|
-
function resolvePseudoParameters(value: unknown, entity: string, unresolved: UnresolvedPseudo[]): unknown {
|
|
261
|
-
if (Array.isArray(value)) return value.map((v) => resolvePseudoParameters(v, entity, unresolved));
|
|
318
|
+
function resolvePseudoParameters(value: unknown, entity: string, unresolved: UnresolvedPseudo[], project?: string): unknown {
|
|
319
|
+
if (Array.isArray(value)) return value.map((v) => resolvePseudoParameters(v, entity, unresolved, project));
|
|
262
320
|
if (value === null || typeof value !== "object") return value;
|
|
263
321
|
const obj = value as Record<string, unknown>;
|
|
264
322
|
const keys = Object.keys(obj);
|
|
265
323
|
if (keys.length === 1 && keys[0] === "Ref" && typeof obj.Ref === "string" && obj.Ref in PSEUDO_ENV_MAP) {
|
|
266
324
|
const { envVar } = PSEUDO_ENV_MAP[obj.Ref];
|
|
267
|
-
const resolved = process.env[envVar];
|
|
325
|
+
const resolved = (obj.Ref === "GCP::ProjectId" ? project : undefined) ?? process.env[envVar];
|
|
268
326
|
if (resolved) return resolved;
|
|
269
327
|
unresolved.push({ entity, ref: obj.Ref, envVar });
|
|
270
328
|
return value;
|
|
271
329
|
}
|
|
272
330
|
const out: Record<string, unknown> = {};
|
|
273
|
-
for (const [k, v] of Object.entries(obj)) out[k] = resolvePseudoParameters(v, entity, unresolved);
|
|
331
|
+
for (const [k, v] of Object.entries(obj)) out[k] = resolvePseudoParameters(v, entity, unresolved, project);
|
|
274
332
|
return out;
|
|
275
333
|
}
|
|
276
334
|
|