@intentius/chant-lexicon-k8s 0.41.20 → 0.44.1
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/api/classify.d.ts.map +1 -1
- package/dist/api/connect.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/components/argo-app.d.ts +104 -0
- package/dist/components/argo-app.d.ts.map +1 -0
- package/dist/components/builders.d.ts +8 -0
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +1 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/flux-reconcile.d.ts +84 -0
- package/dist/components/flux-reconcile.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/kustomize-apply.d.ts +29 -7
- package/dist/components/kustomize-apply.d.ts.map +1 -1
- package/dist/composites/flux-app.d.ts +132 -0
- package/dist/composites/flux-app.d.ts.map +1 -0
- package/dist/composites/index.d.ts +2 -0
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/config-schema.d.ts +3 -0
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config.d.ts +32 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/crd/crd-sources.d.ts.map +1 -1
- package/dist/crd/parser.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +8 -0
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +10 -8
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/generated/index.d.ts +15 -4
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +10 -6
- package/dist/kube/connect.d.ts +5 -4
- package/dist/kube/connect.d.ts.map +1 -1
- package/dist/kube/get.d.ts +4 -4
- package/dist/kustomize/render.d.ts +15 -0
- package/dist/kustomize/render.d.ts.map +1 -0
- package/dist/kustomize/rendered-entity.d.ts +39 -0
- package/dist/kustomize/rendered-entity.d.ts.map +1 -0
- package/dist/kustomize/root.d.ts +19 -0
- package/dist/kustomize/root.d.ts.map +1 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/argo-helpers.d.ts +2 -0
- package/dist/lint/post-synth/argo-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/flux002.d.ts +14 -0
- package/dist/lint/post-synth/flux002.d.ts.map +1 -0
- package/dist/lint/post-synth/flux003.d.ts +17 -0
- package/dist/lint/post-synth/flux003.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/rules/flux-source-ref-pin.d.ts +16 -0
- package/dist/lint/rules/flux-source-ref-pin.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +92 -16
- package/dist/okf/index.md +256 -0
- package/dist/okf/rules/ARGO001.md +16 -0
- package/dist/okf/rules/ARGO002.md +16 -0
- package/dist/okf/rules/ARGO003.md +15 -0
- package/dist/okf/rules/ARGO004.md +17 -0
- package/dist/okf/rules/ARGO005.md +15 -0
- package/dist/okf/rules/FLUX001.md +17 -0
- package/dist/okf/rules/FLUX002.md +19 -0
- package/dist/okf/rules/FLUX003.md +15 -0
- package/dist/okf/rules/WK8001.md +16 -0
- package/dist/okf/rules/WK8002.md +20 -0
- package/dist/okf/rules/WK8003.md +19 -0
- package/dist/okf/rules/WK8005.md +11 -0
- package/dist/okf/rules/WK8006.md +11 -0
- package/dist/okf/rules/WK8041.md +11 -0
- package/dist/okf/rules/WK8042.md +11 -0
- package/dist/okf/rules/WK8101.md +11 -0
- package/dist/okf/rules/WK8102.md +11 -0
- package/dist/okf/rules/WK8103.md +11 -0
- package/dist/okf/rules/WK8104.md +15 -0
- package/dist/okf/rules/WK8105.md +11 -0
- package/dist/okf/rules/WK8201.md +11 -0
- package/dist/okf/rules/WK8202.md +11 -0
- package/dist/okf/rules/WK8203.md +11 -0
- package/dist/okf/rules/WK8204.md +11 -0
- package/dist/okf/rules/WK8205.md +11 -0
- package/dist/okf/rules/WK8207.md +11 -0
- package/dist/okf/rules/WK8208.md +11 -0
- package/dist/okf/rules/WK8209.md +11 -0
- package/dist/okf/rules/WK8301.md +16 -0
- package/dist/okf/rules/WK8302.md +11 -0
- package/dist/okf/rules/WK8303.md +15 -0
- package/dist/okf/rules/WK8304.md +15 -0
- package/dist/okf/rules/WK8305.md +15 -0
- package/dist/okf/rules/WK8306.md +11 -0
- package/dist/okf/rules/WK8401.md +15 -0
- package/dist/okf/rules/WK8402.md +15 -0
- package/dist/okf/rules/WK8403.md +15 -0
- package/dist/okf/types/APIGroup.md +16 -0
- package/dist/okf/types/APIGroupList.md +13 -0
- package/dist/okf/types/APIResourceList.md +14 -0
- package/dist/okf/types/APIService.md +14 -0
- package/dist/okf/types/APIServiceList.md +14 -0
- package/dist/okf/types/APIVersions.md +14 -0
- package/dist/okf/types/Addon.md +14 -0
- package/dist/okf/types/Alert.md +14 -0
- package/dist/okf/types/AppProject.md +19 -0
- package/dist/okf/types/Application.md +23 -0
- package/dist/okf/types/ApplicationSet.md +18 -0
- package/dist/okf/types/Backup.md +14 -0
- package/dist/okf/types/BatchJob.md +14 -0
- package/dist/okf/types/Binding.md +14 -0
- package/dist/okf/types/Bucket.md +18 -0
- package/dist/okf/types/CM.md +16 -0
- package/dist/okf/types/CSIDriver.md +14 -0
- package/dist/okf/types/CSIDriverList.md +14 -0
- package/dist/okf/types/CSINode.md +14 -0
- package/dist/okf/types/CSINodeList.md +14 -0
- package/dist/okf/types/CSIStorageCapacity.md +17 -0
- package/dist/okf/types/CSIStorageCapacityList.md +14 -0
- package/dist/okf/types/Certificate.md +18 -0
- package/dist/okf/types/CertificateRequest.md +14 -0
- package/dist/okf/types/CertificateSigningRequest.md +14 -0
- package/dist/okf/types/CertificateSigningRequestList.md +14 -0
- package/dist/okf/types/Challenge.md +14 -0
- package/dist/okf/types/Cluster.md +14 -0
- package/dist/okf/types/ClusterIssuer.md +14 -0
- package/dist/okf/types/ClusterRole.md +15 -0
- package/dist/okf/types/ClusterRoleBinding.md +15 -0
- package/dist/okf/types/ClusterRoleBindingList.md +14 -0
- package/dist/okf/types/ClusterRoleList.md +14 -0
- package/dist/okf/types/ClusterTrustBundle.md +14 -0
- package/dist/okf/types/ClusterTrustBundleList.md +14 -0
- package/dist/okf/types/ComponentStatus.md +14 -0
- package/dist/okf/types/ComponentStatusList.md +14 -0
- package/dist/okf/types/ConfigMapList.md +14 -0
- package/dist/okf/types/ControllerRevision.md +15 -0
- package/dist/okf/types/ControllerRevisionList.md +14 -0
- package/dist/okf/types/CoreEventList.md +14 -0
- package/dist/okf/types/CrdbCluster.md +14 -0
- package/dist/okf/types/CronJob.md +20 -0
- package/dist/okf/types/CronJobList.md +14 -0
- package/dist/okf/types/CustomResourceDefinition.md +14 -0
- package/dist/okf/types/CustomResourceDefinitionList.md +14 -0
- package/dist/okf/types/DaemonSet.md +19 -0
- package/dist/okf/types/DaemonSetList.md +14 -0
- package/dist/okf/types/DeleteOptions.md +18 -0
- package/dist/okf/types/Deploy.md +14 -0
- package/dist/okf/types/DeploymentList.md +14 -0
- package/dist/okf/types/DeviceClass.md +14 -0
- package/dist/okf/types/DeviceClassList.md +14 -0
- package/dist/okf/types/DeviceTaintRule.md +14 -0
- package/dist/okf/types/DeviceTaintRuleList.md +14 -0
- package/dist/okf/types/EndpointSlice.md +16 -0
- package/dist/okf/types/EndpointSliceList.md +14 -0
- package/dist/okf/types/Endpoints.md +14 -0
- package/dist/okf/types/EndpointsList.md +14 -0
- package/dist/okf/types/Event.md +27 -0
- package/dist/okf/types/EventsEvent.md +27 -0
- package/dist/okf/types/EventsEventList.md +14 -0
- package/dist/okf/types/Eviction.md +14 -0
- package/dist/okf/types/FlowSchema.md +14 -0
- package/dist/okf/types/FlowSchemaList.md +14 -0
- package/dist/okf/types/FluxHelmChart.md +14 -0
- package/dist/okf/types/FluxInstance.md +14 -0
- package/dist/okf/types/FluxReport.md +14 -0
- package/dist/okf/types/GRPCRoute.md +14 -0
- package/dist/okf/types/Gateway.md +14 -0
- package/dist/okf/types/GatewayClass.md +14 -0
- package/dist/okf/types/GitRepository.md +19 -0
- package/dist/okf/types/HTTPRoute.md +14 -0
- package/dist/okf/types/HelmChartConfig.md +14 -0
- package/dist/okf/types/HelmRelease.md +14 -0
- package/dist/okf/types/HelmRepository.md +14 -0
- package/dist/okf/types/HorizontalPodAutoscaler.md +14 -0
- package/dist/okf/types/HorizontalPodAutoscalerList.md +14 -0
- package/dist/okf/types/IPAddress.md +14 -0
- package/dist/okf/types/IPAddressList.md +14 -0
- package/dist/okf/types/ImagePolicy.md +14 -0
- package/dist/okf/types/ImageRepository.md +14 -0
- package/dist/okf/types/ImageUpdateAutomation.md +14 -0
- package/dist/okf/types/InfisicalDynamicSecret.md +14 -0
- package/dist/okf/types/InfisicalPushSecret.md +14 -0
- package/dist/okf/types/InfisicalSecret.md +14 -0
- package/dist/okf/types/Ing.md +14 -0
- package/dist/okf/types/IngressClass.md +14 -0
- package/dist/okf/types/IngressClassList.md +14 -0
- package/dist/okf/types/IngressList.md +14 -0
- package/dist/okf/types/IngressRoute.md +14 -0
- package/dist/okf/types/IngressRouteTCP.md +14 -0
- package/dist/okf/types/IngressRouteUDP.md +14 -0
- package/dist/okf/types/Issuer.md +14 -0
- package/dist/okf/types/JobList.md +14 -0
- package/dist/okf/types/K3sHelmChart.md +14 -0
- package/dist/okf/types/Kustomization.md +20 -0
- package/dist/okf/types/Lease.md +14 -0
- package/dist/okf/types/LeaseCandidate.md +14 -0
- package/dist/okf/types/LeaseCandidateList.md +14 -0
- package/dist/okf/types/LeaseList.md +14 -0
- package/dist/okf/types/LimitRange.md +14 -0
- package/dist/okf/types/LimitRangeList.md +14 -0
- package/dist/okf/types/LocalSubjectAccessReview.md +14 -0
- package/dist/okf/types/MicroVM.md +14 -0
- package/dist/okf/types/MicroVMClass.md +14 -0
- package/dist/okf/types/MicroVMImage.md +14 -0
- package/dist/okf/types/MicroVMNetwork.md +14 -0
- package/dist/okf/types/MicroVMReplicaSet.md +14 -0
- package/dist/okf/types/Middleware.md +14 -0
- package/dist/okf/types/MiddlewareTCP.md +14 -0
- package/dist/okf/types/MutatingAdmissionPolicy.md +14 -0
- package/dist/okf/types/MutatingAdmissionPolicyBinding.md +14 -0
- package/dist/okf/types/MutatingAdmissionPolicyBindingList.md +14 -0
- package/dist/okf/types/MutatingAdmissionPolicyList.md +14 -0
- package/dist/okf/types/MutatingWebhookConfiguration.md +14 -0
- package/dist/okf/types/MutatingWebhookConfigurationList.md +14 -0
- package/dist/okf/types/Namespace.md +19 -0
- package/dist/okf/types/NamespaceList.md +14 -0
- package/dist/okf/types/NetPol.md +14 -0
- package/dist/okf/types/NetworkPolicyList.md +14 -0
- package/dist/okf/types/Node.md +22 -0
- package/dist/okf/types/NodeList.md +14 -0
- package/dist/okf/types/OCIRepository.md +18 -0
- package/dist/okf/types/ObjectStore.md +14 -0
- package/dist/okf/types/Order.md +14 -0
- package/dist/okf/types/PDB.md +18 -0
- package/dist/okf/types/PersistentVolume.md +14 -0
- package/dist/okf/types/PersistentVolumeClaim.md +14 -0
- package/dist/okf/types/PersistentVolumeClaimList.md +14 -0
- package/dist/okf/types/PersistentVolumeList.md +14 -0
- package/dist/okf/types/Pod.md +18 -0
- package/dist/okf/types/PodCertificateRequest.md +14 -0
- package/dist/okf/types/PodCertificateRequestList.md +14 -0
- package/dist/okf/types/PodDisruptionBudgetList.md +14 -0
- package/dist/okf/types/PodList.md +14 -0
- package/dist/okf/types/PodTemplate.md +14 -0
- package/dist/okf/types/PodTemplateList.md +14 -0
- package/dist/okf/types/Pooler.md +14 -0
- package/dist/okf/types/PriorityClass.md +17 -0
- package/dist/okf/types/PriorityClassList.md +14 -0
- package/dist/okf/types/PriorityLevelConfiguration.md +14 -0
- package/dist/okf/types/PriorityLevelConfigurationList.md +14 -0
- package/dist/okf/types/PrometheusRule.md +14 -0
- package/dist/okf/types/Provider.md +14 -0
- package/dist/okf/types/RayCluster.md +20 -0
- package/dist/okf/types/RayJob.md +14 -0
- package/dist/okf/types/RayService.md +14 -0
- package/dist/okf/types/Receiver.md +14 -0
- package/dist/okf/types/ReferenceGrant.md +14 -0
- package/dist/okf/types/ReplicaSet.md +19 -0
- package/dist/okf/types/ReplicaSetList.md +14 -0
- package/dist/okf/types/ReplicationController.md +14 -0
- package/dist/okf/types/ReplicationControllerList.md +14 -0
- package/dist/okf/types/ResourceClaim.md +14 -0
- package/dist/okf/types/ResourceClaimList.md +14 -0
- package/dist/okf/types/ResourceClaimTemplate.md +14 -0
- package/dist/okf/types/ResourceClaimTemplateList.md +14 -0
- package/dist/okf/types/ResourceQuota.md +14 -0
- package/dist/okf/types/ResourceQuotaList.md +14 -0
- package/dist/okf/types/ResourceSet.md +14 -0
- package/dist/okf/types/ResourceSetInputProvider.md +14 -0
- package/dist/okf/types/ResourceSlice.md +14 -0
- package/dist/okf/types/ResourceSliceList.md +14 -0
- package/dist/okf/types/Role.md +14 -0
- package/dist/okf/types/RoleBinding.md +15 -0
- package/dist/okf/types/RoleBindingList.md +14 -0
- package/dist/okf/types/RoleList.md +14 -0
- package/dist/okf/types/RuntimeClass.md +16 -0
- package/dist/okf/types/RuntimeClassList.md +14 -0
- package/dist/okf/types/SA.md +16 -0
- package/dist/okf/types/Scale.md +14 -0
- package/dist/okf/types/ScheduledBackup.md +14 -0
- package/dist/okf/types/Sec.md +17 -0
- package/dist/okf/types/SecretList.md +14 -0
- package/dist/okf/types/SelfSubjectAccessReview.md +14 -0
- package/dist/okf/types/SelfSubjectReview.md +13 -0
- package/dist/okf/types/SelfSubjectRulesReview.md +14 -0
- package/dist/okf/types/ServersTransport.md +14 -0
- package/dist/okf/types/ServersTransportTCP.md +14 -0
- package/dist/okf/types/Service.md +20 -0
- package/dist/okf/types/ServiceAccountList.md +14 -0
- package/dist/okf/types/ServiceCIDR.md +14 -0
- package/dist/okf/types/ServiceCIDRList.md +14 -0
- package/dist/okf/types/ServiceList.md +14 -0
- package/dist/okf/types/ServiceMonitor.md +14 -0
- package/dist/okf/types/StatefulSet.md +19 -0
- package/dist/okf/types/StatefulSetList.md +14 -0
- package/dist/okf/types/Status.md +17 -0
- package/dist/okf/types/StorageClass.md +20 -0
- package/dist/okf/types/StorageClassList.md +14 -0
- package/dist/okf/types/StorageVersionMigration.md +14 -0
- package/dist/okf/types/StorageVersionMigrationList.md +14 -0
- package/dist/okf/types/SubjectAccessReview.md +14 -0
- package/dist/okf/types/TLSOption.md +14 -0
- package/dist/okf/types/TLSStore.md +14 -0
- package/dist/okf/types/TokenRequest.md +14 -0
- package/dist/okf/types/TokenReview.md +14 -0
- package/dist/okf/types/TraefikService.md +14 -0
- package/dist/okf/types/ValidatingAdmissionPolicy.md +14 -0
- package/dist/okf/types/ValidatingAdmissionPolicyBinding.md +14 -0
- package/dist/okf/types/ValidatingAdmissionPolicyBindingList.md +14 -0
- package/dist/okf/types/ValidatingAdmissionPolicyList.md +14 -0
- package/dist/okf/types/ValidatingWebhookConfiguration.md +14 -0
- package/dist/okf/types/ValidatingWebhookConfigurationList.md +14 -0
- package/dist/okf/types/VolumeAttachment.md +14 -0
- package/dist/okf/types/VolumeAttachmentList.md +14 -0
- package/dist/okf/types/VolumeAttributesClass.md +15 -0
- package/dist/okf/types/VolumeAttributesClassList.md +14 -0
- package/dist/okf/types/WatchEvent.md +14 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/argo-helpers.ts +2 -0
- package/dist/rules/flux-source-ref-pin.ts +52 -0
- package/dist/rules/flux002.ts +77 -0
- package/dist/rules/flux003.ts +75 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-k8s-flux.md +162 -0
- package/dist/types/index.d.ts +41 -3
- package/dist/validate.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/api/classify.test.ts +20 -0
- package/src/api/classify.ts +8 -1
- package/src/api/connect.ts +10 -0
- package/src/codegen/docs.ts +11 -0
- package/src/components/argo-app.test.ts +137 -0
- package/src/components/argo-app.ts +163 -0
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin.ts +11 -1
- package/src/components/flux-reconcile.test.ts +145 -0
- package/src/components/flux-reconcile.ts +159 -0
- package/src/components/index.ts +13 -0
- package/src/components/kustomize-apply.ts +12 -41
- package/src/composites/composites.test.ts +122 -0
- package/src/composites/flux-app.ts +274 -0
- package/src/composites/index.ts +9 -0
- package/src/config-augmentation.test.ts +72 -0
- package/src/config-schema.ts +5 -0
- package/src/config.ts +33 -5
- package/src/crd/crd-sources.ts +37 -0
- package/src/crd/parser.ts +6 -0
- package/src/deep-observe-hooks.ts +60 -0
- package/src/deep-observe.test.ts +114 -0
- package/src/describe-resources.test.ts +215 -6
- package/src/describe-resources.ts +197 -118
- package/src/generated/index.d.ts +41 -3
- package/src/generated/index.ts +15 -4
- package/src/generated/lexicon-k8s.json +92 -16
- package/src/generated/operations.json +48 -0
- package/src/index.ts +4 -0
- package/src/kube/connect.ts +5 -4
- package/src/kube/get.ts +4 -4
- package/src/kustomize/render.ts +84 -0
- package/src/kustomize/rendered-entity.ts +72 -0
- package/src/kustomize/root.test.ts +268 -0
- package/src/kustomize/root.ts +109 -0
- package/src/lint/audit-catalog.ts +2 -0
- package/src/lint/post-synth/argo-helpers.ts +2 -0
- package/src/lint/post-synth/flux002.ts +77 -0
- package/src/lint/post-synth/flux003.ts +75 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/post-synth.test.ts +132 -0
- package/src/lint/rules/flux-source-ref-pin.ts +52 -0
- package/src/lint/rules/rules.test.ts +51 -0
- package/src/plugin.test.ts +2 -0
- package/src/plugin.ts +47 -0
- package/src/serializer.ts +34 -4
- package/src/skills/chant-k8s-flux.md +162 -0
- package/src/testdata/kustomize-root/base/deployment.yaml +17 -0
- package/src/testdata/kustomize-root/base/kustomization.yaml +3 -0
- package/src/testdata/kustomize-root/base/service.yaml +9 -0
- package/src/testdata/kustomize-root/overlays/prod/kustomization.yaml +6 -0
- package/src/validate.ts +4 -0
package/src/crd/crd-sources.ts
CHANGED
|
@@ -277,6 +277,40 @@ const FLUX_OPERATOR_INSTALL = `https://github.com/controlplaneio-fluxcd/flux-ope
|
|
|
277
277
|
const KUBEMICROVM_CHART = "oci://ghcr.io/codriverlabs/helm/kube-microvm-operator";
|
|
278
278
|
const KUBEMICROVM_VERSION = "1.0.11";
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* k3s bundled-controller CRDs — helm.cattle.io/v1 + k3s.cattle.io/v1
|
|
282
|
+
*
|
|
283
|
+
* k3s's manifest auto-deploy machinery speaks these CRDs: drop a HelmChart
|
|
284
|
+
* into /var/lib/rancher/k3s/server/manifests and the embedded helm-controller
|
|
285
|
+
* installs the chart; every auto-deployed manifest is tracked by an Addon.
|
|
286
|
+
* Typed here for the same reason the Flux toolkit kinds are — they are what a
|
|
287
|
+
* k3s estate's GitOps surface is written in.
|
|
288
|
+
*
|
|
289
|
+
* Produces (both groups map to the `K3s` namespace — see
|
|
290
|
+
* GROUP_NAMESPACE_OVERRIDES in crd/parser.ts):
|
|
291
|
+
* K8s::K3s::HelmChart → apiVersion: helm.cattle.io/v1, kind: HelmChart
|
|
292
|
+
* K8s::K3s::HelmChartConfig → apiVersion: helm.cattle.io/v1, kind: HelmChartConfig
|
|
293
|
+
* K8s::K3s::Addon → apiVersion: k3s.cattle.io/v1, kind: Addon
|
|
294
|
+
*
|
|
295
|
+
* The pin is k3s v1.36.3+k3s1 (the release the k3s lexicon pins), but the k3s
|
|
296
|
+
* repo itself publishes no CRD YAMLs — its manifests/ directory carries only
|
|
297
|
+
* the bundled charts, and the API types live in dependency repos. The URLs
|
|
298
|
+
* below point at the exact versions that release vendors in its go.mod:
|
|
299
|
+
* github.com/k3s-io/helm-controller v0.17.7 (helm.cattle.io CRDs)
|
|
300
|
+
* github.com/k3s-io/api v0.1.4 (k3s.cattle.io CRDs)
|
|
301
|
+
* When bumping, re-read go.mod at the new k3s tag and move both pins together.
|
|
302
|
+
*
|
|
303
|
+
* k3s-io/api also ships an ETCDSnapshotFile CRD — deliberately left out. It is
|
|
304
|
+
* status-only controller bookkeeping (the server writes them to describe
|
|
305
|
+
* snapshots it took); nothing an author would declare.
|
|
306
|
+
*
|
|
307
|
+
* Controller install: none — both controllers are embedded in the k3s binary.
|
|
308
|
+
*/
|
|
309
|
+
const K3S_HELM_CONTROLLER_VERSION = "v0.17.7"; // vendored by k3s v1.36.3+k3s1
|
|
310
|
+
const K3S_API_VERSION = "v0.1.4"; // vendored by k3s v1.36.3+k3s1
|
|
311
|
+
const K3S_HELM_CONTROLLER_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/helm-controller/${K3S_HELM_CONTROLLER_VERSION}/pkg/crds/yaml/generated`;
|
|
312
|
+
const K3S_API_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/api/${K3S_API_VERSION}/pkg/crds/yaml/generated`;
|
|
313
|
+
|
|
280
314
|
export const CRD_SOURCES: CRDSource[] = [
|
|
281
315
|
{ type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayclusters.yaml` },
|
|
282
316
|
{ type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayjobs.yaml` },
|
|
@@ -335,6 +369,9 @@ export const CRD_SOURCES: CRDSource[] = [
|
|
|
335
369
|
url: FLUX_OPERATOR_INSTALL,
|
|
336
370
|
kinds: ["FluxInstance", "FluxReport", "ResourceSet", "ResourceSetInputProvider"],
|
|
337
371
|
},
|
|
372
|
+
{ type: "url", url: `${K3S_HELM_CONTROLLER_CRD_BASE}/helm.cattle.io_helmcharts.yaml` },
|
|
373
|
+
{ type: "url", url: `${K3S_HELM_CONTROLLER_CRD_BASE}/helm.cattle.io_helmchartconfigs.yaml` },
|
|
374
|
+
{ type: "url", url: `${K3S_API_CRD_BASE}/k3s.cattle.io_addons.yaml` },
|
|
338
375
|
{
|
|
339
376
|
type: "helm",
|
|
340
377
|
chart: KUBEMICROVM_CHART,
|
package/src/crd/parser.ts
CHANGED
|
@@ -52,6 +52,12 @@ const GROUP_NAMESPACE_OVERRIDES: Record<string, string> = {
|
|
|
52
52
|
// Not `Secrets`: `K8s::Secrets::InfisicalSecret` reads like a core Secret,
|
|
53
53
|
// and `K8s::Core::Secret` is right there to be confused with.
|
|
54
54
|
"secrets.infisical.com": "Infisical",
|
|
55
|
+
// k3s's bundled controllers ship under two groups but are one thing to an
|
|
56
|
+
// author — the k3s auto-deploy surface. The first-segment rule would give
|
|
57
|
+
// `K8s::Helm::HelmChart`, which reads like it belongs to the helm lexicon,
|
|
58
|
+
// and would split HelmChart from the Addon that tracks its deployment.
|
|
59
|
+
"helm.cattle.io": "K3s",
|
|
60
|
+
"k3s.cattle.io": "K3s",
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
/**
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
import { createRequire } from "module";
|
|
45
45
|
import type { DeepArrayElement, DeepNode, DeepNormalizationHooks } from "@intentius/chant/lexicon";
|
|
46
46
|
import { K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS, k8sListMapOrderKey } from "@intentius/chant/managed-fields";
|
|
47
|
+
import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
|
|
47
48
|
|
|
48
49
|
// This module ships as ESM (tsx strips types from src/ directly), where a bare
|
|
49
50
|
// `require` is not defined — the same reason serializer.ts and the LSP modules
|
|
@@ -86,13 +87,64 @@ export const K8S_SERVICE_DEFAULTS: Record<string, Record<string, unknown>> = {
|
|
|
86
87
|
"spec.template.spec.restartPolicy": "Always",
|
|
87
88
|
"spec.template.spec.terminationGracePeriodSeconds": 30,
|
|
88
89
|
"spec.template.spec.schedulerName": "default-scheduler",
|
|
90
|
+
// Widened on the CC canonical estate (#1214): a container declaring an
|
|
91
|
+
// httpGet probe, a named port, or nothing about termination gets exactly
|
|
92
|
+
// these from the API server. SSA attributes a server-defaulted field to no
|
|
93
|
+
// manager, so the managed-fields prune deliberately leaves them diffable
|
|
94
|
+
// (see ./deep-observe.ts) — this table is where they subtract.
|
|
95
|
+
"spec.template.spec.containers[].livenessProbe.failureThreshold": 3,
|
|
96
|
+
"spec.template.spec.containers[].livenessProbe.periodSeconds": 10,
|
|
97
|
+
"spec.template.spec.containers[].livenessProbe.successThreshold": 1,
|
|
98
|
+
"spec.template.spec.containers[].livenessProbe.timeoutSeconds": 1,
|
|
99
|
+
"spec.template.spec.containers[].livenessProbe.httpGet.scheme": "HTTP",
|
|
100
|
+
"spec.template.spec.containers[].readinessProbe.failureThreshold": 3,
|
|
101
|
+
"spec.template.spec.containers[].readinessProbe.periodSeconds": 10,
|
|
102
|
+
"spec.template.spec.containers[].readinessProbe.successThreshold": 1,
|
|
103
|
+
"spec.template.spec.containers[].readinessProbe.timeoutSeconds": 1,
|
|
104
|
+
"spec.template.spec.containers[].readinessProbe.httpGet.scheme": "HTTP",
|
|
105
|
+
"spec.template.spec.containers[].ports[].protocol": "TCP",
|
|
106
|
+
"spec.template.spec.containers[].terminationMessagePath": "/dev/termination-log",
|
|
107
|
+
"spec.template.spec.containers[].terminationMessagePolicy": "File",
|
|
108
|
+
"spec.template.spec.securityContext": {},
|
|
89
109
|
},
|
|
90
110
|
"K8s::Core::Service": {
|
|
91
111
|
"spec.sessionAffinity": "None",
|
|
92
112
|
"spec.type": "ClusterIP",
|
|
113
|
+
"spec.internalTrafficPolicy": "Cluster",
|
|
114
|
+
"spec.ipFamilyPolicy": "SingleStack",
|
|
115
|
+
"spec.ports[].protocol": "TCP",
|
|
93
116
|
},
|
|
94
117
|
};
|
|
95
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Server-ASSIGNED fields — populated by the API server with a value that
|
|
121
|
+
* varies per object (an allocated ClusterIP, the resolved IP family list), so
|
|
122
|
+
* unlike {@link K8S_SERVICE_DEFAULTS} there is no fixed value to compare
|
|
123
|
+
* against. Counterpart-gated like the defaults table: a declared `clusterIP`
|
|
124
|
+
* is still compared, only the purely server-filled appearance is noise.
|
|
125
|
+
*/
|
|
126
|
+
export const K8S_SERVER_ASSIGNED_PATTERNS: Record<string, ReadonlySet<string>> = {
|
|
127
|
+
"K8s::Core::Service": new Set(["spec.clusterIP", "spec.clusterIPs", "spec.ipFamilies"]),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* chant's own ownership marker, as label paths (#1214 — the k8s edition of
|
|
132
|
+
* azure's #1213 rule). The build stamps `app.kubernetes.io/managed-by` +
|
|
133
|
+
* `chant.intentius.io/{stack,env}` into every manifest it emits, so a managed
|
|
134
|
+
* object always carries them live while the declared *source* the diff
|
|
135
|
+
* compares does not — chant reading its own signature back as drift. The
|
|
136
|
+
* managed-fields prune cannot subtract them: they are in the applied config,
|
|
137
|
+
* so chant's field manager owns them, and chant-owned is always diffable.
|
|
138
|
+
* Counterpart-gated at the call site; managed-by is additionally gated on the
|
|
139
|
+
* value `"chant"`, so `managed-by: helm` appearing out of band still surfaces.
|
|
140
|
+
*/
|
|
141
|
+
const K8S_OWNERSHIP_LABEL_PATTERNS: ReadonlySet<string> = new Set([
|
|
142
|
+
`metadata.labels.${LABEL_OWNERSHIP_KEYS.stack}`,
|
|
143
|
+
`metadata.labels.${LABEL_OWNERSHIP_KEYS.env}`,
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const K8S_MANAGED_BY_LABEL_PATTERN = `metadata.labels.${LABEL_OWNERSHIP_KEYS.managedBy}`;
|
|
147
|
+
|
|
96
148
|
/** Stable JSON with sorted keys — the fallback ordering key for a set-like array without a natural identity field. */
|
|
97
149
|
function canonicalJson(value: unknown): string {
|
|
98
150
|
return (
|
|
@@ -206,6 +258,14 @@ export const k8sDeepNormalizationHooks: DeepNormalizationHooks = {
|
|
|
206
258
|
if (K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS.has(node.pattern)) return true;
|
|
207
259
|
|
|
208
260
|
if (node.side !== "live" || node.counterpart !== "absent") return false;
|
|
261
|
+
|
|
262
|
+
// chant's own ownership marker is not drift (see the sets' docs).
|
|
263
|
+
if (K8S_OWNERSHIP_LABEL_PATTERNS.has(node.pattern)) return true;
|
|
264
|
+
if (node.pattern === K8S_MANAGED_BY_LABEL_PATTERN && node.value === OWNERSHIP_MANAGED_BY_VALUE) return true;
|
|
265
|
+
|
|
266
|
+
// A server-assigned value nobody declared is allocation, not drift.
|
|
267
|
+
if (K8S_SERVER_ASSIGNED_PATTERNS[node.entityType]?.has(node.pattern)) return true;
|
|
268
|
+
|
|
209
269
|
const defaults = K8S_SERVICE_DEFAULTS[node.entityType];
|
|
210
270
|
if (!defaults || !Object.prototype.hasOwnProperty.call(defaults, node.pattern)) return false;
|
|
211
271
|
return canonicalJson(defaults[node.pattern]) === canonicalJson(node.value);
|
package/src/deep-observe.test.ts
CHANGED
|
@@ -96,6 +96,120 @@ describe("k8sDeepNormalizationHooks — the static rules", () => {
|
|
|
96
96
|
expect(declaredExplicit).toEqual({ spec: { strategy: strategyDefault, replicas: 3 } });
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("chant's own stamped labels are not drift; a foreign managed-by and a user label still are", () => {
|
|
100
|
+
const live = {
|
|
101
|
+
metadata: {
|
|
102
|
+
name: "web",
|
|
103
|
+
labels: {
|
|
104
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
105
|
+
"chant.intentius.io/stack": "cc-azure-canonical",
|
|
106
|
+
"chant.intentius.io/env": "local",
|
|
107
|
+
team: "platform",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const out = normalizeDeepProperties(live, {
|
|
112
|
+
entityType: "K8s::Apps::Deployment",
|
|
113
|
+
side: "live",
|
|
114
|
+
hooks: k8sDeepNormalizationHooks,
|
|
115
|
+
counterpartPaths: new Set(["metadata", "metadata.name"]),
|
|
116
|
+
});
|
|
117
|
+
expect(out).toEqual({ metadata: { name: "web", labels: { team: "platform" } } });
|
|
118
|
+
|
|
119
|
+
const foreign = normalizeDeepProperties(
|
|
120
|
+
{ metadata: { name: "web", labels: { "app.kubernetes.io/managed-by": "helm" } } },
|
|
121
|
+
{
|
|
122
|
+
entityType: "K8s::Apps::Deployment",
|
|
123
|
+
side: "live",
|
|
124
|
+
hooks: k8sDeepNormalizationHooks,
|
|
125
|
+
counterpartPaths: new Set(["metadata", "metadata.name"]),
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
expect(foreign).toEqual({ metadata: { name: "web", labels: { "app.kubernetes.io/managed-by": "helm" } } });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a server-assigned ClusterIP nobody declared subtracts; a declared one is compared", () => {
|
|
132
|
+
const undeclared = normalizeDeepProperties(
|
|
133
|
+
{ spec: { clusterIP: "10.43.201.161", clusterIPs: ["10.43.201.161"], ipFamilies: ["IPv4"], selector: { app: "web" } } },
|
|
134
|
+
{
|
|
135
|
+
entityType: "K8s::Core::Service",
|
|
136
|
+
side: "live",
|
|
137
|
+
hooks: k8sDeepNormalizationHooks,
|
|
138
|
+
counterpartPaths: new Set(["spec", "spec.selector", "spec.selector.app"]),
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
expect(undeclared).toEqual({ spec: { selector: { app: "web" } } });
|
|
142
|
+
|
|
143
|
+
const declared = normalizeDeepProperties(
|
|
144
|
+
{ spec: { clusterIP: "10.43.0.10" } },
|
|
145
|
+
{
|
|
146
|
+
entityType: "K8s::Core::Service",
|
|
147
|
+
side: "live",
|
|
148
|
+
hooks: k8sDeepNormalizationHooks,
|
|
149
|
+
counterpartPaths: new Set(["spec", "spec.clusterIP"]),
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
expect(declared).toEqual({ spec: { clusterIP: "10.43.0.10" } });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("probe and termination defaults subtract inside a declared container", () => {
|
|
156
|
+
const out = normalizeDeepProperties(
|
|
157
|
+
{
|
|
158
|
+
spec: {
|
|
159
|
+
template: {
|
|
160
|
+
spec: {
|
|
161
|
+
securityContext: {},
|
|
162
|
+
containers: [
|
|
163
|
+
{
|
|
164
|
+
name: "api",
|
|
165
|
+
image: "example:1",
|
|
166
|
+
terminationMessagePath: "/dev/termination-log",
|
|
167
|
+
terminationMessagePolicy: "File",
|
|
168
|
+
livenessProbe: { httpGet: { path: "/", port: 8080, scheme: "HTTP" }, failureThreshold: 3, periodSeconds: 10, successThreshold: 1, timeoutSeconds: 1 },
|
|
169
|
+
ports: [{ containerPort: 8080, name: "http", protocol: "TCP" }],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
entityType: "K8s::Apps::Deployment",
|
|
178
|
+
side: "live",
|
|
179
|
+
hooks: k8sDeepNormalizationHooks,
|
|
180
|
+
counterpartPaths: new Set([
|
|
181
|
+
"spec", "spec.template", "spec.template.spec",
|
|
182
|
+
"spec.template.spec.containers", "spec.template.spec.containers[]",
|
|
183
|
+
"spec.template.spec.containers[].name", "spec.template.spec.containers[].image",
|
|
184
|
+
"spec.template.spec.containers[].livenessProbe",
|
|
185
|
+
"spec.template.spec.containers[].livenessProbe.httpGet",
|
|
186
|
+
"spec.template.spec.containers[].livenessProbe.httpGet.path",
|
|
187
|
+
"spec.template.spec.containers[].livenessProbe.httpGet.port",
|
|
188
|
+
"spec.template.spec.containers[].ports",
|
|
189
|
+
"spec.template.spec.containers[].ports[]",
|
|
190
|
+
"spec.template.spec.containers[].ports[].containerPort",
|
|
191
|
+
"spec.template.spec.containers[].ports[].name",
|
|
192
|
+
]),
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
expect(out).toEqual({
|
|
196
|
+
spec: {
|
|
197
|
+
template: {
|
|
198
|
+
spec: {
|
|
199
|
+
containers: [
|
|
200
|
+
{
|
|
201
|
+
name: "api",
|
|
202
|
+
image: "example:1",
|
|
203
|
+
livenessProbe: { httpGet: { path: "/", port: 8080 } },
|
|
204
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
99
213
|
test("a one-sided pass never subtracts defaults — the reader has no declared tree yet", () => {
|
|
100
214
|
const out = normalizeDeepProperties(
|
|
101
215
|
{ spec: { strategy: { type: "RollingUpdate", rollingUpdate: { maxSurge: "25%", maxUnavailable: "25%" } } } },
|
|
@@ -465,6 +465,44 @@ describe("k8s describeResources", () => {
|
|
|
465
465
|
warnSpy.mockRestore();
|
|
466
466
|
});
|
|
467
467
|
|
|
468
|
+
test("a failed read names the bound context it read (#1488)", async () => {
|
|
469
|
+
loadChantConfigMock.mockResolvedValue({ config: { k8s: { profiles: { prod: { context: "prod-eks" } } } } });
|
|
470
|
+
const cluster = fakeCluster({
|
|
471
|
+
kubeconfig: twoContexts,
|
|
472
|
+
respond: (req) =>
|
|
473
|
+
req.path.endsWith("/deployments/web")
|
|
474
|
+
? { status: 500, body: statusBody(500, "InternalError", "etcdserver: leader changed") }
|
|
475
|
+
: undefined,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const result = await describeResources(
|
|
479
|
+
{ environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() },
|
|
480
|
+
(o) => defaultK8sConnector({ ...o, client: { kubeconfig: twoContexts, requestLayer: cluster.layer } }),
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
expect(result.unobserved?.web?.reason).toBe("read-failed");
|
|
484
|
+
expect(result.unobserved?.web?.detail).toContain('context "prod-eks" (bound by k8s.profiles.prod.context)');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("an ambient read failure names the context that was read and the missing binding (#1488)", async () => {
|
|
488
|
+
loadChantConfigMock.mockResolvedValue({ config: {} });
|
|
489
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
490
|
+
const cluster = fakeCluster({
|
|
491
|
+
kubeconfig: twoContexts,
|
|
492
|
+
respond: (req) =>
|
|
493
|
+
req.path.endsWith("/deployments/web") ? { status: 500, body: statusBody(500, "InternalError", "nope") } : undefined,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
const result = await describeResources(
|
|
497
|
+
{ environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() },
|
|
498
|
+
(o) => defaultK8sConnector({ ...o, client: { kubeconfig: twoContexts, requestLayer: cluster.layer } }),
|
|
499
|
+
);
|
|
500
|
+
warnSpy.mockRestore();
|
|
501
|
+
|
|
502
|
+
expect(result.unobserved?.web?.reason).toBe("read-failed");
|
|
503
|
+
expect(result.unobserved?.web?.detail).toContain('context "prod-eks" (ambient; no k8s.profiles.prod binding)');
|
|
504
|
+
});
|
|
505
|
+
|
|
468
506
|
test("a bound context the kubeconfig does not have refuses with the context name", async () => {
|
|
469
507
|
loadChantConfigMock.mockResolvedValue({ config: { k8s: { profiles: { prod: { context: "gone-eks" } } } } });
|
|
470
508
|
const oneContext = fakeKubeconfig({ contexts: [{ name: "prod-eks" }], currentContext: "prod-eks" });
|
|
@@ -573,6 +611,161 @@ describe("k8s describeResources", () => {
|
|
|
573
611
|
expect(result.resources["prod/hand-made"]).toBeUndefined();
|
|
574
612
|
});
|
|
575
613
|
|
|
614
|
+
// The issue's own estate: a declared MicroVMReplicaSet whose operator
|
|
615
|
+
// made MicroVMs — a CRD kind no table anticipated. Discovery names it.
|
|
616
|
+
test("a controller-made child of a CRD kind surfaces as a runtime child of the declared CR", async () => {
|
|
617
|
+
const replicaSet = {
|
|
618
|
+
apiVersion: "lambda.aws.amazon.com/v1alpha1",
|
|
619
|
+
kind: "MicroVMReplicaSet",
|
|
620
|
+
metadata: { name: "kmv-dev-a-vm", namespace: "microvm-demo", uid: "mvrs-uid" },
|
|
621
|
+
};
|
|
622
|
+
const vm = {
|
|
623
|
+
apiVersion: "lambda.aws.amazon.com/v1alpha1",
|
|
624
|
+
kind: "MicroVM",
|
|
625
|
+
metadata: {
|
|
626
|
+
name: "kmv-dev-a-vm-42sgf",
|
|
627
|
+
namespace: "microvm-demo",
|
|
628
|
+
uid: "vm-uid",
|
|
629
|
+
ownerReferences: [
|
|
630
|
+
{ apiVersion: "lambda.aws.amazon.com/v1alpha1", kind: "MicroVMReplicaSet", name: "kmv-dev-a-vm", uid: "mvrs-uid", controller: true },
|
|
631
|
+
],
|
|
632
|
+
},
|
|
633
|
+
status: { phase: "Running" },
|
|
634
|
+
};
|
|
635
|
+
const cluster = fakeCluster({
|
|
636
|
+
objects: {
|
|
637
|
+
[objectKey("lambda.aws.amazon.com/v1alpha1", "MicroVMReplicaSet", "kmv-dev-a-vm", "microvm-demo")]: replicaSet,
|
|
638
|
+
[objectKey("lambda.aws.amazon.com/v1alpha1", "MicroVM", "kmv-dev-a-vm-42sgf", "microvm-demo")]: vm,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const result = await describeResources(
|
|
643
|
+
{
|
|
644
|
+
environment: "dev",
|
|
645
|
+
buildOutput: "",
|
|
646
|
+
entityNames: ["workloadReplicaSet"],
|
|
647
|
+
entities: makeEntities([
|
|
648
|
+
{
|
|
649
|
+
name: "workloadReplicaSet",
|
|
650
|
+
entityType: "K8s::KubeMicroVM::MicroVMReplicaSet",
|
|
651
|
+
props: { metadata: { name: "kmv-dev-a-vm", namespace: "microvm-demo" } },
|
|
652
|
+
},
|
|
653
|
+
]),
|
|
654
|
+
},
|
|
655
|
+
cluster.connector,
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
expect(result.resources["microvm-demo/kmv-dev-a-vm-42sgf"]).toMatchObject({
|
|
659
|
+
type: "K8s::Lambda::MicroVM",
|
|
660
|
+
physicalId: "vm-uid",
|
|
661
|
+
ownerChain: { root: "declared", entity: "workloadReplicaSet" },
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("a child in a group the estate never declares surfaces too — discovery names the kinds, not the declaration", async () => {
|
|
666
|
+
// A declared CR whose operator makes ordinary Deployments: ray.io is
|
|
667
|
+
// declared, apps/v1 is not, and the estate-groups sweep never listed it.
|
|
668
|
+
const rayCluster = { apiVersion: "ray.io/v1", kind: "RayCluster", metadata: { name: "ml", namespace: "ray", uid: "ray-uid" } };
|
|
669
|
+
const head = {
|
|
670
|
+
apiVersion: "apps/v1",
|
|
671
|
+
kind: "Deployment",
|
|
672
|
+
metadata: {
|
|
673
|
+
name: "ml-head",
|
|
674
|
+
namespace: "ray",
|
|
675
|
+
uid: "head-uid",
|
|
676
|
+
ownerReferences: [{ apiVersion: "ray.io/v1", kind: "RayCluster", name: "ml", uid: "ray-uid", controller: true }],
|
|
677
|
+
},
|
|
678
|
+
status: { readyReplicas: 1, replicas: 1 },
|
|
679
|
+
};
|
|
680
|
+
const cluster = fakeCluster({
|
|
681
|
+
objects: {
|
|
682
|
+
[objectKey("ray.io/v1", "RayCluster", "ml", "ray")]: rayCluster,
|
|
683
|
+
[objectKey("apps/v1", "Deployment", "ml-head", "ray")]: head,
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const result = await describeResources(
|
|
688
|
+
{
|
|
689
|
+
environment: "prod",
|
|
690
|
+
buildOutput: "",
|
|
691
|
+
entityNames: ["ml"],
|
|
692
|
+
entities: makeEntities([
|
|
693
|
+
{ name: "ml", entityType: "K8s::Ray::RayCluster", props: { metadata: { name: "ml", namespace: "ray" } } },
|
|
694
|
+
]),
|
|
695
|
+
},
|
|
696
|
+
cluster.connector,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
expect(result.resources["ray/ml-head"]).toMatchObject({
|
|
700
|
+
type: "K8s::Apps::Deployment",
|
|
701
|
+
physicalId: "head-uid",
|
|
702
|
+
ownerChain: { root: "declared", entity: "ml" },
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("a discovered kind the sweep cannot list is an unobserved hole, never silently absent", async () => {
|
|
707
|
+
const cluster = fakeCluster({
|
|
708
|
+
objects: { [objectKey("batch/v1", "CronJob", "backup", "prod")]: cronJob },
|
|
709
|
+
respond: (req) =>
|
|
710
|
+
req.path === "/apis/batch/v1/namespaces/prod/jobs"
|
|
711
|
+
? { status: 403, body: statusBody(403, "Forbidden", "jobs is forbidden") }
|
|
712
|
+
: undefined,
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
const result = await describeResources(
|
|
716
|
+
{ environment: "prod", buildOutput: "", entityNames: ["backup"], entities: declaredCronJob() },
|
|
717
|
+
cluster.connector,
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
// Declared resolution is never failed by the sweep.
|
|
721
|
+
expect(result.resources.backup).toMatchObject({ type: "K8s::Batch::CronJob", physicalId: "cj-uid" });
|
|
722
|
+
// The failed list is a hole the observation admits, with the failure's
|
|
723
|
+
// own classification — a 403 is a credentials problem, and silence here
|
|
724
|
+
// would have read as "no runtime children in prod".
|
|
725
|
+
expect(result.unobserved?.["runtime-sweep:batch/v1/Job"]).toMatchObject({
|
|
726
|
+
type: "K8s::Batch::Job",
|
|
727
|
+
reason: "no-credentials",
|
|
728
|
+
});
|
|
729
|
+
expect(result.unobserved?.["runtime-sweep:batch/v1/Job"]?.detail).toContain("namespace prod");
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("a cluster-scoped child chains to a declared cluster-scoped entity — the one case cluster-scoped kinds are swept", async () => {
|
|
733
|
+
const ns = { apiVersion: "v1", kind: "Namespace", metadata: { name: "team-a", uid: "ns-uid" }, status: { phase: "Active" } };
|
|
734
|
+
const role = {
|
|
735
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
736
|
+
kind: "ClusterRole",
|
|
737
|
+
metadata: {
|
|
738
|
+
name: "team-a-admin",
|
|
739
|
+
uid: "role-uid",
|
|
740
|
+
ownerReferences: [{ apiVersion: "v1", kind: "Namespace", name: "team-a", uid: "ns-uid", controller: true }],
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
const cluster = fakeCluster({
|
|
744
|
+
objects: {
|
|
745
|
+
[objectKey("v1", "Namespace", "team-a")]: ns,
|
|
746
|
+
[objectKey("rbac.authorization.k8s.io/v1", "ClusterRole", "team-a-admin")]: role,
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
const result = await describeResources(
|
|
751
|
+
{
|
|
752
|
+
environment: "prod",
|
|
753
|
+
buildOutput: "",
|
|
754
|
+
entityNames: ["team"],
|
|
755
|
+
entities: makeEntities([
|
|
756
|
+
{ name: "team", entityType: "K8s::Core::Namespace", props: { metadata: { name: "team-a" } } },
|
|
757
|
+
]),
|
|
758
|
+
},
|
|
759
|
+
cluster.connector,
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
expect(result.resources["cluster:team-a-admin"]).toMatchObject({
|
|
763
|
+
type: "K8s::Rbac::ClusterRole",
|
|
764
|
+
physicalId: "role-uid",
|
|
765
|
+
ownerChain: { root: "declared", entity: "team" },
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
|
|
576
769
|
test("a Flux-managed Deployment is attributed to its declared Kustomization by the controller's labels (#1549)", async () => {
|
|
577
770
|
// Flux stamps managed objects with name+namespace labels instead of
|
|
578
771
|
// ownerReferences, so the chain walk reaches nothing — the label
|
|
@@ -702,7 +895,7 @@ describe("k8s describeResources", () => {
|
|
|
702
895
|
expect(result.resources["flux-system/cert-manager"]).toBeUndefined();
|
|
703
896
|
});
|
|
704
897
|
|
|
705
|
-
test("
|
|
898
|
+
test("the sweep's bound is the estate's namespaces, not a kind list — and no cluster-scoped list without a cluster-scoped entity", async () => {
|
|
706
899
|
const svc = { apiVersion: "v1", kind: "Service", metadata: { name: "web", namespace: "prod", uid: "svc-uid" } };
|
|
707
900
|
const cluster = fakeCluster({
|
|
708
901
|
objects: { [objectKey("v1", "Service", "web", "prod")]: svc },
|
|
@@ -720,10 +913,20 @@ describe("k8s describeResources", () => {
|
|
|
720
913
|
cluster.connector,
|
|
721
914
|
);
|
|
722
915
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
916
|
+
const paths = cluster.layer.paths();
|
|
917
|
+
const isDiscovery = (p: string) =>
|
|
918
|
+
p === "/api" || p === "/apis" || /^\/api\/[^/]+$/.test(p) || /^\/apis\/[^/]+\/[^/]+$/.test(p);
|
|
919
|
+
const reads = paths.filter((p) => !isDiscovery(p));
|
|
920
|
+
// Discovery names the kinds; every actual read or list stays inside the
|
|
921
|
+
// one namespace the estate touches. Nothing is listed cluster-wide.
|
|
922
|
+
expect(reads.length).toBeGreaterThan(0);
|
|
923
|
+
expect(reads.every((p) => p.includes("/namespaces/prod/"))).toBe(true);
|
|
924
|
+
// Nothing declared is cluster-scoped, so no owner chain could reach a
|
|
925
|
+
// cluster-scoped object — their kinds are not listed at all.
|
|
926
|
+
expect(paths.some((p) => p.endsWith("/clusterroles"))).toBe(false);
|
|
927
|
+
// Events churn by the thousands and can never chain (`involvedObject`,
|
|
928
|
+
// not `ownerReferences`) — discovery serves them, the sweep skips them.
|
|
929
|
+
expect(paths.some((p) => p.endsWith("/events"))).toBe(false);
|
|
727
930
|
});
|
|
728
931
|
});
|
|
729
932
|
|
|
@@ -914,7 +1117,7 @@ describe("k8s describeResources", () => {
|
|
|
914
1117
|
expect(Object.keys(result.resources)).toHaveLength(2); // web + standalonePod, no synthetic duplicate
|
|
915
1118
|
});
|
|
916
1119
|
|
|
917
|
-
test("a Pod-list failure
|
|
1120
|
+
test("a Pod-list failure never fails declared resolution — and surfaces as an unobserved hole (#1517)", async () => {
|
|
918
1121
|
const cluster = fakeCluster({
|
|
919
1122
|
objects: { [objectKey("apps/v1", "Deployment", "web", "prod")]: web },
|
|
920
1123
|
respond: (req) => (req.path.endsWith("/pods") ? { status: 403, body: statusBody(403, "Forbidden", "no pods list") } : undefined),
|
|
@@ -927,6 +1130,12 @@ describe("k8s describeResources", () => {
|
|
|
927
1130
|
|
|
928
1131
|
expect(result.resources.web).toMatchObject({ type: "K8s::Apps::Deployment", physicalId: "uid-1" });
|
|
929
1132
|
expect(Object.keys(result.resources)).toEqual(["web"]);
|
|
1133
|
+
// Silence would read as "no runtime Pods in prod", which the failed
|
|
1134
|
+
// list never proved — the sweep admits the hole instead.
|
|
1135
|
+
expect(result.unobserved?.["runtime-sweep:v1/Pod"]).toMatchObject({
|
|
1136
|
+
type: "K8s::Core::Pod",
|
|
1137
|
+
reason: "no-credentials",
|
|
1138
|
+
});
|
|
930
1139
|
});
|
|
931
1140
|
|
|
932
1141
|
test("a lexicon-supported entity type with no namespaced entities resolved does no Pod scan at all", async () => {
|