@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,622 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes OpenAPI Swagger 2.0 parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses the single swagger.json into multiple K8sParseResult entries —
|
|
5
|
+
* one per resource identified by `x-kubernetes-group-version-kind`.
|
|
6
|
+
* Property types (Container, PodSpec, Volume, etc.) are extracted as
|
|
7
|
+
* nested property types within their parent resources.
|
|
8
|
+
*/
|
|
9
|
+
import { extractConstraints as coreExtractConstraints, primaryType, } from "@intentius/chant/codegen/json-schema";
|
|
10
|
+
import { namespaceSegmentForGroup } from "../group-namespace.js";
|
|
11
|
+
// ── Well-known property type definitions ───────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Definitions that should be extracted as standalone property types
|
|
14
|
+
* even though they don't have GVK. Mapped to friendly names.
|
|
15
|
+
*/
|
|
16
|
+
const PROPERTY_TYPE_DEFS = {
|
|
17
|
+
"io.k8s.api.core.v1.Container": { typeName: "K8s::Core::Container", description: "A container definition for a pod" },
|
|
18
|
+
"io.k8s.api.core.v1.ContainerPort": { typeName: "K8s::Core::ContainerPort", description: "A port to expose from a container" },
|
|
19
|
+
"io.k8s.api.core.v1.EnvVar": { typeName: "K8s::Core::EnvVar", description: "An environment variable for a container" },
|
|
20
|
+
"io.k8s.api.core.v1.EnvFromSource": { typeName: "K8s::Core::EnvFromSource", description: "Source for environment variables" },
|
|
21
|
+
"io.k8s.api.core.v1.Volume": { typeName: "K8s::Core::Volume", description: "A volume that can be mounted by containers" },
|
|
22
|
+
"io.k8s.api.core.v1.VolumeMount": { typeName: "K8s::Core::VolumeMount", description: "A volume mount in a container" },
|
|
23
|
+
"io.k8s.api.core.v1.PodSpec": { typeName: "K8s::Core::PodSpec", description: "Specification of a pod" },
|
|
24
|
+
"io.k8s.api.core.v1.PodTemplateSpec": { typeName: "K8s::Core::PodTemplateSpec", description: "Pod template specification" },
|
|
25
|
+
"io.k8s.api.core.v1.ServicePort": { typeName: "K8s::Core::ServicePort", description: "A port exposed by a service" },
|
|
26
|
+
"io.k8s.api.core.v1.Probe": { typeName: "K8s::Core::Probe", description: "A health check probe" },
|
|
27
|
+
"io.k8s.api.core.v1.ResourceRequirements": { typeName: "K8s::Core::ResourceRequirements", description: "CPU and memory resource requirements" },
|
|
28
|
+
"io.k8s.api.core.v1.SecurityContext": { typeName: "K8s::Core::SecurityContext", description: "Security options for a container" },
|
|
29
|
+
"io.k8s.api.core.v1.PodSecurityContext": { typeName: "K8s::Core::PodSecurityContext", description: "Security options for a pod" },
|
|
30
|
+
"io.k8s.api.core.v1.Capabilities": { typeName: "K8s::Core::Capabilities", description: "Linux capabilities to add or drop" },
|
|
31
|
+
"io.k8s.api.core.v1.ConfigMapKeySelector": { typeName: "K8s::Core::ConfigMapKeySelector", description: "Reference to a key in a ConfigMap" },
|
|
32
|
+
"io.k8s.api.core.v1.SecretKeySelector": { typeName: "K8s::Core::SecretKeySelector", description: "Reference to a key in a Secret" },
|
|
33
|
+
"io.k8s.api.core.v1.EnvVarSource": { typeName: "K8s::Core::EnvVarSource", description: "Source for an environment variable value" },
|
|
34
|
+
"io.k8s.api.core.v1.ObjectReference": { typeName: "K8s::Core::ObjectReference", description: "Reference to another Kubernetes object" },
|
|
35
|
+
"io.k8s.api.core.v1.LocalObjectReference": { typeName: "K8s::Core::LocalObjectReference", description: "Reference to a local object" },
|
|
36
|
+
"io.k8s.api.core.v1.Toleration": { typeName: "K8s::Core::Toleration", description: "A toleration for pod scheduling" },
|
|
37
|
+
"io.k8s.api.core.v1.Affinity": { typeName: "K8s::Core::Affinity", description: "Scheduling affinity rules" },
|
|
38
|
+
"io.k8s.api.core.v1.TopologySpreadConstraint": { typeName: "K8s::Core::TopologySpreadConstraint", description: "Pod topology spread constraint" },
|
|
39
|
+
"io.k8s.api.core.v1.PersistentVolumeClaimSpec": { typeName: "K8s::Core::PersistentVolumeClaimSpec", description: "PVC spec for StatefulSet volume templates" },
|
|
40
|
+
"io.k8s.api.core.v1.HTTPGetAction": { typeName: "K8s::Core::HTTPGetAction", description: "HTTP GET probe action" },
|
|
41
|
+
"io.k8s.api.core.v1.TCPSocketAction": { typeName: "K8s::Core::TCPSocketAction", description: "TCP socket probe action" },
|
|
42
|
+
"io.k8s.api.core.v1.ExecAction": { typeName: "K8s::Core::ExecAction", description: "Exec probe action" },
|
|
43
|
+
"io.k8s.api.core.v1.HostAlias": { typeName: "K8s::Core::HostAlias", description: "Host alias entry for /etc/hosts" },
|
|
44
|
+
"io.k8s.api.core.v1.EphemeralContainer": { typeName: "K8s::Core::EphemeralContainer", description: "An ephemeral container for debugging" },
|
|
45
|
+
"io.k8s.api.core.v1.KeyToPath": { typeName: "K8s::Core::KeyToPath", description: "Maps a key to a file path" },
|
|
46
|
+
"io.k8s.api.apps.v1.DeploymentStrategy": { typeName: "K8s::Apps::DeploymentStrategy", description: "Deployment rolling update strategy" },
|
|
47
|
+
"io.k8s.api.apps.v1.RollingUpdateDeployment": { typeName: "K8s::Apps::RollingUpdateDeployment", description: "Rolling update parameters" },
|
|
48
|
+
"io.k8s.api.networking.v1.IngressRule": { typeName: "K8s::Networking::IngressRule", description: "Ingress routing rule" },
|
|
49
|
+
"io.k8s.api.networking.v1.IngressTLS": { typeName: "K8s::Networking::IngressTLS", description: "Ingress TLS configuration" },
|
|
50
|
+
"io.k8s.api.networking.v1.HTTPIngressPath": { typeName: "K8s::Networking::HTTPIngressPath", description: "HTTP Ingress path" },
|
|
51
|
+
"io.k8s.api.networking.v1.IngressBackend": { typeName: "K8s::Networking::IngressBackend", description: "Ingress backend reference" },
|
|
52
|
+
"io.k8s.api.networking.v1.IngressServiceBackend": { typeName: "K8s::Networking::IngressServiceBackend", description: "Ingress service backend" },
|
|
53
|
+
"io.k8s.api.networking.v1.ServiceBackendPort": { typeName: "K8s::Networking::ServiceBackendPort", description: "Service port reference" },
|
|
54
|
+
"io.k8s.api.networking.v1.NetworkPolicyIngressRule": { typeName: "K8s::Networking::NetworkPolicyIngressRule", description: "NetworkPolicy ingress rule" },
|
|
55
|
+
"io.k8s.api.networking.v1.NetworkPolicyEgressRule": { typeName: "K8s::Networking::NetworkPolicyEgressRule", description: "NetworkPolicy egress rule" },
|
|
56
|
+
"io.k8s.api.networking.v1.NetworkPolicyPeer": { typeName: "K8s::Networking::NetworkPolicyPeer", description: "NetworkPolicy peer selector" },
|
|
57
|
+
"io.k8s.api.networking.v1.NetworkPolicyPort": { typeName: "K8s::Networking::NetworkPolicyPort", description: "NetworkPolicy port" },
|
|
58
|
+
"io.k8s.api.rbac.v1.PolicyRule": { typeName: "K8s::Rbac::PolicyRule", description: "RBAC policy rule" },
|
|
59
|
+
"io.k8s.api.rbac.v1.RoleRef": { typeName: "K8s::Rbac::RoleRef", description: "RBAC role reference" },
|
|
60
|
+
"io.k8s.api.rbac.v1.Subject": { typeName: "K8s::Rbac::Subject", description: "RBAC subject" },
|
|
61
|
+
"io.k8s.api.autoscaling.v2.MetricSpec": { typeName: "K8s::Autoscaling::MetricSpec", description: "HPA metric specification" },
|
|
62
|
+
"io.k8s.api.autoscaling.v2.HorizontalPodAutoscalerBehavior": { typeName: "K8s::Autoscaling::HorizontalPodAutoscalerBehavior", description: "HPA scaling behavior" },
|
|
63
|
+
"io.k8s.api.policy.v1.PodDisruptionBudgetSpec": { typeName: "K8s::Policy::PodDisruptionBudgetSpec", description: "PDB specification" },
|
|
64
|
+
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { typeName: "K8s::Meta::ObjectMeta", description: "Standard object metadata" },
|
|
65
|
+
"io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": { typeName: "K8s::Meta::LabelSelector", description: "Label selector" },
|
|
66
|
+
"io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": { typeName: "K8s::Meta::LabelSelectorRequirement", description: "Label selector requirement" },
|
|
67
|
+
};
|
|
68
|
+
// ── Parser ─────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Parse the Kubernetes OpenAPI swagger.json into multiple resource results.
|
|
71
|
+
* Returns one result per top-level resource identified by x-kubernetes-group-version-kind.
|
|
72
|
+
*/
|
|
73
|
+
export function parseK8sSwagger(data) {
|
|
74
|
+
return parseK8sSwaggerTypes(data).results;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse the swagger document into resource and property-type results, and the
|
|
78
|
+
* declaration-only interfaces for every other object definition a resource
|
|
79
|
+
* reaches (chant #3093).
|
|
80
|
+
*/
|
|
81
|
+
export function parseK8sSwaggerTypes(data) {
|
|
82
|
+
const spec = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
|
|
83
|
+
const definitions = spec.definitions ?? {};
|
|
84
|
+
const results = [];
|
|
85
|
+
const operations = parseOperations(spec.paths);
|
|
86
|
+
// Which definitions become resources: those with a GVK, preferred version only.
|
|
87
|
+
const resourceDefs = [];
|
|
88
|
+
for (const [defKey, def] of Object.entries(definitions)) {
|
|
89
|
+
const gvks = def["x-kubernetes-group-version-kind"];
|
|
90
|
+
if (!gvks || gvks.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
// Take the first GVK (most definitions have exactly one)
|
|
93
|
+
const gvk = gvks[0];
|
|
94
|
+
// Skip internal/legacy API versions — only take the preferred version
|
|
95
|
+
if (!isPreferredVersion(defKey, gvk, definitions))
|
|
96
|
+
continue;
|
|
97
|
+
resourceDefs.push({ defKey, def, gvk });
|
|
98
|
+
}
|
|
99
|
+
// Name every object definition a resource reaches before any property is
|
|
100
|
+
// typed, so a `$ref` resolves to its interface instead of a Record.
|
|
101
|
+
const reachable = reachableDefinitions(resourceDefs.map((r) => r.def), definitions);
|
|
102
|
+
const ctx = {
|
|
103
|
+
definitions,
|
|
104
|
+
names: nameDefinitions(reachable, definitions),
|
|
105
|
+
resources: new Set(resourceDefs.map((r) => r.defKey)),
|
|
106
|
+
};
|
|
107
|
+
// Phase 1: Extract top-level resources (definitions with GVK)
|
|
108
|
+
for (const { defKey, def, gvk } of resourceDefs) {
|
|
109
|
+
const typeName = gvkToTypeName(gvk);
|
|
110
|
+
const result = extractResource(defKey, def, typeName, gvk, ctx);
|
|
111
|
+
if (result) {
|
|
112
|
+
const operation = operations.get(gvkKey(gvk));
|
|
113
|
+
if (operation)
|
|
114
|
+
result.operation = operation;
|
|
115
|
+
results.push(result);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Phase 2: Extract well-known property types
|
|
119
|
+
for (const [defKey, config] of Object.entries(PROPERTY_TYPE_DEFS)) {
|
|
120
|
+
const def = definitions[defKey];
|
|
121
|
+
if (!def)
|
|
122
|
+
continue;
|
|
123
|
+
const result = extractPropertyType(defKey, def, config.typeName, config.description, ctx);
|
|
124
|
+
if (result)
|
|
125
|
+
results.push(result);
|
|
126
|
+
}
|
|
127
|
+
// Phase 3: Every other reachable object definition, as an interface.
|
|
128
|
+
const definitionTypes = [];
|
|
129
|
+
for (const defKey of reachable) {
|
|
130
|
+
if (PROPERTY_TYPE_DEFS[defKey])
|
|
131
|
+
continue;
|
|
132
|
+
const def = definitions[defKey];
|
|
133
|
+
definitionTypes.push({
|
|
134
|
+
name: ctx.names.get(defKey),
|
|
135
|
+
defKey,
|
|
136
|
+
description: def.description,
|
|
137
|
+
properties: parseProperties(def.properties ?? {}, new Set(def.required ?? []), ctx),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
definitionTypes.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
141
|
+
return { results, definitionTypes };
|
|
142
|
+
}
|
|
143
|
+
// ── Definition interfaces (chant #3093) ────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Definitions that stay open however they are reached: `RawExtension` holds an
|
|
146
|
+
* arbitrary embedded object, and the rest are scalars on the wire. A Quantity
|
|
147
|
+
* is a string in the schema, but the API server also decodes a number
|
|
148
|
+
* (`cpu: 1`), and manifests write it both ways.
|
|
149
|
+
*/
|
|
150
|
+
const OPEN_OR_SCALAR_DEFS = {
|
|
151
|
+
"io.k8s.apimachinery.pkg.util.intstr.IntOrString": "string | number",
|
|
152
|
+
"io.k8s.apimachinery.pkg.api.resource.Quantity": "string | number",
|
|
153
|
+
"io.k8s.apimachinery.pkg.apis.meta.v1.Time": "string",
|
|
154
|
+
"io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime": "string",
|
|
155
|
+
"io.k8s.apimachinery.pkg.runtime.RawExtension": "Record<string, any>",
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* A resource embedded in another object (`StatefulSetSpec.volumeClaimTemplates`
|
|
159
|
+
* holds PersistentVolumeClaims, a List holds its items) is typed inline with
|
|
160
|
+
* the props its class takes. A name would have to avoid the class's own.
|
|
161
|
+
*/
|
|
162
|
+
function inlineResourceType(defKey, ctx) {
|
|
163
|
+
const inlining = ctx.inlining ?? new Set();
|
|
164
|
+
if (inlining.has(defKey))
|
|
165
|
+
return "Record<string, any>";
|
|
166
|
+
const def = ctx.definitions[defKey];
|
|
167
|
+
const inner = { ...ctx, inlining: new Set([...inlining, defKey]) };
|
|
168
|
+
const required = new Set(def.required ?? []);
|
|
169
|
+
const members = Object.entries(def.properties ?? {})
|
|
170
|
+
.filter(([name]) => name !== "apiVersion" && name !== "kind" && name !== "status")
|
|
171
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
172
|
+
.map(([name, prop]) => `${name}${required.has(name) ? "" : "?"}: ${resolvePropertyType(prop, inner)}`);
|
|
173
|
+
return members.length === 0 ? "Record<string, any>" : `{ ${members.join("; ")} }`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Whether a definition becomes a named type: an object with properties that
|
|
177
|
+
* is not itself a resource. A `$ref` to a resource is typed inline instead
|
|
178
|
+
* ({@link inlineResourceType}).
|
|
179
|
+
*/
|
|
180
|
+
function isNamedObjectDef(defKey, def) {
|
|
181
|
+
if (!def?.properties || defKey in OPEN_OR_SCALAR_DEFS)
|
|
182
|
+
return false;
|
|
183
|
+
return !def["x-kubernetes-group-version-kind"]?.length;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The object definitions reachable from the resources' authored properties
|
|
187
|
+
* (everything but `apiVersion`, `kind` and `status`), plus the well-known
|
|
188
|
+
* property types. Sorted by key. A recursive definition such as
|
|
189
|
+
* `JSONSchemaProps` is visited once and refers to itself by name, so no depth
|
|
190
|
+
* bound is needed.
|
|
191
|
+
*/
|
|
192
|
+
function reachableDefinitions(resources, definitions) {
|
|
193
|
+
const seen = new Set();
|
|
194
|
+
const queue = [];
|
|
195
|
+
const visitRef = (defKey) => {
|
|
196
|
+
if (seen.has(defKey) || !isNamedObjectDef(defKey, definitions[defKey]))
|
|
197
|
+
return;
|
|
198
|
+
seen.add(defKey);
|
|
199
|
+
queue.push(defKey);
|
|
200
|
+
};
|
|
201
|
+
const walk = (prop) => {
|
|
202
|
+
if (!prop)
|
|
203
|
+
return;
|
|
204
|
+
if (prop.$ref) {
|
|
205
|
+
if (prop.$ref.startsWith("#/definitions/"))
|
|
206
|
+
visitRef(prop.$ref.slice("#/definitions/".length));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
walk(prop.items);
|
|
210
|
+
if (prop.additionalProperties && typeof prop.additionalProperties === "object")
|
|
211
|
+
walk(prop.additionalProperties);
|
|
212
|
+
};
|
|
213
|
+
for (const def of resources) {
|
|
214
|
+
for (const [name, prop] of Object.entries(def.properties ?? {})) {
|
|
215
|
+
if (name !== "apiVersion" && name !== "kind" && name !== "status")
|
|
216
|
+
walk(prop);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const defKey of Object.keys(PROPERTY_TYPE_DEFS))
|
|
220
|
+
visitRef(defKey);
|
|
221
|
+
while (queue.length > 0) {
|
|
222
|
+
const def = definitions[queue.shift()];
|
|
223
|
+
for (const prop of Object.values(def.properties ?? {}))
|
|
224
|
+
walk(prop);
|
|
225
|
+
}
|
|
226
|
+
return [...seen].sort();
|
|
227
|
+
}
|
|
228
|
+
const VERSION_SEGMENT = /^v\d+((alpha|beta)\d+)?$/;
|
|
229
|
+
/** `io.k8s.api.flowcontrol.v1.Subject` → `{ group: "Flowcontrol", version: "V1", kind: "Subject" }`. */
|
|
230
|
+
function definitionKeyParts(defKey) {
|
|
231
|
+
const parts = defKey.split(".");
|
|
232
|
+
const kind = parts[parts.length - 1];
|
|
233
|
+
const vIdx = parts.length - 2;
|
|
234
|
+
const version = VERSION_SEGMENT.test(parts[vIdx] ?? "") ? parts[vIdx] : "";
|
|
235
|
+
const group = (version ? parts[vIdx - 1] : parts[vIdx]) ?? "";
|
|
236
|
+
const pascal = (s) => s.split(/[^A-Za-z0-9]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
237
|
+
return { group: pascal(group), version: pascal(version), kind };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Give every reachable definition a TypeScript name.
|
|
241
|
+
*
|
|
242
|
+
* The well-known property types keep their friendly names. Every other
|
|
243
|
+
* definition takes the last segment of its key (`DeploymentSpec`) when no
|
|
244
|
+
* other reachable definition shares that segment and no resource kind or
|
|
245
|
+
* friendly name already uses it. Otherwise each contender is qualified by its
|
|
246
|
+
* API group (`FlowcontrolSubject`, `CoreResourceClaim`), and by group and
|
|
247
|
+
* version when one group has several (`ResourceV1beta2Device`). The rule
|
|
248
|
+
* reads only the set of keys, so the result does not depend on the order of
|
|
249
|
+
* the document.
|
|
250
|
+
*/
|
|
251
|
+
function nameDefinitions(reachable, definitions) {
|
|
252
|
+
const names = new Map();
|
|
253
|
+
const taken = new Set();
|
|
254
|
+
for (const def of Object.values(definitions)) {
|
|
255
|
+
for (const gvk of def["x-kubernetes-group-version-kind"] ?? [])
|
|
256
|
+
taken.add(gvk.kind);
|
|
257
|
+
}
|
|
258
|
+
for (const [defKey, config] of Object.entries(PROPERTY_TYPE_DEFS)) {
|
|
259
|
+
const name = k8sShortName(config.typeName);
|
|
260
|
+
names.set(defKey, name);
|
|
261
|
+
taken.add(name);
|
|
262
|
+
}
|
|
263
|
+
const byKind = new Map();
|
|
264
|
+
for (const defKey of reachable) {
|
|
265
|
+
if (names.has(defKey))
|
|
266
|
+
continue;
|
|
267
|
+
const { kind } = definitionKeyParts(defKey);
|
|
268
|
+
byKind.set(kind, [...(byKind.get(kind) ?? []), defKey]);
|
|
269
|
+
}
|
|
270
|
+
for (const [kind, keys] of byKind) {
|
|
271
|
+
if (keys.length === 1 && !taken.has(kind)) {
|
|
272
|
+
names.set(keys[0], kind);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const groupCount = new Map();
|
|
276
|
+
for (const k of keys) {
|
|
277
|
+
const { group } = definitionKeyParts(k);
|
|
278
|
+
groupCount.set(group, (groupCount.get(group) ?? 0) + 1);
|
|
279
|
+
}
|
|
280
|
+
for (const k of keys) {
|
|
281
|
+
const { group, version } = definitionKeyParts(k);
|
|
282
|
+
names.set(k, groupCount.get(group) > 1 ? `${group}${version}${kind}` : `${group}${kind}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const owner = new Map();
|
|
286
|
+
for (const [defKey, name] of names) {
|
|
287
|
+
const prev = owner.get(name);
|
|
288
|
+
if (prev)
|
|
289
|
+
throw new Error(`k8s codegen: definitions ${prev} and ${defKey} both map to the type name ${name}`);
|
|
290
|
+
owner.set(name, defKey);
|
|
291
|
+
}
|
|
292
|
+
return names;
|
|
293
|
+
}
|
|
294
|
+
/** Stable key for a GVK, used to join the `paths` pass onto the `definitions` pass. */
|
|
295
|
+
/**
|
|
296
|
+
* Every `(property name, list-map keys)` pair the spec declares, across ALL
|
|
297
|
+
* definitions (chant #1441).
|
|
298
|
+
*
|
|
299
|
+
* Deliberately independent of {@link parseK8sSwagger}'s resource/property-type
|
|
300
|
+
* extraction. That extraction keeps 257 resources and 320 property types out
|
|
301
|
+
* of roughly a thousand definitions, so collecting merge keys from its output
|
|
302
|
+
* alone finds 18 of the 32 properties the spec annotates — and misses
|
|
303
|
+
* `ServicePort.port`, whose absence would silently demote every Service port
|
|
304
|
+
* list to the hand-written fallback. A definition is worth reading for its
|
|
305
|
+
* merge semantics whether or not chant emits a type for it.
|
|
306
|
+
*/
|
|
307
|
+
export function specListMapKeyPairs(data) {
|
|
308
|
+
const spec = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
|
|
309
|
+
const pairs = [];
|
|
310
|
+
for (const def of Object.values(spec.definitions ?? {})) {
|
|
311
|
+
for (const [name, prop] of Object.entries(def.properties ?? {})) {
|
|
312
|
+
if (prop["x-kubernetes-list-type"] !== "map")
|
|
313
|
+
continue;
|
|
314
|
+
const keys = prop["x-kubernetes-list-map-keys"];
|
|
315
|
+
if (keys?.length)
|
|
316
|
+
pairs.push([name, [...keys]]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return pairs;
|
|
320
|
+
}
|
|
321
|
+
export function gvkKey(gvk) {
|
|
322
|
+
return `${gvk.group}|${gvk.version}|${gvk.kind}`;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Derive the operation surface from the OpenAPI `paths` — chant #1074.
|
|
326
|
+
*
|
|
327
|
+
* Every Kubernetes operation carries `x-kubernetes-group-version-kind` and
|
|
328
|
+
* `x-kubernetes-action`, and the path itself carries the two facts a REST call
|
|
329
|
+
* needs and a definition does not have: the plural segment, and whether the
|
|
330
|
+
* resource is namespaced (`/namespaces/{namespace}/` appears in its path).
|
|
331
|
+
*
|
|
332
|
+
* Only paths addressing a single named object (`.../{plural}/{name}`) are read,
|
|
333
|
+
* so subresource paths (`.../{name}/status`, `.../{name}/scale`) and collection
|
|
334
|
+
* paths do not supply the plural — but their verbs are collected, because
|
|
335
|
+
* "this kind can be listed" is worth knowing.
|
|
336
|
+
*/
|
|
337
|
+
export function parseOperations(paths) {
|
|
338
|
+
const out = new Map();
|
|
339
|
+
if (!paths)
|
|
340
|
+
return out;
|
|
341
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
342
|
+
for (const operation of Object.values(item ?? {})) {
|
|
343
|
+
if (!operation || typeof operation !== "object")
|
|
344
|
+
continue;
|
|
345
|
+
const op = operation;
|
|
346
|
+
const gvk = op["x-kubernetes-group-version-kind"];
|
|
347
|
+
const action = op["x-kubernetes-action"];
|
|
348
|
+
if (!gvk || !action)
|
|
349
|
+
continue;
|
|
350
|
+
const segments = path.split("/").filter(Boolean);
|
|
351
|
+
const last = segments[segments.length - 1];
|
|
352
|
+
// `.../{plural}/{name}` — the only shape that names the plural
|
|
353
|
+
// unambiguously. `/api/v1/namespaces/{name}` is such a shape too, and
|
|
354
|
+
// correctly yields plural `namespaces`, cluster-scoped.
|
|
355
|
+
if (last !== "{name}")
|
|
356
|
+
continue;
|
|
357
|
+
const plural = segments[segments.length - 2];
|
|
358
|
+
if (!plural || plural.startsWith("{"))
|
|
359
|
+
continue;
|
|
360
|
+
const key = gvkKey(gvk);
|
|
361
|
+
const existing = out.get(key);
|
|
362
|
+
if (existing) {
|
|
363
|
+
if (!existing.verbs.includes(action))
|
|
364
|
+
existing.verbs.push(action);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
out.set(key, {
|
|
368
|
+
plural,
|
|
369
|
+
scope: path.includes("/namespaces/{namespace}/") ? "Namespaced" : "Cluster",
|
|
370
|
+
verbs: [action],
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
for (const operation of out.values())
|
|
375
|
+
operation.verbs.sort();
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Convert GVK to our type name convention: K8s::{Group}::{Kind}
|
|
380
|
+
*/
|
|
381
|
+
export function gvkToTypeName(gvk) {
|
|
382
|
+
const group = normalizeGroup(gvk.group);
|
|
383
|
+
return `K8s::${group}::${gvk.kind}`;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Convert GVK to apiVersion string for serialization.
|
|
387
|
+
*/
|
|
388
|
+
export function gvkToApiVersion(gvk) {
|
|
389
|
+
if (!gvk.group || gvk.group === "") {
|
|
390
|
+
return gvk.version; // core group: "v1"
|
|
391
|
+
}
|
|
392
|
+
return `${gvk.group}/${gvk.version}`; // e.g. "apps/v1"
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Normalize API group to a PascalCase segment — the shared rule, so the
|
|
396
|
+
* swagger-generated surface, kustomize-rendered entities and live discovery
|
|
397
|
+
* (all three reach here through `gvkToTypeName`) spell a kind exactly as CRD
|
|
398
|
+
* codegen and `chant import` do. See `../group-namespace`.
|
|
399
|
+
*/
|
|
400
|
+
function normalizeGroup(group) {
|
|
401
|
+
return namespaceSegmentForGroup(group);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Check if this definition key represents the preferred API version.
|
|
405
|
+
* Prefer stable (v1) over beta/alpha, and prefer the highest stable version.
|
|
406
|
+
*/
|
|
407
|
+
function isPreferredVersion(defKey, gvk, definitions) {
|
|
408
|
+
// Skip alpha versions
|
|
409
|
+
if (gvk.version.includes("alpha"))
|
|
410
|
+
return false;
|
|
411
|
+
// Find all definitions with the same GVK kind+group
|
|
412
|
+
const sameName = [];
|
|
413
|
+
for (const [key, def] of Object.entries(definitions)) {
|
|
414
|
+
const gvks = def["x-kubernetes-group-version-kind"];
|
|
415
|
+
if (!gvks)
|
|
416
|
+
continue;
|
|
417
|
+
for (const g of gvks) {
|
|
418
|
+
if (g.kind === gvk.kind && g.group === gvk.group) {
|
|
419
|
+
sameName.push({ key, version: g.version });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (sameName.length <= 1)
|
|
424
|
+
return true;
|
|
425
|
+
// Prefer stable (v1, v2) over beta
|
|
426
|
+
const stable = sameName.filter((s) => !s.version.includes("beta") && !s.version.includes("alpha"));
|
|
427
|
+
if (stable.length > 0) {
|
|
428
|
+
// Among stable, pick the highest version
|
|
429
|
+
stable.sort((a, b) => b.version.localeCompare(a.version));
|
|
430
|
+
return defKey === stable[0].key;
|
|
431
|
+
}
|
|
432
|
+
// All beta — pick highest
|
|
433
|
+
sameName.sort((a, b) => b.version.localeCompare(a.version));
|
|
434
|
+
return defKey === sameName[0].key;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Extract a top-level resource from a swagger definition.
|
|
438
|
+
*/
|
|
439
|
+
function extractResource(defKey, def, typeName, gvk, ctx) {
|
|
440
|
+
if (!def.properties)
|
|
441
|
+
return null;
|
|
442
|
+
const requiredSet = new Set(def.required ?? []);
|
|
443
|
+
// K8s resources have standard fields (apiVersion, kind, metadata, spec, status)
|
|
444
|
+
// We only expose user-configurable properties — skip apiVersion, kind, status
|
|
445
|
+
const skipProps = new Set(["apiVersion", "kind", "status"]);
|
|
446
|
+
const filteredProps = {};
|
|
447
|
+
for (const [name, prop] of Object.entries(def.properties)) {
|
|
448
|
+
if (!skipProps.has(name)) {
|
|
449
|
+
filteredProps[name] = prop;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const properties = parseProperties(filteredProps, requiredSet, ctx);
|
|
453
|
+
return {
|
|
454
|
+
resource: {
|
|
455
|
+
typeName,
|
|
456
|
+
description: def.description,
|
|
457
|
+
properties,
|
|
458
|
+
attributes: [
|
|
459
|
+
{ name: "name", tsType: "string" },
|
|
460
|
+
{ name: "namespace", tsType: "string" },
|
|
461
|
+
{ name: "uid", tsType: "string" },
|
|
462
|
+
],
|
|
463
|
+
deprecatedProperties: [],
|
|
464
|
+
},
|
|
465
|
+
propertyTypes: [],
|
|
466
|
+
enums: [],
|
|
467
|
+
gvk,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Extract a property type definition (not a top-level resource).
|
|
472
|
+
*/
|
|
473
|
+
function extractPropertyType(defKey, def, typeName, description, ctx) {
|
|
474
|
+
if (!def.properties)
|
|
475
|
+
return null;
|
|
476
|
+
const requiredSet = new Set(def.required ?? []);
|
|
477
|
+
const properties = parseProperties(def.properties, requiredSet, ctx);
|
|
478
|
+
const gvkParts = typeName.split("::");
|
|
479
|
+
return {
|
|
480
|
+
resource: {
|
|
481
|
+
typeName,
|
|
482
|
+
description,
|
|
483
|
+
properties,
|
|
484
|
+
attributes: [],
|
|
485
|
+
deprecatedProperties: [],
|
|
486
|
+
},
|
|
487
|
+
propertyTypes: [],
|
|
488
|
+
enums: [],
|
|
489
|
+
gvk: { group: gvkParts[1]?.toLowerCase() ?? "", version: "v1", kind: gvkParts[2] ?? "" },
|
|
490
|
+
isProperty: true,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Parse properties from a swagger definition into ParsedProperty[].
|
|
495
|
+
*/
|
|
496
|
+
function parseProperties(properties, requiredSet, ctx) {
|
|
497
|
+
const result = [];
|
|
498
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
499
|
+
const tsType = resolvePropertyType(prop, ctx);
|
|
500
|
+
const parsed = {
|
|
501
|
+
name,
|
|
502
|
+
tsType,
|
|
503
|
+
required: requiredSet.has(name),
|
|
504
|
+
description: prop.description,
|
|
505
|
+
enum: prop.enum,
|
|
506
|
+
constraints: coreExtractConstraints(prop),
|
|
507
|
+
};
|
|
508
|
+
// chant #1441 — the merge semantics the API server itself uses. Kept only
|
|
509
|
+
// when present; a spec that says nothing leaves both fields absent rather
|
|
510
|
+
// than asserting a default the schema did not state.
|
|
511
|
+
const listType = prop["x-kubernetes-list-type"];
|
|
512
|
+
if (listType)
|
|
513
|
+
parsed.listType = listType;
|
|
514
|
+
const listMapKeys = prop["x-kubernetes-list-map-keys"];
|
|
515
|
+
if (listMapKeys?.length)
|
|
516
|
+
parsed.listMapKeys = [...listMapKeys];
|
|
517
|
+
result.push(parsed);
|
|
518
|
+
}
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Resolve a swagger property to its TypeScript type string.
|
|
523
|
+
*/
|
|
524
|
+
function resolvePropertyType(prop, ctx) {
|
|
525
|
+
if (!prop)
|
|
526
|
+
return "any";
|
|
527
|
+
// Handle $ref
|
|
528
|
+
if (prop.$ref) {
|
|
529
|
+
return resolveRefType(prop.$ref, ctx);
|
|
530
|
+
}
|
|
531
|
+
// x-kubernetes-int-or-string
|
|
532
|
+
if (prop["x-kubernetes-int-or-string"]) {
|
|
533
|
+
return "string | number";
|
|
534
|
+
}
|
|
535
|
+
// Inline enum
|
|
536
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
537
|
+
return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
538
|
+
}
|
|
539
|
+
const pt = primaryType(prop.type);
|
|
540
|
+
switch (pt) {
|
|
541
|
+
case "string":
|
|
542
|
+
return "string";
|
|
543
|
+
case "integer":
|
|
544
|
+
case "number":
|
|
545
|
+
return "number";
|
|
546
|
+
case "boolean":
|
|
547
|
+
return "boolean";
|
|
548
|
+
case "array":
|
|
549
|
+
if (prop.items) {
|
|
550
|
+
const itemType = resolvePropertyType(prop.items, ctx);
|
|
551
|
+
if (itemType.includes(" | "))
|
|
552
|
+
return `(${itemType})[]`;
|
|
553
|
+
return `${itemType}[]`;
|
|
554
|
+
}
|
|
555
|
+
return "any[]";
|
|
556
|
+
case "object":
|
|
557
|
+
if (prop.additionalProperties && typeof prop.additionalProperties === "object") {
|
|
558
|
+
const valueType = resolvePropertyType(prop.additionalProperties, ctx);
|
|
559
|
+
return `Record<string, ${valueType}>`;
|
|
560
|
+
}
|
|
561
|
+
return "Record<string, any>";
|
|
562
|
+
default:
|
|
563
|
+
return "any";
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Resolve a $ref to a TypeScript type.
|
|
568
|
+
*/
|
|
569
|
+
function resolveRefType(ref, ctx) {
|
|
570
|
+
const prefix = "#/definitions/";
|
|
571
|
+
if (!ref.startsWith(prefix))
|
|
572
|
+
return "any";
|
|
573
|
+
const defKey = ref.slice(prefix.length);
|
|
574
|
+
// Well-known scalars, and RawExtension, which stays open.
|
|
575
|
+
const fixed = OPEN_OR_SCALAR_DEFS[defKey];
|
|
576
|
+
if (fixed)
|
|
577
|
+
return fixed;
|
|
578
|
+
// A property class or a definition interface (chant #3093).
|
|
579
|
+
const named = ctx.names.get(defKey);
|
|
580
|
+
if (named)
|
|
581
|
+
return named;
|
|
582
|
+
if (ctx.resources.has(defKey))
|
|
583
|
+
return inlineResourceType(defKey, ctx);
|
|
584
|
+
// Resolve the definition
|
|
585
|
+
const def = ctx.definitions[defKey];
|
|
586
|
+
if (!def)
|
|
587
|
+
return "any";
|
|
588
|
+
// Enum
|
|
589
|
+
if (def.enum && def.enum.length > 0 && !def.properties) {
|
|
590
|
+
return def.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
591
|
+
}
|
|
592
|
+
// Primitive type
|
|
593
|
+
if (def.type && !def.properties) {
|
|
594
|
+
const pt = primaryType(def.type);
|
|
595
|
+
switch (pt) {
|
|
596
|
+
case "string": return "string";
|
|
597
|
+
case "integer":
|
|
598
|
+
case "number": return "number";
|
|
599
|
+
case "boolean": return "boolean";
|
|
600
|
+
default: return "any";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// An object this pass does not name: a resource of a non-preferred
|
|
604
|
+
// version, or a definition with no properties (`JSON`, `FieldsV1`).
|
|
605
|
+
if (def.properties)
|
|
606
|
+
return "Record<string, any>";
|
|
607
|
+
return "any";
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Extract short name: "K8s::Apps::Deployment" → "Deployment"
|
|
611
|
+
*/
|
|
612
|
+
export function k8sShortName(typeName) {
|
|
613
|
+
const parts = typeName.split("::");
|
|
614
|
+
return parts[parts.length - 1];
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Extract service/group name: "K8s::Apps::Deployment" → "Apps"
|
|
618
|
+
*/
|
|
619
|
+
export function k8sServiceName(typeName) {
|
|
620
|
+
const parts = typeName.split("::");
|
|
621
|
+
return parts.length >= 2 ? parts[1] : "Core";
|
|
622
|
+
}
|