@intentius/chant-lexicon-k8s 0.124.30 → 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.
- package/dist/actions/apps.js +18 -0
- package/dist/actions/batch.js +6 -0
- package/dist/actions/core.js +48 -0
- package/dist/actions/index.js +13 -0
- package/dist/actions/networking.js +11 -0
- package/dist/actions/rbac.js +8 -0
- package/dist/api/classify.js +123 -0
- package/dist/api/connect.js +62 -0
- package/dist/api/fake-cluster.js +175 -0
- package/dist/api/operation-surface.d.ts.map +1 -1
- package/dist/api/operation-surface.js +109 -0
- package/dist/api/owner-chain.js +100 -0
- package/dist/api/sweep-types.js +35 -0
- package/dist/behaviour-kinds.js +82 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +146 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +74 -0
- package/dist/codegen/generate-list-map-keys.js +88 -0
- package/dist/codegen/generate-operations.js +53 -0
- package/dist/codegen/generate-typescript.js +124 -0
- package/dist/codegen/generate.js +173 -0
- package/dist/codegen/naming.js +174 -0
- package/dist/codegen/package.js +36 -0
- package/dist/codegen/patches.js +88 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/components/argo-app.js +51 -0
- package/dist/components/builders.js +22 -0
- package/dist/components/capability-plugin.js +31 -0
- package/dist/components/flux-reconcile.js +61 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +18 -0
- package/dist/components/kubectl-apply.d.ts +83 -1
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +274 -0
- package/dist/components/kustomize-apply.js +25 -0
- package/dist/composites/adot-collector.js +184 -0
- package/dist/composites/agic-ingress.js +85 -0
- package/dist/composites/aks-external-dns-agent.js +146 -0
- package/dist/composites/alb-ingress.js +86 -0
- package/dist/composites/argo-app.js +199 -0
- package/dist/composites/autoscaled-service.js +174 -0
- package/dist/composites/azure-disk-storage-class.js +47 -0
- package/dist/composites/azure-file-storage-class.js +48 -0
- package/dist/composites/azure-monitor-collector.js +176 -0
- package/dist/composites/batch-job.js +139 -0
- package/dist/composites/catalog.js +4110 -0
- package/dist/composites/cockroachdb-cluster.js +371 -0
- package/dist/composites/cockroachdb-region-stack.js +369 -0
- package/dist/composites/config-connector-context.js +40 -0
- package/dist/composites/configured-app.js +135 -0
- package/dist/composites/cron-schedule.js +38 -0
- package/dist/composites/cron-workload.js +109 -0
- package/dist/composites/ebs-storage-class.js +54 -0
- package/dist/composites/efs-storage-class.js +46 -0
- package/dist/composites/external-dns-agent.js +122 -0
- package/dist/composites/filestore-storage-class.js +49 -0
- package/dist/composites/fluent-bit-agent.js +157 -0
- package/dist/composites/flux-app.js +156 -0
- package/dist/composites/gce-ingress.js +83 -0
- package/dist/composites/gce-pd-storage-class.js +49 -0
- package/dist/composites/gke-external-dns-agent.js +129 -0
- package/dist/composites/gke-fluent-bit-agent.js +165 -0
- package/dist/composites/gke-gateway.js +91 -0
- package/dist/composites/gke-otel-collector.js +94 -0
- package/dist/composites/index.js +51 -0
- package/dist/composites/inference-service.js +76 -0
- package/dist/composites/irsa-service-account.js +77 -0
- package/dist/composites/member-defaults.js +14 -0
- package/dist/composites/metrics-server.js +181 -0
- package/dist/composites/model.js +92 -0
- package/dist/composites/monitored-service.js +152 -0
- package/dist/composites/namespace-env.js +114 -0
- package/dist/composites/network-isolated-app.js +124 -0
- package/dist/composites/node-agent.js +149 -0
- package/dist/composites/operator-stack.js +268 -0
- package/dist/composites/otel-collector-agent.js +142 -0
- package/dist/composites/otel-collector-gateway.js +216 -0
- package/dist/composites/otel-collector-node.js +176 -0
- package/dist/composites/otel-collector-rbac.js +115 -0
- package/dist/composites/otel-collector-shape.js +128 -0
- package/dist/composites/otel-collector.js +115 -0
- package/dist/composites/otel-operator-collector.js +123 -0
- package/dist/composites/ray-cluster.js +378 -0
- package/dist/composites/ray-job.js +136 -0
- package/dist/composites/ray-service.js +178 -0
- package/dist/composites/secure-ingress.js +100 -0
- package/dist/composites/security-context.js +1 -0
- package/dist/composites/sidecar-app.js +105 -0
- package/dist/composites/stateful-app.js +111 -0
- package/dist/composites/vllm-serving-runtime.js +114 -0
- package/dist/composites/web-app.js +157 -0
- package/dist/composites/worker-pool.js +167 -0
- package/dist/composites/workload-identity-sa.js +81 -0
- package/dist/composites/workload-identity-service-account.js +77 -0
- package/dist/config-schema.js +72 -0
- package/dist/config.js +59 -0
- package/dist/coverage.js +27 -0
- package/dist/crd/crd-sources.js +615 -0
- package/dist/crd/loader.js +181 -0
- package/dist/crd/oci-chart.js +144 -0
- package/dist/crd/parser.js +382 -0
- package/dist/crd/project-codegen.js +273 -0
- package/dist/crd/types.js +7 -0
- package/dist/deep-observe-hooks.js +306 -0
- package/dist/deep-observe.js +234 -0
- package/dist/default-labels.js +87 -0
- package/dist/describe-resources.js +699 -0
- package/dist/describe-stack-status.js +99 -0
- package/dist/detect.js +11 -0
- package/dist/effect-receipt-row.js +205 -0
- package/dist/export-resources.js +39 -0
- package/dist/generated/index.js +1092 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/group-namespace.js +96 -0
- package/dist/import/embedded.js +197 -0
- package/dist/import/generator.js +288 -0
- package/dist/import/live-export.js +85 -0
- package/dist/import/parser.js +204 -0
- package/dist/import/testdata/embedded/fixtures.js +60 -0
- package/dist/include/entity.js +78 -0
- package/dist/include/resolve.js +170 -0
- package/dist/index.js +59 -0
- package/dist/integrity.json +3 -3
- package/dist/kube/apply.js +90 -0
- package/dist/kube/connect.js +46 -0
- package/dist/kube/delete.js +138 -0
- package/dist/kube/describe.js +169 -0
- package/dist/kube/events.js +132 -0
- package/dist/kube/flags.js +91 -0
- package/dist/kube/get.js +159 -0
- package/dist/kube/group.js +123 -0
- package/dist/kube/logs.js +82 -0
- package/dist/kube/project.js +94 -0
- package/dist/kube/render.js +227 -0
- package/dist/kube/source.js +122 -0
- package/dist/kube/target.js +63 -0
- package/dist/kube/testing.js +28 -0
- package/dist/kube/top.js +99 -0
- package/dist/kube/verdict.js +72 -0
- package/dist/kube/wait.js +129 -0
- package/dist/kustomize/render.js +73 -0
- package/dist/kustomize/rendered-entity.js +67 -0
- package/dist/kustomize/root.js +68 -0
- package/dist/lint/audit-catalog.js +61 -0
- package/dist/lint/audit-lineage.js +118 -0
- package/dist/lint/post-synth/argo-helpers.js +36 -0
- package/dist/lint/post-synth/argo002.js +31 -0
- package/dist/lint/post-synth/argo003.js +53 -0
- package/dist/lint/post-synth/argo005.js +54 -0
- package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
- package/dist/lint/post-synth/flux002.js +56 -0
- package/dist/lint/post-synth/flux003.js +50 -0
- package/dist/lint/post-synth/index.js +104 -0
- package/dist/lint/post-synth/k8s-helpers.js +186 -0
- package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
- package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
- package/dist/lint/post-synth/sops-helpers.js +43 -0
- package/dist/lint/post-synth/wk8005.js +44 -0
- package/dist/lint/post-synth/wk8006.js +56 -0
- package/dist/lint/post-synth/wk8041.js +60 -0
- package/dist/lint/post-synth/wk8042.js +36 -0
- package/dist/lint/post-synth/wk8101.js +52 -0
- package/dist/lint/post-synth/wk8102.js +30 -0
- package/dist/lint/post-synth/wk8103.js +33 -0
- package/dist/lint/post-synth/wk8104.js +58 -0
- package/dist/lint/post-synth/wk8105.js +33 -0
- package/dist/lint/post-synth/wk8201.js +44 -0
- package/dist/lint/post-synth/wk8202.js +34 -0
- package/dist/lint/post-synth/wk8203.js +34 -0
- package/dist/lint/post-synth/wk8204.js +71 -0
- package/dist/lint/post-synth/wk8205.js +38 -0
- package/dist/lint/post-synth/wk8207.js +33 -0
- package/dist/lint/post-synth/wk8208.js +33 -0
- package/dist/lint/post-synth/wk8209.js +33 -0
- package/dist/lint/post-synth/wk8301.js +49 -0
- package/dist/lint/post-synth/wk8302.js +34 -0
- package/dist/lint/post-synth/wk8303.js +68 -0
- package/dist/lint/post-synth/wk8304.js +58 -0
- package/dist/lint/post-synth/wk8305.js +96 -0
- package/dist/lint/post-synth/wk8306.js +38 -0
- package/dist/lint/post-synth/wk8401.js +86 -0
- package/dist/lint/post-synth/wk8402.js +31 -0
- package/dist/lint/post-synth/wk8403.js +49 -0
- package/dist/lint/post-synth/wk8404.js +36 -0
- package/dist/lint/post-synth/wk8405.js +65 -0
- package/dist/lint/post-synth/wk8406.js +50 -0
- package/dist/lint/post-synth/wk8407.js +58 -0
- package/dist/lint/post-synth/wk8501.js +36 -0
- package/dist/lint/post-synth/wk8502.js +35 -0
- package/dist/lint/post-synth/wk8503.js +164 -0
- package/dist/lint/post-synth/wk8504.js +20 -0
- package/dist/lint/post-synth/wk8505.js +31 -0
- package/dist/lint/post-synth/wk8601.js +40 -0
- package/dist/lint/post-synth/wk8602.js +120 -0
- package/dist/lint/post-synth/wk8603.js +63 -0
- package/dist/lint/post-synth/wk8604.js +31 -0
- package/dist/lint/post-synth/wk8605.js +124 -0
- package/dist/lint/post-synth/wk8606.js +22 -0
- package/dist/lint/post-synth/wk8701.js +31 -0
- package/dist/lint/post-synth/wk8702.js +37 -0
- package/dist/lint/post-synth/wk8703.js +31 -0
- package/dist/lint/post-synth/wk8704.js +135 -0
- package/dist/lint/post-synth/wk8705.js +38 -0
- package/dist/lint/rules/argo-appset-single-project.js +56 -0
- package/dist/lint/rules/argo-ast.js +94 -0
- package/dist/lint/rules/argo-automated-prune.js +59 -0
- package/dist/lint/rules/flux-source-ref-pin.js +44 -0
- package/dist/lint/rules/hardcoded-namespace.js +57 -0
- package/dist/lint/rules/latest-image-tag.js +103 -0
- package/dist/lint/rules/missing-resource-limits.js +85 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +57 -0
- package/dist/manifest-entity.js +71 -0
- package/dist/manifest.json +1 -1
- package/dist/micro-time.js +49 -0
- package/dist/op/activities/apply-diff.d.ts +31 -0
- package/dist/op/activities/apply-diff.d.ts.map +1 -0
- package/dist/op/activities/apply-diff.js +79 -0
- package/dist/op/activities/argo.js +98 -0
- package/dist/op/activities/ensure-secret.js +38 -0
- package/dist/op/activities/index.js +51 -0
- package/dist/op/activities/kubectl.d.ts +64 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/kubectl.js +517 -0
- package/dist/op/activities/wait-for-ready.js +229 -0
- package/dist/op/builders.js +68 -0
- package/dist/package-cli.js +21 -0
- package/dist/patch/json-patch.js +187 -0
- package/dist/patch/rendered-patch.js +94 -0
- package/dist/plugin.js +741 -0
- package/dist/project-kinds.js +56 -0
- package/dist/receipt-store.js +216 -0
- package/dist/rules/crd-schema-helpers.ts +2 -1
- package/dist/secret-labels.js +33 -0
- package/dist/secret-store.js +116 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +578 -0
- package/dist/sops/encrypted-secret-file.js +194 -0
- package/dist/sops/entity.js +32 -0
- package/dist/spec/fetch.js +58 -0
- package/dist/spec/parse.js +622 -0
- package/dist/subscribe-changes.js +168 -0
- package/dist/teardown.js +197 -0
- package/dist/telemetry-release.js +86 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +47 -0
- package/dist/variables.js +34 -0
- package/dist/whoami.js +109 -0
- package/package.json +12 -12
- package/src/api/operation-surface.ts +2 -1
- package/src/codegen/package.ts +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
- package/src/components/kubectl-apply.test.ts +262 -0
- package/src/components/kubectl-apply.ts +341 -12
- package/src/deep-observe-hooks.ts +2 -2
- package/src/import/roundtrip-build.test.ts +46 -0
- package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/op/activities/apply-diff.ts +87 -0
- package/src/op/activities/kubectl-plan.test.ts +224 -0
- package/src/op/activities/kubectl.ts +204 -16
- 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,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;
|
|
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
|
+
}
|