@intentius/chant-lexicon-k8s 0.38.0 → 0.41.0
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/codegen/generate-list-map-keys.d.ts +53 -0
- package/dist/codegen/generate-list-map-keys.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/composites/adot-collector.d.ts +7 -1
- package/dist/composites/adot-collector.d.ts.map +1 -1
- package/dist/composites/agic-ingress.d.ts +3 -1
- package/dist/composites/agic-ingress.d.ts.map +1 -1
- package/dist/composites/aks-external-dns-agent.d.ts +6 -1
- package/dist/composites/aks-external-dns-agent.d.ts.map +1 -1
- package/dist/composites/alb-ingress.d.ts +3 -1
- package/dist/composites/alb-ingress.d.ts.map +1 -1
- package/dist/composites/autoscaled-service.d.ts +6 -1
- package/dist/composites/autoscaled-service.d.ts.map +1 -1
- package/dist/composites/azure-disk-storage-class.d.ts +3 -1
- package/dist/composites/azure-disk-storage-class.d.ts.map +1 -1
- package/dist/composites/azure-file-storage-class.d.ts +3 -1
- package/dist/composites/azure-file-storage-class.d.ts.map +1 -1
- package/dist/composites/azure-monitor-collector.d.ts +7 -1
- package/dist/composites/azure-monitor-collector.d.ts.map +1 -1
- package/dist/composites/batch-job.d.ts +1 -1
- package/dist/composites/batch-job.d.ts.map +1 -1
- package/dist/composites/cockroachdb-cluster.d.ts +13 -1
- package/dist/composites/cockroachdb-cluster.d.ts.map +1 -1
- package/dist/composites/config-connector-context.d.ts +3 -1
- package/dist/composites/config-connector-context.d.ts.map +1 -1
- package/dist/composites/configured-app.d.ts +1 -1
- package/dist/composites/configured-app.d.ts.map +1 -1
- package/dist/composites/cron-workload.d.ts +6 -1
- package/dist/composites/cron-workload.d.ts.map +1 -1
- package/dist/composites/ebs-storage-class.d.ts +3 -1
- package/dist/composites/ebs-storage-class.d.ts.map +1 -1
- package/dist/composites/efs-storage-class.d.ts +3 -1
- package/dist/composites/efs-storage-class.d.ts.map +1 -1
- package/dist/composites/external-dns-agent.d.ts +6 -1
- package/dist/composites/external-dns-agent.d.ts.map +1 -1
- package/dist/composites/filestore-storage-class.d.ts +3 -1
- package/dist/composites/filestore-storage-class.d.ts.map +1 -1
- package/dist/composites/fluent-bit-agent.d.ts +7 -1
- package/dist/composites/fluent-bit-agent.d.ts.map +1 -1
- package/dist/composites/gce-ingress.d.ts +3 -1
- package/dist/composites/gce-ingress.d.ts.map +1 -1
- package/dist/composites/gce-pd-storage-class.d.ts +3 -1
- package/dist/composites/gce-pd-storage-class.d.ts.map +1 -1
- package/dist/composites/gke-external-dns-agent.d.ts +6 -1
- package/dist/composites/gke-external-dns-agent.d.ts.map +1 -1
- package/dist/composites/gke-fluent-bit-agent.d.ts +7 -1
- package/dist/composites/gke-fluent-bit-agent.d.ts.map +1 -1
- package/dist/composites/gke-gateway.d.ts +4 -1
- package/dist/composites/gke-gateway.d.ts.map +1 -1
- package/dist/composites/gke-otel-collector.d.ts +7 -1
- package/dist/composites/gke-otel-collector.d.ts.map +1 -1
- package/dist/composites/irsa-service-account.d.ts +1 -1
- package/dist/composites/irsa-service-account.d.ts.map +1 -1
- package/dist/composites/metrics-server.d.ts +10 -1
- package/dist/composites/metrics-server.d.ts.map +1 -1
- package/dist/composites/monitored-service.d.ts +1 -1
- package/dist/composites/monitored-service.d.ts.map +1 -1
- package/dist/composites/namespace-env.d.ts +1 -1
- package/dist/composites/namespace-env.d.ts.map +1 -1
- package/dist/composites/network-isolated-app.d.ts +5 -1
- package/dist/composites/network-isolated-app.d.ts.map +1 -1
- package/dist/composites/node-agent.d.ts +1 -1
- package/dist/composites/node-agent.d.ts.map +1 -1
- package/dist/composites/ray-cluster.d.ts +10 -1
- package/dist/composites/ray-cluster.d.ts.map +1 -1
- package/dist/composites/ray-job.d.ts +8 -1
- package/dist/composites/ray-job.d.ts.map +1 -1
- package/dist/composites/ray-service.d.ts +10 -1
- package/dist/composites/ray-service.d.ts.map +1 -1
- package/dist/composites/secure-ingress.d.ts +1 -1
- package/dist/composites/secure-ingress.d.ts.map +1 -1
- package/dist/composites/sidecar-app.d.ts +4 -1
- package/dist/composites/sidecar-app.d.ts.map +1 -1
- package/dist/composites/stateful-app.d.ts +1 -1
- package/dist/composites/stateful-app.d.ts.map +1 -1
- package/dist/composites/web-app.d.ts +1 -1
- package/dist/composites/web-app.d.ts.map +1 -1
- package/dist/composites/worker-pool.d.ts +1 -1
- package/dist/composites/worker-pool.d.ts.map +1 -1
- package/dist/composites/workload-identity-sa.d.ts +1 -1
- package/dist/composites/workload-identity-sa.d.ts.map +1 -1
- package/dist/composites/workload-identity-service-account.d.ts +1 -1
- package/dist/composites/workload-identity-service-account.d.ts.map +1 -1
- package/dist/config-schema.d.ts +40 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +15 -18
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +1 -0
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/spec/parse.d.ts +17 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/codegen/generate-list-map-keys.test.ts +112 -0
- package/src/codegen/generate-list-map-keys.ts +99 -0
- package/src/codegen/generate.ts +9 -1
- package/src/composites/adot-collector.ts +1 -1
- package/src/composites/agic-ingress.ts +1 -1
- package/src/composites/aks-external-dns-agent.ts +1 -1
- package/src/composites/alb-ingress.ts +1 -1
- package/src/composites/autoscaled-service.ts +1 -1
- package/src/composites/azure-disk-storage-class.ts +1 -1
- package/src/composites/azure-file-storage-class.ts +1 -1
- package/src/composites/azure-monitor-collector.ts +1 -1
- package/src/composites/batch-job.ts +1 -1
- package/src/composites/cockroachdb-cluster.ts +1 -1
- package/src/composites/config-connector-context.ts +1 -1
- package/src/composites/configured-app.ts +1 -1
- package/src/composites/cron-workload.ts +1 -1
- package/src/composites/ebs-storage-class.ts +1 -1
- package/src/composites/efs-storage-class.ts +1 -1
- package/src/composites/external-dns-agent.ts +1 -1
- package/src/composites/filestore-storage-class.ts +1 -1
- package/src/composites/fluent-bit-agent.ts +1 -1
- package/src/composites/gce-ingress.ts +1 -1
- package/src/composites/gce-pd-storage-class.ts +1 -1
- package/src/composites/gke-external-dns-agent.ts +1 -1
- package/src/composites/gke-fluent-bit-agent.ts +1 -1
- package/src/composites/gke-gateway.ts +1 -1
- package/src/composites/gke-otel-collector.ts +1 -1
- package/src/composites/irsa-service-account.ts +1 -1
- package/src/composites/metrics-server.ts +1 -1
- package/src/composites/monitored-service.ts +1 -1
- package/src/composites/namespace-env.ts +1 -1
- package/src/composites/network-isolated-app.ts +1 -1
- package/src/composites/node-agent.ts +1 -1
- package/src/composites/ray-cluster.ts +1 -1
- package/src/composites/ray-job.ts +1 -1
- package/src/composites/ray-service.ts +1 -1
- package/src/composites/secure-ingress.ts +1 -1
- package/src/composites/sidecar-app.ts +1 -1
- package/src/composites/stateful-app.ts +1 -1
- package/src/composites/web-app.ts +1 -1
- package/src/composites/worker-pool.ts +1 -1
- package/src/composites/workload-identity-sa.ts +1 -1
- package/src/composites/workload-identity-service-account.ts +1 -1
- package/src/config-schema.ts +46 -0
- package/src/config.ts +25 -1
- package/src/deep-observe-hooks.ts +90 -10
- package/src/deep-observe.ts +7 -0
- package/src/describe-resources.test.ts +138 -1
- package/src/describe-resources.ts +98 -0
- package/src/generated/list-map-keys.json +166 -0
- package/src/list-map-order-key.test.ts +119 -0
- package/src/plugin.ts +2 -0
- package/src/spec/parse.test.ts +42 -0
- package/src/spec/parse.ts +51 -2
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "4c4e0afb787bdb40c3ecae9bcb1018002a85bcd41dcbf5ac12478d1ba092c3d6",
|
|
5
5
|
"meta.json": "c0b141b882c51483c1d1aead748a0eb171aceca7432edd2a0404b062bf6d7831",
|
|
6
6
|
"types/index.d.ts": "3be2af7494aca94adfa16269cb9c4e7a155529872207a3d70b0c4d39914f9eec",
|
|
7
7
|
"rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9",
|
|
51
51
|
"skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5"
|
|
52
52
|
},
|
|
53
|
-
"composite": "
|
|
53
|
+
"composite": "5f8423c25fc4a50e91f0e7f2a8a8064fc8cc36b150f4b0c630a6cc56e0924995"
|
|
54
54
|
}
|
package/dist/manifest.json
CHANGED
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AAwBjG,eAAO,MAAM,SAAS,EAAE,aA6mBvB,CAAC"}
|
package/dist/spec/parse.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface ParsedProperty {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
enum?: string[];
|
|
17
17
|
constraints: PropertyConstraints;
|
|
18
|
+
/** `x-kubernetes-list-type` — `map`, `set` or `atomic`. Absent when the spec does not say (chant #1441). */
|
|
19
|
+
listType?: string;
|
|
20
|
+
/** `x-kubernetes-list-map-keys` — the fields that jointly identify one element. Present only alongside `listType: "map"`. */
|
|
21
|
+
listMapKeys?: string[];
|
|
18
22
|
}
|
|
19
23
|
export interface ParsedPropertyType {
|
|
20
24
|
name: string;
|
|
@@ -81,6 +85,19 @@ type SwaggerPathItem = Record<string, SwaggerOperation | unknown>;
|
|
|
81
85
|
*/
|
|
82
86
|
export declare function parseK8sSwagger(data: string | Buffer): K8sParseResult[];
|
|
83
87
|
/** Stable key for a GVK, used to join the `paths` pass onto the `definitions` pass. */
|
|
88
|
+
/**
|
|
89
|
+
* Every `(property name, list-map keys)` pair the spec declares, across ALL
|
|
90
|
+
* definitions (chant #1441).
|
|
91
|
+
*
|
|
92
|
+
* Deliberately independent of {@link parseK8sSwagger}'s resource/property-type
|
|
93
|
+
* extraction. That extraction keeps 257 resources and 320 property types out
|
|
94
|
+
* of roughly a thousand definitions, so collecting merge keys from its output
|
|
95
|
+
* alone finds 18 of the 32 properties the spec annotates — and misses
|
|
96
|
+
* `ServicePort.port`, whose absence would silently demote every Service port
|
|
97
|
+
* list to the hand-written fallback. A definition is worth reading for its
|
|
98
|
+
* merge semantics whether or not chant emits a type for it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function specListMapKeyPairs(data: string | Buffer): Array<[string, string[]]>;
|
|
84
101
|
export declare function gvkKey(gvk: GroupVersionKind): string;
|
|
85
102
|
/**
|
|
86
103
|
* Derive the operation surface from the OpenAPI `paths` — chant #1074.
|
package/dist/spec/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAShF,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-k8s",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"typescript": "^5.9.3"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"
|
|
81
|
+
"zod": "^4.3.6",
|
|
82
|
+
"@intentius/chant": "^0.41.0",
|
|
82
83
|
"typescript": "^5.9.3"
|
|
83
84
|
}
|
|
84
85
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { buildListMapKeyTable, parsedListMapKeyPairs, generateListMapKeysJSON } from "./generate-list-map-keys";
|
|
3
|
+
import type { K8sParseResult, ParsedProperty } from "../spec/parse";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* chant #1441 — the table the drift engine reads instead of six hardcoded
|
|
7
|
+
* property names.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function prop(name: string, over: Partial<ParsedProperty> = {}): ParsedProperty {
|
|
11
|
+
return { name, tsType: "any[]", required: false, constraints: {}, ...over };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function result(properties: ParsedProperty[], propertyTypeProps: ParsedProperty[] = []): K8sParseResult {
|
|
15
|
+
return {
|
|
16
|
+
resource: { typeName: "K8s::Test", properties, attributes: [], deprecatedProperties: [] },
|
|
17
|
+
propertyTypes: propertyTypeProps.length
|
|
18
|
+
? [{ name: "Nested", defType: "io.k8s.Nested", properties: propertyTypeProps }]
|
|
19
|
+
: [],
|
|
20
|
+
enums: [],
|
|
21
|
+
gvk: { group: "", version: "v1", kind: "Test" },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("buildListMapKeyTable", () => {
|
|
26
|
+
test("folds pairs into a by-name table with sorted key fields", () => {
|
|
27
|
+
expect(buildListMapKeyTable([["ports", ["protocol", "containerPort"]]])).toEqual({
|
|
28
|
+
ports: [["containerPort", "protocol"]],
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("keeps both variants when one name is keyed two ways", () => {
|
|
33
|
+
// The real case: container ports vs Service ports. Collapsing these would
|
|
34
|
+
// silently mis-identify one of them.
|
|
35
|
+
const table = buildListMapKeyTable([
|
|
36
|
+
["ports", ["containerPort", "protocol"]],
|
|
37
|
+
["ports", ["port", "protocol"]],
|
|
38
|
+
]);
|
|
39
|
+
expect(table.ports).toEqual([
|
|
40
|
+
["containerPort", "protocol"],
|
|
41
|
+
["port", "protocol"],
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("deduplicates the same key set seen on many definitions", () => {
|
|
46
|
+
// `conditions` is keyed by `type` at 31 sites in v1.36.2.
|
|
47
|
+
const pairs: Array<[string, string[]]> = Array.from({ length: 31 }, () => ["conditions", ["type"]]);
|
|
48
|
+
expect(buildListMapKeyTable(pairs)).toEqual({ conditions: [["type"]] });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("orders candidates longest-first, so a specific set is tried before a shorter one", () => {
|
|
52
|
+
const table = buildListMapKeyTable([
|
|
53
|
+
["devices", ["driver"]],
|
|
54
|
+
["devices", ["device", "driver", "pool", "shareID"]],
|
|
55
|
+
]);
|
|
56
|
+
expect(table.devices[0]).toHaveLength(4);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("merges independent sources", () => {
|
|
60
|
+
const table = buildListMapKeyTable([["containers", ["name"]]], [["conditions", ["type"]]]);
|
|
61
|
+
expect(Object.keys(table).sort()).toEqual(["conditions", "containers"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("names are emitted in sorted order, so regeneration produces a stable file", () => {
|
|
65
|
+
const table = buildListMapKeyTable([
|
|
66
|
+
["zeta", ["name"]],
|
|
67
|
+
["alpha", ["name"]],
|
|
68
|
+
]);
|
|
69
|
+
expect(Object.keys(table)).toEqual(["alpha", "zeta"]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("parsedListMapKeyPairs", () => {
|
|
74
|
+
test("collects from resource properties and nested property types alike", () => {
|
|
75
|
+
const pairs = parsedListMapKeyPairs([
|
|
76
|
+
result(
|
|
77
|
+
[prop("conditions", { listType: "map", listMapKeys: ["type"] })],
|
|
78
|
+
[prop("containers", { listType: "map", listMapKeys: ["name"] })],
|
|
79
|
+
),
|
|
80
|
+
]);
|
|
81
|
+
expect(pairs).toEqual([
|
|
82
|
+
["conditions", ["type"]],
|
|
83
|
+
["containers", ["name"]],
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("ignores atomic and set lists, and map lists with no keys", () => {
|
|
88
|
+
const pairs = parsedListMapKeyPairs([
|
|
89
|
+
result([
|
|
90
|
+
prop("args", { listType: "atomic" }),
|
|
91
|
+
prop("finalizers", { listType: "set" }),
|
|
92
|
+
prop("broken", { listType: "map" }),
|
|
93
|
+
prop("plain"),
|
|
94
|
+
]),
|
|
95
|
+
]);
|
|
96
|
+
expect(pairs).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("generateListMapKeysJSON", () => {
|
|
101
|
+
test("emits both sources and ends with a newline", () => {
|
|
102
|
+
const json = generateListMapKeysJSON(
|
|
103
|
+
[["ports", ["port", "protocol"]]],
|
|
104
|
+
[result([prop("myItems", { listType: "map", listMapKeys: ["id"] })])],
|
|
105
|
+
);
|
|
106
|
+
expect(json.endsWith("\n")).toBe(true);
|
|
107
|
+
expect(JSON.parse(json)).toEqual({
|
|
108
|
+
myItems: [["id"]],
|
|
109
|
+
ports: [["port", "protocol"]],
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Associative-list key table, derived from the spec rather than hardcoded
|
|
3
|
+
* (chant #1441).
|
|
4
|
+
*
|
|
5
|
+
* The Kubernetes OpenAPI spec states, per property, how the API server merges
|
|
6
|
+
* a list: `x-kubernetes-list-type: map` plus `x-kubernetes-list-map-keys`
|
|
7
|
+
* naming the fields that jointly identify one element. chant's drift engine
|
|
8
|
+
* needs exactly that to compare `containers` by name rather than by index.
|
|
9
|
+
* Before this, `k8sListMapOrderKey` (core's `managed-fields.ts`) hardcoded six
|
|
10
|
+
* properties; the spec annotates 32.
|
|
11
|
+
*
|
|
12
|
+
* ## Why the table is keyed by property name
|
|
13
|
+
*
|
|
14
|
+
* The consumer is `DeepNormalizationHooks.orderKey`, which receives an
|
|
15
|
+
* index-erased `pattern` (`spec.template.spec.containers`) and keys off its
|
|
16
|
+
* LAST segment. A by-name table matches that lookup exactly and needs no
|
|
17
|
+
* resolution of the definition graph at generation time.
|
|
18
|
+
*
|
|
19
|
+
* The cost is that two definitions declaring the same property name with
|
|
20
|
+
* different keys collapse into one entry. That is real but small, and it is
|
|
21
|
+
* represented rather than lost: each name maps to a LIST of candidate key
|
|
22
|
+
* sets, and the consumer picks the first whose fields are all present on the
|
|
23
|
+
* element. In v1.36.2 exactly one property needs it — `ports`, keyed
|
|
24
|
+
* `(containerPort, protocol)` on a container and `(port, protocol)` on a
|
|
25
|
+
* Service — which is the same dual shape the hardcoded implementation already
|
|
26
|
+
* special-cased by hand.
|
|
27
|
+
*
|
|
28
|
+
* ## Two sources
|
|
29
|
+
*
|
|
30
|
+
* The whole spec is read directly, because chant emits types for 257 of
|
|
31
|
+
* roughly a thousand definitions and the merge semantics of a definition are
|
|
32
|
+
* worth having whether or not it becomes a type. Reading only the parsed
|
|
33
|
+
* results finds 18 of the 32 properties and misses `ServicePort.port`.
|
|
34
|
+
*
|
|
35
|
+
* Parsed results are read too, because CRDs arrive that way — an operator CRD
|
|
36
|
+
* that declares these extensions contributes its keys for free.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import type { K8sParseResult, ParsedProperty } from "../spec/parse";
|
|
40
|
+
|
|
41
|
+
/** Property name → candidate key-field sets, most-specific first. */
|
|
42
|
+
export type ListMapKeyTable = Record<string, string[][]>;
|
|
43
|
+
|
|
44
|
+
function everyProperty(results: K8sParseResult[]): ParsedProperty[] {
|
|
45
|
+
const out: ParsedProperty[] = [];
|
|
46
|
+
for (const r of results) {
|
|
47
|
+
out.push(...r.resource.properties);
|
|
48
|
+
for (const pt of r.propertyTypes) out.push(...pt.properties);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The `(name, keys)` pairs carried on parsed properties — core types and every CRD. */
|
|
54
|
+
export function parsedListMapKeyPairs(results: K8sParseResult[]): Array<[string, string[]]> {
|
|
55
|
+
const pairs: Array<[string, string[]]> = [];
|
|
56
|
+
for (const p of everyProperty(results)) {
|
|
57
|
+
if (p.listType === "map" && p.listMapKeys?.length) pairs.push([p.name, p.listMapKeys]);
|
|
58
|
+
}
|
|
59
|
+
return pairs;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fold `(name, keys)` pairs into a by-name table.
|
|
64
|
+
*
|
|
65
|
+
* Candidate sets are deduplicated by content and ordered longest-first, so a
|
|
66
|
+
* more specific key set is tried before a shorter one whose fields a longer
|
|
67
|
+
* element would also satisfy.
|
|
68
|
+
*/
|
|
69
|
+
export function buildListMapKeyTable(...sources: Array<Array<[string, string[]]>>): ListMapKeyTable {
|
|
70
|
+
const byName = new Map<string, Map<string, string[]>>();
|
|
71
|
+
|
|
72
|
+
for (const pairs of sources) {
|
|
73
|
+
for (const [name, rawKeys] of pairs) {
|
|
74
|
+
const keys = [...rawKeys].sort();
|
|
75
|
+
const seen = byName.get(name) ?? new Map<string, string[]>();
|
|
76
|
+
seen.set(keys.join(" "), keys);
|
|
77
|
+
byName.set(name, seen);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const table: ListMapKeyTable = {};
|
|
82
|
+
for (const name of [...byName.keys()].sort()) {
|
|
83
|
+
const variants = byName.get(name);
|
|
84
|
+
if (!variants) continue;
|
|
85
|
+
const sets = [...variants.values()];
|
|
86
|
+
sets.sort((a, b) => b.length - a.length || a.join().localeCompare(b.join()));
|
|
87
|
+
table[name] = sets;
|
|
88
|
+
}
|
|
89
|
+
return table;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Serialize the `list-map-keys.json` generated artifact. */
|
|
93
|
+
export function generateListMapKeysJSON(
|
|
94
|
+
specPairs: Array<[string, string[]]>,
|
|
95
|
+
results: K8sParseResult[],
|
|
96
|
+
): string {
|
|
97
|
+
const table = buildListMapKeyTable(specPairs, parsedListMapKeyPairs(results));
|
|
98
|
+
return `${JSON.stringify(table, null, 2)}\n`;
|
|
99
|
+
}
|
package/src/codegen/generate.ts
CHANGED
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
type GeneratePipelineConfig,
|
|
13
13
|
} from "@intentius/chant/codegen/generate";
|
|
14
14
|
import { fetchSchemas } from "../spec/fetch";
|
|
15
|
-
import { parseK8sSwagger, k8sShortName, type K8sParseResult } from "../spec/parse";
|
|
15
|
+
import { parseK8sSwagger, specListMapKeyPairs, k8sShortName, type K8sParseResult } from "../spec/parse";
|
|
16
16
|
import { loadMultipleCRDs } from "../crd/loader";
|
|
17
17
|
import { CRD_SOURCES } from "../crd/crd-sources";
|
|
18
18
|
import { NamingStrategy, propertyTypeName, extractDefName } from "./naming";
|
|
19
19
|
import { generateLexiconJSON } from "./generate-lexicon";
|
|
20
20
|
import { generateOperationsJSON } from "./generate-operations";
|
|
21
|
+
import { generateListMapKeysJSON } from "./generate-list-map-keys";
|
|
21
22
|
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
22
23
|
import {
|
|
23
24
|
generateRuntimeIndex as coreGenerateRuntimeIndex,
|
|
@@ -38,6 +39,9 @@ export interface K8sGenerateOptions extends GenerateOptions {
|
|
|
38
39
|
export async function generate(opts: K8sGenerateOptions = {}): Promise<GenerateResult> {
|
|
39
40
|
// Pipeline state captured in closure — no module-level mutation
|
|
40
41
|
let pendingResults: K8sParseResult[] = [];
|
|
42
|
+
// chant #1441 — read off the raw document in `parseSchema`, since the
|
|
43
|
+
// result set covers only the definitions chant emits types for.
|
|
44
|
+
let specPairs: Array<[string, string[]]> = [];
|
|
41
45
|
|
|
42
46
|
const config: GeneratePipelineConfig<K8sParseResult> = {
|
|
43
47
|
fetchSchemas: async (fetchOpts) => {
|
|
@@ -49,6 +53,7 @@ export async function generate(opts: K8sGenerateOptions = {}): Promise<GenerateR
|
|
|
49
53
|
// The pipeline calls this once per schema entry. We return the first result
|
|
50
54
|
// and use augmentResults to inject the rest.
|
|
51
55
|
const results = parseK8sSwagger(data);
|
|
56
|
+
specPairs = specListMapKeyPairs(data);
|
|
52
57
|
if (results.length === 0) return null;
|
|
53
58
|
// Return the first result; stash the rest for augmentResults
|
|
54
59
|
pendingResults = results.slice(1);
|
|
@@ -105,6 +110,9 @@ export async function generate(opts: K8sGenerateOptions = {}): Promise<GenerateR
|
|
|
105
110
|
// differently from how the declarable surface names it.
|
|
106
111
|
generateExtraArtifacts: (results) => ({
|
|
107
112
|
"operations.json": generateOperationsJSON(results),
|
|
113
|
+
// chant #1441 — the spec's own associative-list keys, so drift compares
|
|
114
|
+
// list elements by identity instead of by index.
|
|
115
|
+
"list-map-keys.json": generateListMapKeysJSON(specPairs, results),
|
|
108
116
|
}),
|
|
109
117
|
};
|
|
110
118
|
|
|
@@ -66,7 +66,7 @@ export interface AksExternalDnsAgentResult {
|
|
|
66
66
|
* });
|
|
67
67
|
* ```
|
|
68
68
|
*/
|
|
69
|
-
export const AksExternalDnsAgent = Composite
|
|
69
|
+
export const AksExternalDnsAgent = Composite((props: AksExternalDnsAgentProps) => {
|
|
70
70
|
const {
|
|
71
71
|
clientId,
|
|
72
72
|
resourceGroup,
|
|
@@ -104,7 +104,7 @@ export interface AutoscaledServiceResult {
|
|
|
104
104
|
* });
|
|
105
105
|
* ```
|
|
106
106
|
*/
|
|
107
|
-
export const AutoscaledService = Composite
|
|
107
|
+
export const AutoscaledService = Composite((props: AutoscaledServiceProps) => {
|
|
108
108
|
const {
|
|
109
109
|
name,
|
|
110
110
|
image,
|
|
@@ -49,7 +49,7 @@ export interface AzureDiskStorageClassResult {
|
|
|
49
49
|
* });
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
export const AzureDiskStorageClass = Composite
|
|
52
|
+
export const AzureDiskStorageClass = Composite((props: AzureDiskStorageClassProps) => {
|
|
53
53
|
const {
|
|
54
54
|
name,
|
|
55
55
|
skuName = "Premium_LRS",
|
|
@@ -47,7 +47,7 @@ export interface AzureFileStorageClassResult {
|
|
|
47
47
|
* });
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
export const AzureFileStorageClass = Composite
|
|
50
|
+
export const AzureFileStorageClass = Composite((props: AzureFileStorageClassProps) => {
|
|
51
51
|
const {
|
|
52
52
|
name,
|
|
53
53
|
skuName = "Premium_LRS",
|
|
@@ -64,7 +64,7 @@ export interface AzureMonitorCollectorResult {
|
|
|
64
64
|
* });
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
-
export const AzureMonitorCollector = Composite
|
|
67
|
+
export const AzureMonitorCollector = Composite((props: AzureMonitorCollectorProps) => {
|
|
68
68
|
const {
|
|
69
69
|
workspaceId,
|
|
70
70
|
clusterName,
|
|
@@ -133,7 +133,7 @@ export interface CockroachDbClusterResult {
|
|
|
133
133
|
* });
|
|
134
134
|
* ```
|
|
135
135
|
*/
|
|
136
|
-
export const CockroachDbCluster = Composite
|
|
136
|
+
export const CockroachDbCluster = Composite((props: CockroachDbClusterProps) => {
|
|
137
137
|
const {
|
|
138
138
|
name,
|
|
139
139
|
namespace,
|
|
@@ -42,7 +42,7 @@ export interface ConfigConnectorContextResult {
|
|
|
42
42
|
* });
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export const ConfigConnectorContext = Composite
|
|
45
|
+
export const ConfigConnectorContext = Composite((props: ConfigConnectorContextProps) => {
|
|
46
46
|
const {
|
|
47
47
|
name = "configconnectorcontext.core.cnrm.cloud.google.com",
|
|
48
48
|
googleServiceAccountEmail,
|
|
@@ -59,7 +59,7 @@ export interface ExternalDnsAgentResult {
|
|
|
59
59
|
* });
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
export const ExternalDnsAgent = Composite
|
|
62
|
+
export const ExternalDnsAgent = Composite((props: ExternalDnsAgentProps) => {
|
|
63
63
|
const {
|
|
64
64
|
iamRoleArn,
|
|
65
65
|
domainFilters,
|
|
@@ -47,7 +47,7 @@ export interface FilestoreStorageClassResult {
|
|
|
47
47
|
* });
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
export const FilestoreStorageClass = Composite
|
|
50
|
+
export const FilestoreStorageClass = Composite((props: FilestoreStorageClassProps) => {
|
|
51
51
|
const {
|
|
52
52
|
name,
|
|
53
53
|
tier = "standard",
|
|
@@ -67,7 +67,7 @@ export interface FluentBitAgentResult {
|
|
|
67
67
|
* });
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
export const FluentBitAgent = Composite
|
|
70
|
+
export const FluentBitAgent = Composite((props: FluentBitAgentProps) => {
|
|
71
71
|
const {
|
|
72
72
|
name = "fluent-bit",
|
|
73
73
|
image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable",
|
|
@@ -49,7 +49,7 @@ export interface GcePdStorageClassResult {
|
|
|
49
49
|
* });
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
export const GcePdStorageClass = Composite
|
|
52
|
+
export const GcePdStorageClass = Composite((props: GcePdStorageClassProps) => {
|
|
53
53
|
const {
|
|
54
54
|
name,
|
|
55
55
|
type = "pd-balanced",
|
|
@@ -60,7 +60,7 @@ export interface GkeExternalDnsAgentResult {
|
|
|
60
60
|
* });
|
|
61
61
|
* ```
|
|
62
62
|
*/
|
|
63
|
-
export const GkeExternalDnsAgent = Composite
|
|
63
|
+
export const GkeExternalDnsAgent = Composite((props: GkeExternalDnsAgentProps) => {
|
|
64
64
|
const {
|
|
65
65
|
gcpServiceAccountEmail,
|
|
66
66
|
gcpProjectId,
|
|
@@ -65,7 +65,7 @@ export interface GkeFluentBitAgentResult {
|
|
|
65
65
|
* });
|
|
66
66
|
* ```
|
|
67
67
|
*/
|
|
68
|
-
export const GkeFluentBitAgent = Composite
|
|
68
|
+
export const GkeFluentBitAgent = Composite((props: GkeFluentBitAgentProps) => {
|
|
69
69
|
const {
|
|
70
70
|
clusterName,
|
|
71
71
|
projectId,
|
|
@@ -68,7 +68,7 @@ export interface GkeGatewayResult {
|
|
|
68
68
|
* });
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
|
-
export const GkeGateway = Composite
|
|
71
|
+
export const GkeGateway = Composite((props: GkeGatewayProps) => {
|
|
72
72
|
const {
|
|
73
73
|
name,
|
|
74
74
|
gatewayClassName = "gke-l7-global-external-managed",
|
|
@@ -65,7 +65,7 @@ export interface GkeOtelCollectorResult {
|
|
|
65
65
|
* });
|
|
66
66
|
* ```
|
|
67
67
|
*/
|
|
68
|
-
export const GkeOtelCollector = Composite
|
|
68
|
+
export const GkeOtelCollector = Composite((props: GkeOtelCollectorProps) => {
|
|
69
69
|
const {
|
|
70
70
|
clusterName,
|
|
71
71
|
projectId,
|
|
@@ -55,7 +55,7 @@ export interface IrsaServiceAccountResult {
|
|
|
55
55
|
* });
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
export const IrsaServiceAccount = Composite
|
|
58
|
+
export const IrsaServiceAccount = Composite((props: IrsaServiceAccountProps) => {
|
|
59
59
|
const {
|
|
60
60
|
name,
|
|
61
61
|
iamRoleArn,
|
|
@@ -57,7 +57,7 @@ export interface MetricsServerResult {
|
|
|
57
57
|
* const ms = MetricsServer({});
|
|
58
58
|
* ```
|
|
59
59
|
*/
|
|
60
|
-
export const MetricsServer = Composite
|
|
60
|
+
export const MetricsServer = Composite((props: MetricsServerProps) => {
|
|
61
61
|
const {
|
|
62
62
|
name = "metrics-server",
|
|
63
63
|
image = "registry.k8s.io/metrics-server/metrics-server:v0.7.2",
|
|
@@ -85,7 +85,7 @@ export interface NetworkIsolatedAppResult {
|
|
|
85
85
|
* });
|
|
86
86
|
* ```
|
|
87
87
|
*/
|
|
88
|
-
export const NetworkIsolatedApp = Composite
|
|
88
|
+
export const NetworkIsolatedApp = Composite((props: NetworkIsolatedAppProps) => {
|
|
89
89
|
const {
|
|
90
90
|
name,
|
|
91
91
|
image,
|