@intentius/chant-lexicon-k8s 0.124.31 → 0.124.32

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,122 @@
1
+ /**
2
+ * `chant kube source` (chant #1079) — resolve a live object back to the
3
+ * `.ts` file and composite that declared it, reusing the exact build
4
+ * provenance the gitlab/forgejo lexicons' own `:source` context tools already
5
+ * expose (`@intentius/chant/provenance`, stamped during discovery — chant
6
+ * #1064). That precedent is explicit that provenance here is **entity-level
7
+ * (file + composite), not a YAML-line source map** — chant does not carry
8
+ * one, for k8s or for anything else — so this verb reports the same two
9
+ * facts (never a fabricated line number) and says so plainly rather than
10
+ * silently coming up one field short of the issue's wording.
11
+ */
12
+ import { formatUnobserved } from "@intentius/chant/observation";
13
+ import { defaultK8sConnector } from "../api/connect.js";
14
+ import { classifyApiFailure } from "../api/classify.js";
15
+ import { kubeConnect } from "./connect.js";
16
+ import { parseKubeFlags, connectOptionsFrom } from "./flags.js";
17
+ import { loadKubeProjectContext, relativeProvenance } from "./project.js";
18
+ import { resolveKubeTarget, matchLiveObject, isTargetError } from "./target.js";
19
+ function targetLabel(target) {
20
+ return "apiVersion" in target.selector ? target.selector.kind : target.selector.resource;
21
+ }
22
+ export async function runSource(rawArgs, deps = {}) {
23
+ const connect = deps.connect ?? defaultK8sConnector;
24
+ const loadProject = deps.loadProject ?? loadKubeProjectContext;
25
+ let flags;
26
+ try {
27
+ flags = parseKubeFlags(rawArgs);
28
+ }
29
+ catch (err) {
30
+ console.error(err instanceof Error ? err.message : String(err));
31
+ return 1;
32
+ }
33
+ const json = flags.values.output === "json";
34
+ const project = await loadProject();
35
+ if (!project) {
36
+ if (json)
37
+ console.log(JSON.stringify({ available: false, note: "not running inside a chant project (no chant.config.ts, or the project has build errors)" }));
38
+ else
39
+ console.log("chant source: unavailable — not running inside a chant project (no chant.config.ts, or the project has build errors)");
40
+ return 1;
41
+ }
42
+ const target = resolveKubeTarget(flags.positional, flags.values.namespace, project);
43
+ if (isTargetError(target)) {
44
+ console.error(`error: ${target.error}`);
45
+ return 1;
46
+ }
47
+ if (!target.name) {
48
+ console.error("error: chant kube source requires a single named resource, e.g. `chant kube source deployment web`");
49
+ return 1;
50
+ }
51
+ const connected = await kubeConnect(connectOptionsFrom(flags.values), connect);
52
+ if (connected.kind === "unobserved") {
53
+ console.error(formatUnobserved(targetLabel(target), { reason: connected.reason, detail: connected.detail }));
54
+ return 1;
55
+ }
56
+ const { client } = connected;
57
+ let info;
58
+ try {
59
+ info = await client.resolve(target.selector);
60
+ }
61
+ catch (err) {
62
+ const outcome = classifyApiFailure(err);
63
+ console.error(formatUnobserved(targetLabel(target), {
64
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
65
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
66
+ }));
67
+ return 1;
68
+ }
69
+ if (!info) {
70
+ console.error(`error: the server doesn't have a resource type "${targetLabel(target)}"`);
71
+ return 1;
72
+ }
73
+ const namespace = target.namespace ?? client.defaultNamespace;
74
+ let obj;
75
+ try {
76
+ obj = await client.readIfPresent({ apiVersion: info.apiVersion, kind: info.kind, name: target.name, namespace });
77
+ }
78
+ catch (err) {
79
+ const outcome = classifyApiFailure(err);
80
+ console.error(formatUnobserved(`${info.kind}/${target.name}`, {
81
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
82
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
83
+ }));
84
+ return 1;
85
+ }
86
+ if (!obj) {
87
+ console.error(`Error from server (NotFound): ${info.name} "${target.name}" not found`);
88
+ return 1;
89
+ }
90
+ const match = target.declaredMatch ??
91
+ matchLiveObject(project, {
92
+ apiVersion: obj.apiVersion ?? info.apiVersion,
93
+ kind: obj.kind ?? info.kind,
94
+ name: obj.metadata?.name ?? target.name,
95
+ namespace: obj.metadata?.namespace ?? namespace,
96
+ });
97
+ if (!match) {
98
+ const note = `no declared entity in this project names ${info.kind}/${target.name}`;
99
+ if (json)
100
+ console.log(JSON.stringify({ found: false, note }));
101
+ else
102
+ console.log(`${info.kind}/${target.name}: ${note}`);
103
+ return 0;
104
+ }
105
+ const prov = relativeProvenance(project, match.entity);
106
+ if (json) {
107
+ console.log(JSON.stringify({
108
+ found: true,
109
+ entity: match.entityName,
110
+ file: prov?.sourceFile ?? null,
111
+ composite: prov?.composite ?? null,
112
+ compositeInstance: prov?.compositeInstance ?? null,
113
+ }));
114
+ }
115
+ else {
116
+ console.log(`${info.kind}/${target.name} <- ${match.entityName}`);
117
+ console.log(` file: ${prov?.sourceFile ?? "(unknown)"}`);
118
+ console.log(` composite: ${prov?.composite ?? "(none — declared directly)"}`);
119
+ console.log(` line: not tracked — chant's build provenance is entity-level (file + composite), not a source map`);
120
+ }
121
+ return 0;
122
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Resolving `chant kube <verb> <target...>`'s positional arguments to an
3
+ * addressable resource (chant #1079).
4
+ *
5
+ * "Every verb resolves entity names through chant's own vocabulary where
6
+ * applicable ... while also accepting raw kind/name kubectl-style." Both
7
+ * forms share one positional shape, disambiguated by arity:
8
+ *
9
+ * - One token, and it names a declared entity in this project → chant's own
10
+ * vocabulary: the entity's `metadata.name`(/`namespace`) is looked up in
11
+ * its own source, not typed twice.
12
+ * - One or two tokens otherwise → kubectl's own shape, `<kind> [name]`; a
13
+ * bare kind with no name is a list, matching `kubectl get <kind>`.
14
+ *
15
+ * `<kind>` is never parsed here beyond splitting the `.group` suffix — that
16
+ * job belongs to the typed client's own `resolve()` (kubectl's own
17
+ * plural/singular/kind/short-name precedence, `packages/k8s-client/src/client.ts`),
18
+ * so a raw kind string reaches the cluster exactly as `kubectl get raycluster.ray.io`
19
+ * would.
20
+ */
21
+ import { findDeclaredMatch } from "./project.js";
22
+ import { operationFor } from "../api/operation-surface.js";
23
+ export function isTargetError(t) {
24
+ return "error" in t;
25
+ }
26
+ /**
27
+ * Resolve `positional` (whatever's left after flags) plus an explicit
28
+ * `-n/--namespace`, against `project`'s declared entities when available.
29
+ */
30
+ export function resolveKubeTarget(positional, namespaceFlag, project) {
31
+ if (positional.length === 0) {
32
+ return { error: "a resource kind (and, usually, a name) is required — e.g. `chant kube get deployments` or `chant kube get deployment web`" };
33
+ }
34
+ const [first, second] = positional;
35
+ if (project && second === undefined) {
36
+ const entity = project.entities.get(first);
37
+ if (entity && entity.lexicon === "k8s") {
38
+ const operation = operationFor(entity.entityType);
39
+ const props = "props" in entity ? entity.props : undefined;
40
+ const metadata = props?.metadata;
41
+ if (operation && metadata?.name) {
42
+ return {
43
+ selector: { apiVersion: operation.apiVersion, kind: operation.kind },
44
+ name: metadata.name,
45
+ namespace: namespaceFlag ?? metadata.namespace,
46
+ declaredMatch: { entityName: first, entity, props },
47
+ };
48
+ }
49
+ }
50
+ }
51
+ return { selector: { resource: first }, name: second, namespace: namespaceFlag };
52
+ }
53
+ /**
54
+ * After a live read resolves an object's real `apiVersion`/`kind` (a kubectl
55
+ * kind string like `deploy` isn't one), reverse-map it against the project
56
+ * for the verdict column / `source` — reusing the exact match `resolveKubeTarget`
57
+ * would have made had the caller spelled out the entity name.
58
+ */
59
+ export function matchLiveObject(project, ref) {
60
+ if (!project)
61
+ return undefined;
62
+ return findDeclaredMatch(project.entities, ref);
63
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Test doubles for `chant kube` (chant #1079).
3
+ *
4
+ * `./project.ts`'s real `loadKubeProjectContext` builds an actual project
5
+ * (`chant.config.ts` read, source discovered, serialized) — every verb takes
6
+ * it as an injectable dependency for exactly this reason: a unit test builds
7
+ * a `KubeProjectContext` by hand instead, the same way `../api/fake-cluster.ts`
8
+ * lets a test build a client without a socket.
9
+ */
10
+ import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
11
+ import { setProvenance } from "@intentius/chant/provenance";
12
+ /** A minimal `Declarable` carrying `props`, optionally stamped with build provenance. */
13
+ export function fakeDeclarable(entityType, props, provenance) {
14
+ const entity = {
15
+ lexicon: "k8s",
16
+ entityType,
17
+ kind: "resource",
18
+ props,
19
+ [DECLARABLE_MARKER]: true,
20
+ };
21
+ if (provenance)
22
+ setProvenance(entity, provenance);
23
+ return entity;
24
+ }
25
+ /** A `KubeProjectContext` built from a plain name → entity map, no disk I/O. */
26
+ export function fakeProjectContext(entities, config = {}, cwd = "/project") {
27
+ return { cwd, config, entities: new Map(Object.entries(entities)) };
28
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `chant kube top pods|nodes` (chant #1079).
3
+ *
4
+ * `metrics.k8s.io` is not part of the standard Kubernetes OpenAPI schema chant's
5
+ * codegen ingests — it is an aggregated API a cluster may or may not be running
6
+ * (metrics-server). It needs no entry in the generated operation surface either
7
+ * way: the typed client's kubectl-style selector (`{ resource, group }`) resolves
8
+ * purely off the *cluster's own* discovery, the same path `waitForReady`'s `kind`
9
+ * argument already uses for CRDs — so this needs no client change, only the
10
+ * right selector.
11
+ *
12
+ * Kubectl's own `top` sums each Pod's per-container usage into one CPU/memory
13
+ * figure, which needs real quantity-unit arithmetic (`250m` + `100m`, `128Mi` +
14
+ * `64Mi`). That normalization is not implemented here — each container's raw
15
+ * usage string is shown as the API reported it, unsummed. Good enough for "is
16
+ * anything obviously starved", not a replacement for `kubectl top --containers`.
17
+ */
18
+ import { formatUnobserved } from "@intentius/chant/observation";
19
+ import { defaultK8sConnector } from "../api/connect.js";
20
+ import { classifyApiFailure } from "../api/classify.js";
21
+ import { kubeConnect } from "./connect.js";
22
+ import { parseKubeFlags, connectOptionsFrom } from "./flags.js";
23
+ import { renderTable } from "./render.js";
24
+ const RESOURCE_ALIASES = {
25
+ pod: "pods",
26
+ pods: "pods",
27
+ po: "pods",
28
+ node: "nodes",
29
+ nodes: "nodes",
30
+ no: "nodes",
31
+ };
32
+ export async function runTop(rawArgs, deps = {}) {
33
+ const connect = deps.connect ?? defaultK8sConnector;
34
+ let flags;
35
+ try {
36
+ flags = parseKubeFlags(rawArgs);
37
+ }
38
+ catch (err) {
39
+ console.error(err instanceof Error ? err.message : String(err));
40
+ return 1;
41
+ }
42
+ const resource = RESOURCE_ALIASES[flags.positional[0] ?? "pods"];
43
+ if (!resource) {
44
+ console.error(`error: unknown resource "${flags.positional[0]}" — chant kube top supports "pods" and "nodes"`);
45
+ return 1;
46
+ }
47
+ const connected = await kubeConnect(connectOptionsFrom(flags.values), connect);
48
+ if (connected.kind === "unobserved") {
49
+ console.error(formatUnobserved(resource, { reason: connected.reason, detail: connected.detail }));
50
+ return 1;
51
+ }
52
+ const { client } = connected;
53
+ let info;
54
+ try {
55
+ info = await client.resolve({ resource, group: "metrics.k8s.io" });
56
+ }
57
+ catch (err) {
58
+ const outcome = classifyApiFailure(err);
59
+ console.error(formatUnobserved(resource, {
60
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
61
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
62
+ }));
63
+ return 1;
64
+ }
65
+ if (!info) {
66
+ console.error("error: the metrics API (metrics.k8s.io) is not available in this cluster — is metrics-server installed?");
67
+ return 1;
68
+ }
69
+ const namespace = resource === "pods" && !flags.flags.allNamespaces ? (flags.values.namespace ?? client.defaultNamespace) : undefined;
70
+ let items;
71
+ try {
72
+ items = await client.list({ apiVersion: info.apiVersion, kind: info.kind }, { ...(namespace ? { namespace } : {}) });
73
+ }
74
+ catch (err) {
75
+ const outcome = classifyApiFailure(err);
76
+ console.error(formatUnobserved(resource, {
77
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
78
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
79
+ }));
80
+ return 1;
81
+ }
82
+ if (resource === "nodes") {
83
+ const rows = items.map((n) => [
84
+ n.metadata?.name ?? "",
85
+ String(n.usage?.cpu ?? "<unknown>"),
86
+ String(n.usage?.memory ?? "<unknown>"),
87
+ ]);
88
+ console.log(renderTable(["NAME", "CPU", "MEMORY"], rows));
89
+ return 0;
90
+ }
91
+ const rows = items.map((p) => {
92
+ const containers = p.containers ?? [];
93
+ const cpu = containers.map((c) => `${c.name}:${c.usage?.cpu ?? "?"}`).join(",") || "<none>";
94
+ const memory = containers.map((c) => `${c.name}:${c.usage?.memory ?? "?"}`).join(",") || "<none>";
95
+ return [p.metadata?.namespace ?? "", p.metadata?.name ?? "", cpu, memory];
96
+ });
97
+ console.log(renderTable(["NAMESPACE", "NAME", "CPU (per container)", "MEMORY (per container)"], rows));
98
+ return 0;
99
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The chant verdict column `chant kube get` carries on every row (chant
3
+ * #1079's acceptance criterion: "get annotates each row with the declared /
4
+ * owned / drifted / foreign-owned verdict").
5
+ *
6
+ * Four states:
7
+ *
8
+ * - `declared` — this project's source names an entity at this exact
9
+ * apiVersion/kind/name(/namespace), and the live object agrees with it on
10
+ * every field the source declares.
11
+ * - `drifted` — same match, but the live object disagrees with the source on
12
+ * at least one declared field.
13
+ * - `owned` — no declared match, but the live object carries chant's
14
+ * ownership marker (`app.kubernetes.io/managed-by=chant`) — something
15
+ * chant applied that source no longer declares, or a stack/entity this
16
+ * project's build didn't resolve.
17
+ * - `foreign-owned` — no declared match and no marker: chant does not
18
+ * consider this object its own.
19
+ *
20
+ * `unavailable` is not a verdict about the object; it is what every row gets
21
+ * when the project context could not be loaded at all (chant #1079's
22
+ * "degrades gracefully outside a chant project" — `get` and `describe` still
23
+ * work, this column just has nothing to say).
24
+ *
25
+ * The drift check here is a fast, best-effort *declared-subset* comparison —
26
+ * every path the source sets must agree on the live object — not the
27
+ * managed-fields-precise diff `chant lifecycle diff --live --deep` computes
28
+ * (chant #1076). It exists for a table column that must render for dozens of
29
+ * rows in one `get` call; the deep, field-ownership-aware answer stays the
30
+ * lifecycle command's job. A live object can disagree with the source on a
31
+ * field a controller owns (Kubernetes rewriting `spec.replicas` under an
32
+ * HPA, say) and still show `drifted` here — reach for the deep diff before
33
+ * treating this column as a verdict rather than a hint.
34
+ */
35
+ import { hasOwnershipMarker, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
36
+ function isPlainObject(v) {
37
+ return typeof v === "object" && v !== null && !Array.isArray(v);
38
+ }
39
+ /**
40
+ * True when every path `declared` sets is present and equal on `live`.
41
+ * Extra fields on `live` (server defaults, status, anything the source never
42
+ * mentions) are not drift — chant never declared an opinion about them.
43
+ */
44
+ export function declaredSubsetMatches(declared, live) {
45
+ if (Array.isArray(declared)) {
46
+ if (!Array.isArray(live) || declared.length !== live.length)
47
+ return false;
48
+ return declared.every((item, i) => declaredSubsetMatches(item, live[i]));
49
+ }
50
+ if (isPlainObject(declared)) {
51
+ if (!isPlainObject(live))
52
+ return false;
53
+ return Object.entries(declared).every(([key, value]) => declaredSubsetMatches(value, live[key]));
54
+ }
55
+ return declared === live;
56
+ }
57
+ /** The declared fields worth comparing — everything but the name/namespace identity itself. */
58
+ function declaredComparable(props) {
59
+ if (!props)
60
+ return {};
61
+ const { metadata, ...rest } = props;
62
+ if (!isPlainObject(metadata))
63
+ return rest;
64
+ const { name: _name, namespace: _namespace, ...restMetadata } = metadata;
65
+ return { ...rest, ...(Object.keys(restMetadata).length > 0 ? { metadata: restMetadata } : {}) };
66
+ }
67
+ export function verdictFor(live, match) {
68
+ if (match) {
69
+ return declaredSubsetMatches(declaredComparable(match.props), live) ? "declared" : "drifted";
70
+ }
71
+ return hasOwnershipMarker(live.metadata?.labels, LABEL_OWNERSHIP_KEYS) ? "owned" : "foreign-owned";
72
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `chant kube wait` (chant #1079) — the issue names this one explicitly:
3
+ * "`wait` should use the existing readiness-spec registry in
4
+ * `op/activities/wait-for-ready.ts`, which already handles the Argo case
5
+ * where health and sync replace a `Ready` condition — something `kubectl
6
+ * wait --for` cannot express without jsonpath gymnastics." This verb is a
7
+ * thin CLI shell around that exact registry (`readinessFor`/`isReady`/
8
+ * `firstTerminal`/`waitForReady`) — not a second implementation of it.
9
+ *
10
+ * `--for=condition=<Type>[=<Status>]` overrides the registry with a single
11
+ * condition check, matching kubectl's own flag. `--for=delete` polls for
12
+ * absence instead. Neither given falls back to the registry: the generic
13
+ * `Ready` condition, or a resource-specific override (Argo's `Application`
14
+ * today).
15
+ */
16
+ import { formatUnobserved } from "@intentius/chant/observation";
17
+ import { defaultK8sConnector } from "../api/connect.js";
18
+ import { classifyApiFailure } from "../api/classify.js";
19
+ import { waitForReady, readinessFor, ReadinessFailedError, } from "../op/activities/wait-for-ready.js";
20
+ import { kubeConnect } from "./connect.js";
21
+ import { parseKubeFlags, connectOptionsFrom, parseDurationMs } from "./flags.js";
22
+ import { loadKubeProjectContext } from "./project.js";
23
+ import { resolveKubeTarget, isTargetError } from "./target.js";
24
+ function parseFor(value) {
25
+ if (value === undefined)
26
+ return undefined;
27
+ if (value === "delete")
28
+ return { kind: "delete" };
29
+ const match = /^condition=([^=]+)(?:=(.+))?$/.exec(value);
30
+ if (!match) {
31
+ throw new Error(`--for expects "condition=<Type>[=<Status>]" or "delete", got "${value}"`);
32
+ }
33
+ return { kind: "spec", spec: { ready: [{ conditionType: match[1], status: match[2] ?? "True" }] } };
34
+ }
35
+ function targetLabel(target) {
36
+ return "apiVersion" in target.selector ? target.selector.kind : target.selector.resource;
37
+ }
38
+ export async function runWait(rawArgs, deps = {}) {
39
+ const connect = deps.connect ?? defaultK8sConnector;
40
+ const loadProject = deps.loadProject ?? loadKubeProjectContext;
41
+ let flags;
42
+ let forSpec;
43
+ let timeoutMs;
44
+ try {
45
+ flags = parseKubeFlags(rawArgs, { value: { "--for": "for", "--timeout": "timeout" } });
46
+ forSpec = parseFor(flags.values.for);
47
+ timeoutMs = flags.values.timeout !== undefined ? parseDurationMs(flags.values.timeout) : 30000;
48
+ }
49
+ catch (err) {
50
+ console.error(err instanceof Error ? err.message : String(err));
51
+ return 1;
52
+ }
53
+ const project = await loadProject();
54
+ const target = resolveKubeTarget(flags.positional, flags.values.namespace, project);
55
+ if (isTargetError(target)) {
56
+ console.error(`error: ${target.error}`);
57
+ return 1;
58
+ }
59
+ if (!target.name) {
60
+ console.error("error: chant kube wait requires a single named resource, e.g. `chant kube wait deployment web`");
61
+ return 1;
62
+ }
63
+ const connected = await kubeConnect(connectOptionsFrom(flags.values), connect);
64
+ if (connected.kind === "unobserved") {
65
+ console.error(formatUnobserved(targetLabel(target), { reason: connected.reason, detail: connected.detail }));
66
+ return 1;
67
+ }
68
+ const { client } = connected;
69
+ let info;
70
+ try {
71
+ info = await client.resolve(target.selector);
72
+ }
73
+ catch (err) {
74
+ const outcome = classifyApiFailure(err);
75
+ console.error(formatUnobserved(targetLabel(target), {
76
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
77
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
78
+ }));
79
+ return 1;
80
+ }
81
+ if (!info) {
82
+ console.error(`error: the server doesn't have a resource type "${targetLabel(target)}"`);
83
+ return 1;
84
+ }
85
+ const namespace = target.namespace ?? client.defaultNamespace;
86
+ const controller = new AbortController();
87
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
88
+ try {
89
+ if (forSpec?.kind === "delete") {
90
+ await waitForDeletion(client, { apiVersion: info.apiVersion, kind: info.kind, name: target.name, namespace }, controller.signal);
91
+ console.log(`${info.kind}/${target.name} deleted`);
92
+ return 0;
93
+ }
94
+ const spec = forSpec?.kind === "spec" ? forSpec.spec : readinessFor(info.group, info.kind);
95
+ const fetcher = async (args, signal) => (await client.read({ apiVersion: info.apiVersion, kind: info.kind, name: args.name, ...(args.namespace ? { namespace: args.namespace } : {}) }, { signal }));
96
+ await waitForReady({ kind: info.kind, name: target.name, namespace, group: info.group, spec }, controller.signal, fetcher);
97
+ console.log(`${info.kind}/${target.name} condition met`);
98
+ return 0;
99
+ }
100
+ catch (err) {
101
+ if (err instanceof ReadinessFailedError) {
102
+ console.error(err.message);
103
+ return 1;
104
+ }
105
+ if (controller.signal.aborted) {
106
+ console.error(`error: timed out after ${timeoutMs}ms waiting for ${info.kind}/${target.name}`);
107
+ return 1;
108
+ }
109
+ const outcome = classifyApiFailure(err);
110
+ console.error(formatUnobserved(`${info.kind}/${target.name}`, {
111
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
112
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
113
+ }));
114
+ return 1;
115
+ }
116
+ finally {
117
+ clearTimeout(timer);
118
+ }
119
+ }
120
+ async function waitForDeletion(client, ref, signal, intervalMs = 5000) {
121
+ while (true) {
122
+ if (signal.aborted)
123
+ throw new Error("wait --for=delete aborted");
124
+ const obj = await client.readIfPresent(ref, { signal });
125
+ if (!obj)
126
+ return;
127
+ await new Promise((r) => setTimeout(r, intervalMs));
128
+ }
129
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The kustomize renderer (#1548) — one implementation, two consumers.
3
+ *
4
+ * Piece 1 (`components/kustomize-apply.ts`) renders at DEPLOY time in front
5
+ * of the server-side applier; piece 3 (`./root.ts`) renders at BUILD time to
6
+ * give an overlay estate a declared side. Both run the same command with the
7
+ * same fallback through the same injectable runner, so a test asserts the
8
+ * exact invocation without either binary installed and the two paths cannot
9
+ * drift apart on how an overlay is rendered.
10
+ *
11
+ * Renderer resolution: `kustomize build`, falling back to `kubectl
12
+ * kustomize` (the same renderer vendored into kubectl) when the standalone
13
+ * binary is absent. When NEITHER binary exists the failure names both — a
14
+ * missing renderer is an environment problem the operator can fix in one
15
+ * install, and it must read as that, not as a stack trace. A render failure
16
+ * from an existing binary (a broken overlay, a missing base) propagates
17
+ * untouched: that error text is kustomize's own diagnosis and is strictly
18
+ * better than anything this module could wrap it in.
19
+ *
20
+ * The 64MiB maxBuffer mirrors helm-upgrade's — a big overlay renders
21
+ * megabytes.
22
+ */
23
+ import { exec } from "node:child_process";
24
+ import { promisify } from "node:util";
25
+ import { loadAll } from "js-yaml";
26
+ const execAsync = promisify(exec);
27
+ export const defaultKustomizeRunner = (command) => execAsync(command, { maxBuffer: 64 * 1024 * 1024 });
28
+ /** Single-quote shell escaping, as helm-upgrade quotes its argv. */
29
+ function q(v) {
30
+ return `'${v.replace(/'/g, "'\\''")}'`;
31
+ }
32
+ /** The exact render command, standalone binary first. Pure; exported for tests. */
33
+ export function renderCommand(dir, tool = "kustomize") {
34
+ return tool === "kustomize" ? `kustomize build ${q(dir)}` : `kubectl kustomize ${q(dir)}`;
35
+ }
36
+ /** Does this error mean "the binary is not installed" (vs "the render failed")? */
37
+ function isMissingBinary(err) {
38
+ const message = err instanceof Error ? err.message : String(err);
39
+ return /ENOENT|not found|command not found|127/.test(message);
40
+ }
41
+ /**
42
+ * Render the kustomization at `dir` and parse the emitted YAML into document
43
+ * objects. Falls back to kubectl's vendored kustomize when the standalone
44
+ * binary is missing; when both binaries are missing, fails with a message
45
+ * naming them.
46
+ */
47
+ export async function renderKustomizeDocuments(dir, run = defaultKustomizeRunner) {
48
+ let stdout;
49
+ try {
50
+ ({ stdout } = await run(renderCommand(dir, "kustomize")));
51
+ }
52
+ catch (err) {
53
+ if (!isMissingBinary(err))
54
+ throw err;
55
+ try {
56
+ ({ stdout } = await run(renderCommand(dir, "kubectl")));
57
+ }
58
+ catch (err2) {
59
+ if (isMissingBinary(err2)) {
60
+ throw new Error(`Cannot render kustomization "${dir}": neither the \`kustomize\` binary nor \`kubectl\` ` +
61
+ `(whose vendored \`kubectl kustomize\` is the fallback) was found on PATH. ` +
62
+ `Install kustomize or kubectl and retry.`);
63
+ }
64
+ throw err2;
65
+ }
66
+ }
67
+ const documents = [];
68
+ for (const doc of loadAll(stdout)) {
69
+ if (doc && typeof doc === "object" && !Array.isArray(doc))
70
+ documents.push(doc);
71
+ }
72
+ return documents;
73
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A rendered kustomize document as a chant entity (#1548 piece 3).
3
+ *
4
+ * A kustomize build root's output is a set of finished manifests, not typed
5
+ * chant source — the overlay already decided every field. The entity that
6
+ * carries one into the build is therefore the verbatim manifest entity
7
+ * (`../manifest-entity`), shared since #999 with `chant carve emit`'s adoption
8
+ * of a Terraform `kubernetes_manifest`: `props` IS the document, and the
9
+ * serializer emits it as-is.
10
+ *
11
+ * Provenance: every rendered document is stamped with an annotation naming
12
+ * the overlay dir it came from, so a consumer (behold#171's overlay boxes)
13
+ * can group by declared origin instead of guessing from paths — the one thing
14
+ * this adds over a plain manifest entity.
15
+ */
16
+ import { manifestEntity, RENDERED_MANIFEST_MARKER, isRenderedManifestEntity } from "../manifest-entity.js";
17
+ export { RENDERED_MANIFEST_MARKER, isRenderedManifestEntity };
18
+ /** Annotation stamped on every rendered document, valued with the root dir. */
19
+ export const KUSTOMIZE_ROOT_ANNOTATION = "chant.intentius.io/kustomize-root";
20
+ /**
21
+ * Wrap one rendered document. Returns null when the document has no string
22
+ * `apiVersion`/`kind` — not a Kubernetes object, nothing to declare.
23
+ *
24
+ * `annotation` is the provenance key: the kustomize root annotation by
25
+ * default, `k8sInclude`'s source annotation for an included file (#3566).
26
+ */
27
+ export function renderedManifestEntity(doc, root, annotation = KUSTOMIZE_ROOT_ANNOTATION) {
28
+ const metadata = { ...(doc.metadata ?? {}) };
29
+ metadata.annotations = {
30
+ ...(metadata.annotations ?? {}),
31
+ [annotation]: root,
32
+ };
33
+ return manifestEntity({ ...doc, metadata });
34
+ }
35
+ /** `Deployment` + `my-app` → `deploymentMyApp`, the import path's naming. */
36
+ export function renderedLogicalId(kind, name) {
37
+ const prefix = kind.charAt(0).toLowerCase() + kind.slice(1);
38
+ if (!name)
39
+ return prefix;
40
+ const pascal = name
41
+ .split(/[^a-zA-Z0-9]+/)
42
+ .filter(Boolean)
43
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
44
+ .join("");
45
+ return `${prefix}${pascal}`;
46
+ }
47
+ /**
48
+ * Add a rendered entity under a deterministic, human-readable key:
49
+ * `<prefix>/<kindName>`. Two documents can share kind+name (different
50
+ * namespaces, or different API groups with the same kind word), so a
51
+ * collision is qualified by namespace first, then numbered in document
52
+ * order. Returns the key used.
53
+ */
54
+ export function addRenderedEntity(entities, prefix, entity) {
55
+ const metadata = entity.props.metadata;
56
+ const kind = entity.props.kind;
57
+ const name = typeof metadata.name === "string" ? metadata.name : undefined;
58
+ const namespace = typeof metadata.namespace === "string" ? metadata.namespace : undefined;
59
+ const base = `${prefix}/${renderedLogicalId(kind, name)}`;
60
+ let key = base;
61
+ if (entities.has(key) && namespace)
62
+ key = `${base}.${namespace}`;
63
+ for (let n = 2; entities.has(key); n++)
64
+ key = `${base}~${n}`;
65
+ entities.set(key, entity);
66
+ return key;
67
+ }