@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,159 @@
1
+ /**
2
+ * `chant kube get` (chant #1079) — kubectl's own muscle-memory verb, plus the
3
+ * chant verdict column the issue's acceptance criterion names.
4
+ *
5
+ * The cluster edge is the typed client (chant #1074/#1177), reached the same
6
+ * way every other read does: `--env` resolves and uses the cluster
7
+ * binding (chant #1100/#1155, revised by #1488 — the declared context is
8
+ * used regardless of what is ambient), `--context` is an explicit override,
9
+ * neither given falls back to the ambient kubeconfig context. A connect failure, an RBAC-denied read, or a kind the cluster's
10
+ * discovery has never heard of all render as an honest message rather than
11
+ * an empty table — "No resources found." is reserved for the one case that
12
+ * actually means it: the kind is real and there are zero live instances.
13
+ */
14
+ import { formatUnobserved } from "@intentius/chant/observation";
15
+ import { defaultK8sConnector } from "../api/connect.js";
16
+ import { classifyApiFailure, isUnknownResource } from "../api/classify.js";
17
+ import { statusFromObject } from "../describe-resources.js";
18
+ import { kubeConnect } from "./connect.js";
19
+ import { parseKubeFlags, connectOptionsFrom } from "./flags.js";
20
+ import { loadKubeProjectContext } from "./project.js";
21
+ import { resolveKubeTarget, matchLiveObject, isTargetError } from "./target.js";
22
+ import { verdictFor } from "./verdict.js";
23
+ import { parseOutput, renderKubeTable, relativeAge, renderJson, renderYaml, renderName, renderChantSource, renderJsonPath, renderCustomColumns, } from "./render.js";
24
+ function targetLabel(target) {
25
+ return "apiVersion" in target.selector ? target.selector.kind : target.selector.resource;
26
+ }
27
+ export async function runGet(rawArgs, deps = {}) {
28
+ const connect = deps.connect ?? defaultK8sConnector;
29
+ const loadProject = deps.loadProject ?? loadKubeProjectContext;
30
+ let flags;
31
+ try {
32
+ flags = parseKubeFlags(rawArgs);
33
+ }
34
+ catch (err) {
35
+ console.error(err instanceof Error ? err.message : String(err));
36
+ return 1;
37
+ }
38
+ let output;
39
+ try {
40
+ output = parseOutput(flags.values.output);
41
+ }
42
+ catch (err) {
43
+ console.error(err instanceof Error ? err.message : String(err));
44
+ return 1;
45
+ }
46
+ const project = await loadProject();
47
+ const target = resolveKubeTarget(flags.positional, flags.values.namespace, project);
48
+ if (isTargetError(target)) {
49
+ console.error(`error: ${target.error}`);
50
+ return 1;
51
+ }
52
+ const connected = await kubeConnect(connectOptionsFrom(flags.values), connect);
53
+ if (connected.kind === "unobserved") {
54
+ console.error(formatUnobserved(targetLabel(target), unobservedEntity(connected)));
55
+ return 1;
56
+ }
57
+ const { client } = connected;
58
+ let info;
59
+ try {
60
+ info = await client.resolve(target.selector);
61
+ }
62
+ catch (err) {
63
+ const outcome = classifyApiFailure(err);
64
+ console.error(formatUnobserved(targetLabel(target), {
65
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
66
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
67
+ }));
68
+ return 1;
69
+ }
70
+ if (!info) {
71
+ console.error(`error: the server doesn't have a resource type "${targetLabel(target)}"`);
72
+ return 1;
73
+ }
74
+ const namespace = flags.flags.allNamespaces ? undefined : (target.namespace ?? client.defaultNamespace);
75
+ const ref = { apiVersion: info.apiVersion, kind: info.kind };
76
+ let objects;
77
+ if (target.name) {
78
+ try {
79
+ const obj = await client.readIfPresent({ ...ref, name: target.name, ...(namespace ? { namespace } : {}) });
80
+ if (!obj) {
81
+ console.error(`Error from server (NotFound): ${info.name} "${target.name}" not found`);
82
+ return 1;
83
+ }
84
+ objects = [obj];
85
+ }
86
+ catch (err) {
87
+ if (isUnknownResource(err)) {
88
+ console.error(`error: the server doesn't have a resource type "${targetLabel(target)}"`);
89
+ return 1;
90
+ }
91
+ const outcome = classifyApiFailure(err);
92
+ console.error(formatUnobserved(`${info.kind}/${target.name}`, {
93
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
94
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
95
+ }));
96
+ return 1;
97
+ }
98
+ }
99
+ else {
100
+ try {
101
+ objects = await client.list(ref, {
102
+ ...(namespace ? { namespace } : {}),
103
+ ...(flags.values.selector ? { labelSelector: flags.values.selector } : {}),
104
+ });
105
+ }
106
+ catch (err) {
107
+ const outcome = classifyApiFailure(err);
108
+ console.error(formatUnobserved(info.kind, {
109
+ reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
110
+ detail: outcome.kind === "unobserved" ? outcome.detail : String(err),
111
+ }));
112
+ return 1;
113
+ }
114
+ }
115
+ switch (output.kind) {
116
+ case "json":
117
+ console.log(renderJson(objects));
118
+ return 0;
119
+ case "yaml":
120
+ console.log(renderYaml(objects));
121
+ return 0;
122
+ case "name":
123
+ console.log(renderName(objects));
124
+ return 0;
125
+ case "chant":
126
+ console.log(renderChantSource(objects));
127
+ return 0;
128
+ case "jsonpath":
129
+ console.log(renderJsonPath(output.expr, objects));
130
+ return 0;
131
+ case "custom-columns":
132
+ console.log(renderCustomColumns(output.spec, objects));
133
+ return 0;
134
+ }
135
+ const allNamespaces = flags.flags.allNamespaces ?? false;
136
+ const rows = objects.map((obj) => {
137
+ const match = matchLiveObject(project, {
138
+ apiVersion: obj.apiVersion ?? info.apiVersion,
139
+ kind: obj.kind ?? info.kind,
140
+ name: obj.metadata?.name ?? "",
141
+ namespace: obj.metadata?.namespace,
142
+ });
143
+ return {
144
+ namespace: obj.metadata?.namespace,
145
+ name: obj.metadata?.name ?? "",
146
+ kind: info.kind,
147
+ status: statusFromObject(obj),
148
+ age: relativeAge(obj.metadata?.creationTimestamp),
149
+ verdict: project ? verdictFor(obj, match) : "unavailable",
150
+ labels: obj.metadata?.labels,
151
+ object: obj,
152
+ };
153
+ });
154
+ console.log(renderKubeTable(rows, { allNamespaces, wide: output.kind === "wide", showVerdict: true }));
155
+ return 0;
156
+ }
157
+ function unobservedEntity(connected) {
158
+ return { reason: connected.reason, detail: connected.detail };
159
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * `chant kube` — the terminal surface over the typed client (chant #1079),
3
+ * mounted through the command-group seam #1078 built and reserved this exact
4
+ * namespace for (`lexicons/k8s/src/plugin.ts`'s `commands()`).
5
+ *
6
+ * Every verb below except `version` dynamically imports its own module. That
7
+ * is not a style choice: `describe-resources.ts`, `api/connect.ts` and the
8
+ * rest of the read/write paths sit behind a dynamic import specifically so
9
+ * `chant build` never has to resolve `@intentius/chant-k8s-client`
10
+ * (chant #1074, `examples/k8s-client-boundary.test.ts`). This module IS
11
+ * statically reachable from `plugin.ts` — it is plain data, a list of verb
12
+ * names and descriptions — so every verb whose implementation touches the
13
+ * client must be reached from here only through `await import(...)`, exactly
14
+ * like `version`'s existing `await import("../spec/fetch.js")`.
15
+ */
16
+ import { splitJoinedFlags, unknownFlagError } from "@intentius/chant/cli/command-group";
17
+ async function versionHandler(ctx) {
18
+ const args = splitJoinedFlags(ctx.rawArgs);
19
+ let format = "text";
20
+ for (let i = 0; i < args.length; i++) {
21
+ const a = args[i];
22
+ if (a === "--format" || a === "-f") {
23
+ format = args[++i] === "json" ? "json" : "text";
24
+ }
25
+ else if (a.startsWith("-")) {
26
+ throw unknownFlagError(a, `"chant kube version" only accepts --format <text|json>.`);
27
+ }
28
+ }
29
+ const { K8S_SCHEMA_VERSION } = await import("../spec/fetch.js");
30
+ if (format === "json") {
31
+ console.log(JSON.stringify({ schemaVersion: K8S_SCHEMA_VERSION }));
32
+ }
33
+ else {
34
+ console.log(K8S_SCHEMA_VERSION);
35
+ }
36
+ return 0;
37
+ }
38
+ /** The full `chant kube` verb group. */
39
+ export function kubeCommandGroup() {
40
+ return {
41
+ name: "kube",
42
+ description: "Terminal surface over the k8s typed client — kubectl-compatible reads plus chant's own verdict, source, and gated writes",
43
+ commands: [
44
+ {
45
+ name: "version",
46
+ description: "Print the pinned Kubernetes API schema version this lexicon was generated from",
47
+ handler: versionHandler,
48
+ },
49
+ {
50
+ name: "get",
51
+ description: "List or read live resources — kubectl's own flags, plus a declared/owned/drifted/foreign-owned column",
52
+ async handler(ctx) {
53
+ const { runGet } = await import("./get.js");
54
+ return runGet(ctx.rawArgs);
55
+ },
56
+ },
57
+ {
58
+ name: "describe",
59
+ description: "Human-readable detail for one or more resources, including chant's provenance and the object's Events",
60
+ async handler(ctx) {
61
+ const { runDescribe } = await import("./describe.js");
62
+ return runDescribe(ctx.rawArgs);
63
+ },
64
+ },
65
+ {
66
+ name: "logs",
67
+ description: "A Pod's log (snapshot only — no --follow)",
68
+ async handler(ctx) {
69
+ const { runLogs } = await import("./logs.js");
70
+ return runLogs(ctx.rawArgs);
71
+ },
72
+ },
73
+ {
74
+ name: "events",
75
+ description: "List Events, oldest first, optionally filtered with --for=Kind/name",
76
+ async handler(ctx) {
77
+ const { runEvents } = await import("./events.js");
78
+ return runEvents(ctx.rawArgs);
79
+ },
80
+ },
81
+ {
82
+ name: "top",
83
+ description: "Pod/node resource usage from metrics.k8s.io, when the cluster runs metrics-server",
84
+ async handler(ctx) {
85
+ const { runTop } = await import("./top.js");
86
+ return runTop(ctx.rawArgs);
87
+ },
88
+ },
89
+ {
90
+ name: "wait",
91
+ description: "Block until a resource's readiness spec is met (registry-driven — the same one op/activities/wait-for-ready.ts uses)",
92
+ async handler(ctx) {
93
+ const { runWait } = await import("./wait.js");
94
+ return runWait(ctx.rawArgs);
95
+ },
96
+ },
97
+ {
98
+ name: "source",
99
+ description: "Resolve a live object back to the .ts file and composite that declared it",
100
+ async handler(ctx) {
101
+ const { runSource } = await import("./source.js");
102
+ return runSource(ctx.rawArgs);
103
+ },
104
+ },
105
+ {
106
+ name: "apply",
107
+ description: "Server-side apply a manifest (dry-run preview by default — pass --yes to persist); same machinery the Op activity uses",
108
+ async handler(ctx) {
109
+ const { runApply } = await import("./apply.js");
110
+ return runApply(ctx.rawArgs);
111
+ },
112
+ },
113
+ {
114
+ name: "delete",
115
+ description: "Delete one named resource (never a bare kind sweep; preview by default — pass --yes to delete)",
116
+ async handler(ctx) {
117
+ const { runDelete } = await import("./delete.js");
118
+ return runDelete(ctx.rawArgs);
119
+ },
120
+ },
121
+ ],
122
+ };
123
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `chant kube logs` (chant #1079) — a Pod's `/log` subresource, over the
3
+ * typed client's new `readLog` (`packages/k8s-client/src/client.ts`). A
4
+ * snapshot only: `--follow`/`-f` would need the transport seam to expose a
5
+ * stream rather than a completed response body, which it does not today —
6
+ * left out deliberately (see `readLog`'s own doc) rather than faked with a
7
+ * poll loop that isn't actually following anything.
8
+ */
9
+ import { formatUnobserved } from "@intentius/chant/observation";
10
+ import { defaultK8sConnector } from "../api/connect.js";
11
+ import { classifyApiFailure } from "../api/classify.js";
12
+ import { operationFor } from "../api/operation-surface.js";
13
+ import { kubeConnect } from "./connect.js";
14
+ import { parseKubeFlags, connectOptionsFrom, parseDurationSeconds } from "./flags.js";
15
+ import { loadKubeProjectContext } from "./project.js";
16
+ export async function runLogs(rawArgs, deps = {}) {
17
+ const connect = deps.connect ?? defaultK8sConnector;
18
+ const loadProject = deps.loadProject ?? loadKubeProjectContext;
19
+ let flags;
20
+ try {
21
+ flags = parseKubeFlags(rawArgs, {
22
+ value: { "-c": "container", "--container": "container", "--tail": "tail", "--since": "since" },
23
+ boolean: { "-p": "previous", "--previous": "previous", "--timestamps": "timestamps" },
24
+ });
25
+ }
26
+ catch (err) {
27
+ console.error(err instanceof Error ? err.message : String(err));
28
+ return 1;
29
+ }
30
+ const podArg = flags.positional[0];
31
+ if (!podArg) {
32
+ console.error("error: a pod name (or a declared entity name) is required — e.g. `chant kube logs web-6c8f`");
33
+ return 1;
34
+ }
35
+ const project = await loadProject();
36
+ let podName = podArg;
37
+ let namespace = flags.values.namespace;
38
+ const entity = project?.entities.get(podArg);
39
+ if (entity && entity.lexicon === "k8s") {
40
+ const operation = operationFor(entity.entityType);
41
+ if (operation && operation.kind !== "Pod") {
42
+ console.error(`error: "${podArg}" declares a ${operation.kind}, not a Pod — chant kube logs only reads Pod logs`);
43
+ return 1;
44
+ }
45
+ if (operation?.kind === "Pod") {
46
+ const props = "props" in entity ? entity.props : undefined;
47
+ const metadata = props?.metadata;
48
+ if (metadata?.name) {
49
+ podName = metadata.name;
50
+ namespace = namespace ?? metadata.namespace;
51
+ }
52
+ }
53
+ }
54
+ const connected = await kubeConnect(connectOptionsFrom(flags.values), connect);
55
+ if (connected.kind === "unobserved") {
56
+ console.error(formatUnobserved(`pod/${podName}`, { reason: connected.reason, detail: connected.detail }));
57
+ return 1;
58
+ }
59
+ const { client } = connected;
60
+ const tailLines = flags.values.tail !== undefined ? Number(flags.values.tail) : undefined;
61
+ const sinceSeconds = flags.values.since !== undefined ? parseDurationSeconds(flags.values.since) : undefined;
62
+ try {
63
+ const text = await client.readLog({ apiVersion: "v1", kind: "Pod", name: podName, namespace: namespace ?? client.defaultNamespace }, {
64
+ ...(flags.values.container ? { container: flags.values.container } : {}),
65
+ ...(tailLines !== undefined ? { tailLines } : {}),
66
+ ...(sinceSeconds !== undefined ? { sinceSeconds } : {}),
67
+ previous: flags.flags.previous ?? false,
68
+ timestamps: flags.flags.timestamps ?? false,
69
+ });
70
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
71
+ return 0;
72
+ }
73
+ catch (err) {
74
+ const outcome = classifyApiFailure(err);
75
+ if (outcome.kind === "absent") {
76
+ console.error(`Error from server (NotFound): pods "${podName}" not found`);
77
+ return 1;
78
+ }
79
+ console.error(formatUnobserved(`pod/${podName}`, { reason: outcome.reason, detail: outcome.detail }));
80
+ return 1;
81
+ }
82
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Best-effort chant-project context for `chant kube` (chant #1079).
3
+ *
4
+ * `get`'s chant-verdict column and `source` both need to answer "does a
5
+ * declared entity in this project's source name this live object" — which
6
+ * needs the project built (chant.config.ts read, source discovered and
7
+ * resolved). `chant kube` is also usable against a bare cluster with no
8
+ * chant project in sight (a checkout of someone else's manifests, a
9
+ * throwaway directory) — the acceptance criterion is that reads still work
10
+ * there and only the chant-specific parts degrade, quietly, to
11
+ * "unavailable" rather than erroring. Every failure mode here — no
12
+ * `chant.config.ts`, a project with build errors, an unreadable directory —
13
+ * collapses to `undefined` for exactly that reason.
14
+ *
15
+ * Deliberately does not import anything client-shaped: this runs before a
16
+ * cluster is ever reached (`source`/`get` need it to compute a verdict for
17
+ * objects the client already read), and matching is pure once given a live
18
+ * object's coordinates.
19
+ */
20
+ import { resolve, relative, isAbsolute } from "node:path";
21
+ import { build } from "@intentius/chant/build";
22
+ import { loadChantConfig } from "@intentius/chant/config";
23
+ import { getProvenance } from "@intentius/chant/provenance";
24
+ import { k8sSerializer } from "../serializer.js";
25
+ import { operationFor } from "../api/operation-surface.js";
26
+ /**
27
+ * Build the project rooted at `cwd` (or its configured `sourceDir`) with the
28
+ * k8s serializer, so `entities` carries resolved props (composites expanded,
29
+ * AttrRefs resolved) and build provenance (chant #1064's entity-level
30
+ * `sourceFile`/`composite`, stamped during discovery). Returns undefined —
31
+ * never throws — for anything that stops the project from resolving: no
32
+ * `chant.config.ts` here or above, a build with errors, an inaccessible
33
+ * directory. This IS the "outside a chant project" signal every caller
34
+ * checks for.
35
+ */
36
+ export async function loadKubeProjectContext(cwd = process.cwd()) {
37
+ try {
38
+ const { config, configPath } = await loadChantConfig(cwd);
39
+ // `loadChantConfig` does not throw when neither `chant.config.ts` nor
40
+ // `chant.config.json` exists at `cwd` — it returns the default empty
41
+ // config instead (`configPath` absent). Building anyway would treat an
42
+ // arbitrary directory (a bare cluster checkout, chant's own monorepo
43
+ // root run from the wrong cwd) as a chant project rooted right there —
44
+ // exactly the "outside a chant project" case this function exists to
45
+ // detect, not paper over.
46
+ if (!configPath)
47
+ return undefined;
48
+ const root = resolve(cwd, config.sourceDir ?? ".");
49
+ const result = await build(root, [k8sSerializer]);
50
+ if (result.errors.length > 0)
51
+ return undefined;
52
+ return { cwd, config, entities: result.entities };
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ function propsOf(entity) {
59
+ return "props" in entity ? entity.props : undefined;
60
+ }
61
+ /**
62
+ * Reverse-map a live object's coordinates (apiVersion/kind/name/namespace) to
63
+ * the declared k8s entity that names it, if this project declares one.
64
+ * `operationFor` is the same generated operation surface `describeResources`
65
+ * addresses entities with, so a match here is addressed exactly the way the
66
+ * live read that produced the object was — no separate naming convention.
67
+ */
68
+ export function findDeclaredMatch(entities, ref) {
69
+ for (const [entityName, entity] of entities) {
70
+ if (entity.lexicon !== "k8s")
71
+ continue;
72
+ const operation = operationFor(entity.entityType);
73
+ if (!operation || operation.apiVersion !== ref.apiVersion || operation.kind !== ref.kind)
74
+ continue;
75
+ const props = propsOf(entity);
76
+ const metadata = props?.metadata;
77
+ if (metadata?.name !== ref.name)
78
+ continue;
79
+ if ((metadata?.namespace ?? undefined) !== (ref.namespace ?? undefined))
80
+ continue;
81
+ return { entityName, entity, props };
82
+ }
83
+ return undefined;
84
+ }
85
+ /** Provenance for a matched entity, relative to the project root when possible. */
86
+ export function relativeProvenance(ctx, entity) {
87
+ const prov = getProvenance(entity);
88
+ if (!prov)
89
+ return undefined;
90
+ if (!prov.sourceFile || !isAbsolute(prov.sourceFile))
91
+ return prov;
92
+ const rel = relative(ctx.cwd, prov.sourceFile);
93
+ return { ...prov, sourceFile: rel.startsWith("..") ? prov.sourceFile : rel };
94
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Output rendering for `chant kube get`/`describe` (chant #1079).
3
+ *
4
+ * kubectl's own `-o` vocabulary, minus the one form the issue says to drop
5
+ * openly rather than half-implement: `-o go-template[-file]` is Go template
6
+ * syntax, and chant does not carry a Go template engine — {@link parseOutput}
7
+ * refuses it by name rather than pretending a partial implementation covers
8
+ * it. `-o jsonpath` and `-o custom-columns` are "tractable" per the issue's
9
+ * own notes, and are implemented here as a deliberately small subset: dot
10
+ * paths, bracket indices, and a `[*]` wildcard — the paths kubectl one-liners
11
+ * actually use — not the full jsonpath grammar (filters, `range`/`end`
12
+ * templates). `-o chant` is chant's own addition: a live object rendered as
13
+ * the typed source that would declare it, reusing `./import/live-export.ts`
14
+ * and the lexicon's own `K8sGenerator` — the same machinery `chant import`
15
+ * uses, so every read is an adoption path, per the issue's framing.
16
+ */
17
+ import { dump } from "js-yaml";
18
+ import { buildExportFromObjects } from "../import/live-export.js";
19
+ import { K8sGenerator } from "../import/generator.js";
20
+ /** Parse an `-o`/`--output` value. Throws on `go-template[-file]`. */
21
+ export function parseOutput(value) {
22
+ if (value === undefined || value === "" || value === "text")
23
+ return { kind: "text" };
24
+ if (value === "wide")
25
+ return { kind: "wide" };
26
+ if (value === "json")
27
+ return { kind: "json" };
28
+ if (value === "yaml")
29
+ return { kind: "yaml" };
30
+ if (value === "name")
31
+ return { kind: "name" };
32
+ if (value === "chant")
33
+ return { kind: "chant" };
34
+ if (value.startsWith("jsonpath="))
35
+ return { kind: "jsonpath", expr: value.slice("jsonpath=".length) };
36
+ if (value.startsWith("custom-columns="))
37
+ return { kind: "custom-columns", spec: value.slice("custom-columns=".length) };
38
+ if (value === "go-template" || value === "go-template-file" || value.startsWith("go-template")) {
39
+ throw new Error("-o go-template is not supported — chant kube does not carry a Go template engine, and a partial " +
40
+ "implementation would be worse than none. Use -o jsonpath=, -o custom-columns=, or -o chant instead.");
41
+ }
42
+ throw new Error(`unsupported -o value: ${value}`);
43
+ }
44
+ /** kubectl-style relative age, e.g. "45s", "12m", "3h", "5d". */
45
+ export function relativeAge(creationTimestamp, now = Date.now()) {
46
+ if (!creationTimestamp)
47
+ return "<unknown>";
48
+ const then = Date.parse(creationTimestamp);
49
+ if (Number.isNaN(then))
50
+ return "<unknown>";
51
+ const seconds = Math.max(0, Math.floor((now - then) / 1000));
52
+ if (seconds < 60)
53
+ return `${seconds}s`;
54
+ const minutes = Math.floor(seconds / 60);
55
+ if (minutes < 60)
56
+ return `${minutes}m`;
57
+ const hours = Math.floor(minutes / 60);
58
+ if (hours < 24)
59
+ return `${hours}h`;
60
+ const days = Math.floor(hours / 24);
61
+ if (days < 365)
62
+ return `${days}d`;
63
+ return `${Math.floor(days / 365)}y`;
64
+ }
65
+ /** Render a fixed-width table. `columns` is header → per-row value. */
66
+ export function renderTable(headers, rows) {
67
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
68
+ const line = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
69
+ return [line(headers), ...rows.map(line)].join("\n");
70
+ }
71
+ export function renderKubeTable(rows, opts) {
72
+ if (rows.length === 0)
73
+ return "No resources found.";
74
+ const headers = [];
75
+ if (opts.allNamespaces)
76
+ headers.push("NAMESPACE");
77
+ headers.push("NAME", "STATUS", "AGE");
78
+ if (opts.showVerdict)
79
+ headers.push("CHANT");
80
+ if (opts.wide)
81
+ headers.push("LABELS");
82
+ const cells = rows.map((r) => {
83
+ const row = [];
84
+ if (opts.allNamespaces)
85
+ row.push(r.namespace ?? "");
86
+ row.push(r.name, r.status, r.age ?? "<unknown>");
87
+ if (opts.showVerdict)
88
+ row.push(r.verdict ?? "unavailable");
89
+ if (opts.wide)
90
+ row.push(labelsText(r.labels));
91
+ return row;
92
+ });
93
+ return renderTable(headers, cells);
94
+ }
95
+ function labelsText(labels) {
96
+ if (!labels || Object.keys(labels).length === 0)
97
+ return "<none>";
98
+ return Object.entries(labels)
99
+ .map(([k, v]) => `${k}=${v}`)
100
+ .join(",");
101
+ }
102
+ // ── json / yaml / name ─────────────────────────────────────────────────────
103
+ /** kubectl wraps >1 item in a synthetic List; a single item is printed bare. */
104
+ function asPrintable(objects) {
105
+ if (objects.length === 1)
106
+ return objects[0];
107
+ return { apiVersion: "v1", kind: "List", items: objects, metadata: {} };
108
+ }
109
+ export function renderJson(objects) {
110
+ return JSON.stringify(asPrintable(objects), null, 2);
111
+ }
112
+ export function renderYaml(objects) {
113
+ return dump(asPrintable(objects));
114
+ }
115
+ /** `kubectl get -o name`: `<lowercased-kind>.<group>/<name>` (`<lowercased-kind>/<name>` for the core group). */
116
+ export function renderName(objects) {
117
+ return objects
118
+ .map((o) => {
119
+ const [group] = (o.apiVersion ?? "").includes("/") ? [o.apiVersion.split("/")[0]] : [""];
120
+ const kind = (o.kind ?? "").toLowerCase();
121
+ const prefix = group ? `${kind}.${group}` : kind;
122
+ return `${prefix}/${o.metadata?.name ?? ""}`;
123
+ })
124
+ .join("\n");
125
+ }
126
+ // ── -o chant ────────────────────────────────────────────────────────────────
127
+ /** Render live objects as the typed chant source that would declare them. */
128
+ export function renderChantSource(objects) {
129
+ const ir = buildExportFromObjects(objects);
130
+ const files = new K8sGenerator().generate(ir);
131
+ return files.map((f) => f.content).join("\n\n");
132
+ }
133
+ function parsePath(path) {
134
+ // ".a.b[0].c[*]" → segments; a leading "." (or none) both mean "from root".
135
+ const segments = [];
136
+ const re = /\.([^.[\]]+)|\[(\*|\d+)\]/g;
137
+ let match;
138
+ while ((match = re.exec(path))) {
139
+ if (match[1] !== undefined)
140
+ segments.push({ key: match[1] });
141
+ else if (match[2] === "*")
142
+ segments.push({ wildcard: true });
143
+ else
144
+ segments.push({ index: Number(match[2]) });
145
+ }
146
+ return segments;
147
+ }
148
+ function evalPath(root, segments) {
149
+ let current = [root];
150
+ for (const seg of segments) {
151
+ const next = [];
152
+ for (const value of current) {
153
+ if (value === null || value === undefined)
154
+ continue;
155
+ if ("wildcard" in seg) {
156
+ if (Array.isArray(value))
157
+ next.push(...value);
158
+ else if (typeof value === "object")
159
+ next.push(...Object.values(value));
160
+ continue;
161
+ }
162
+ if ("index" in seg) {
163
+ if (Array.isArray(value))
164
+ next.push(value[seg.index]);
165
+ continue;
166
+ }
167
+ if (typeof value === "object")
168
+ next.push(value[seg.key]);
169
+ }
170
+ current = next;
171
+ }
172
+ return current;
173
+ }
174
+ function scalarText(v) {
175
+ if (v === undefined || v === null)
176
+ return "";
177
+ if (typeof v === "string")
178
+ return v;
179
+ return JSON.stringify(v);
180
+ }
181
+ /**
182
+ * Evaluate a kubectl-style jsonpath template against one root value: literal
183
+ * text passes through, `{"..."}` is a quoted literal, `{.a.b[*].c}` extracts
184
+ * and space-joins matches. Multiple `{...}` blocks concatenate directly, as
185
+ * kubectl's own `-o jsonpath` does.
186
+ */
187
+ export function evalJsonPath(template, root) {
188
+ let out = "";
189
+ const re = /\{([^}]*)\}/g;
190
+ let lastIndex = 0;
191
+ let match;
192
+ while ((match = re.exec(template))) {
193
+ out += template.slice(lastIndex, match.index);
194
+ const inner = match[1];
195
+ if (inner.startsWith('"') && inner.endsWith('"')) {
196
+ out += inner.slice(1, -1);
197
+ }
198
+ else {
199
+ const segments = parsePath(inner);
200
+ out += evalPath(root, segments).map(scalarText).join(" ");
201
+ }
202
+ lastIndex = match.index + match[0].length;
203
+ }
204
+ out += template.slice(lastIndex);
205
+ return out;
206
+ }
207
+ /** `-o jsonpath=`: kubectl runs the whole template once per top-level object printed. */
208
+ export function renderJsonPath(expr, objects) {
209
+ const root = asPrintable(objects);
210
+ return evalJsonPath(expr, root);
211
+ }
212
+ function parseCustomColumns(spec) {
213
+ return spec.split(",").map((entry) => {
214
+ const colon = entry.indexOf(":");
215
+ if (colon === -1)
216
+ throw new Error(`custom-columns entry "${entry}" is missing ":" — expected HEADER:.json.path`);
217
+ const header = entry.slice(0, colon);
218
+ const path = entry.slice(colon + 1);
219
+ return { header, path: parsePath(path.startsWith(".") ? path : `.${path}`) };
220
+ });
221
+ }
222
+ export function renderCustomColumns(spec, objects) {
223
+ const columns = parseCustomColumns(spec);
224
+ const headers = columns.map((c) => c.header);
225
+ const rows = objects.map((o) => columns.map((c) => evalPath(o, c.path).map(scalarText).join(",") || "<none>"));
226
+ return renderTable(headers, rows);
227
+ }