@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,18 @@
1
+ /**
2
+ * RBAC verb constants for apps API group resources.
3
+ */
4
+ import { StandardVerbs } from "./core.js";
5
+ export const DeploymentActions = {
6
+ ...StandardVerbs,
7
+ rollback: "create", // deployments/rollback subresource
8
+ scale: "update", // deployments/scale subresource
9
+ };
10
+ export const StatefulSetActions = {
11
+ ...StandardVerbs,
12
+ scale: "update",
13
+ };
14
+ export const DaemonSetActions = { ...StandardVerbs };
15
+ export const ReplicaSetActions = {
16
+ ...StandardVerbs,
17
+ scale: "update",
18
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * RBAC verb constants for batch API group resources.
3
+ */
4
+ import { StandardVerbs } from "./core.js";
5
+ export const JobActions = { ...StandardVerbs };
6
+ export const CronJobActions = { ...StandardVerbs };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * RBAC verb constants for core API group resources.
3
+ */
4
+ /** Standard RBAC verbs applicable to most resources. */
5
+ export const StandardVerbs = {
6
+ get: "get",
7
+ list: "list",
8
+ watch: "watch",
9
+ create: "create",
10
+ update: "update",
11
+ patch: "patch",
12
+ delete: "delete",
13
+ };
14
+ export const PodActions = {
15
+ ...StandardVerbs,
16
+ exec: "create", // pods/exec subresource
17
+ portForward: "create", // pods/portforward subresource
18
+ log: "get", // pods/log subresource
19
+ };
20
+ export const ServiceActions = {
21
+ ...StandardVerbs,
22
+ proxy: "create",
23
+ };
24
+ export const ConfigMapActions = { ...StandardVerbs };
25
+ export const SecretActions = { ...StandardVerbs };
26
+ export const NamespaceActions = { ...StandardVerbs };
27
+ export const ServiceAccountActions = {
28
+ ...StandardVerbs,
29
+ impersonate: "impersonate",
30
+ };
31
+ export const PersistentVolumeActions = { ...StandardVerbs };
32
+ export const PersistentVolumeClaimActions = { ...StandardVerbs };
33
+ export const NodeActions = {
34
+ get: "get",
35
+ list: "list",
36
+ watch: "watch",
37
+ update: "update",
38
+ patch: "patch",
39
+ };
40
+ export const EventActions = {
41
+ get: "get",
42
+ list: "list",
43
+ watch: "watch",
44
+ create: "create",
45
+ };
46
+ export const ResourceQuotaActions = { ...StandardVerbs };
47
+ export const LimitRangeActions = { ...StandardVerbs };
48
+ export const EndpointsActions = { ...StandardVerbs };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Kubernetes RBAC verb constants — organized by API group.
3
+ */
4
+ // Core
5
+ export { StandardVerbs, PodActions, ServiceActions, ConfigMapActions, SecretActions, NamespaceActions, ServiceAccountActions, PersistentVolumeActions, PersistentVolumeClaimActions, NodeActions, EventActions, ResourceQuotaActions, LimitRangeActions, EndpointsActions, } from "./core.js";
6
+ // Apps
7
+ export { DeploymentActions, StatefulSetActions, DaemonSetActions, ReplicaSetActions, } from "./apps.js";
8
+ // RBAC
9
+ export { RoleActions, ClusterRoleActions, RoleBindingActions, ClusterRoleBindingActions, } from "./rbac.js";
10
+ // Batch
11
+ export { JobActions, CronJobActions, } from "./batch.js";
12
+ // Networking
13
+ export { IngressActions, IngressClassActions, NetworkPolicyActions, } from "./networking.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * RBAC verb constants for networking.k8s.io API group resources.
3
+ */
4
+ import { StandardVerbs } from "./core.js";
5
+ export const IngressActions = { ...StandardVerbs };
6
+ export const IngressClassActions = {
7
+ get: "get",
8
+ list: "list",
9
+ watch: "watch",
10
+ };
11
+ export const NetworkPolicyActions = { ...StandardVerbs };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * RBAC verb constants for rbac.authorization.k8s.io API group.
3
+ */
4
+ import { StandardVerbs } from "./core.js";
5
+ export const RoleActions = { ...StandardVerbs };
6
+ export const ClusterRoleActions = { ...StandardVerbs };
7
+ export const RoleBindingActions = { ...StandardVerbs };
8
+ export const ClusterRoleBindingActions = { ...StandardVerbs };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Typed API failure → the observation tri-state (chant #1089, #1074).
3
+ *
4
+ * `classifyKubectlFailure` in core does this by matching English on stderr,
5
+ * because that was all `kubectl` gave it. The API server has always sent a
6
+ * `Status` object with a numeric code and a `reason` enum; the typed client
7
+ * carries it through, so classification here reads fields rather than
8
+ * searching for substrings. The verdicts are deliberately the same ones the
9
+ * kubectl path produced — this replaces the evidence, not the contract.
10
+ *
11
+ * The one that reads as a surprise, and is not: **a kind the cluster does not
12
+ * serve is an absence, not a hole.** No instance of an unserved kind can exist
13
+ * there, and the usual cause is a CRD the very plan being computed has not
14
+ * applied yet. Calling it NOT-OBSERVED would suppress the create that is
15
+ * genuinely needed. That is core's existing rule (`classifyKubectlFailure`
16
+ * treats "the server doesn't have a resource type" as absent) and it survives
17
+ * the move intact.
18
+ *
19
+ * Errors are discriminated by `name`, not `instanceof`. The client is an
20
+ * optional dependency reached through a dynamic import, so this module must
21
+ * not carry a static value import of it — and `name` is set explicitly by
22
+ * every one of its error classes, which also makes the classification survive
23
+ * a duplicated copy of the package in a consumer's tree.
24
+ */
25
+ /** The client's error names, as its classes stamp them. */
26
+ const NAMES = {
27
+ api: "K8sApiError",
28
+ transport: "K8sTransportError",
29
+ unavailable: "K8sClientUnavailableError",
30
+ execRefused: "ExecCredentialNotAllowedError",
31
+ kubeconfig: "KubeConfigError",
32
+ unknownResource: "UnknownResourceError",
33
+ };
34
+ function shapeOf(err) {
35
+ return (typeof err === "object" && err !== null ? err : {});
36
+ }
37
+ /** Classify a failure from the typed client into the observation tri-state. */
38
+ export function classifyApiFailure(err) {
39
+ const e = shapeOf(err);
40
+ switch (e.name) {
41
+ // The cluster's discovery does not serve this kind, so no instance of it
42
+ // exists here. A real absence — see the module comment.
43
+ case NAMES.unknownResource:
44
+ return { kind: "absent" };
45
+ case NAMES.api: {
46
+ const code = e.statusCode;
47
+ if (code === 404 || e.reason === "NotFound")
48
+ return { kind: "absent" };
49
+ if (code === 401 || code === 403 || e.reason === "Unauthorized" || e.reason === "Forbidden") {
50
+ return { kind: "unobserved", reason: "no-credentials", detail: detailOf(err) };
51
+ }
52
+ return { kind: "unobserved", reason: "read-failed", detail: detailOf(err) };
53
+ }
54
+ // Never reached an API server: DNS, TCP, TLS, proxy, abort. Says nothing
55
+ // about what is running there. Same verdict the kubectl path gave
56
+ // "unable to connect to the server".
57
+ case NAMES.transport:
58
+ return { kind: "unobserved", reason: "no-binding", detail: detailOf(err) };
59
+ // No usable kubeconfig / cluster / context for this environment.
60
+ case NAMES.kubeconfig:
61
+ return { kind: "unobserved", reason: "no-binding", detail: detailOf(err) };
62
+ // The credential path itself was refused, so nothing was ever authorized.
63
+ case NAMES.execRefused:
64
+ return { kind: "unobserved", reason: "no-credentials", detail: detailOf(err) };
65
+ // The client package is not installed — chant could not look, which is a
66
+ // hole and emphatically not an empty cluster.
67
+ case NAMES.unavailable:
68
+ return { kind: "unobserved", reason: "read-failed", detail: detailOf(err) };
69
+ default:
70
+ return { kind: "unobserved", reason: "read-failed", detail: detailOf(err) };
71
+ }
72
+ }
73
+ /**
74
+ * The cluster's own discovery reports no such kind at all — distinct from a
75
+ * kind that exists but currently has zero live instances. `classifyApiFailure`
76
+ * folds both into `absent` for the declared-entity read path, where "no
77
+ * instance can exist" is exactly the signal a `create` needs. `chant kube get`
78
+ * (chant #1079) needs the finer distinction: an ad hoc `chant kube get
79
+ * widgets` against a kind the cluster has never heard of should say so, not
80
+ * print "No resources found." as if `widgets` were a real, empty kind.
81
+ */
82
+ export function isUnknownResource(err) {
83
+ return shapeOf(err).name === NAMES.unknownResource;
84
+ }
85
+ /**
86
+ * Whether a failure kills the whole observation rather than one entity. A
87
+ * refused binding, a missing client package and a rejected credential plugin
88
+ * are all true of every entity, so they propagate and core marks the lot
89
+ * NOT-OBSERVED with one reason instead of repeating the same failure N times.
90
+ */
91
+ export function isWholeLexiconFailure(err) {
92
+ const name = shapeOf(err).name;
93
+ return name === NAMES.kubeconfig || name === NAMES.execRefused || name === NAMES.unavailable;
94
+ }
95
+ /**
96
+ * A dynamic import of the optional client package that failed to resolve looks
97
+ * like an ordinary module error. Recognizing it lets `describeResources` report
98
+ * a missing dependency as a missing dependency rather than as a broken cluster.
99
+ */
100
+ export function isMissingClientPackage(err) {
101
+ const e = shapeOf(err);
102
+ if (e.name === NAMES.unavailable)
103
+ return true;
104
+ if (e.code !== "ERR_MODULE_NOT_FOUND" && e.code !== "MODULE_NOT_FOUND")
105
+ return false;
106
+ return (e.message ?? "").includes("@intentius/chant-k8s-client") || (e.message ?? "").includes("@kubernetes/client-node");
107
+ }
108
+ /** The message a missing client package should produce. */
109
+ export const MISSING_CLIENT_DETAIL = "the Kubernetes API client is not installed — run `npm i @intentius/chant-k8s-client` " +
110
+ "(it is an optional dependency of @intentius/chant-lexicon-k8s, so `--omit=optional` installs skip it)";
111
+ function detailOf(err) {
112
+ const text = err instanceof Error ? err.message : String(err);
113
+ const line = text.split("\n").find((l) => l.trim().length > 0)?.trim() ?? text.trim();
114
+ const capped = line.length > 200 ? `${line.slice(0, 197)}...` : line;
115
+ // #1488 — a reason that does not say which cluster was read turned an
116
+ // ambient-context switch into an afternoon. The client stamps the context it
117
+ // resolved onto every failure. The note LEADS the detail (#1620): consumers
118
+ // truncate a reason to its first line and ~160 chars, and a note appended
119
+ // after a long error is cut off exactly when the error is long enough to
120
+ // need it. Leading, it survives any suffix truncation.
121
+ const note = shapeOf(err).contextNote;
122
+ return note ? `${note} — ${capped}` : capped;
123
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Getting from a chant environment to a live API client — chant #1074.
3
+ *
4
+ * The cluster binding (chant #1100/#1155, revised by #1488):
5
+ * `resolveClusterTarget` in core stays the single resolver, and a declared
6
+ * binding is now *used*, never policed against the ambient context. Two
7
+ * outcomes:
8
+ *
9
+ * - **Bound** — the bound context is passed explicitly to the client,
10
+ * regardless of what `kubectl` is ambiently pointed at. A binding that
11
+ * names a context the kubeconfig does not have fails in the client with an
12
+ * error naming both the context and the `k8s.profiles.<env>.context`
13
+ * binding — never by falling back to ambient. (#1488: the old
14
+ * ambient-mismatch refusal meant any other project's k3d cluster stealing
15
+ * the current-context turned a healthy estate grey with `read-failed` as
16
+ * the only explanation.)
17
+ * - **Unbound** — the kubeconfig's own current-context, with a visible
18
+ * warning naming the environment and the missing binding.
19
+ */
20
+ import { loadChantConfig } from "@intentius/chant/config";
21
+ import { resolveClusterTarget } from "@intentius/chant/kubectl-context";
22
+ /**
23
+ * The production connector: read the project's config, resolve the binding,
24
+ * build a client honoring it.
25
+ */
26
+ export const defaultK8sConnector = async (options) => {
27
+ const { createK8sClient } = await import("@intentius/chant-k8s-client");
28
+ if (options.context !== undefined) {
29
+ const client = await createK8sClient({
30
+ context: options.context,
31
+ contextSource: "bound",
32
+ contextLabel: "explicit --context",
33
+ ...options.client,
34
+ });
35
+ return { client, target: { context: options.context, source: "bound" } };
36
+ }
37
+ if (options.environment === undefined) {
38
+ const client = await createK8sClient({ contextSource: "ambient", ...options.client });
39
+ return { client, target: { source: "ambient" } };
40
+ }
41
+ const { config } = await loadChantConfig(options.cwd ?? process.cwd());
42
+ const k8sConfig = config.k8s;
43
+ const execAllowlist = k8sConfig?.execCredentialPlugins;
44
+ // #1488 — the resolver returns the declared binding (or ambient-with-warning
45
+ // when unbound); it no longer reads the ambient context at all, so no reader
46
+ // is supplied.
47
+ const target = await resolveClusterTarget(config, options.environment, "k8s");
48
+ // #1488 — every failure the client throws names the cluster it read and how
49
+ // that cluster was selected, so a `read-failed` against the wrong context is
50
+ // legible from the reason alone.
51
+ const contextLabel = target.source === "bound"
52
+ ? `bound by k8s.profiles.${options.environment}.context`
53
+ : `ambient; no k8s.profiles.${options.environment} binding`;
54
+ const client = await createK8sClient({
55
+ ...options.client,
56
+ ...(target.context ? { context: target.context } : {}),
57
+ contextSource: target.source,
58
+ contextLabel,
59
+ ...(execAllowlist ? { execAllowlist } : {}),
60
+ });
61
+ return { client, target };
62
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * A fake cluster to point the k8s lexicon at, for tests (chant #1074).
3
+ *
4
+ * It builds a **real** `@intentius/chant-k8s-client` over a literal kubeconfig
5
+ * with `@kubernetes/client-node`'s HTTP send replaced. So kubeconfig parsing,
6
+ * context selection, the credential policy, API discovery, path construction
7
+ * and the auth path all run for real; the only thing that does not happen is
8
+ * the socket. Nothing reads `~/.kube/config` or `$KUBECONFIG`, and nothing can
9
+ * reach a cluster the developer happens to have a context for.
10
+ *
11
+ * Shipped rather than kept in a test file because the lexicon's own tests, the
12
+ * cross-lexicon lifecycle suite and a consumer wiring chant into their harness
13
+ * all need the same thing.
14
+ */
15
+ import { apiResourceList, fakeKubeconfig, fakeRequestLayer, statusBody } from "@intentius/chant-k8s-client/testing";
16
+ import { createK8sClient } from "@intentius/chant-k8s-client";
17
+ import { operationTable } from "./operation-surface.js";
18
+ /** Key an object the way {@link fakeCluster} indexes them. */
19
+ export function objectKey(apiVersion, kind, name, namespace) {
20
+ return `${apiVersion}/${kind}/${namespace ?? "_"}/${name}`;
21
+ }
22
+ /** Build a live object with chant's ownership marker already on it. */
23
+ export function ownedObject(apiVersion, kind, name, namespace, extra = {}) {
24
+ return {
25
+ apiVersion,
26
+ kind,
27
+ metadata: {
28
+ name,
29
+ ...(namespace ? { namespace } : {}),
30
+ uid: `uid-${name}`,
31
+ resourceVersion: "1",
32
+ labels: { "app.kubernetes.io/managed-by": "chant" },
33
+ ...(extra.metadata ?? {}),
34
+ },
35
+ ...(extra.status ? { status: extra.status } : {}),
36
+ };
37
+ }
38
+ function servedResources(serves) {
39
+ const table = operationTable();
40
+ const entries = serves
41
+ ? serves.map((s) => table[s]).filter((d) => d !== undefined)
42
+ : Object.values(table);
43
+ return entries
44
+ .filter((d) => d.verbs.length > 0)
45
+ .map((d) => ({
46
+ apiVersion: d.apiVersion,
47
+ kind: d.kind,
48
+ plural: d.plural,
49
+ namespaced: d.scope === "Namespaced",
50
+ }));
51
+ }
52
+ /**
53
+ * A cluster that answers discovery for the kinds it serves and holds the
54
+ * objects it was given. Anything else 404s with a real `Status` body, which is
55
+ * how a test drives the absent branch of the observation tri-state.
56
+ */
57
+ export function fakeCluster(options = {}) {
58
+ const resources = servedResources(options.serves);
59
+ const byApiVersion = new Map();
60
+ for (const r of resources) {
61
+ const list = byApiVersion.get(r.apiVersion) ?? [];
62
+ list.push(r);
63
+ byApiVersion.set(r.apiVersion, list);
64
+ }
65
+ // Path → the object it addresses, and list path → its members, precomputed
66
+ // so the responder below is a pair of lookups.
67
+ const byPath = new Map();
68
+ const listPaths = new Map();
69
+ const addToList = (path, object) => {
70
+ const list = listPaths.get(path) ?? [];
71
+ list.push(object);
72
+ listPaths.set(path, list);
73
+ };
74
+ for (const [key, object] of Object.entries(options.objects ?? {})) {
75
+ const [group, version, kind, namespace, name] = splitKey(key);
76
+ const apiVersion = group ? `${group}/${version}` : version;
77
+ const resource = resources.find((r) => r.apiVersion === apiVersion && r.kind === kind);
78
+ if (!resource)
79
+ continue;
80
+ const base = apiVersion.includes("/") ? `/apis/${apiVersion}` : `/api/${apiVersion}`;
81
+ if (resource.namespaced) {
82
+ const ns = namespace === "_" ? "default" : namespace;
83
+ byPath.set(`${base}/namespaces/${ns}/${resource.plural}/${name}`, object);
84
+ addToList(`${base}/namespaces/${ns}/${resource.plural}`, object);
85
+ }
86
+ else {
87
+ byPath.set(`${base}/${resource.plural}/${name}`, object);
88
+ }
89
+ // Cluster-wide list, which is what `chant import` sweeps.
90
+ addToList(`${base}/${resource.plural}`, object);
91
+ }
92
+ const groups = new Map();
93
+ for (const apiVersion of byApiVersion.keys()) {
94
+ if (!apiVersion.includes("/"))
95
+ continue;
96
+ const group = apiVersion.slice(0, apiVersion.indexOf("/"));
97
+ if (!groups.has(group))
98
+ groups.set(group, apiVersion);
99
+ }
100
+ const layer = fakeRequestLayer((request) => {
101
+ const custom = options.respond?.(request);
102
+ if (custom !== undefined)
103
+ return custom;
104
+ if (request.path === "/api")
105
+ return { body: { kind: "APIVersions", versions: ["v1"] } };
106
+ if (request.path === "/apis") {
107
+ return {
108
+ body: {
109
+ kind: "APIGroupList",
110
+ groups: [...groups].map(([name, apiVersion]) => ({
111
+ name,
112
+ preferredVersion: { groupVersion: apiVersion, version: apiVersion.split("/")[1] },
113
+ versions: [{ groupVersion: apiVersion }],
114
+ })),
115
+ },
116
+ };
117
+ }
118
+ const apiVersion = apiVersionFromDiscoveryPath(request.path);
119
+ if (apiVersion) {
120
+ const served = byApiVersion.get(apiVersion);
121
+ if (!served)
122
+ return { status: 404, body: statusBody(404, "NotFound", "the server could not find the requested resource") };
123
+ return {
124
+ body: apiResourceList(apiVersion, served.map((r) => ({ name: r.plural, kind: r.kind, namespaced: r.namespaced }))),
125
+ };
126
+ }
127
+ const object = byPath.get(request.path);
128
+ if (object)
129
+ return { body: object };
130
+ if (isListPath(request.path, resources)) {
131
+ // A `resourceVersion` on every list: it is what a watch resumes from
132
+ // (chant #1981), and a list without one is not a shape any API server
133
+ // produces.
134
+ return {
135
+ body: {
136
+ kind: "List",
137
+ items: listPaths.get(request.path) ?? [],
138
+ metadata: { resourceVersion: "1" },
139
+ },
140
+ };
141
+ }
142
+ return { status: 404, body: statusBody(404, "NotFound", `${request.path} not found`) };
143
+ });
144
+ const connects = [];
145
+ const connector = async (connectOptions) => {
146
+ connects.push(connectOptions);
147
+ const client = await createK8sClient({
148
+ kubeconfig: options.kubeconfig ?? fakeKubeconfig(),
149
+ requestLayer: layer,
150
+ ...(connectOptions.context ? { context: connectOptions.context } : {}),
151
+ ...connectOptions.client,
152
+ });
153
+ return { client, target: { source: "ambient" } };
154
+ };
155
+ return { connector, layer, connects };
156
+ }
157
+ function splitKey(key) {
158
+ const parts = key.split("/");
159
+ // `apps/v1/Deployment/prod/web` (5) or `v1/Service/prod/web` (4)
160
+ if (parts.length === 5)
161
+ return [parts[0], parts[1], parts[2], parts[3], parts[4]];
162
+ return ["", parts[0], parts[1], parts[2], parts[3]];
163
+ }
164
+ function apiVersionFromDiscoveryPath(path) {
165
+ if (/^\/api\/[^/]+$/.test(path))
166
+ return path.slice("/api/".length);
167
+ if (/^\/apis\/[^/]+\/[^/]+$/.test(path))
168
+ return path.slice("/apis/".length);
169
+ return undefined;
170
+ }
171
+ /** True when the path addresses a collection of a kind this cluster serves. */
172
+ function isListPath(path, resources) {
173
+ const plural = path.split("/").pop();
174
+ return resources.some((r) => r.plural === plural);
175
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"operation-surface.d.ts","sourceRoot":"","sources":["../../src/api/operation-surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,2EAA2E;AAC3E,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,4DAA4D;AAC5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlD;AAqBD,+EAA+E;AAC/E,wBAAgB,cAAc,IAAI,iBAAiB,CAQlD;AAED,4CAA4C;AAC5C,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAatF;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD"}
1
+ {"version":3,"file":"operation-surface.d.ts","sourceRoot":"","sources":["../../src/api/operation-surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,2EAA2E;AAC3E,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,4DAA4D;AAC5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlD;AAqBD,+EAA+E;AAC/E,wBAAgB,cAAc,IAAI,iBAAiB,CAQlD;AAED,4CAA4C;AAC5C,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAatF;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Reading the generated operation surface — chant #1074.
3
+ *
4
+ * The table itself is written by `chant generate` (see
5
+ * `../codegen/generate-operations.ts`); this is the runtime side, and it is
6
+ * deliberately the only place in the lexicon that knows how an entity type
7
+ * becomes an API address.
8
+ *
9
+ * Loading mirrors the serializer's handling of `lexicon-k8s.json`: a `require`
10
+ * behind a try/catch, so a checkout that has not run `chant generate` yet
11
+ * degrades to derivation rather than failing to import. Nothing here touches
12
+ * the filesystem at module load (chant #1081) — the first call does, and caches.
13
+ */
14
+ import { createRequire } from "module";
15
+ import { moduleSourceUrl } from "@intentius/chant/module-source";
16
+ const require = createRequire(moduleSourceUrl(import.meta.url));
17
+ /**
18
+ * Fallback plural for a kind whose schema documented no named-object path.
19
+ *
20
+ * Kubernetes' own pluralization for resource names is the lowercased kind with
21
+ * English plural rules applied — the three cases `kubectl` implements. It only
22
+ * fires for kinds the OpenAPI paths do not cover, and the live client overrides
23
+ * it from the cluster's discovery regardless, so it is a placeholder rather
24
+ * than a second table to maintain.
25
+ */
26
+ export function pluralizeKind(kind) {
27
+ const lower = kind.toLowerCase();
28
+ if (/(s|x|z|ch|sh)$/.test(lower))
29
+ return `${lower}es`;
30
+ if (/[^aeiou]y$/.test(lower))
31
+ return `${lower.slice(0, -1)}ies`;
32
+ return `${lower}s`;
33
+ }
34
+ /**
35
+ * Well-known API group → apiVersion, for a checkout with no generated table.
36
+ * Deliberately the same list the serializer falls back to, so a lexicon
37
+ * running without generated artifacts addresses what it serializes.
38
+ */
39
+ const FALLBACK_GROUP_VERSIONS = {
40
+ Core: "v1",
41
+ Apps: "apps/v1",
42
+ Batch: "batch/v1",
43
+ Networking: "networking.k8s.io/v1",
44
+ Policy: "policy/v1",
45
+ Rbac: "rbac.authorization.k8s.io/v1",
46
+ Storage: "storage.k8s.io/v1",
47
+ Autoscaling: "autoscaling/v2",
48
+ Admissionregistration: "admissionregistration.k8s.io/v1",
49
+ };
50
+ let cached = null;
51
+ /** The generated table, loaded once. Empty when nothing has been generated. */
52
+ export function operationTable() {
53
+ if (cached)
54
+ return cached;
55
+ try {
56
+ cached = require("../generated/operations.json");
57
+ }
58
+ catch {
59
+ cached = {};
60
+ }
61
+ return cached;
62
+ }
63
+ /** Reset the memoized table. Tests only. */
64
+ export function resetOperationTableForTests() {
65
+ cached = null;
66
+ }
67
+ /**
68
+ * How to address a declared entity type over the API.
69
+ *
70
+ * Returns undefined only for a type the generated surface does not carry and
71
+ * whose `K8s::<Group>::<Kind>` shape yields no known group — which is the one
72
+ * case where chant genuinely does not know what to ask for. Every generated
73
+ * resource type, including every CRD baked in at generation time, is in the
74
+ * table; that is the difference between this and the twenty-entry map it
75
+ * replaces.
76
+ */
77
+ export function operationFor(entityType) {
78
+ const table = operationTable();
79
+ const found = table[entityType];
80
+ if (found)
81
+ return found;
82
+ return deriveOperation(entityType);
83
+ }
84
+ /**
85
+ * Last-resort derivation from the entity type's own shape, for a lexicon
86
+ * running without generated artifacts. It cannot invent an API group it has
87
+ * never seen, so an unknown group returns undefined rather than a guess that
88
+ * would address the wrong thing.
89
+ */
90
+ export function deriveOperation(entityType) {
91
+ const parts = entityType.split("::");
92
+ if (parts.length !== 3 || parts[0] !== "K8s")
93
+ return undefined;
94
+ const apiVersion = FALLBACK_GROUP_VERSIONS[parts[1]];
95
+ if (!apiVersion)
96
+ return undefined;
97
+ return {
98
+ entityType,
99
+ apiVersion,
100
+ kind: parts[2],
101
+ plural: pluralizeKind(parts[2]),
102
+ scope: "Namespaced",
103
+ verbs: [],
104
+ };
105
+ }
106
+ /** Every entity type the generated surface can address. */
107
+ export function addressableEntityTypes() {
108
+ return Object.keys(operationTable()).sort();
109
+ }