@intentius/chant-lexicon-k8s 0.44.12 → 0.44.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.
@@ -90,6 +90,28 @@ export declare function revisionAttributes(obj: K8sObject): {
90
90
  lastAttemptedRevision?: string;
91
91
  artifactRevision?: string;
92
92
  };
93
+ /**
94
+ * The declared repository protocol of a Flux HelmRepository (behold#298).
95
+ *
96
+ * A HelmRepository with `spec.type: oci` is never reconciled by
97
+ * source-controller — no conditions, no artifact, by design — so a consumer
98
+ * classifying health off conditions reads a healthy OCI repository exactly
99
+ * like one whose controller has not shown up. `spec.type` is the field that
100
+ * separates the two, and no spec field reached the wire before this. It rides
101
+ * the attributes flattened, as `type`, the same way `artifactRevision`
102
+ * flattens `status.artifact.revision`: every scalar on the wire is a plain
103
+ * key next to the others, and the wire stays lean — one field, not the spec.
104
+ *
105
+ * Kind-gated, unlike the module's usual read-by-path rule, because the PATH
106
+ * is not distinctive: a core Service's `spec.type` is
107
+ * `ClusterIP`/`LoadBalancer`, and forwarding that would change the wire shape
108
+ * of a kind this was never about. Only source.toolkit.fluxcd.io's
109
+ * HelmRepository speaks this meaning of the field.
110
+ */
111
+ export declare function helmRepositoryTypeAttribute(gvk: {
112
+ apiVersion: string;
113
+ kind: string;
114
+ }, obj: K8sObject): string | undefined;
93
115
  export interface DescribeResourcesOptions {
94
116
  environment: string;
95
117
  buildOutput: string;
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAwD,MAAM,0BAA0B,CAAC;AAGxH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAmBvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CA2CvD;AA4FD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAuBtE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAUA;AAQD,MAAM,WAAW,wBAAwB;IACvC,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;IAChB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CA6J5B"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAwD,MAAM,0BAA0B,CAAC;AAGxH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAmBvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CA2CvD;AA4FD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAuBtE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzC,GAAG,EAAE,SAAS,GACb,MAAM,GAAG,SAAS,CAIpB;AAQD,MAAM,WAAW,wBAAwB;IACvC,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;IAChB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAgK5B"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "79f4f4af7c9611c5f4ca2843ae3e1222520dd8549c888a3c0b4ad4f20cab1a25",
4
+ "manifest.json": "b6c625f7af4e812f458d344b2a2a8bf8eccdb1508fbe9eaf7c42108bf5f8179e",
5
5
  "meta.json": "f568fb8a0cbd4adf041baccaa3e1164c2e3c5c0090186236f887f0084f5521a8",
6
6
  "types/index.d.ts": "ef2c9df6a4d1ebfbe33f96c094fbbe3fc7909522ea38e086666fbf3c8ac477fd",
7
7
  "rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
@@ -54,5 +54,5 @@
54
54
  "skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5",
55
55
  "skills/chant-k8s-flux.md": "9670efc2e9854bd14686b5adeb707396b9030a4dcaee6529b30abf56efe2c50c"
56
56
  },
57
- "composite": "052a02845dd4fcecbee0564f851a014f7d202f26750afcbae9e3492ec5c956c1"
57
+ "composite": "e9afd4601ffb6af967ef1826619470ff515e865d6c66ce1ce68bb8b8a3bedb7d"
58
58
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.44.12",
3
+ "version": "0.44.13",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.44.12",
3
+ "version": "0.44.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",
@@ -76,7 +76,7 @@
76
76
  "@types/js-yaml": "^4.0.9"
77
77
  },
78
78
  "optionalDependencies": {
79
- "@intentius/chant-k8s-client": "^0.44.12"
79
+ "@intentius/chant-k8s-client": "^0.44.13"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@intentius/chant": "*",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "peerDependencies": {
86
86
  "zod": "^4.3.6",
87
- "@intentius/chant": "^0.44.12",
87
+ "@intentius/chant": "^0.44.13",
88
88
  "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -15,7 +15,7 @@ vi.mock("@intentius/chant/config", () => ({
15
15
  loadChantConfig: (...args: unknown[]) => loadChantConfigMock(...args),
16
16
  }));
17
17
 
18
- const { describeResources, statusFromObject, unhappyConditions, revisionAttributes } = await import("./describe-resources");
18
+ const { describeResources, statusFromObject, unhappyConditions, revisionAttributes, helmRepositoryTypeAttribute } = await import("./describe-resources");
19
19
  const { fakeCluster, objectKey, ownedObject } = await import("./api/fake-cluster");
20
20
  const { defaultK8sConnector } = await import("./api/connect");
21
21
  const { fakeKubeconfig, statusBody } = await import("@intentius/chant-k8s-client/testing");
@@ -1628,6 +1628,126 @@ describe("observed attributes carry the Flux revisions (#1632)", () => {
1628
1628
  });
1629
1629
  });
1630
1630
 
1631
+ describe("helmRepositoryTypeAttribute — the one spec field on the wire (behold#298)", () => {
1632
+ const gvk = { apiVersion: "source.toolkit.fluxcd.io/v1", kind: "HelmRepository" };
1633
+
1634
+ test("carries a HelmRepository's spec.type when set", () => {
1635
+ expect(helmRepositoryTypeAttribute(gvk, { spec: { type: "oci" } } as never)).toBe("oci");
1636
+ expect(helmRepositoryTypeAttribute(gvk, { spec: { type: "default" } } as never)).toBe("default");
1637
+ });
1638
+
1639
+ test("says nothing when the field is absent — the CRD does not default it", () => {
1640
+ expect(helmRepositoryTypeAttribute(gvk, { spec: { url: "https://charts.example.com" } } as never)).toBeUndefined();
1641
+ expect(helmRepositoryTypeAttribute(gvk, {} as never)).toBeUndefined();
1642
+ });
1643
+
1644
+ test("ignores non-string and empty values", () => {
1645
+ expect(helmRepositoryTypeAttribute(gvk, { spec: { type: "" } } as never)).toBeUndefined();
1646
+ expect(helmRepositoryTypeAttribute(gvk, { spec: { type: 7 } } as never)).toBeUndefined();
1647
+ });
1648
+
1649
+ test("declines every other kind — spec.type is not a distinctive path", () => {
1650
+ // A core Service HAS a spec.type; forwarding it would change a wire shape
1651
+ // this field was never about.
1652
+ expect(
1653
+ helmRepositoryTypeAttribute({ apiVersion: "v1", kind: "Service" }, { spec: { type: "LoadBalancer" } } as never),
1654
+ ).toBeUndefined();
1655
+ // Same kind name in a foreign group claims nothing either.
1656
+ expect(
1657
+ helmRepositoryTypeAttribute({ apiVersion: "example.io/v1", kind: "HelmRepository" }, { spec: { type: "oci" } } as never),
1658
+ ).toBeUndefined();
1659
+ });
1660
+ });
1661
+
1662
+ describe("observed attributes carry a HelmRepository's spec.type (behold#298)", () => {
1663
+ test("an OCI HelmRepository reads type: 'oci'; a default one grows no type key", async () => {
1664
+ // The behold#298 shape: `spec.type: oci` means source-controller never
1665
+ // reconciles the object — no conditions, no artifact, by design — so a
1666
+ // consumer classifying off conditions needs this field to tell a healthy
1667
+ // OCI repository from a wedged default one.
1668
+ const oci = {
1669
+ apiVersion: "source.toolkit.fluxcd.io/v1",
1670
+ kind: "HelmRepository",
1671
+ metadata: { name: "charts-oci", namespace: "flux-system", uid: "hrepo-oci-uid" },
1672
+ spec: { type: "oci", url: "oci://ghcr.io/example/charts" },
1673
+ // No status at all — exactly what the API returns for an OCI one.
1674
+ };
1675
+ const plain = {
1676
+ apiVersion: "source.toolkit.fluxcd.io/v1",
1677
+ kind: "HelmRepository",
1678
+ metadata: { name: "charts-http", namespace: "flux-system", uid: "hrepo-http-uid" },
1679
+ // The CRD does not default spec.type: a default repository simply
1680
+ // carries no `type` field, and the wire says nothing for it.
1681
+ spec: { url: "https://charts.example.com" },
1682
+ status: { conditions: [{ type: "Ready", status: "True" }], artifact: { revision: "sha256:abc123" } },
1683
+ };
1684
+ const cluster = fakeCluster({
1685
+ objects: {
1686
+ [objectKey("source.toolkit.fluxcd.io/v1", "HelmRepository", "charts-oci", "flux-system")]: oci,
1687
+ [objectKey("source.toolkit.fluxcd.io/v1", "HelmRepository", "charts-http", "flux-system")]: plain,
1688
+ },
1689
+ });
1690
+
1691
+ const result = await describeResources(
1692
+ {
1693
+ environment: "prod",
1694
+ buildOutput: "",
1695
+ entityNames: ["chartsOci", "chartsHttp"],
1696
+ entities: makeEntities([
1697
+ {
1698
+ name: "chartsOci",
1699
+ entityType: "K8s::Flux::HelmRepository",
1700
+ props: { metadata: { name: "charts-oci", namespace: "flux-system" }, spec: { type: "oci", url: "oci://ghcr.io/example/charts" } },
1701
+ },
1702
+ {
1703
+ name: "chartsHttp",
1704
+ entityType: "K8s::Flux::HelmRepository",
1705
+ props: { metadata: { name: "charts-http", namespace: "flux-system" }, spec: { url: "https://charts.example.com" } },
1706
+ },
1707
+ ]),
1708
+ },
1709
+ cluster.connector,
1710
+ );
1711
+
1712
+ expect(result.resources.chartsOci?.attributes).toMatchObject({ type: "oci" });
1713
+ // No conditions is the healthy shape for OCI — nothing unhappy to carry.
1714
+ expect(result.resources.chartsOci?.attributes).not.toHaveProperty("conditions");
1715
+ // The default repository's attributes are byte-for-byte what they were:
1716
+ // no `type` key appears, and its revision still rides #1632's path.
1717
+ expect(result.resources.chartsHttp?.attributes).not.toHaveProperty("type");
1718
+ expect(result.resources.chartsHttp?.attributes).toMatchObject({ artifactRevision: "sha256:abc123" });
1719
+ });
1720
+
1721
+ test("no other kind grows a type attribute — a Service's spec.type stays off the wire", async () => {
1722
+ const lb = {
1723
+ apiVersion: "v1",
1724
+ kind: "Service",
1725
+ metadata: { name: "web-lb", namespace: "prod", uid: "lb-uid" },
1726
+ spec: { type: "LoadBalancer", ports: [{ port: 80 }] },
1727
+ };
1728
+ const cluster = fakeCluster({ objects: { [objectKey("v1", "Service", "web-lb", "prod")]: lb } });
1729
+
1730
+ const result = await describeResources(
1731
+ {
1732
+ environment: "prod",
1733
+ buildOutput: "",
1734
+ entityNames: ["webLb"],
1735
+ entities: makeEntities([
1736
+ {
1737
+ name: "webLb",
1738
+ entityType: "K8s::Core::Service",
1739
+ props: { metadata: { name: "web-lb", namespace: "prod" }, spec: { type: "LoadBalancer" } },
1740
+ },
1741
+ ]),
1742
+ },
1743
+ cluster.connector,
1744
+ );
1745
+
1746
+ expect(result.resources.webLb).toBeDefined();
1747
+ expect(result.resources.webLb?.attributes).not.toHaveProperty("type");
1748
+ });
1749
+ });
1750
+
1631
1751
  // The behold#221 shape (#1629): the estate's control-plane project declares
1632
1752
  // the Kustomization carrying `spec.targetNamespace`, and the app project
1633
1753
  // declares bare objects the controller stamps at apply time. Reading the app
@@ -280,6 +280,33 @@ export function revisionAttributes(obj: K8sObject): {
280
280
  };
281
281
  }
282
282
 
283
+ /**
284
+ * The declared repository protocol of a Flux HelmRepository (behold#298).
285
+ *
286
+ * A HelmRepository with `spec.type: oci` is never reconciled by
287
+ * source-controller — no conditions, no artifact, by design — so a consumer
288
+ * classifying health off conditions reads a healthy OCI repository exactly
289
+ * like one whose controller has not shown up. `spec.type` is the field that
290
+ * separates the two, and no spec field reached the wire before this. It rides
291
+ * the attributes flattened, as `type`, the same way `artifactRevision`
292
+ * flattens `status.artifact.revision`: every scalar on the wire is a plain
293
+ * key next to the others, and the wire stays lean — one field, not the spec.
294
+ *
295
+ * Kind-gated, unlike the module's usual read-by-path rule, because the PATH
296
+ * is not distinctive: a core Service's `spec.type` is
297
+ * `ClusterIP`/`LoadBalancer`, and forwarding that would change the wire shape
298
+ * of a kind this was never about. Only source.toolkit.fluxcd.io's
299
+ * HelmRepository speaks this meaning of the field.
300
+ */
301
+ export function helmRepositoryTypeAttribute(
302
+ gvk: { apiVersion: string; kind: string },
303
+ obj: K8sObject,
304
+ ): string | undefined {
305
+ if (gvk.kind !== "HelmRepository" || !gvk.apiVersion.startsWith("source.toolkit.fluxcd.io/")) return undefined;
306
+ const type = (obj.spec as { type?: unknown } | undefined)?.type;
307
+ return typeof type === "string" && type.length > 0 ? type : undefined;
308
+ }
309
+
283
310
  interface Declared {
284
311
  entityName: string;
285
312
  entityType: string;
@@ -454,6 +481,9 @@ export async function describeResources(
454
481
  // What a GitOps controller says it has applied and is applying
455
482
  // (#1632) — the convergence signal conditions cannot carry.
456
483
  ...revisionAttributes(obj),
484
+ // A HelmRepository's spec.type — `oci` means "no conditions is
485
+ // healthy", and only this field can say so (behold#298).
486
+ type: helmRepositoryTypeAttribute(operation, obj),
457
487
  }),
458
488
  };
459
489
  } catch (err) {
@@ -750,6 +780,10 @@ async function addRuntimeChildren(
750
780
  labels: obj.metadata?.labels,
751
781
  resourceVersion: obj.metadata?.resourceVersion,
752
782
  conditions: unhappyConditions(obj),
783
+ // The same one spec field the declared read forwards (behold#298):
784
+ // a swept HelmRepository (Flux's own bootstrap manages them through
785
+ // a Kustomization) must not read differently from a declared one.
786
+ type: helmRepositoryTypeAttribute(swept, obj),
753
787
  }),
754
788
  };
755
789
  });