@intentius/chant-lexicon-k8s 0.41.19 → 0.42.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/components/builders.d.ts +4 -0
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +1 -1
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/kustomize-apply.d.ts +48 -0
- package/dist/components/kustomize-apply.d.ts.map +1 -0
- 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.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +250 -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/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/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 +14 -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/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 +14 -0
- package/dist/okf/types/HTTPRoute.md +14 -0
- package/dist/okf/types/HelmChart.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/Kustomization.md +14 -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 +18 -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 +21 -0
- package/dist/okf/types/NodeList.md +14 -0
- package/dist/okf/types/OCIRepository.md +14 -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/op/activities/kubectl.d.ts +15 -1
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/wait-for-ready.d.ts +16 -6
- package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/builders.ts +5 -0
- package/src/components/capability-plugin.ts +3 -2
- package/src/components/index.ts +7 -0
- package/src/components/kustomize-apply.test.ts +98 -0
- package/src/components/kustomize-apply.ts +133 -0
- package/src/deep-observe-hooks.ts +60 -0
- package/src/deep-observe.test.ts +114 -0
- package/src/describe-resources.test.ts +159 -0
- package/src/describe-resources.ts +142 -2
- package/src/index.ts +1 -0
- package/src/op/activities/kubectl.test.ts +22 -0
- package/src/op/activities/kubectl.ts +15 -2
- package/src/op/activities/wait-for-ready.test.ts +27 -0
- package/src/op/activities/wait-for-ready.ts +84 -6
|
@@ -104,9 +104,54 @@ export function statusFromObject(obj: K8sObject): string {
|
|
|
104
104
|
? "READY"
|
|
105
105
|
: `PROGRESSING(${status.readyReplicas}/${status.replicas})`;
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
// The kstatus rung (#1549): an object with NO phase and NO replica counts —
|
|
109
|
+
// a Flux Kustomization/HelmRelease, cert-manager, any conditions-first CRD —
|
|
110
|
+
// used to fall straight to PRESENT, so a wedged reconciler read exactly like
|
|
111
|
+
// a healthy one. `Ready` is the contract those objects speak: False → its
|
|
112
|
+
// reason (`BuildFailed`, `UpgradeFailed`, …), True → READY. Deliberately
|
|
113
|
+
// BELOW the phase/replica rungs so no Pod or workload word changes — for
|
|
114
|
+
// those objects a False Ready is already visible through the rungs above.
|
|
115
|
+
const ready = readyConditionWord(status);
|
|
116
|
+
if (ready) return ready;
|
|
117
|
+
|
|
118
|
+
// Argo's Application speaks health/sync, not a Ready condition (the same
|
|
119
|
+
// split the readiness registry encodes) — `Degraded` / `OutOfSync` beats
|
|
120
|
+
// PRESENT for the one object whose whole job is convergence.
|
|
121
|
+
const argo = argoStatusWord(status);
|
|
122
|
+
if (argo) return argo;
|
|
123
|
+
|
|
107
124
|
return "PRESENT";
|
|
108
125
|
}
|
|
109
126
|
|
|
127
|
+
/** The `Ready` condition rendered as a status word: False → its reason (or
|
|
128
|
+
* NOT-READY when the controller gave none), True → READY, absent → nothing. */
|
|
129
|
+
function readyConditionWord(status: K8sObject["status"]): string | undefined {
|
|
130
|
+
const conditions = (status as { conditions?: unknown } | undefined)?.conditions;
|
|
131
|
+
if (!Array.isArray(conditions)) return undefined;
|
|
132
|
+
for (const c of conditions) {
|
|
133
|
+
const cond = c as { type?: unknown; status?: unknown; reason?: unknown };
|
|
134
|
+
if (cond.type !== "Ready") continue;
|
|
135
|
+
if (cond.status === "True") return "READY";
|
|
136
|
+
if (cond.status === "False") {
|
|
137
|
+
return typeof cond.reason === "string" && cond.reason.length > 0 ? cond.reason : "NOT-READY";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Argo Application health/sync as a status word: anything but the happy pair
|
|
144
|
+
* surfaces the unhappy half; Healthy+Synced reads as READY. */
|
|
145
|
+
function argoStatusWord(status: K8sObject["status"]): string | undefined {
|
|
146
|
+
const s = status as { health?: { status?: unknown }; sync?: { status?: unknown } } | undefined;
|
|
147
|
+
const health = typeof s?.health?.status === "string" ? s.health.status : undefined;
|
|
148
|
+
const sync = typeof s?.sync?.status === "string" ? s.sync.status : undefined;
|
|
149
|
+
if (!health && !sync) return undefined;
|
|
150
|
+
if (health && health !== "Healthy") return health;
|
|
151
|
+
if (sync && sync !== "Synced") return sync;
|
|
152
|
+
return "READY";
|
|
153
|
+
}
|
|
154
|
+
|
|
110
155
|
/**
|
|
111
156
|
* The reason a container is stuck, if one is — `CrashLoopBackOff`,
|
|
112
157
|
* `ImagePullBackOff`, `CreateContainerConfigError`.
|
|
@@ -333,7 +378,7 @@ export async function describeResources(
|
|
|
333
378
|
if (op && op.apiVersion.includes("/")) runtimeGroupVersions.add(op.apiVersion);
|
|
334
379
|
}
|
|
335
380
|
|
|
336
|
-
await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions);
|
|
381
|
+
await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions, declared);
|
|
337
382
|
|
|
338
383
|
return observation(resources, unobserved);
|
|
339
384
|
}
|
|
@@ -360,22 +405,99 @@ export async function describeResources(
|
|
|
360
405
|
* axis for them to report against, the same way an out-of-band AWS child
|
|
361
406
|
* resource has none either.
|
|
362
407
|
*/
|
|
408
|
+
/** The declared GitOps CRs, indexed the way their controllers' labels can be
|
|
409
|
+
* resolved (#1549). `byRef` keys are `type\0namespace\0name` (Flux — exact);
|
|
410
|
+
* `argoByName` maps an Application's name to its declared entity, or null
|
|
411
|
+
* when two declared Applications share the name (ambiguous → no claim). */
|
|
412
|
+
interface GitopsIndex {
|
|
413
|
+
present: boolean;
|
|
414
|
+
byRef: Map<string, string>;
|
|
415
|
+
argoByName: Map<string, string | null>;
|
|
416
|
+
targetNamespaces: Set<string>;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function gitopsIndex(declared: readonly Declared[]): GitopsIndex {
|
|
420
|
+
const byRef = new Map<string, string>();
|
|
421
|
+
const argoByName = new Map<string, string | null>();
|
|
422
|
+
const targetNamespaces = new Set<string>();
|
|
423
|
+
let present = false;
|
|
424
|
+
for (const { entityName, entityType, props } of declared) {
|
|
425
|
+
const isArgo = entityType === "K8s::Argo::Application";
|
|
426
|
+
const isFlux = entityType === "K8s::Flux::Kustomization" || entityType === "K8s::Flux::HelmRelease";
|
|
427
|
+
if (!isArgo && !isFlux) continue;
|
|
428
|
+
present = true;
|
|
429
|
+
const meta = props.metadata as { name?: string; namespace?: string } | undefined;
|
|
430
|
+
const spec = props.spec as { targetNamespace?: unknown; destination?: { namespace?: unknown } } | undefined;
|
|
431
|
+
const name = meta?.name;
|
|
432
|
+
if (!name) continue;
|
|
433
|
+
if (isFlux) {
|
|
434
|
+
byRef.set(`${entityType}\0${meta?.namespace ?? "default"}\0${name}`, entityName);
|
|
435
|
+
if (typeof spec?.targetNamespace === "string") targetNamespaces.add(spec.targetNamespace);
|
|
436
|
+
} else {
|
|
437
|
+
argoByName.set(name, argoByName.has(name) ? null : entityName);
|
|
438
|
+
if (typeof spec?.destination?.namespace === "string") targetNamespaces.add(spec.destination.namespace);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return { present, byRef, argoByName, targetNamespaces };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** The declared GitOps CR a swept object's controller labels resolve to, or
|
|
445
|
+
* undefined. Flux's label pairs are exact; Argo's bare instance label claims
|
|
446
|
+
* only a UNIQUE declared Application. */
|
|
447
|
+
function gitopsOwner(labels: Record<string, string> | undefined, gitops: GitopsIndex): string | undefined {
|
|
448
|
+
if (!labels || !gitops.present) return undefined;
|
|
449
|
+
const ksName = labels["kustomize.toolkit.fluxcd.io/name"];
|
|
450
|
+
const ksNs = labels["kustomize.toolkit.fluxcd.io/namespace"];
|
|
451
|
+
if (ksName && ksNs) {
|
|
452
|
+
const hit = gitops.byRef.get(`K8s::Flux::Kustomization\0${ksNs}\0${ksName}`);
|
|
453
|
+
if (hit) return hit;
|
|
454
|
+
}
|
|
455
|
+
const hrName = labels["helm.toolkit.fluxcd.io/name"];
|
|
456
|
+
const hrNs = labels["helm.toolkit.fluxcd.io/namespace"];
|
|
457
|
+
if (hrName && hrNs) {
|
|
458
|
+
const hit = gitops.byRef.get(`K8s::Flux::HelmRelease\0${hrNs}\0${hrName}`);
|
|
459
|
+
if (hit) return hit;
|
|
460
|
+
}
|
|
461
|
+
const instance = labels["app.kubernetes.io/instance"];
|
|
462
|
+
if (instance) {
|
|
463
|
+
const hit = gitops.argoByName.get(instance);
|
|
464
|
+
if (hit) return hit; // null (ambiguous) and undefined both decline
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
363
469
|
async function addRuntimeChildren(
|
|
364
470
|
client: K8sClient,
|
|
365
471
|
resources: Record<string, ResourceMetadata>,
|
|
366
472
|
owned: boolean | undefined,
|
|
367
473
|
groupVersions: ReadonlySet<string> = new Set(),
|
|
474
|
+
declared: readonly Declared[] = [],
|
|
368
475
|
): Promise<void> {
|
|
369
476
|
const declaredByUid = new Map<string, string>();
|
|
370
477
|
for (const [entityName, meta] of Object.entries(resources)) {
|
|
371
478
|
if (meta.physicalId) declaredByUid.set(meta.physicalId, entityName);
|
|
372
479
|
}
|
|
373
480
|
|
|
481
|
+
// GitOps attribution (#1549): Argo and Flux stamp their managed objects with
|
|
482
|
+
// labels, not ownerReferences, so the chain walk never reaches a declared
|
|
483
|
+
// entity for them. The label channel resolves against the DECLARED CRs by
|
|
484
|
+
// (type, namespace, name) — exact for Flux (its labels carry both halves) —
|
|
485
|
+
// and by unique name for Argo (its instance label carries no namespace, and
|
|
486
|
+
// it doubles as a generic Helm label, so anything ambiguous resolves to
|
|
487
|
+
// nothing: the module's standing conservatism).
|
|
488
|
+
const gitops = gitopsIndex(declared);
|
|
489
|
+
|
|
374
490
|
const namespaces = new Set<string>();
|
|
375
491
|
for (const meta of Object.values(resources)) {
|
|
376
492
|
const namespace = (meta.attributes as { namespace?: string } | undefined)?.namespace;
|
|
377
493
|
if (namespace) namespaces.add(namespace);
|
|
378
494
|
}
|
|
495
|
+
// The namespaces a declared GitOps CR TARGETS are declared literals too
|
|
496
|
+
// (Argo `spec.destination.namespace`, Flux `spec.targetNamespace`) — the
|
|
497
|
+
// controller puts the workloads there, so the scan must look there, and
|
|
498
|
+
// reading it off the declaration keeps this bounded by the estate, never a
|
|
499
|
+
// cluster-wide sweep.
|
|
500
|
+
for (const ns of gitops.targetNamespaces) namespaces.add(ns);
|
|
379
501
|
if (namespaces.size === 0) return;
|
|
380
502
|
|
|
381
503
|
// The kinds to scan: Pods, plus each declared groupVersion's namespaced,
|
|
@@ -384,6 +506,18 @@ async function addRuntimeChildren(
|
|
|
384
506
|
const kinds: Array<{ apiVersion: string; kind: string; typeName: string }> = [
|
|
385
507
|
{ apiVersion: "v1", kind: "Pod", typeName: "K8s::Core::Pod" },
|
|
386
508
|
];
|
|
509
|
+
// A GitOps estate's managed WORKLOADS live in groups the estate itself never
|
|
510
|
+
// declares (a pure Argo/Flux estate declares only the CRs), so without this
|
|
511
|
+
// the label channel could only ever fire on Pods (#1549).
|
|
512
|
+
if (gitops.present) {
|
|
513
|
+
kinds.push(
|
|
514
|
+
{ apiVersion: "apps/v1", kind: "Deployment", typeName: "K8s::Apps::Deployment" },
|
|
515
|
+
{ apiVersion: "apps/v1", kind: "StatefulSet", typeName: "K8s::Apps::StatefulSet" },
|
|
516
|
+
{ apiVersion: "apps/v1", kind: "DaemonSet", typeName: "K8s::Apps::DaemonSet" },
|
|
517
|
+
{ apiVersion: "v1", kind: "Service", typeName: "K8s::Core::Service" },
|
|
518
|
+
{ apiVersion: "v1", kind: "ConfigMap", typeName: "K8s::Core::ConfigMap" },
|
|
519
|
+
);
|
|
520
|
+
}
|
|
387
521
|
for (const gv of [...groupVersions].sort()) {
|
|
388
522
|
let infos;
|
|
389
523
|
try {
|
|
@@ -413,7 +547,13 @@ async function addRuntimeChildren(
|
|
|
413
547
|
if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
|
|
414
548
|
if (resources[`${namespace}/${name}`]) return; // already reported by an earlier kind
|
|
415
549
|
|
|
416
|
-
|
|
550
|
+
let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
|
|
551
|
+
// The label channel (#1549) — only when the chain itself said nothing:
|
|
552
|
+
// a verdict the walk DID reach is never overridden.
|
|
553
|
+
if (ownerChain.root !== "declared") {
|
|
554
|
+
const entity = gitopsOwner(obj.metadata?.labels, gitops);
|
|
555
|
+
if (entity) ownerChain = { root: "declared", entity };
|
|
556
|
+
}
|
|
417
557
|
|
|
418
558
|
// `--owned`: withhold an object that is neither a runtime child of a
|
|
419
559
|
// declared entity nor carrying chant's own marker — the same rule the
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { k8sPlugin } from "./plugin";
|
|
|
9
9
|
// cfn-deploy.
|
|
10
10
|
export { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./components/capability-plugin";
|
|
11
11
|
export { kubectlApplyCapability, createKubectlApplyCapability, type KubectlApplyInput } from "./components/kubectl-apply";
|
|
12
|
+
export { kustomizeApplyCapability, createKustomizeApplyCapability, type KustomizeApplyInput } from "./components/kustomize-apply";
|
|
12
13
|
|
|
13
14
|
// Default labels & annotations
|
|
14
15
|
export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotations } from "./default-labels";
|
|
@@ -281,6 +281,28 @@ describe("kubectlApply", () => {
|
|
|
281
281
|
expect(body.metadata?.labels).toBeUndefined();
|
|
282
282
|
});
|
|
283
283
|
|
|
284
|
+
test("inline documents bypass the filesystem — the kustomize-apply seam (#1548)", async () => {
|
|
285
|
+
const cluster = fakeCluster({ respond: echoApplies });
|
|
286
|
+
const result = await applyManifest(
|
|
287
|
+
{
|
|
288
|
+
manifest: "kustomize:overlays/dev", // a label; nothing at this path
|
|
289
|
+
documents: [
|
|
290
|
+
{ apiVersion: "v1", kind: "Namespace", metadata: { name: "web" } },
|
|
291
|
+
{ apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "app", namespace: "web" } },
|
|
292
|
+
],
|
|
293
|
+
stack: "web",
|
|
294
|
+
},
|
|
295
|
+
undefined,
|
|
296
|
+
cluster.connector,
|
|
297
|
+
);
|
|
298
|
+
expect(result.applied.map((r) => r.kind)).toEqual(["Namespace", "Deployment"]);
|
|
299
|
+
// The documents were stamped like any read-from-disk manifest.
|
|
300
|
+
const body = JSON.parse(String(cluster.layer.requests.find((r) => r.method === "PATCH")!.body)) as {
|
|
301
|
+
metadata?: { labels?: Record<string, string> };
|
|
302
|
+
};
|
|
303
|
+
expect(body.metadata?.labels?.["chant.intentius.io/stack"]).toBe("web");
|
|
304
|
+
});
|
|
305
|
+
|
|
284
306
|
test("an explicit context is honored and skips the environment lookup entirely", async () => {
|
|
285
307
|
const file = join(dir, "k8s.yaml");
|
|
286
308
|
writeFileSync(file, deploymentYaml);
|
|
@@ -55,8 +55,21 @@ import { DEFAULT_IMPORT_TYPES } from "../../api/sweep-types";
|
|
|
55
55
|
export type ApplyDeleteMode = "never" | "owned-only" | "gated";
|
|
56
56
|
|
|
57
57
|
export interface KubectlApplyArgs {
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Path to a manifest file, or a directory of them. With `documents` given,
|
|
60
|
+
* this becomes only the human-facing label the heartbeats and logs carry
|
|
61
|
+
* (e.g. `kustomize:<dir>`), and nothing is read from disk.
|
|
62
|
+
*/
|
|
59
63
|
manifest: string;
|
|
64
|
+
/**
|
|
65
|
+
* Already-parsed documents to apply INSTEAD of reading `manifest` (#1548):
|
|
66
|
+
* a renderer that produced the objects in memory — kustomize-apply's
|
|
67
|
+
* `kustomize build` — hands them straight in rather than round-tripping
|
|
68
|
+
* through a temp file. Everything downstream (ownership stamping, the
|
|
69
|
+
* marker-scoped prune, the self-conflict retake) is document-driven and
|
|
70
|
+
* behaves identically.
|
|
71
|
+
*/
|
|
72
|
+
documents?: K8sObject[];
|
|
60
73
|
/**
|
|
61
74
|
* kubectl context name. Uses the ambient context if omitted. To target the
|
|
62
75
|
* same cluster the read path (`describeResources`) resolved for an
|
|
@@ -285,7 +298,7 @@ export async function applyManifest(
|
|
|
285
298
|
signal?: AbortSignal,
|
|
286
299
|
connect: K8sConnector = defaultK8sConnector,
|
|
287
300
|
): Promise<ApplyManifestResult> {
|
|
288
|
-
const documents = readManifestDocuments(args.manifest);
|
|
301
|
+
const documents = args.documents ?? readManifestDocuments(args.manifest);
|
|
289
302
|
const { fieldManager, stack } = await resolveApplyIdentity(args);
|
|
290
303
|
const heartbeatInterval = setInterval(() => {
|
|
291
304
|
safeHeartbeat({ step: "kubectl apply", manifest: args.manifest });
|
|
@@ -49,6 +49,33 @@ describe("readiness model", () => {
|
|
|
49
49
|
expect(isReady(obj, DEFAULT_READINESS)).toBe(true);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
test("Flux overrides fail fast on Ready=False wedge reasons, wait on ordinary progress (#1549)", () => {
|
|
53
|
+
// A Flux wedge (bad build, failed upgrade, unreachable source) keeps
|
|
54
|
+
// Ready=False forever — before these entries the generic poll waited out
|
|
55
|
+
// its whole timeout. The ready half is still the kstatus default.
|
|
56
|
+
const spec = readinessFor("kustomize.toolkit.fluxcd.io", "Kustomization");
|
|
57
|
+
expect(spec).not.toBe(DEFAULT_READINESS);
|
|
58
|
+
expect(isReady(ready([{ type: "Ready", status: "True" }]), spec)).toBe(true);
|
|
59
|
+
|
|
60
|
+
const wedged = ready([{ type: "Ready", status: "False", reason: "BuildFailed" } as never]);
|
|
61
|
+
expect(isReady(wedged, spec)).toBe(false);
|
|
62
|
+
expect(firstTerminal(wedged, spec)).toBeDefined();
|
|
63
|
+
|
|
64
|
+
// Ready=False with a PROGRESS reason (a reconcile in flight) is not a
|
|
65
|
+
// wedge — keep polling.
|
|
66
|
+
const progressing = ready([{ type: "Ready", status: "False", reason: "Progressing" } as never]);
|
|
67
|
+
expect(firstTerminal(progressing, spec)).toBeUndefined();
|
|
68
|
+
|
|
69
|
+
// A reason on a TRUE condition is vocabulary, never terminal.
|
|
70
|
+
const healthyWithReason = ready([{ type: "Ready", status: "True", reason: "ReconciliationSucceeded" } as never]);
|
|
71
|
+
expect(firstTerminal(healthyWithReason, spec)).toBeUndefined();
|
|
72
|
+
|
|
73
|
+
const hr = readinessFor("helm.toolkit.fluxcd.io", "HelmRelease");
|
|
74
|
+
expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "UpgradeFailed" } as never]), hr)).toBeDefined();
|
|
75
|
+
const git = readinessFor("source.toolkit.fluxcd.io", "GitRepository");
|
|
76
|
+
expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "AuthenticationFailed" } as never]), git)).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
52
79
|
test("Argo override uses health/sync, not a Ready condition", () => {
|
|
53
80
|
const spec = readinessFor("argoproj.io", "Application");
|
|
54
81
|
expect(spec).not.toBe(DEFAULT_READINESS);
|
|
@@ -40,7 +40,23 @@ export interface PathMatch {
|
|
|
40
40
|
// With neither `equals` nor `oneOf`, holds when the value is present (non-null).
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Match a `status.conditions[]` entry by `type` AND its `reason` (#1549).
|
|
45
|
+
* The dot-path walk can't index into a conditions array, and the Flux
|
|
46
|
+
* toolkit's failure signal is exactly `conditions[type=Ready].reason` — a
|
|
47
|
+
* wedge like `BuildFailed` keeps `Ready=False` forever while the generic
|
|
48
|
+
* poll waits out its whole timeout. `status` defaults to "False": a reason
|
|
49
|
+
* on a True condition is progress vocabulary, not a wedge.
|
|
50
|
+
*/
|
|
51
|
+
export interface ConditionReasonMatch {
|
|
52
|
+
conditionType: string;
|
|
53
|
+
/** Required `status` of the condition (default "False"). */
|
|
54
|
+
status?: string;
|
|
55
|
+
/** Holds when the condition's `reason` is one of these. */
|
|
56
|
+
reasonOneOf: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ReadinessMatch = ConditionMatch | PathMatch | ConditionReasonMatch;
|
|
44
60
|
|
|
45
61
|
export interface ReadinessSpec {
|
|
46
62
|
/** All must hold for the resource to be ready. */
|
|
@@ -69,6 +85,19 @@ export const DEFAULT_READINESS: ReadinessSpec = {
|
|
|
69
85
|
* reports `health`/`sync`, not a `Ready` condition — the case that proves the
|
|
70
86
|
* override is necessary (#365).
|
|
71
87
|
*/
|
|
88
|
+
/**
|
|
89
|
+
* Flux toolkit wedge reasons (#1549): a `Ready=False` with one of these
|
|
90
|
+
* `reason`s does not heal by waiting — a bad build, a failed install, an
|
|
91
|
+
* unreachable or invalid source. The toolkit is kstatus-conformant, so the
|
|
92
|
+
* READY half of every entry below is just the default; the value added is
|
|
93
|
+
* failing fast instead of polling out the full timeout.
|
|
94
|
+
*/
|
|
95
|
+
const FLUX_TERMINAL = (reasons: string[]): ReadinessSpec => ({
|
|
96
|
+
ready: [{ conditionType: "Ready", status: "True" }],
|
|
97
|
+
terminal: [{ conditionType: "Ready", reasonOneOf: reasons }],
|
|
98
|
+
observedGeneration: true,
|
|
99
|
+
});
|
|
100
|
+
|
|
72
101
|
export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
|
|
73
102
|
"argoproj.io/Application": {
|
|
74
103
|
ready: [
|
|
@@ -78,6 +107,38 @@ export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
|
|
|
78
107
|
terminal: [{ path: "status.health.status", oneOf: ["Degraded", "Missing"] }],
|
|
79
108
|
observedGeneration: false,
|
|
80
109
|
},
|
|
110
|
+
"kustomize.toolkit.fluxcd.io/Kustomization": FLUX_TERMINAL([
|
|
111
|
+
"BuildFailed",
|
|
112
|
+
"HealthCheckFailed",
|
|
113
|
+
"ReconciliationFailed",
|
|
114
|
+
"ArtifactFailed",
|
|
115
|
+
"PruneFailed",
|
|
116
|
+
]),
|
|
117
|
+
"helm.toolkit.fluxcd.io/HelmRelease": FLUX_TERMINAL([
|
|
118
|
+
"InstallFailed",
|
|
119
|
+
"UpgradeFailed",
|
|
120
|
+
"RollbackFailed",
|
|
121
|
+
"UninstallFailed",
|
|
122
|
+
"ArtifactFailed",
|
|
123
|
+
"ChartPullError",
|
|
124
|
+
]),
|
|
125
|
+
"source.toolkit.fluxcd.io/GitRepository": FLUX_TERMINAL([
|
|
126
|
+
"AuthenticationFailed",
|
|
127
|
+
"GitOperationFailed",
|
|
128
|
+
"URLInvalid",
|
|
129
|
+
"StorageOperationFailed",
|
|
130
|
+
]),
|
|
131
|
+
"source.toolkit.fluxcd.io/OCIRepository": FLUX_TERMINAL([
|
|
132
|
+
"AuthenticationFailed",
|
|
133
|
+
"OCIPullFailed",
|
|
134
|
+
"URLInvalid",
|
|
135
|
+
"StorageOperationFailed",
|
|
136
|
+
]),
|
|
137
|
+
"source.toolkit.fluxcd.io/HelmChart": FLUX_TERMINAL([
|
|
138
|
+
"ChartPullError",
|
|
139
|
+
"ChartPackageError",
|
|
140
|
+
"StorageOperationFailed",
|
|
141
|
+
]),
|
|
81
142
|
};
|
|
82
143
|
|
|
83
144
|
/** Resolve the readiness spec for a resource: registry override, else default. */
|
|
@@ -94,15 +155,31 @@ function getPath(obj: unknown, path: string): unknown {
|
|
|
94
155
|
);
|
|
95
156
|
}
|
|
96
157
|
|
|
158
|
+
function isConditionReason(m: ReadinessMatch): m is ConditionReasonMatch {
|
|
159
|
+
return (m as ConditionReasonMatch).reasonOneOf !== undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
97
162
|
function isCondition(m: ReadinessMatch): m is ConditionMatch {
|
|
98
|
-
return (m as ConditionMatch).conditionType !== undefined;
|
|
163
|
+
return (m as ConditionMatch).conditionType !== undefined && !isConditionReason(m);
|
|
99
164
|
}
|
|
100
165
|
|
|
101
|
-
function
|
|
166
|
+
function findCondition(obj: unknown, conditionType: string): Record<string, unknown> | undefined {
|
|
102
167
|
const conds = getPath(obj, "status.conditions");
|
|
103
|
-
if (!Array.isArray(conds)) return
|
|
104
|
-
|
|
105
|
-
|
|
168
|
+
if (!Array.isArray(conds)) return undefined;
|
|
169
|
+
return conds.find((x) => x && typeof x === "object" && (x as Record<string, unknown>).type === conditionType) as
|
|
170
|
+
| Record<string, unknown>
|
|
171
|
+
| undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function matchCondition(obj: unknown, m: ConditionMatch): boolean {
|
|
175
|
+
const c = findCondition(obj, m.conditionType);
|
|
176
|
+
return !!c && String(c.status) === (m.status ?? "True");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function matchConditionReason(obj: unknown, m: ConditionReasonMatch): boolean {
|
|
180
|
+
const c = findCondition(obj, m.conditionType);
|
|
181
|
+
if (!c || String(c.status) !== (m.status ?? "False")) return false;
|
|
182
|
+
return typeof c.reason === "string" && m.reasonOneOf.includes(c.reason);
|
|
106
183
|
}
|
|
107
184
|
|
|
108
185
|
function matchPath(obj: unknown, m: PathMatch): boolean {
|
|
@@ -113,6 +190,7 @@ function matchPath(obj: unknown, m: PathMatch): boolean {
|
|
|
113
190
|
}
|
|
114
191
|
|
|
115
192
|
function matches(obj: unknown, m: ReadinessMatch): boolean {
|
|
193
|
+
if (isConditionReason(m)) return matchConditionReason(obj, m);
|
|
116
194
|
return isCondition(m) ? matchCondition(obj, m) : matchPath(obj, m);
|
|
117
195
|
}
|
|
118
196
|
|