@intentius/chant-lexicon-k8s 0.97.0 → 0.99.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/import/embedded.d.ts +27 -0
- package/dist/import/embedded.d.ts.map +1 -0
- package/dist/import/generator.d.ts +2 -0
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts +9 -3
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/import/testdata/embedded/fixtures.d.ts +21 -0
- package/dist/import/testdata/embedded/fixtures.d.ts.map +1 -0
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +3 -3
- package/src/import/embedded-roundtrip.test.ts +231 -0
- package/src/import/embedded-types.e2e.test.ts +43 -0
- package/src/import/embedded.test.ts +125 -0
- package/src/import/embedded.ts +104 -0
- package/src/import/generator.ts +36 -7
- package/src/import/parser.test.ts +18 -0
- package/src/import/parser.ts +19 -11
- package/src/import/testdata/embedded/README.md +11 -0
- package/src/import/testdata/embedded/agent-observability.yaml +2905 -0
- package/src/import/testdata/embedded/fixtures.ts +53 -0
- package/src/import/testdata/embedded/grafana-dashboard-configmap.yaml +364 -0
- package/src/import/testdata/embedded/node-exporter-prometheusrule.yaml +425 -0
- package/src/import/testdata/embedded/otel-collector-daemonset.yaml +225 -0
package/src/import/generator.ts
CHANGED
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
import type { TypeScriptGenerator, GeneratedFile } from "@intentius/chant/import/generator";
|
|
9
9
|
import type { TemplateIR, ResourceIR } from "@intentius/chant/import/parser";
|
|
10
|
+
import {
|
|
11
|
+
EmbeddedImportScope,
|
|
12
|
+
isEmbeddedReference,
|
|
13
|
+
renderEmbeddedReference,
|
|
14
|
+
} from "@intentius/chant/import/embedded";
|
|
15
|
+
|
|
16
|
+
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
17
|
+
|
|
18
|
+
/** An object key as TypeScript: bare when it is an identifier, quoted otherwise (`"config.yaml"`, `"app.kubernetes.io/name"`). */
|
|
19
|
+
function propertyKey(key: string): string {
|
|
20
|
+
return IDENT.test(key) ? key : JSON.stringify(key);
|
|
21
|
+
}
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* Map K8s entity types to their constructor class names.
|
|
@@ -99,6 +111,9 @@ const SERVICE_PORT_TYPES = new Set([
|
|
|
99
111
|
* Generate TypeScript source code from a Kubernetes IR.
|
|
100
112
|
*/
|
|
101
113
|
export class K8sGenerator implements TypeScriptGenerator {
|
|
114
|
+
/** The imports for embedded content the current `generate()` call references (#2962). */
|
|
115
|
+
private scope = new EmbeddedImportScope("");
|
|
116
|
+
|
|
102
117
|
generate(ir: TemplateIR): GeneratedFile[] {
|
|
103
118
|
const lines: string[] = [];
|
|
104
119
|
|
|
@@ -112,10 +127,13 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
112
127
|
this.collectNestedConstructors(resource.properties, usedConstructors, resource.type);
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
// Names the embedded content's imports must not take: every constructor this file may use, and its exports.
|
|
131
|
+
this.scope = new EmbeddedImportScope("", [
|
|
132
|
+
...usedConstructors,
|
|
133
|
+
...Object.values(PROPERTY_CONSTRUCTORS),
|
|
134
|
+
"ServicePort",
|
|
135
|
+
...ir.resources.map((r) => r.logicalId),
|
|
136
|
+
]);
|
|
119
137
|
|
|
120
138
|
// Emit namespaces as comments
|
|
121
139
|
if (ir.metadata?.namespaces && Array.isArray(ir.metadata.namespaces)) {
|
|
@@ -139,7 +157,16 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
139
157
|
lines.push("");
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
|
|
160
|
+
// Import statements: the k8s constructors, then what embedded content references.
|
|
161
|
+
// The constructors are read back from what was emitted (string literals
|
|
162
|
+
// taken out), so a nested one the walk above does not reach (a
|
|
163
|
+
// container's env) is imported too.
|
|
164
|
+
const code = lines.join("\n").replace(/"(?:[^"\\\n]|\\.)*"/g, '""');
|
|
165
|
+
for (const m of code.matchAll(/\bnew ([A-Z][A-Za-z0-9_]*)\(/g)) usedConstructors.add(m[1]);
|
|
166
|
+
const imports = [...usedConstructors].sort().join(", ");
|
|
167
|
+
const head = [`import { ${imports} } from "@intentius/chant-lexicon-k8s";`, ...this.scope.lines(), ""];
|
|
168
|
+
|
|
169
|
+
return [{ path: "main.ts", content: [...head, ...lines].join("\n") }];
|
|
143
170
|
}
|
|
144
171
|
|
|
145
172
|
/**
|
|
@@ -199,7 +226,7 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
199
226
|
for (const [key, value] of Object.entries(props)) {
|
|
200
227
|
if (value === undefined || value === null) continue;
|
|
201
228
|
const emitted = this.emitValue(key, value, depth + 1, parentType);
|
|
202
|
-
entries.push(`${innerIndent}${key}: ${emitted},`);
|
|
229
|
+
entries.push(`${innerIndent}${propertyKey(key)}: ${emitted},`);
|
|
203
230
|
}
|
|
204
231
|
|
|
205
232
|
if (entries.length === 0) return "{}";
|
|
@@ -208,6 +235,7 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
208
235
|
|
|
209
236
|
private emitValue(key: string, value: unknown, depth: number, parentType: string): string {
|
|
210
237
|
if (value === null || value === undefined) return "undefined";
|
|
238
|
+
if (isEmbeddedReference(value)) return renderEmbeddedReference(value, this.scope, depth * 2);
|
|
211
239
|
|
|
212
240
|
// Special case: "ports" on Service → ServicePort constructor
|
|
213
241
|
if (key === "ports" && SERVICE_PORT_TYPES.has(parentType) && Array.isArray(value)) {
|
|
@@ -256,6 +284,7 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
256
284
|
|
|
257
285
|
private emitLiteral(value: unknown, depth: number, parentType: string): string {
|
|
258
286
|
if (value === null || value === undefined) return "undefined";
|
|
287
|
+
if (isEmbeddedReference(value)) return renderEmbeddedReference(value, this.scope, depth * 2);
|
|
259
288
|
if (typeof value === "string") return JSON.stringify(value);
|
|
260
289
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
261
290
|
|
|
@@ -275,7 +304,7 @@ export class K8sGenerator implements TypeScriptGenerator {
|
|
|
275
304
|
const indent = " ".repeat(depth);
|
|
276
305
|
const innerIndent = " ".repeat(depth + 1);
|
|
277
306
|
const items = entries.map(
|
|
278
|
-
([k, v]) => `${innerIndent}${k}: ${this.emitLiteral(v, depth + 1, parentType)},`,
|
|
307
|
+
([k, v]) => `${innerIndent}${propertyKey(k)}: ${this.emitLiteral(v, depth + 1, parentType)},`,
|
|
279
308
|
);
|
|
280
309
|
return `{\n${items.join("\n")}\n${indent}}`;
|
|
281
310
|
}
|
|
@@ -43,6 +43,24 @@ metadata:
|
|
|
43
43
|
expect(ir.resources.length).toBe(2);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
test("multi-doc YAML splits on commented separators and document ends (#2965)", () => {
|
|
47
|
+
const yaml = `--- # the app
|
|
48
|
+
apiVersion: apps/v1
|
|
49
|
+
kind: Deployment
|
|
50
|
+
metadata:
|
|
51
|
+
name: app
|
|
52
|
+
...
|
|
53
|
+
--- # its service
|
|
54
|
+
apiVersion: v1
|
|
55
|
+
kind: Service
|
|
56
|
+
metadata:
|
|
57
|
+
name: svc
|
|
58
|
+
...
|
|
59
|
+
`;
|
|
60
|
+
const ir = parser.parse(yaml);
|
|
61
|
+
expect(ir.resources.map((r) => r.type)).toEqual(["K8s::Apps::Deployment", "K8s::Core::Service"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
46
64
|
test("apiVersion: apps/v1, kind: Deployment → K8s::Apps::Deployment", () => {
|
|
47
65
|
const yaml = `
|
|
48
66
|
apiVersion: apps/v1
|
package/src/import/parser.ts
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* convention.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { TemplateParser, TemplateIR, ResourceIR } from "@intentius/chant/import/parser";
|
|
10
|
-
import {
|
|
9
|
+
import type { TemplateParser, TemplateIR, ResourceIR, ParseContext } from "@intentius/chant/import/parser";
|
|
10
|
+
import type { EmbeddedContentResolver } from "@intentius/chant/import/embedded";
|
|
11
|
+
import { parseYAML, splitYAMLDocuments } from "@intentius/chant/yaml";
|
|
11
12
|
import { namespaceSegmentForGroup } from "../group-namespace";
|
|
13
|
+
import { delegateEmbedded } from "./embedded";
|
|
12
14
|
|
|
13
15
|
// ── GVK to type name mapping ───────────────────────────────────────
|
|
14
16
|
|
|
@@ -130,25 +132,29 @@ function toLogicalId(kind: string, name: string | undefined): string {
|
|
|
130
132
|
/**
|
|
131
133
|
* Kubernetes YAML parser implementation.
|
|
132
134
|
*
|
|
133
|
-
* Supports multi-document YAML (split on
|
|
135
|
+
* Supports multi-document YAML (split on `---`, see `splitYAMLDocuments`). Each document is expected
|
|
134
136
|
* to be a standard Kubernetes object with apiVersion, kind, and metadata.
|
|
135
137
|
*/
|
|
136
138
|
export class K8sParser implements TemplateParser {
|
|
137
|
-
|
|
139
|
+
/**
|
|
140
|
+
* With `context.embedded` (`chant import`), content another lexicon owns
|
|
141
|
+
* is offered to it: every ConfigMap `data` value and a PrometheusRule's
|
|
142
|
+
* `spec.groups` (see ./embedded.ts). Without it, everything is kept as
|
|
143
|
+
* written.
|
|
144
|
+
*/
|
|
145
|
+
parse(content: string, context?: ParseContext): TemplateIR {
|
|
138
146
|
const resources: ResourceIR[] = [];
|
|
139
147
|
const namespaces = new Set<string>();
|
|
140
148
|
|
|
141
|
-
// Split on
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
.map((d) => d.trim())
|
|
145
|
-
.filter((d) => d.length > 0 && !/^[\s#]*$/.test(d.replace(/#[^\n]*/g, "")));
|
|
149
|
+
// Split into documents on `---` / `...` markers (a trailing `# comment`
|
|
150
|
+
// allowed); empty and comment-only documents are dropped (#2965).
|
|
151
|
+
const documents = splitYAMLDocuments(content);
|
|
146
152
|
|
|
147
153
|
for (const docStr of documents) {
|
|
148
154
|
const doc = parseYAML(docStr);
|
|
149
155
|
if (!doc || typeof doc !== "object") continue;
|
|
150
156
|
|
|
151
|
-
const resource = this.parseDocument(doc as Record<string, unknown
|
|
157
|
+
const resource = this.parseDocument(doc as Record<string, unknown>, context?.embedded);
|
|
152
158
|
if (resource) {
|
|
153
159
|
resources.push(resource);
|
|
154
160
|
|
|
@@ -172,7 +178,7 @@ export class K8sParser implements TemplateParser {
|
|
|
172
178
|
};
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
private parseDocument(doc: Record<string, unknown
|
|
181
|
+
private parseDocument(doc: Record<string, unknown>, embedded?: EmbeddedContentResolver): ResourceIR | null {
|
|
176
182
|
const apiVersion = doc.apiVersion as string | undefined;
|
|
177
183
|
const kind = doc.kind as string | undefined;
|
|
178
184
|
|
|
@@ -190,6 +196,8 @@ export class K8sParser implements TemplateParser {
|
|
|
190
196
|
properties[key] = value;
|
|
191
197
|
}
|
|
192
198
|
|
|
199
|
+
if (embedded) delegateEmbedded(type, kind, properties, embedded);
|
|
200
|
+
|
|
193
201
|
return {
|
|
194
202
|
logicalId,
|
|
195
203
|
type,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Embedded-content import fixtures
|
|
2
|
+
|
|
3
|
+
Manifests that hold another lexicon's content, for `embedded-roundtrip.test.ts`
|
|
4
|
+
and `embedded-types.e2e.test.ts` (#2962).
|
|
5
|
+
|
|
6
|
+
| File | Source |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `otel-collector-daemonset.yaml` | open-telemetry/opentelemetry-helm-charts at `b0bce1305083b574c63d6d537431f0b08d36e17f`, chart opentelemetry-collector 0.173.1 (collector 0.160.0): `charts/opentelemetry-collector/examples/daemonset-only/rendered/configmap-agent.yaml` followed by `daemonset.yaml` from the same directory, unchanged. The config is under the chart's `relay` key. |
|
|
9
|
+
| `node-exporter-prometheusrule.yaml` | prometheus-operator/kube-prometheus `v0.19.0`, `manifests/nodeExporter-prometheusRule.yaml`, unchanged. |
|
|
10
|
+
| `grafana-dashboard-configmap.yaml` | prometheus-operator/kube-prometheus `v0.19.0`, `manifests/grafana-dashboardDefinitions.yaml`: the `grafana-dashboard-alertmanager-overview` item of that ConfigMapList, written as a ConfigMap of its own with the Grafana sidecar's `grafana_dashboard: "1"` label added, re-serialized by js-yaml. The dashboard JSON is unchanged. |
|
|
11
|
+
| `agent-observability.yaml` | `chant build src --lexicon k8s` of `examples/agent-observability` as of #2954: collector ConfigMaps from the k8s collector composites, Prometheus's `rules.yml` from `ruleFileYaml`, and the Grafana dashboard, datasource and provider ConfigMaps from `GrafanaConfigMaps` (`@intentius/chant-lexicon-grafana/k8s`). |
|