@intentius/chant-lexicon-k8s 0.1.11 → 0.1.13

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "6de26c6a6dacd8f2231ef7347a2e49c14c486536a8524d288b3d5dafaf57f530",
4
+ "manifest.json": "f494467144f83651a59a46bbe17430b0b255af80d9cf03a2a2c7d990b896cf89",
5
5
  "meta.json": "970c70eb6eea1686f7cb8ce6ceccc46ce2e57d696d344f1dd52460e266f9a56c",
6
6
  "types/index.d.ts": "07473e029254345488bc9952585e88bcf18da79d1026cc26744027683f472554",
7
7
  "rules/hardcoded-namespace.ts": "ba3f43f2adbffdd87db20a2c45839354ceecda1b9f04f29ae31c4c077dddc7ec",
@@ -42,5 +42,5 @@
42
42
  "skills/chant-k8s-gke.md": "8938840bf9ef5ed58d6333fdd773b3dd54ecaf25a9df35e58f7f5c3355d4928f",
43
43
  "skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9"
44
44
  },
45
- "composite": "b9199760d98da9cfac7eeb4fed7e8ecbe5a974861c58e661dfea8ead21773e82"
45
+ "composite": "638663928c965eceb5de58d6cc9a952e935ec92c6cc767de1278d341e9d6f3bc"
46
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/intentius/chant.git",
9
+ "url": "https://github.com/INTENTIUS/chant.git",
10
10
  "directory": "lexicons/k8s"
11
11
  },
12
12
  "bugs": {
13
- "url": "https://github.com/intentius/chant/issues"
13
+ "url": "https://github.com/INTENTIUS/chant/issues"
14
14
  },
15
15
  "keywords": [
16
16
  "infrastructure-as-code",
@@ -392,4 +392,50 @@ describe("k8sSerializer", () => {
392
392
  expect(kindIdx).toBeLessThan(metaIdx);
393
393
  expect(metaIdx).toBeLessThan(specIdx);
394
394
  });
395
+
396
+ test("CRD-wrapper trick: props.apiVersion/kind override gvk-derived defaults", () => {
397
+ // The Deployment-as-generic-CRD-wrapper pattern: pass arbitrary
398
+ // apiVersion + kind in props and the serializer should respect them
399
+ // (for emitting non-built-in CRDs through chant).
400
+ const entities = new Map<string, any>();
401
+ entities.set(
402
+ "esoStore",
403
+ mockResource("K8s::Apps::Deployment", {
404
+ apiVersion: "external-secrets.io/v1beta1",
405
+ kind: "ClusterSecretStore",
406
+ metadata: { name: "my-store" },
407
+ spec: { provider: { gcpsm: { projectID: "my-proj" } } },
408
+ }),
409
+ );
410
+
411
+ const result = k8sSerializer.serialize(entities);
412
+ expect(result).toContain("apiVersion: external-secrets.io/v1beta1");
413
+ expect(result).toContain("kind: ClusterSecretStore");
414
+ expect(result).not.toContain("apiVersion: apps/v1");
415
+ expect(result).not.toContain("kind: Deployment");
416
+ });
417
+
418
+ test("RBAC-shaped resources (rules/subjects/roleRef) stay at top level, not pushed under spec", () => {
419
+ // ClusterRole / Role / RoleBinding / ClusterRoleBinding don't have a
420
+ // spec field; their data is at the manifest root. When such a resource
421
+ // arrives via the CRD-wrapper trick (no spec set in props), the
422
+ // serializer used to dump rules/subjects/roleRef into a synthetic spec
423
+ // — produced invalid manifests. Now they stay at top level.
424
+ const entities = new Map<string, any>();
425
+ entities.set(
426
+ "viewerRole",
427
+ mockResource("K8s::Apps::Deployment", {
428
+ apiVersion: "rbac.authorization.k8s.io/v1",
429
+ kind: "ClusterRole",
430
+ metadata: { name: "viewer" },
431
+ rules: [{ apiGroups: [""], resources: ["pods"], verbs: ["get", "list"] }],
432
+ }),
433
+ );
434
+
435
+ const result = k8sSerializer.serialize(entities);
436
+ expect(result).toContain("kind: ClusterRole");
437
+ expect(result).toMatch(/^rules:/m);
438
+ // The bug being guarded against: rules ending up under spec.
439
+ expect(result).not.toMatch(/^spec:\s*\n\s+rules:/m);
440
+ });
395
441
  });
package/src/serializer.ts CHANGED
@@ -218,6 +218,28 @@ export const k8sSerializer: Serializer = {
218
218
 
219
219
  manifest.metadata = metadata;
220
220
 
221
+ // Properties that always belong at the manifest root, never inside
222
+ // spec. apiVersion/kind allow consumers to override the gvk-derived
223
+ // defaults (the "CRD wrapper" trick used to declare arbitrary K8s
224
+ // resources via a generic Declarable class). rules/subjects/roleRef
225
+ // are the top-level fields for RBAC kinds. data/stringData are for
226
+ // ConfigMap/Secret. binaryData covers ConfigMap binary entries.
227
+ const TOP_LEVEL_PROPS = new Set([
228
+ "apiVersion",
229
+ "kind",
230
+ "rules",
231
+ "subjects",
232
+ "roleRef",
233
+ "data",
234
+ "stringData",
235
+ "binaryData",
236
+ "type",
237
+ "immutable",
238
+ "automountServiceAccountToken",
239
+ "secrets",
240
+ "imagePullSecrets",
241
+ ]);
242
+
221
243
  // The remaining properties go under spec (or directly on the manifest for certain types)
222
244
  if (SPECLESS_TYPES.has(gvk.kind)) {
223
245
  // These types have their data directly on the manifest (data, stringData, etc.)
@@ -235,10 +257,14 @@ export const k8sSerializer: Serializer = {
235
257
  }
236
258
  }
237
259
  } else {
238
- // Place remaining props under spec
260
+ // Place remaining props under spec — except known top-level fields
261
+ // (apiVersion/kind for CRD-wrapper overrides; rules/subjects for RBAC).
239
262
  const spec: Record<string, unknown> = {};
240
263
  for (const [key, value] of Object.entries(props)) {
241
- if (key !== "metadata") {
264
+ if (key === "metadata") continue;
265
+ if (TOP_LEVEL_PROPS.has(key)) {
266
+ manifest[key] = value;
267
+ } else {
242
268
  spec[key] = value;
243
269
  }
244
270
  }