@intentius/chant-lexicon-k8s 0.124.31 → 0.124.33

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.
Files changed (270) hide show
  1. package/dist/actions/apps.js +18 -0
  2. package/dist/actions/batch.js +6 -0
  3. package/dist/actions/core.js +48 -0
  4. package/dist/actions/index.js +13 -0
  5. package/dist/actions/networking.js +11 -0
  6. package/dist/actions/rbac.js +8 -0
  7. package/dist/api/classify.js +123 -0
  8. package/dist/api/connect.js +62 -0
  9. package/dist/api/fake-cluster.js +175 -0
  10. package/dist/api/operation-surface.d.ts.map +1 -1
  11. package/dist/api/operation-surface.js +109 -0
  12. package/dist/api/owner-chain.js +100 -0
  13. package/dist/api/sweep-types.js +35 -0
  14. package/dist/behaviour-kinds.js +82 -0
  15. package/dist/codegen/docs-cli.js +2 -0
  16. package/dist/codegen/docs.js +146 -0
  17. package/dist/codegen/generate-cli.js +30 -0
  18. package/dist/codegen/generate-lexicon.js +74 -0
  19. package/dist/codegen/generate-list-map-keys.js +88 -0
  20. package/dist/codegen/generate-operations.js +53 -0
  21. package/dist/codegen/generate-typescript.js +124 -0
  22. package/dist/codegen/generate.js +173 -0
  23. package/dist/codegen/naming.js +174 -0
  24. package/dist/codegen/package.js +36 -0
  25. package/dist/codegen/patches.js +88 -0
  26. package/dist/codegen/typecheck.js +4 -0
  27. package/dist/codegen/versions.js +41 -0
  28. package/dist/components/argo-app.js +51 -0
  29. package/dist/components/builders.js +22 -0
  30. package/dist/components/capability-plugin.js +31 -0
  31. package/dist/components/flux-reconcile.js +61 -0
  32. package/dist/components/index.d.ts +1 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +18 -0
  35. package/dist/components/kubectl-apply.d.ts +83 -1
  36. package/dist/components/kubectl-apply.d.ts.map +1 -1
  37. package/dist/components/kubectl-apply.js +274 -0
  38. package/dist/components/kustomize-apply.js +25 -0
  39. package/dist/composites/adot-collector.js +184 -0
  40. package/dist/composites/agic-ingress.js +85 -0
  41. package/dist/composites/aks-external-dns-agent.js +146 -0
  42. package/dist/composites/alb-ingress.js +86 -0
  43. package/dist/composites/argo-app.js +199 -0
  44. package/dist/composites/autoscaled-service.js +174 -0
  45. package/dist/composites/azure-disk-storage-class.js +47 -0
  46. package/dist/composites/azure-file-storage-class.js +48 -0
  47. package/dist/composites/azure-monitor-collector.js +176 -0
  48. package/dist/composites/batch-job.js +139 -0
  49. package/dist/composites/catalog.js +4110 -0
  50. package/dist/composites/cockroachdb-cluster.js +371 -0
  51. package/dist/composites/cockroachdb-region-stack.js +369 -0
  52. package/dist/composites/config-connector-context.js +40 -0
  53. package/dist/composites/configured-app.js +135 -0
  54. package/dist/composites/cron-schedule.js +38 -0
  55. package/dist/composites/cron-workload.js +109 -0
  56. package/dist/composites/ebs-storage-class.js +54 -0
  57. package/dist/composites/efs-storage-class.js +46 -0
  58. package/dist/composites/external-dns-agent.js +122 -0
  59. package/dist/composites/filestore-storage-class.js +49 -0
  60. package/dist/composites/fluent-bit-agent.js +157 -0
  61. package/dist/composites/flux-app.js +156 -0
  62. package/dist/composites/gce-ingress.js +83 -0
  63. package/dist/composites/gce-pd-storage-class.js +49 -0
  64. package/dist/composites/gke-external-dns-agent.js +129 -0
  65. package/dist/composites/gke-fluent-bit-agent.js +165 -0
  66. package/dist/composites/gke-gateway.js +91 -0
  67. package/dist/composites/gke-otel-collector.js +94 -0
  68. package/dist/composites/index.js +51 -0
  69. package/dist/composites/inference-service.js +76 -0
  70. package/dist/composites/irsa-service-account.js +77 -0
  71. package/dist/composites/member-defaults.js +14 -0
  72. package/dist/composites/metrics-server.js +181 -0
  73. package/dist/composites/model.js +92 -0
  74. package/dist/composites/monitored-service.js +152 -0
  75. package/dist/composites/namespace-env.js +114 -0
  76. package/dist/composites/network-isolated-app.js +124 -0
  77. package/dist/composites/node-agent.js +149 -0
  78. package/dist/composites/operator-stack.js +268 -0
  79. package/dist/composites/otel-collector-agent.js +142 -0
  80. package/dist/composites/otel-collector-gateway.js +216 -0
  81. package/dist/composites/otel-collector-node.js +176 -0
  82. package/dist/composites/otel-collector-rbac.js +115 -0
  83. package/dist/composites/otel-collector-shape.js +128 -0
  84. package/dist/composites/otel-collector.js +115 -0
  85. package/dist/composites/otel-operator-collector.js +123 -0
  86. package/dist/composites/ray-cluster.js +378 -0
  87. package/dist/composites/ray-job.js +136 -0
  88. package/dist/composites/ray-service.js +178 -0
  89. package/dist/composites/secure-ingress.js +100 -0
  90. package/dist/composites/security-context.js +1 -0
  91. package/dist/composites/sidecar-app.js +105 -0
  92. package/dist/composites/stateful-app.js +111 -0
  93. package/dist/composites/vllm-serving-runtime.js +114 -0
  94. package/dist/composites/web-app.js +157 -0
  95. package/dist/composites/worker-pool.js +167 -0
  96. package/dist/composites/workload-identity-sa.js +81 -0
  97. package/dist/composites/workload-identity-service-account.js +77 -0
  98. package/dist/config-schema.js +72 -0
  99. package/dist/config.js +59 -0
  100. package/dist/coverage.js +27 -0
  101. package/dist/crd/crd-sources.js +615 -0
  102. package/dist/crd/loader.js +181 -0
  103. package/dist/crd/oci-chart.js +144 -0
  104. package/dist/crd/parser.js +382 -0
  105. package/dist/crd/project-codegen.js +273 -0
  106. package/dist/crd/types.js +7 -0
  107. package/dist/deep-observe-hooks.js +306 -0
  108. package/dist/deep-observe.js +234 -0
  109. package/dist/default-labels.js +87 -0
  110. package/dist/describe-resources.js +699 -0
  111. package/dist/describe-stack-status.js +99 -0
  112. package/dist/detect.js +11 -0
  113. package/dist/effect-receipt-row.js +205 -0
  114. package/dist/export-resources.js +39 -0
  115. package/dist/generated/index.js +1092 -0
  116. package/dist/generated/runtime.js +4 -0
  117. package/dist/group-namespace.js +96 -0
  118. package/dist/import/embedded.js +197 -0
  119. package/dist/import/generator.js +288 -0
  120. package/dist/import/live-export.js +85 -0
  121. package/dist/import/parser.js +204 -0
  122. package/dist/import/testdata/embedded/fixtures.js +60 -0
  123. package/dist/include/entity.js +78 -0
  124. package/dist/include/resolve.js +170 -0
  125. package/dist/index.js +59 -0
  126. package/dist/integrity.json +3 -3
  127. package/dist/kube/apply.js +90 -0
  128. package/dist/kube/connect.js +46 -0
  129. package/dist/kube/delete.js +138 -0
  130. package/dist/kube/describe.js +169 -0
  131. package/dist/kube/events.js +132 -0
  132. package/dist/kube/flags.js +91 -0
  133. package/dist/kube/get.js +159 -0
  134. package/dist/kube/group.js +123 -0
  135. package/dist/kube/logs.js +82 -0
  136. package/dist/kube/project.js +94 -0
  137. package/dist/kube/render.js +227 -0
  138. package/dist/kube/source.js +122 -0
  139. package/dist/kube/target.js +63 -0
  140. package/dist/kube/testing.js +28 -0
  141. package/dist/kube/top.js +99 -0
  142. package/dist/kube/verdict.js +72 -0
  143. package/dist/kube/wait.js +129 -0
  144. package/dist/kustomize/render.js +73 -0
  145. package/dist/kustomize/rendered-entity.js +67 -0
  146. package/dist/kustomize/root.js +68 -0
  147. package/dist/lint/audit-catalog.js +61 -0
  148. package/dist/lint/audit-lineage.js +118 -0
  149. package/dist/lint/post-synth/argo-helpers.js +36 -0
  150. package/dist/lint/post-synth/argo002.js +31 -0
  151. package/dist/lint/post-synth/argo003.js +53 -0
  152. package/dist/lint/post-synth/argo005.js +54 -0
  153. package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
  154. package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
  155. package/dist/lint/post-synth/flux002.js +56 -0
  156. package/dist/lint/post-synth/flux003.js +50 -0
  157. package/dist/lint/post-synth/index.js +104 -0
  158. package/dist/lint/post-synth/k8s-helpers.js +186 -0
  159. package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
  160. package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
  161. package/dist/lint/post-synth/sops-helpers.js +43 -0
  162. package/dist/lint/post-synth/wk8005.js +44 -0
  163. package/dist/lint/post-synth/wk8006.js +56 -0
  164. package/dist/lint/post-synth/wk8041.js +60 -0
  165. package/dist/lint/post-synth/wk8042.js +36 -0
  166. package/dist/lint/post-synth/wk8101.js +52 -0
  167. package/dist/lint/post-synth/wk8102.js +30 -0
  168. package/dist/lint/post-synth/wk8103.js +33 -0
  169. package/dist/lint/post-synth/wk8104.js +58 -0
  170. package/dist/lint/post-synth/wk8105.js +33 -0
  171. package/dist/lint/post-synth/wk8201.js +44 -0
  172. package/dist/lint/post-synth/wk8202.js +34 -0
  173. package/dist/lint/post-synth/wk8203.js +34 -0
  174. package/dist/lint/post-synth/wk8204.js +71 -0
  175. package/dist/lint/post-synth/wk8205.js +38 -0
  176. package/dist/lint/post-synth/wk8207.js +33 -0
  177. package/dist/lint/post-synth/wk8208.js +33 -0
  178. package/dist/lint/post-synth/wk8209.js +33 -0
  179. package/dist/lint/post-synth/wk8301.js +49 -0
  180. package/dist/lint/post-synth/wk8302.js +34 -0
  181. package/dist/lint/post-synth/wk8303.js +68 -0
  182. package/dist/lint/post-synth/wk8304.js +58 -0
  183. package/dist/lint/post-synth/wk8305.js +96 -0
  184. package/dist/lint/post-synth/wk8306.js +38 -0
  185. package/dist/lint/post-synth/wk8401.js +86 -0
  186. package/dist/lint/post-synth/wk8402.js +31 -0
  187. package/dist/lint/post-synth/wk8403.js +49 -0
  188. package/dist/lint/post-synth/wk8404.js +36 -0
  189. package/dist/lint/post-synth/wk8405.js +65 -0
  190. package/dist/lint/post-synth/wk8406.js +50 -0
  191. package/dist/lint/post-synth/wk8407.js +58 -0
  192. package/dist/lint/post-synth/wk8501.js +36 -0
  193. package/dist/lint/post-synth/wk8502.js +35 -0
  194. package/dist/lint/post-synth/wk8503.js +164 -0
  195. package/dist/lint/post-synth/wk8504.js +20 -0
  196. package/dist/lint/post-synth/wk8505.js +31 -0
  197. package/dist/lint/post-synth/wk8601.js +40 -0
  198. package/dist/lint/post-synth/wk8602.js +120 -0
  199. package/dist/lint/post-synth/wk8603.js +63 -0
  200. package/dist/lint/post-synth/wk8604.js +31 -0
  201. package/dist/lint/post-synth/wk8605.js +124 -0
  202. package/dist/lint/post-synth/wk8606.js +22 -0
  203. package/dist/lint/post-synth/wk8701.js +31 -0
  204. package/dist/lint/post-synth/wk8702.js +37 -0
  205. package/dist/lint/post-synth/wk8703.js +31 -0
  206. package/dist/lint/post-synth/wk8704.js +135 -0
  207. package/dist/lint/post-synth/wk8705.js +38 -0
  208. package/dist/lint/rules/argo-appset-single-project.js +56 -0
  209. package/dist/lint/rules/argo-ast.js +94 -0
  210. package/dist/lint/rules/argo-automated-prune.js +59 -0
  211. package/dist/lint/rules/flux-source-ref-pin.js +44 -0
  212. package/dist/lint/rules/hardcoded-namespace.js +57 -0
  213. package/dist/lint/rules/latest-image-tag.js +103 -0
  214. package/dist/lint/rules/missing-resource-limits.js +85 -0
  215. package/dist/lsp/completions.d.ts.map +1 -1
  216. package/dist/lsp/completions.js +18 -0
  217. package/dist/lsp/hover.d.ts.map +1 -1
  218. package/dist/lsp/hover.js +57 -0
  219. package/dist/manifest-entity.js +71 -0
  220. package/dist/manifest.json +1 -1
  221. package/dist/micro-time.js +49 -0
  222. package/dist/op/activities/apply-diff.d.ts +31 -0
  223. package/dist/op/activities/apply-diff.d.ts.map +1 -0
  224. package/dist/op/activities/apply-diff.js +79 -0
  225. package/dist/op/activities/argo.js +98 -0
  226. package/dist/op/activities/ensure-secret.js +38 -0
  227. package/dist/op/activities/index.js +51 -0
  228. package/dist/op/activities/kubectl.d.ts +64 -0
  229. package/dist/op/activities/kubectl.d.ts.map +1 -1
  230. package/dist/op/activities/kubectl.js +517 -0
  231. package/dist/op/activities/wait-for-ready.js +229 -0
  232. package/dist/op/builders.js +68 -0
  233. package/dist/package-cli.js +21 -0
  234. package/dist/patch/json-patch.js +187 -0
  235. package/dist/patch/rendered-patch.js +94 -0
  236. package/dist/plugin.js +741 -0
  237. package/dist/project-kinds.js +56 -0
  238. package/dist/receipt-store.js +216 -0
  239. package/dist/rules/crd-schema-helpers.ts +2 -1
  240. package/dist/secret-labels.js +33 -0
  241. package/dist/secret-store.js +116 -0
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/serializer.js +578 -0
  244. package/dist/sops/encrypted-secret-file.js +194 -0
  245. package/dist/sops/entity.js +32 -0
  246. package/dist/spec/fetch.js +58 -0
  247. package/dist/spec/parse.js +622 -0
  248. package/dist/subscribe-changes.js +168 -0
  249. package/dist/teardown.js +197 -0
  250. package/dist/telemetry-release.js +86 -0
  251. package/dist/validate-cli.js +17 -0
  252. package/dist/validate.js +47 -0
  253. package/dist/variables.js +34 -0
  254. package/dist/whoami.js +109 -0
  255. package/package.json +12 -12
  256. package/src/api/operation-surface.ts +2 -1
  257. package/src/codegen/package.ts +2 -2
  258. package/src/components/index.ts +3 -0
  259. package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
  260. package/src/components/kubectl-apply.test.ts +262 -0
  261. package/src/components/kubectl-apply.ts +341 -12
  262. package/src/deep-observe-hooks.ts +2 -2
  263. package/src/import/roundtrip-build.test.ts +46 -0
  264. package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
  265. package/src/lsp/completions.ts +2 -1
  266. package/src/lsp/hover.ts +2 -1
  267. package/src/op/activities/apply-diff.ts +87 -0
  268. package/src/op/activities/kubectl-plan.test.ts +224 -0
  269. package/src/op/activities/kubectl.ts +204 -16
  270. package/src/serializer.ts +2 -1
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Runtime factory constructors — re-exported from core.
3
+ */
4
+ export { createResource, createProperty } from "@intentius/chant/runtime";
@@ -0,0 +1,96 @@
1
+ /**
2
+ * API group → `K8s::{Namespace}::{Kind}` namespace segment — the ONE copy.
3
+ *
4
+ * Four surfaces build entityTypes from a group string: CRD codegen
5
+ * (`crd/parser.ts`), the swagger pass (`spec/parse.ts`), live discovery
6
+ * (`describe-resources.ts` via `gvkToTypeName`), and YAML import
7
+ * (`import/parser.ts`). Until #1628 each had its own copy of the rule and only
8
+ * CRD codegen consulted the overrides, so a Flux Kustomization arriving through
9
+ * a kustomize root or `chant import` was typed `K8s::Kustomize::Kustomization`
10
+ * — a name that matches nothing in operations.json (NOT-OBSERVED live) and
11
+ * nothing downstream that dispatches on kind. Every path resolves here now;
12
+ * the declared, rendered, imported and discovered spellings of a kind cannot
13
+ * skew.
14
+ */
15
+ /**
16
+ * Group names whose first segment doesn't yield the conventional namespace.
17
+ * "argoproj.io" → "Argo" (not "Argoproj") to match the Argo CD vocabulary
18
+ * and the ArgoAppFor / ArgoAppSetForRegions composites.
19
+ *
20
+ * The Flux toolkit spreads across five `*.toolkit.fluxcd.io` groups plus the
21
+ * Flux Operator's `fluxcd.controlplane.io`; all six collapse to a single `Flux`
22
+ * namespace so a GitRepository and a Kustomization read as `K8s::Flux::*`
23
+ * siblings rather than scattering to Source / Kustomize / Helm / Notification /
24
+ * Image / Fluxcd. (`helm.toolkit.fluxcd.io` → `Helm` would also collide
25
+ * confusingly with the separate helm lexicon.)
26
+ *
27
+ * KubeMicroVM's `lambda.aws.amazon.com` would take `Lambda` by the
28
+ * first-segment rule, which reads as AWS Lambda proper and would sit
29
+ * confusingly beside the aws lexicon's real Lambda functions. These are
30
+ * Kubernetes CRs belonging to a community operator, so they take the
31
+ * operator's own name. `MicroVM` alone was the alternative and was rejected
32
+ * for stuttering: `K8s::MicroVM::MicroVM`.
33
+ *
34
+ * Note the official AWS controller uses a different group,
35
+ * `lambdamicrovms.services.k8s.aws`, so it can coexist here and will want its
36
+ * own entry rather than sharing this one.
37
+ */
38
+ export const GROUP_NAMESPACE_OVERRIDES = {
39
+ "argoproj.io": "Argo",
40
+ "source.toolkit.fluxcd.io": "Flux",
41
+ "kustomize.toolkit.fluxcd.io": "Flux",
42
+ "helm.toolkit.fluxcd.io": "Flux",
43
+ "notification.toolkit.fluxcd.io": "Flux",
44
+ "image.toolkit.fluxcd.io": "Flux",
45
+ "fluxcd.controlplane.io": "Flux",
46
+ "lambda.aws.amazon.com": "KubeMicroVM",
47
+ // CNPG and its barman-cloud plugin ship under two groups but are one thing to
48
+ // an author: a Cluster's `plugins[]` names an ObjectStore. The first-segment
49
+ // rule would scatter them into `Postgresql` and `Barmancloud`.
50
+ "postgresql.cnpg.io": "Cnpg",
51
+ "barmancloud.cnpg.io": "Cnpg",
52
+ // Not `Secrets`: `K8s::Secrets::InfisicalSecret` reads like a core Secret,
53
+ // and `K8s::Core::Secret` is right there to be confused with.
54
+ "secrets.infisical.com": "Infisical",
55
+ // k3s's bundled controllers ship under two groups but are one thing to an
56
+ // author — the k3s auto-deploy surface. The first-segment rule would give
57
+ // `K8s::Helm::HelmChart`, which reads like it belongs to the helm lexicon,
58
+ // and would split HelmChart from the Addon that tracks its deployment.
59
+ "helm.cattle.io": "K3s",
60
+ "k3s.cattle.io": "K3s",
61
+ // The first-segment rule would give `Serving`, which reads like a generic
62
+ // core concept rather than the KServe operator that owns it.
63
+ "serving.kserve.io": "KServe",
64
+ // The first-segment rule would give `Cluster`, which stutters against the
65
+ // CAPI `Cluster` kind itself (`K8s::Cluster::Cluster`) the same way the
66
+ // rejected `K8s::MicroVM::MicroVM` reads above. `infrastructure.cluster.x-k8s.io`
67
+ // (CAPA's AWSManagedCluster, AWSManagedMachinePool, AWSClusterControllerIdentity)
68
+ // and `controlplane.cluster.x-k8s.io` (CAPA's AWSManagedControlPlane) are left on
69
+ // the first-segment rule — their kinds already carry an `AWS` prefix, so
70
+ // `K8s::Infrastructure::AWSManagedCluster` / `K8s::Controlplane::AWSManagedControlPlane`
71
+ // don't need the same rescue. `addons.cluster.x-k8s.io` (CAAPH's HelmChartProxy,
72
+ // core CAPI's ClusterResourceSet) likewise stays `Addons`.
73
+ "cluster.x-k8s.io": "CAPI",
74
+ // The first-segment rule would give `Opentelemetry`, which misspells the
75
+ // product (OpenTelemetry is one word with two capitals) and leaves the
76
+ // operator's kinds as `K8s::Opentelemetry::OpenTelemetryCollector`.
77
+ "opentelemetry.io": "OpenTelemetry",
78
+ };
79
+ /**
80
+ * Normalize an API group to its PascalCase namespace segment.
81
+ * "" → "Core", overrides first ("argoproj.io" → "Argo"), then the first
82
+ * dot-segment kebab→PascalCased: "cert-manager.io" → "CertManager",
83
+ * "rbac.authorization.k8s.io" → "Rbac", "apps" → "Apps".
84
+ */
85
+ export function namespaceSegmentForGroup(group) {
86
+ if (!group || group === "")
87
+ return "Core";
88
+ const override = GROUP_NAMESPACE_OVERRIDES[group];
89
+ if (override)
90
+ return override;
91
+ const firstSegment = group.split(".")[0];
92
+ return firstSegment
93
+ .split("-")
94
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
95
+ .join("");
96
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Where a Kubernetes manifest embeds another lexicon's content, for
3
+ * `chant import` (#2962).
4
+ *
5
+ * The parser offers two kinds of place to core's embedded-content resolver:
6
+ *
7
+ * - every `data` value of a ConfigMap, as text (a collector's `config.yaml`,
8
+ * a Prometheus rule file, an `alertmanager.yml`, a Grafana dashboard's
9
+ * JSON);
10
+ * - a `monitoring.coreos.com` PrometheusRule's `spec.groups`, as the rule file
11
+ * `{ groups }` those groups would make;
12
+ * - an OpenTelemetry Operator `OpenTelemetryCollector`'s `spec.config` (v1beta1,
13
+ * a structured object), offered as `{ config, header }` with the `# chant:`
14
+ * header lines kept in the `otel.chant.dev/header` annotation (#3367);
15
+ * - a Grafana Operator `GrafanaDashboard`'s `spec.json`, as text (#3015);
16
+ * - the alerting content of the Grafana Operator's `GrafanaAlertRuleGroup`
17
+ * (`spec.rules`), `GrafanaContactPoint` (`spec.receivers`),
18
+ * `GrafanaNotificationPolicy` (`spec.route`), `GrafanaNotificationPolicyRoute`
19
+ * (the whole `spec`, offered as `route`), `GrafanaMuteTiming`
20
+ * (`spec.time_intervals`) and `GrafanaNotificationTemplate`
21
+ * (`spec.template`), each with the whole `spec` as the document so the
22
+ * owner can read the fields beside it (#3538).
23
+ *
24
+ * Which lexicon imports the content is decided by core at run time, from the
25
+ * lexicons that register an `embeddedImporters()`; this module names none of
26
+ * them as a dependency. The `expectedOwner` hints below are the conventions
27
+ * k8s manifests follow, used only to warn when the owner is not installed.
28
+ */
29
+ import { embeddedDocument } from "@intentius/chant/import/embedded";
30
+ function isObject(v) {
31
+ return typeof v === "object" && v !== null && !Array.isArray(v);
32
+ }
33
+ /** The annotation `OtelCollectorCR` keeps the collector config's `# chant:` header lines in (see OTEL_COLLECTOR_ANNOTATIONS). */
34
+ const OTEL_HEADER_ANNOTATION = "otel.chant.dev/header";
35
+ /** The `grafana_dashboard` label the Grafana dashboard sidecar looks for (kube-prometheus-stack, the Grafana Helm chart). */
36
+ export const GRAFANA_DASHBOARD_LABEL = "grafana_dashboard";
37
+ /** The Grafana Operator alerting kinds, and the `spec` field holding what the grafana lexicon imports. */
38
+ const GRAFANA_ALERTING_FIELDS = {
39
+ "K8s::Grafana::GrafanaAlertRuleGroup": { field: "rules", what: "Grafana alert rules" },
40
+ "K8s::Grafana::GrafanaContactPoint": { field: "receivers", what: "Grafana contact point receivers" },
41
+ "K8s::Grafana::GrafanaNotificationPolicy": { field: "route", what: "a Grafana notification policy" },
42
+ "K8s::Grafana::GrafanaMuteTiming": { field: "time_intervals", what: "Grafana mute timing intervals" },
43
+ "K8s::Grafana::GrafanaNotificationTemplate": { field: "template", what: "a Grafana notification template" },
44
+ };
45
+ function looksLikeRuleGroups(groups) {
46
+ return Array.isArray(groups) && groups.length > 0 && groups.every((g) => isObject(g) && typeof g.name === "string" && Array.isArray(g.rules));
47
+ }
48
+ /** Whose content a ConfigMap value looks like, by the conventions manifests follow. */
49
+ function hintFor(key, doc, labels) {
50
+ if (!isObject(doc))
51
+ return undefined;
52
+ if (Array.isArray(doc.panels) && (GRAFANA_DASHBOARD_LABEL in labels || key.endsWith(".json"))) {
53
+ return { lexicon: "grafana", what: "a Grafana dashboard" };
54
+ }
55
+ const v2Dashboard = (doc.kind === "Dashboard" && typeof doc.apiVersion === "string" && doc.apiVersion.startsWith("dashboard.grafana.app/")) ||
56
+ (isObject(doc.elements) && isObject(doc.layout));
57
+ if (v2Dashboard && (GRAFANA_DASHBOARD_LABEL in labels || key.endsWith(".json"))) {
58
+ return { lexicon: "grafana", what: "a Grafana dashboard" };
59
+ }
60
+ if (isObject(doc.service) && isObject(doc.service.pipelines)) {
61
+ return { lexicon: "otel", what: "an OpenTelemetry Collector config" };
62
+ }
63
+ if (looksLikeRuleGroups(doc.groups))
64
+ return { lexicon: "prometheus", what: "a Prometheus rule file" };
65
+ if ((isObject(doc.route) || Array.isArray(doc.receivers)) && !("apiVersion" in doc) && !("kind" in doc)) {
66
+ return { lexicon: "prometheus", what: "an Alertmanager config" };
67
+ }
68
+ return undefined;
69
+ }
70
+ function stringLabels(metadata) {
71
+ const labels = isObject(metadata) && isObject(metadata.labels) ? metadata.labels : {};
72
+ return Object.fromEntries(Object.entries(labels).filter((e) => typeof e[1] === "string"));
73
+ }
74
+ /**
75
+ * Offer the embedded content of one resource to `embedded`, replacing each
76
+ * value an owner imports with the reference it returns. `properties` is the
77
+ * resource's IR properties, changed in place (with fresh copies of the
78
+ * objects on the way down).
79
+ */
80
+ export function delegateEmbedded(type, kind, properties, embedded) {
81
+ const metadata = properties.metadata;
82
+ const name = isObject(metadata) && typeof metadata.name === "string" ? metadata.name : kind.toLowerCase();
83
+ const labels = stringLabels(metadata);
84
+ if (type === "K8s::Core::ConfigMap" && isObject(properties.data)) {
85
+ const keys = Object.keys(properties.data);
86
+ const data = { ...properties.data };
87
+ for (const key of keys) {
88
+ const text = data[key];
89
+ if (typeof text !== "string")
90
+ continue;
91
+ const doc = embeddedDocument(text);
92
+ if (!isObject(doc))
93
+ continue;
94
+ const ref = embedded.resolve({
95
+ host: "k8s",
96
+ hostType: type,
97
+ location: `ConfigMap ${name} data[${JSON.stringify(key)}]`,
98
+ directory: keys.length === 1 ? name : `${name}-${key.replace(/\.[^.]*$/, "")}`,
99
+ text,
100
+ document: doc,
101
+ labels,
102
+ expectedOwner: hintFor(key, doc, labels),
103
+ });
104
+ if (ref)
105
+ data[key] = ref;
106
+ }
107
+ properties.data = data;
108
+ return;
109
+ }
110
+ if (type === "K8s::Grafana::GrafanaDashboard" && isObject(properties.spec) && typeof properties.spec.json === "string") {
111
+ const text = properties.spec.json;
112
+ const doc = embeddedDocument(text);
113
+ if (!isObject(doc))
114
+ return;
115
+ const ref = embedded.resolve({
116
+ host: "k8s",
117
+ hostType: type,
118
+ location: `GrafanaDashboard ${name} spec.json`,
119
+ directory: name,
120
+ text,
121
+ document: doc,
122
+ labels,
123
+ expectedOwner: { lexicon: "grafana", what: "a Grafana dashboard" },
124
+ });
125
+ if (ref)
126
+ properties.spec = { ...properties.spec, json: ref };
127
+ return;
128
+ }
129
+ // A GrafanaNotificationPolicyRoute's spec is the route itself, so the whole spec is offered (as `route`) and replaced.
130
+ if (type === "K8s::Grafana::GrafanaNotificationPolicyRoute" && isObject(properties.spec) && typeof properties.spec.receiver === "string") {
131
+ const ref = embedded.resolve({
132
+ host: "k8s",
133
+ hostType: type,
134
+ location: `${kind} ${name} spec`,
135
+ directory: name,
136
+ document: { route: properties.spec },
137
+ select: "route",
138
+ labels,
139
+ expectedOwner: { lexicon: "grafana", what: "a Grafana notification policy route" },
140
+ });
141
+ if (ref)
142
+ properties.spec = ref;
143
+ return;
144
+ }
145
+ const alerting = GRAFANA_ALERTING_FIELDS[type];
146
+ if (alerting && isObject(properties.spec) && properties.spec[alerting.field] !== undefined) {
147
+ const spec = properties.spec;
148
+ const ref = embedded.resolve({
149
+ host: "k8s",
150
+ hostType: type,
151
+ location: `${kind} ${name} spec.${alerting.field}`,
152
+ directory: name,
153
+ document: spec,
154
+ select: alerting.field,
155
+ labels,
156
+ expectedOwner: { lexicon: "grafana", what: alerting.what },
157
+ });
158
+ if (ref)
159
+ properties.spec = { ...spec, [alerting.field]: ref };
160
+ return;
161
+ }
162
+ // The CR holds the config as an object, so the owner is asked for an object (`select: "config"`,
163
+ // answered with `collectorConfig([...])`) rather than for text, which would put a string where the CRD wants a map.
164
+ if (type === "K8s::OpenTelemetry::OpenTelemetryCollector" && isObject(properties.spec) && isObject(properties.spec.config)) {
165
+ const spec = properties.spec;
166
+ const raw = isObject(metadata) && isObject(metadata.annotations) ? metadata.annotations[OTEL_HEADER_ANNOTATION] : undefined;
167
+ const header = typeof raw === "string" ? raw.split("\n").filter((l) => l.length > 0) : [];
168
+ const ref = embedded.resolve({
169
+ host: "k8s",
170
+ hostType: type,
171
+ location: `OpenTelemetryCollector ${name} spec.config`,
172
+ directory: name,
173
+ document: { config: spec.config, header },
174
+ select: "config",
175
+ labels,
176
+ expectedOwner: { lexicon: "otel", what: "an OpenTelemetry Collector config" },
177
+ });
178
+ if (ref)
179
+ properties.spec = { ...spec, config: ref };
180
+ return;
181
+ }
182
+ if (type === "K8s::Monitoring::PrometheusRule" && isObject(properties.spec) && Array.isArray(properties.spec.groups)) {
183
+ const groups = properties.spec.groups;
184
+ const ref = embedded.resolve({
185
+ host: "k8s",
186
+ hostType: type,
187
+ location: `PrometheusRule ${name} spec.groups`,
188
+ directory: name,
189
+ document: { groups },
190
+ select: "groups",
191
+ labels,
192
+ expectedOwner: { lexicon: "prometheus", what: "Prometheus rule groups" },
193
+ });
194
+ if (ref)
195
+ properties.spec = { ...properties.spec, groups: ref };
196
+ }
197
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * TypeScript code generator for Kubernetes import.
3
+ *
4
+ * Converts a TemplateIR (from parsed K8s manifests) into TypeScript
5
+ * source code using the @intentius/chant-lexicon-k8s constructors.
6
+ */
7
+ import { EmbeddedImportScope, isEmbeddedReference, renderEmbeddedReference, } from "@intentius/chant/import/embedded";
8
+ const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
9
+ /** An object key as TypeScript: bare when it is an identifier, quoted otherwise (`"config.yaml"`, `"app.kubernetes.io/name"`). */
10
+ function propertyKey(key) {
11
+ return IDENT.test(key) ? key : JSON.stringify(key);
12
+ }
13
+ /**
14
+ * Map K8s entity types to their constructor class names.
15
+ */
16
+ const TYPE_TO_CLASS = {
17
+ // Core
18
+ "K8s::Core::Pod": "Pod",
19
+ "K8s::Core::Service": "Service",
20
+ "K8s::Core::ConfigMap": "ConfigMap",
21
+ "K8s::Core::Secret": "Secret",
22
+ "K8s::Core::Namespace": "Namespace",
23
+ "K8s::Core::ServiceAccount": "ServiceAccount",
24
+ "K8s::Core::PersistentVolume": "PersistentVolume",
25
+ "K8s::Core::PersistentVolumeClaim": "PersistentVolumeClaim",
26
+ "K8s::Core::Endpoints": "Endpoints",
27
+ "K8s::Core::LimitRange": "LimitRange",
28
+ "K8s::Core::ResourceQuota": "ResourceQuota",
29
+ // Apps
30
+ "K8s::Apps::Deployment": "Deployment",
31
+ "K8s::Apps::StatefulSet": "StatefulSet",
32
+ "K8s::Apps::DaemonSet": "DaemonSet",
33
+ "K8s::Apps::ReplicaSet": "ReplicaSet",
34
+ // Batch
35
+ "K8s::Batch::Job": "Job",
36
+ "K8s::Batch::CronJob": "CronJob",
37
+ // Networking
38
+ "K8s::Networking::Ingress": "Ingress",
39
+ "K8s::Networking::NetworkPolicy": "NetworkPolicy",
40
+ "K8s::Networking::IngressClass": "IngressClass",
41
+ // RBAC
42
+ "K8s::Rbac::Role": "Role",
43
+ "K8s::Rbac::ClusterRole": "ClusterRole",
44
+ "K8s::Rbac::RoleBinding": "RoleBinding",
45
+ "K8s::Rbac::ClusterRoleBinding": "ClusterRoleBinding",
46
+ // Autoscaling
47
+ "K8s::Autoscaling::HorizontalPodAutoscaler": "HorizontalPodAutoscaler",
48
+ // Policy
49
+ "K8s::Policy::PodDisruptionBudget": "PodDisruptionBudget",
50
+ // Storage
51
+ "K8s::Storage::StorageClass": "StorageClass",
52
+ };
53
+ /**
54
+ * Properties that reference known property entity constructors.
55
+ * Key: property name found in K8s specs → Value: constructor class name.
56
+ */
57
+ const PROPERTY_CONSTRUCTORS = {
58
+ containers: "Container",
59
+ initContainers: "Container",
60
+ ephemeralContainers: "EphemeralContainer",
61
+ volumes: "Volume",
62
+ volumeMounts: "VolumeMount",
63
+ ports: "ContainerPort",
64
+ env: "EnvVar",
65
+ envFrom: "EnvFromSource",
66
+ resources: "ResourceRequirements",
67
+ securityContext: "SecurityContext",
68
+ podSecurityContext: "PodSecurityContext",
69
+ livenessProbe: "Probe",
70
+ readinessProbe: "Probe",
71
+ startupProbe: "Probe",
72
+ selector: "LabelSelector",
73
+ template: "PodTemplateSpec",
74
+ strategy: "DeploymentStrategy",
75
+ affinity: "Affinity",
76
+ tolerations: "Toleration",
77
+ topologySpreadConstraints: "TopologySpreadConstraint",
78
+ rules: "IngressRule",
79
+ tls: "IngressTLS",
80
+ ingress: "NetworkPolicyIngressRule",
81
+ egress: "NetworkPolicyEgressRule",
82
+ };
83
+ /**
84
+ * Service port properties need distinct handling since K8s uses "ports"
85
+ * for both ContainerPort and ServicePort depending on the parent resource.
86
+ */
87
+ const SERVICE_PORT_TYPES = new Set([
88
+ "K8s::Core::Service",
89
+ ]);
90
+ /**
91
+ * A Role's and a ClusterRole's top-level `rules` are PolicyRules, not the
92
+ * IngressRules {@link PROPERTY_CONSTRUCTORS} maps `rules` to. Since the
93
+ * property classes carry their members (chant #3093), an IngressRule built
94
+ * from `apiGroups`/`verbs` no longer type-checks.
95
+ */
96
+ const POLICY_RULE_TYPES = new Set([
97
+ "K8s::Rbac::Role",
98
+ "K8s::Rbac::ClusterRole",
99
+ ]);
100
+ /** The constructor for `key` on a resource of `parentType`. */
101
+ function constructorFor(key, parentType) {
102
+ if (key === "rules" && POLICY_RULE_TYPES.has(parentType))
103
+ return "PolicyRule";
104
+ return PROPERTY_CONSTRUCTORS[key];
105
+ }
106
+ /**
107
+ * Generate TypeScript source code from a Kubernetes IR.
108
+ */
109
+ export class K8sGenerator {
110
+ constructor() {
111
+ /** The imports for embedded content the current `generate()` call references (#2962). */
112
+ this.scope = new EmbeddedImportScope("");
113
+ }
114
+ generate(ir) {
115
+ const lines = [];
116
+ // Collect which constructors are needed
117
+ const usedConstructors = new Set();
118
+ for (const resource of ir.resources) {
119
+ const cls = this.resolveClass(resource.type);
120
+ if (cls)
121
+ usedConstructors.add(cls);
122
+ // Check properties for nested constructors
123
+ this.collectNestedConstructors(resource.properties, usedConstructors, resource.type);
124
+ }
125
+ // Names the embedded content's imports must not take: every constructor this file may use, and its exports.
126
+ this.scope = new EmbeddedImportScope("", [
127
+ ...usedConstructors,
128
+ ...Object.values(PROPERTY_CONSTRUCTORS),
129
+ "ServicePort",
130
+ "PolicyRule",
131
+ ...ir.resources.map((r) => r.logicalId),
132
+ ]);
133
+ // Emit namespaces as comments
134
+ if (ir.metadata?.namespaces && Array.isArray(ir.metadata.namespaces)) {
135
+ lines.push(`// Namespaces: ${ir.metadata.namespaces.join(", ")}`);
136
+ lines.push("");
137
+ }
138
+ // Emit resources
139
+ for (const resource of ir.resources) {
140
+ const cls = this.resolveClass(resource.type);
141
+ if (!cls) {
142
+ lines.push(`// TODO: unsupported type ${resource.type} (${resource.logicalId})`);
143
+ lines.push("");
144
+ continue;
145
+ }
146
+ const varName = resource.logicalId;
147
+ const propsStr = this.emitProps(resource.properties, 1, resource.type);
148
+ lines.push(`export const ${varName} = new ${cls}(${propsStr});`);
149
+ lines.push("");
150
+ }
151
+ // Import statements: the k8s constructors, then what embedded content references.
152
+ // The constructors are read back from what was emitted (string literals
153
+ // taken out), so a nested one the walk above does not reach (a
154
+ // container's env) is imported too.
155
+ const code = lines.join("\n").replace(/"(?:[^"\\\n]|\\.)*"/g, '""');
156
+ for (const m of code.matchAll(/\bnew ([A-Z][A-Za-z0-9_]*)\(/g))
157
+ usedConstructors.add(m[1]);
158
+ const imports = [...usedConstructors].sort().join(", ");
159
+ const head = [`import { ${imports} } from "@intentius/chant-lexicon-k8s";`, ...this.scope.lines(), ""];
160
+ return [{ path: "main.ts", content: [...head, ...lines].join("\n") }];
161
+ }
162
+ /**
163
+ * Resolve a K8s type name to a constructor class name.
164
+ * Falls back to extracting the last segment of the type.
165
+ */
166
+ resolveClass(type) {
167
+ if (TYPE_TO_CLASS[type])
168
+ return TYPE_TO_CLASS[type];
169
+ // Fallback: extract Kind from "K8s::Group::Kind"
170
+ const parts = type.split("::");
171
+ if (parts.length === 3 && parts[0] === "K8s")
172
+ return parts[2];
173
+ return undefined;
174
+ }
175
+ collectNestedConstructors(props, used, parentType) {
176
+ for (const [key, value] of Object.entries(props)) {
177
+ if (value === undefined || value === null)
178
+ continue;
179
+ // Special case: "ports" on Service resources → ServicePort
180
+ if (key === "ports" && SERVICE_PORT_TYPES.has(parentType)) {
181
+ if (Array.isArray(value))
182
+ used.add("ServicePort");
183
+ continue;
184
+ }
185
+ const constructor = constructorFor(key, parentType);
186
+ if (!constructor)
187
+ continue;
188
+ if (Array.isArray(value)) {
189
+ used.add(constructor);
190
+ }
191
+ else if (typeof value === "object") {
192
+ used.add(constructor);
193
+ }
194
+ // Recurse into nested specs (e.g., template.spec.containers)
195
+ if (typeof value === "object" && !Array.isArray(value)) {
196
+ this.collectNestedConstructors(value, used, parentType);
197
+ }
198
+ }
199
+ // Recurse into spec if present
200
+ if (props.spec && typeof props.spec === "object" && !Array.isArray(props.spec)) {
201
+ this.collectNestedConstructors(props.spec, used, parentType);
202
+ }
203
+ }
204
+ emitProps(props, depth, parentType) {
205
+ const indent = " ".repeat(depth);
206
+ const innerIndent = " ".repeat(depth + 1);
207
+ const entries = [];
208
+ for (const [key, value] of Object.entries(props)) {
209
+ if (value === undefined || value === null)
210
+ continue;
211
+ const emitted = this.emitValue(key, value, depth + 1, parentType);
212
+ entries.push(`${innerIndent}${propertyKey(key)}: ${emitted},`);
213
+ }
214
+ if (entries.length === 0)
215
+ return "{}";
216
+ return `{\n${entries.join("\n")}\n${indent}}`;
217
+ }
218
+ emitValue(key, value, depth, parentType) {
219
+ if (value === null || value === undefined)
220
+ return "undefined";
221
+ if (isEmbeddedReference(value))
222
+ return renderEmbeddedReference(value, this.scope, depth * 2);
223
+ // Special case: "ports" on Service → ServicePort constructor
224
+ if (key === "ports" && SERVICE_PORT_TYPES.has(parentType) && Array.isArray(value)) {
225
+ return this.emitConstructorArray("ServicePort", value, depth, parentType);
226
+ }
227
+ // Check if this key maps to a property constructor
228
+ const constructor = constructorFor(key, parentType);
229
+ if (constructor) {
230
+ // Array of constructors (containers, volumes, ports, env, etc.)
231
+ if (Array.isArray(value)) {
232
+ return this.emitConstructorArray(constructor, value, depth, parentType);
233
+ }
234
+ // Single constructor (securityContext, resources, selector, template, strategy, etc.)
235
+ if (typeof value === "object") {
236
+ const propsStr = this.emitProps(value, depth, parentType);
237
+ return `new ${constructor}(${propsStr})`;
238
+ }
239
+ }
240
+ return this.emitLiteral(value, depth, parentType);
241
+ }
242
+ emitConstructorArray(constructor, items, depth, parentType) {
243
+ const indent = " ".repeat(depth);
244
+ const innerIndent = " ".repeat(depth + 1);
245
+ const emitted = items.map((item) => {
246
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
247
+ const propsStr = this.emitProps(item, depth + 1, parentType);
248
+ return `new ${constructor}(${propsStr})`;
249
+ }
250
+ return JSON.stringify(item);
251
+ });
252
+ if (emitted.length === 1 && emitted[0].length < 60) {
253
+ return `[${emitted[0]}]`;
254
+ }
255
+ return `[\n${emitted.map((e) => `${innerIndent}${e},`).join("\n")}\n${indent}]`;
256
+ }
257
+ emitLiteral(value, depth, parentType) {
258
+ if (value === null || value === undefined)
259
+ return "undefined";
260
+ if (isEmbeddedReference(value))
261
+ return renderEmbeddedReference(value, this.scope, depth * 2);
262
+ if (typeof value === "string")
263
+ return JSON.stringify(value);
264
+ if (typeof value === "number" || typeof value === "boolean")
265
+ return String(value);
266
+ if (Array.isArray(value)) {
267
+ if (value.length === 0)
268
+ return "[]";
269
+ const items = value.map((item) => this.emitLiteral(item, depth + 1, parentType));
270
+ const oneLine = `[${items.join(", ")}]`;
271
+ if (oneLine.length < 80)
272
+ return oneLine;
273
+ const indent = " ".repeat(depth);
274
+ const innerIndent = " ".repeat(depth + 1);
275
+ return `[\n${items.map((i) => `${innerIndent}${i},`).join("\n")}\n${indent}]`;
276
+ }
277
+ if (typeof value === "object") {
278
+ const entries = Object.entries(value);
279
+ if (entries.length === 0)
280
+ return "{}";
281
+ const indent = " ".repeat(depth);
282
+ const innerIndent = " ".repeat(depth + 1);
283
+ const items = entries.map(([k, v]) => `${innerIndent}${propertyKey(k)}: ${this.emitLiteral(v, depth + 1, parentType)},`);
284
+ return `{\n${items.join("\n")}\n${indent}}`;
285
+ }
286
+ return String(value);
287
+ }
288
+ }
@@ -0,0 +1,85 @@
1
+ import { hasOwnershipMarker, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
2
+ import { K8sParser } from "./parser.js";
3
+ /**
4
+ * Server-managed metadata fields that the API server fills in. They are not
5
+ * part of the declared shape and would only add noise to regenerated source,
6
+ * so they are stripped unless `verbatim` is set.
7
+ */
8
+ const SERVER_METADATA_FIELDS = [
9
+ "managedFields",
10
+ "uid",
11
+ "resourceVersion",
12
+ "generation",
13
+ "creationTimestamp",
14
+ "selfLink",
15
+ "ownerReferences",
16
+ "finalizers",
17
+ ];
18
+ /** Annotations the cluster writes back that are not authored config. */
19
+ const SERVER_ANNOTATIONS = [
20
+ "kubectl.kubernetes.io/last-applied-configuration",
21
+ "deployment.kubernetes.io/revision",
22
+ ];
23
+ function isRecord(v) {
24
+ return typeof v === "object" && v !== null && !Array.isArray(v);
25
+ }
26
+ /**
27
+ * Strip `status`, `managedFields`, and other server-written fields to reach the
28
+ * declared shape. Returns a new object; the input is not mutated.
29
+ */
30
+ export function stripServerFields(obj) {
31
+ const clone = JSON.parse(JSON.stringify(obj));
32
+ delete clone.status;
33
+ if (isRecord(clone.metadata)) {
34
+ const md = clone.metadata;
35
+ for (const f of SERVER_METADATA_FIELDS)
36
+ delete md[f];
37
+ if (isRecord(md.annotations)) {
38
+ for (const a of SERVER_ANNOTATIONS)
39
+ delete md.annotations[a];
40
+ if (Object.keys(md.annotations).length === 0)
41
+ delete md.annotations;
42
+ }
43
+ }
44
+ return clone;
45
+ }
46
+ /**
47
+ * Build full-fidelity export IR from a list of live Kubernetes objects (the
48
+ * `items` of a `kubectl get -o json` List). Reuses the import K8sParser by
49
+ * feeding it the cleaned objects as JSON documents — JSON is valid YAML, so no
50
+ * separate serializer is needed. Pure: all I/O stays in the caller.
51
+ *
52
+ * The result's `reparse(context)` parses the same objects again with a
53
+ * `ParseContext`, so `chant import --from` hands embedded content (a
54
+ * collector config in a ConfigMap, a PrometheusRule's groups) to the lexicon
55
+ * that owns it, as file import does (#2995).
56
+ */
57
+ export function buildExportFromObjects(objects, opts = {}) {
58
+ const owning = opts.owned
59
+ ? objects.filter((o) => {
60
+ if (!isRecord(o))
61
+ return false;
62
+ const labels = isRecord(o.metadata) ? o.metadata.labels : undefined;
63
+ return hasOwnershipMarker(labels, LABEL_OWNERSHIP_KEYS);
64
+ })
65
+ : objects;
66
+ const cleaned = owning
67
+ .filter(isRecord)
68
+ .map((o) => (opts.verbatim ? o : stripServerFields(o)));
69
+ const content = cleaned.map((o) => JSON.stringify(o, null, 2)).join("\n---\n");
70
+ const parse = (context) => select(new K8sParser().parse(content, context), opts.selector);
71
+ return { ...parse(), reparse: (context) => parse(context) };
72
+ }
73
+ function select(ir, selector) {
74
+ if (!selector || (selector.type === undefined && selector.name === undefined)) {
75
+ return ir;
76
+ }
77
+ return {
78
+ ...ir,
79
+ resources: ir.resources.filter((r) => {
80
+ const liveName = r.metadata?.originalName ?? r.logicalId;
81
+ return ((selector.type === undefined || r.type === selector.type) &&
82
+ (selector.name === undefined || liveName === selector.name));
83
+ }),
84
+ };
85
+ }