@intentius/chant-lexicon-k8s 0.42.1 → 0.44.2
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/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 +7 -1
- 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/types/Addon.md +14 -0
- package/dist/okf/types/Bucket.md +4 -0
- package/dist/okf/types/{HelmChart.md → FluxHelmChart.md} +2 -2
- package/dist/okf/types/GitRepository.md +5 -0
- package/dist/okf/types/HelmChartConfig.md +14 -0
- package/dist/okf/types/K3sHelmChart.md +14 -0
- package/dist/okf/types/Kustomization.md +6 -0
- package/dist/okf/types/Namespace.md +1 -0
- package/dist/okf/types/Node.md +1 -0
- package/dist/okf/types/OCIRepository.md +4 -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 +37 -0
- package/src/api/classify.ts +11 -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/describe-resources.test.ts +290 -6
- package/src/describe-resources.ts +235 -127
- 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
|
@@ -42,17 +42,19 @@
|
|
|
42
42
|
* exactly the shape `lifecycle diff --live` has always classified `orphan`.
|
|
43
43
|
* On Kubernetes that is wrong: it is expected runtime, not drift, and will be
|
|
44
44
|
* recreated the moment it is deleted. After the declared-entity reads above
|
|
45
|
-
* resolve, this
|
|
46
|
-
* each
|
|
47
|
-
* entities this very call resolved. A chain reaching one of them
|
|
48
|
-
* with `ownerChain: { root: "declared", ... }`; core's
|
|
49
|
-
* reads that as `runtime` instead of `orphan`.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
45
|
+
* resolve, this sweeps candidate kinds in the namespaces they actually live in
|
|
46
|
+
* and walks each object's `ownerReferences` chain (`./api/owner-chain.ts`)
|
|
47
|
+
* against the entities this very call resolved. A chain reaching one of them
|
|
48
|
+
* is reported with `ownerChain: { root: "declared", ... }`; core's
|
|
49
|
+
* diff/change-set engine reads that as `runtime` instead of `orphan`.
|
|
50
|
+
*
|
|
51
|
+
* Which kinds are candidates is the cluster's own API discovery's answer
|
|
52
|
+
* (#1517): every listable kind it serves, one version per group. The sweep's
|
|
53
|
+
* bound is the namespaces the declared estate touches, never a kind table —
|
|
54
|
+
* see `addRuntimeChildren`.
|
|
53
55
|
*/
|
|
54
56
|
|
|
55
|
-
import type { ObservationResult, ResourceMetadata, UnobservedEntity } from "@intentius/chant/lexicon";
|
|
57
|
+
import type { ObservationResult, ResourceMetadata, UnobservedEntity, UnobservedReason } from "@intentius/chant/lexicon";
|
|
56
58
|
import { observation, unobservedAll } from "@intentius/chant/observation";
|
|
57
59
|
import { hasOwnershipMarker, classifyOwnership, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
|
|
58
60
|
import type { K8sClient, K8sObject } from "@intentius/chant-k8s-client";
|
|
@@ -253,6 +255,14 @@ export async function describeResources(
|
|
|
253
255
|
): Promise<ObservationResult> {
|
|
254
256
|
const resources: Record<string, ResourceMetadata> = {};
|
|
255
257
|
const unobserved: Record<string, UnobservedEntity> = {};
|
|
258
|
+
// The resolved query address per declared entity (#1620) — the exact request
|
|
259
|
+
// path each read was issued against, with the namespace the client actually
|
|
260
|
+
// defaulted to when the declaration carries none. This is what lets an
|
|
261
|
+
// absence explain itself: a Flux-deployed object declaring no
|
|
262
|
+
// metadata.namespace reads from `default`, correctly comes back 404, and
|
|
263
|
+
// without the address the report says "not there" when the truth is "not
|
|
264
|
+
// looked for where it lives".
|
|
265
|
+
const queried: Record<string, string> = {};
|
|
256
266
|
|
|
257
267
|
const declared: Declared[] = [...options.entities].map(([entityName, entity]) => ({
|
|
258
268
|
entityName,
|
|
@@ -321,13 +331,27 @@ export async function describeResources(
|
|
|
321
331
|
return;
|
|
322
332
|
}
|
|
323
333
|
|
|
334
|
+
const ref = {
|
|
335
|
+
apiVersion: operation.apiVersion,
|
|
336
|
+
kind: operation.kind,
|
|
337
|
+
name,
|
|
338
|
+
...(metadata?.namespace ? { namespace: metadata.namespace } : {}),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Resolve the address BEFORE the read, so every verdict below — present,
|
|
342
|
+
// absent, filtered, read-failed — carries the same record of what was
|
|
343
|
+
// asked. Resolution rides the client's cached discovery; a kind discovery
|
|
344
|
+
// does not serve has no path, and a resolution failure classifies through
|
|
345
|
+
// the read below, never here.
|
|
346
|
+
try {
|
|
347
|
+
const address = typeof client.pathFor === "function" ? await client.pathFor(ref) : undefined;
|
|
348
|
+
if (address) queried[entityName] = address;
|
|
349
|
+
} catch {
|
|
350
|
+
// The read below hits the same resolution and classifies its failure.
|
|
351
|
+
}
|
|
352
|
+
|
|
324
353
|
try {
|
|
325
|
-
const obj = await client.read(
|
|
326
|
-
apiVersion: operation.apiVersion,
|
|
327
|
-
kind: operation.kind,
|
|
328
|
-
name,
|
|
329
|
-
...(metadata?.namespace ? { namespace: metadata.namespace } : {}),
|
|
330
|
-
});
|
|
354
|
+
const obj = await client.read(ref);
|
|
331
355
|
|
|
332
356
|
// owned filter: withhold resources not carrying chant's marker label.
|
|
333
357
|
// Withheld is not absent (chant #1089) — this object exists, it just
|
|
@@ -338,6 +362,7 @@ export async function describeResources(
|
|
|
338
362
|
type: entityType,
|
|
339
363
|
reason: "filtered",
|
|
340
364
|
detail: "live object carries no chant ownership marker and --owned was requested",
|
|
365
|
+
...(queried[entityName] ? { queried: queried[entityName] } : {}),
|
|
341
366
|
};
|
|
342
367
|
return;
|
|
343
368
|
}
|
|
@@ -359,51 +384,60 @@ export async function describeResources(
|
|
|
359
384
|
} catch (err) {
|
|
360
385
|
const outcome = classifyApiFailure(err);
|
|
361
386
|
if (outcome.kind === "unobserved") {
|
|
362
|
-
unobserved[entityName] = {
|
|
387
|
+
unobserved[entityName] = {
|
|
388
|
+
type: entityType,
|
|
389
|
+
reason: outcome.reason,
|
|
390
|
+
detail: outcome.detail,
|
|
391
|
+
...(queried[entityName] ? { queried: queried[entityName] } : {}),
|
|
392
|
+
};
|
|
363
393
|
}
|
|
364
394
|
// `absent` deliberately records nothing: in neither map is how the
|
|
365
|
-
// contract spells "asked, and it is not there".
|
|
395
|
+
// contract spells "asked, and it is not there". Its address stays in
|
|
396
|
+
// `queried` (#1620), which is the only record an absence gets.
|
|
366
397
|
}
|
|
367
398
|
});
|
|
368
399
|
|
|
369
|
-
|
|
370
|
-
// controller's children usually live in its controller's group (a
|
|
371
|
-
// MicroVMReplicaSet makes MicroVMs), so the runtime scan sweeps those
|
|
372
|
-
// groupVersions' kinds besides Pods. Core (`v1`) is excluded from the group
|
|
373
|
-
// sweep — Pods represent it; sweeping every core kind would walk endpoints
|
|
374
|
-
// and events for nothing.
|
|
375
|
-
const runtimeGroupVersions = new Set<string>();
|
|
376
|
-
for (const { entityType } of declared) {
|
|
377
|
-
const op = operationFor(entityType);
|
|
378
|
-
if (op && op.apiVersion.includes("/")) runtimeGroupVersions.add(op.apiVersion);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions, declared);
|
|
400
|
+
await addRuntimeChildren(client, resources, unobserved, options.owned, declared);
|
|
382
401
|
|
|
383
|
-
return observation(resources, unobserved);
|
|
402
|
+
return observation(resources, unobserved, queried);
|
|
384
403
|
}
|
|
385
404
|
|
|
386
405
|
/**
|
|
387
|
-
*
|
|
406
|
+
* Sweep runtime-child candidates in the namespaces of entities this call just
|
|
388
407
|
* resolved, and classify each one's owner-reference chain (#1077). Mutates
|
|
389
408
|
* `resources` in place, adding an entry per object whose chain was worth
|
|
390
|
-
* reporting.
|
|
409
|
+
* reporting, and `unobserved` for the parts of the sweep that could not look.
|
|
391
410
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
411
|
+
* The candidate kinds are the cluster's own API discovery's answer (#1517):
|
|
412
|
+
* every listable kind it serves, one version per group (the group's
|
|
413
|
+
* preferredVersion, so nothing is listed twice at a second served version).
|
|
414
|
+
* The first cut scoped this to Pods, the second to the estate's own declared
|
|
415
|
+
* API groups — and both missed exactly where the runtime tier says the most:
|
|
416
|
+
* an operator's children are whatever kinds its controllers make (a
|
|
417
|
+
* MicroVMReplicaSet's MicroVMs, a CR's Deployments in a group the estate
|
|
418
|
+
* never declares), which no kind table can anticipate. The same open-world
|
|
419
|
+
* inversion behold#74 made for declared CRDs, applied to the runtime axis.
|
|
401
420
|
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
421
|
+
* What bounds the sweep is not a kind list but reach:
|
|
422
|
+
* - **Namespaced kinds** are listed only in the namespaces the declared
|
|
423
|
+
* estate touches (where its entities resolved, plus the namespaces its
|
|
424
|
+
* GitOps CRs target) — owner references cannot cross a namespace, so a
|
|
425
|
+
* child elsewhere could never chain to this estate anyway.
|
|
426
|
+
* - **Cluster-scoped kinds** are listed only when the estate resolved a
|
|
427
|
+
* cluster-scoped entity — a cluster-scoped object's owners are themselves
|
|
428
|
+
* cluster-scoped (Kubernetes' own GC rule), so without one declared there
|
|
429
|
+
* is nothing such a chain could reach.
|
|
430
|
+
* - **It is not an inventory**: outside Pods (whose chain verdicts, foreign
|
|
431
|
+
* included, are #1077's original contract), a swept object is only
|
|
432
|
+
* reported when its chain resolves to a declared entity.
|
|
433
|
+
*
|
|
434
|
+
* Tri-state honesty (#1089, extended to the sweep): a kind discovery names
|
|
435
|
+
* but the sweep cannot list — RBAC denial, a flaking apiserver — becomes a
|
|
436
|
+
* `runtime-sweep:*` entry in `unobserved`, never a silent gap. Silence would
|
|
437
|
+
* read as "no runtime children there", which the failed list never proved.
|
|
438
|
+
* A list 404 (the kind vanished between discovery and the list) stays an
|
|
439
|
+
* absence, per `classifyApiFailure`'s standing rule. Declared-entity
|
|
440
|
+
* resolution is never failed by the sweep.
|
|
407
441
|
*/
|
|
408
442
|
/** The declared GitOps CRs, indexed the way their controllers' labels can be
|
|
409
443
|
* resolved (#1549). `byRef` keys are `type\0namespace\0name` (Flux — exact);
|
|
@@ -466,11 +500,29 @@ function gitopsOwner(labels: Record<string, string> | undefined, gitops: GitopsI
|
|
|
466
500
|
return undefined;
|
|
467
501
|
}
|
|
468
502
|
|
|
503
|
+
/** One candidate kind of the discovery-driven sweep. */
|
|
504
|
+
interface SweptKind {
|
|
505
|
+
apiVersion: string;
|
|
506
|
+
kind: string;
|
|
507
|
+
typeName: string;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Kinds the sweep skips even though discovery serves them. Not a kind table
|
|
512
|
+
* creeping back in: each entry is a kind that CANNOT attribute to a declared
|
|
513
|
+
* owner — an Event names its subject via `involvedObject`, never
|
|
514
|
+
* `ownerReferences` — and churns by the thousands in any active namespace,
|
|
515
|
+
* so listing it can only cost and never attach.
|
|
516
|
+
*/
|
|
517
|
+
function isSweepNoise(info: { kind: string; group: string }): boolean {
|
|
518
|
+
return info.kind === "Event" && (info.group === "" || info.group === "events.k8s.io");
|
|
519
|
+
}
|
|
520
|
+
|
|
469
521
|
async function addRuntimeChildren(
|
|
470
522
|
client: K8sClient,
|
|
471
523
|
resources: Record<string, ResourceMetadata>,
|
|
524
|
+
unobserved: Record<string, UnobservedEntity>,
|
|
472
525
|
owned: boolean | undefined,
|
|
473
|
-
groupVersions: ReadonlySet<string> = new Set(),
|
|
474
526
|
declared: readonly Declared[] = [],
|
|
475
527
|
): Promise<void> {
|
|
476
528
|
const declaredByUid = new Map<string, string>();
|
|
@@ -488,9 +540,11 @@ async function addRuntimeChildren(
|
|
|
488
540
|
const gitops = gitopsIndex(declared);
|
|
489
541
|
|
|
490
542
|
const namespaces = new Set<string>();
|
|
543
|
+
let clusterScopedDeclared = false;
|
|
491
544
|
for (const meta of Object.values(resources)) {
|
|
492
545
|
const namespace = (meta.attributes as { namespace?: string } | undefined)?.namespace;
|
|
493
546
|
if (namespace) namespaces.add(namespace);
|
|
547
|
+
else clusterScopedDeclared = true;
|
|
494
548
|
}
|
|
495
549
|
// The namespaces a declared GitOps CR TARGETS are declared literals too
|
|
496
550
|
// (Argo `spec.destination.namespace`, Flux `spec.targetNamespace`) — the
|
|
@@ -498,93 +552,147 @@ async function addRuntimeChildren(
|
|
|
498
552
|
// reading it off the declaration keeps this bounded by the estate, never a
|
|
499
553
|
// cluster-wide sweep.
|
|
500
554
|
for (const ns of gitops.targetNamespaces) namespaces.add(ns);
|
|
501
|
-
if (namespaces.size === 0) return;
|
|
502
|
-
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
);
|
|
555
|
+
if (namespaces.size === 0 && !clusterScopedDeclared) return;
|
|
556
|
+
|
|
557
|
+
// Every kind the cluster serves, from its own discovery (#1517): one
|
|
558
|
+
// groupVersion per group, every listable kind, split by scope. Failing to
|
|
559
|
+
// enumerate is a hole the observation must admit — silently sweeping
|
|
560
|
+
// nothing would read as "no runtime children", which was never established.
|
|
561
|
+
let groupVersions: string[];
|
|
562
|
+
try {
|
|
563
|
+
groupVersions = await client.preferredGroupVersions();
|
|
564
|
+
} catch (err) {
|
|
565
|
+
const outcome = classifyApiFailure(err);
|
|
566
|
+
unobserved["runtime-sweep"] = {
|
|
567
|
+
reason: outcome.kind === "unobserved" ? outcome.reason : "read-failed",
|
|
568
|
+
detail:
|
|
569
|
+
"cluster API discovery failed, so runtime children were not swept" +
|
|
570
|
+
(outcome.kind === "unobserved" ? `: ${outcome.detail}` : ""),
|
|
571
|
+
};
|
|
572
|
+
return;
|
|
520
573
|
}
|
|
521
|
-
|
|
522
|
-
|
|
574
|
+
|
|
575
|
+
const perGroup = await client.concurrently(groupVersions, async (gv) => {
|
|
523
576
|
try {
|
|
524
|
-
|
|
525
|
-
} catch {
|
|
526
|
-
|
|
577
|
+
return await client.resources(gv);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
const outcome = classifyApiFailure(err);
|
|
580
|
+
if (outcome.kind === "unobserved") {
|
|
581
|
+
unobserved[`runtime-sweep:${gv}`] = {
|
|
582
|
+
reason: outcome.reason,
|
|
583
|
+
detail: `discovery for ${gv} failed, so its kinds were not swept: ${outcome.detail}`,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return [];
|
|
527
587
|
}
|
|
528
|
-
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const namespacedKinds: SweptKind[] = [];
|
|
591
|
+
const clusterKinds: SweptKind[] = [];
|
|
592
|
+
for (const infos of perGroup) {
|
|
529
593
|
for (const info of infos) {
|
|
530
|
-
if (!info.
|
|
531
|
-
|
|
594
|
+
if (!info.verbs.includes("list") || isSweepNoise(info)) continue;
|
|
595
|
+
const swept: SweptKind = {
|
|
596
|
+
apiVersion: info.apiVersion,
|
|
597
|
+
kind: info.kind,
|
|
598
|
+
typeName: gvkToTypeName({ group: info.group, version: info.version, kind: info.kind }),
|
|
599
|
+
};
|
|
600
|
+
(info.namespaced ? namespacedKinds : clusterKinds).push(swept);
|
|
532
601
|
}
|
|
533
602
|
}
|
|
534
603
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
604
|
+
// A failed list is recorded once per kind, naming every scope it failed in,
|
|
605
|
+
// rather than once per (kind, namespace) — one RBAC rule denying a kind
|
|
606
|
+
// across N namespaces is one hole, not N.
|
|
607
|
+
const listFailures = new Map<
|
|
608
|
+
string,
|
|
609
|
+
{ typeName: string; reason: UnobservedReason; scopes: string[]; detail: string }
|
|
610
|
+
>();
|
|
543
611
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (owned && ownerChain.root !== "declared" && !marker) return;
|
|
564
|
-
|
|
565
|
-
// An undeclared object with NO owner chain at all is not a runtime
|
|
566
|
-
// child — reporting every loose object a group serves would turn the
|
|
567
|
-
// scan into an inventory. Pods keep their pre-#1517 reporting (their
|
|
568
|
-
// chain verdicts, including foreign, were already part of #1077's
|
|
569
|
-
// contract); swept group kinds only report when the chain reaches a
|
|
570
|
-
// declared entity.
|
|
571
|
-
if (kind !== "Pod" && ownerChain.root !== "declared") return;
|
|
572
|
-
|
|
573
|
-
resources[`${namespace}/${name}`] = {
|
|
574
|
-
type: typeName,
|
|
575
|
-
physicalId: uid,
|
|
576
|
-
status: statusFromObject(obj),
|
|
577
|
-
lastUpdated: obj.metadata?.creationTimestamp,
|
|
578
|
-
ownership: classifyOwnership(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS),
|
|
579
|
-
ownerChain,
|
|
580
|
-
attributes: pruneUndefined({
|
|
581
|
-
namespace,
|
|
582
|
-
labels: obj.metadata?.labels,
|
|
583
|
-
resourceVersion: obj.metadata?.resourceVersion,
|
|
584
|
-
conditions: unhappyConditions(obj),
|
|
585
|
-
}),
|
|
586
|
-
};
|
|
587
|
-
});
|
|
612
|
+
const sweep = async (swept: SweptKind, namespace: string | undefined): Promise<void> => {
|
|
613
|
+
let objects: K8sObject[];
|
|
614
|
+
try {
|
|
615
|
+
objects = await client.list(
|
|
616
|
+
{ apiVersion: swept.apiVersion, kind: swept.kind },
|
|
617
|
+
namespace ? { namespace } : {},
|
|
618
|
+
);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
const outcome = classifyApiFailure(err);
|
|
621
|
+
// `absent` (a list 404: the kind vanished between discovery and now)
|
|
622
|
+
// proves no instance exists — nothing to record.
|
|
623
|
+
if (outcome.kind === "unobserved") {
|
|
624
|
+
const key = `runtime-sweep:${swept.apiVersion}/${swept.kind}`;
|
|
625
|
+
const scope = namespace ? `namespace ${namespace}` : "cluster scope";
|
|
626
|
+
const failure = listFailures.get(key);
|
|
627
|
+
if (failure) failure.scopes.push(scope);
|
|
628
|
+
else listFailures.set(key, { typeName: swept.typeName, reason: outcome.reason, scopes: [scope], detail: outcome.detail });
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
588
631
|
}
|
|
632
|
+
|
|
633
|
+
await client.concurrently(objects, async (obj) => {
|
|
634
|
+
const uid = obj.metadata?.uid;
|
|
635
|
+
const name = obj.metadata?.name;
|
|
636
|
+
if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
|
|
637
|
+
const key = namespace ? `${namespace}/${name}` : `cluster:${name}`;
|
|
638
|
+
if (resources[key]) return; // already reported by an earlier kind
|
|
639
|
+
|
|
640
|
+
let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
|
|
641
|
+
// The label channel (#1549) — only when the chain itself said nothing:
|
|
642
|
+
// a verdict the walk DID reach is never overridden.
|
|
643
|
+
if (ownerChain.root !== "declared") {
|
|
644
|
+
const entity = gitopsOwner(obj.metadata?.labels, gitops);
|
|
645
|
+
if (entity) ownerChain = { root: "declared", entity };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// `--owned`: withhold an object that is neither a runtime child of a
|
|
649
|
+
// declared entity nor carrying chant's own marker — the same rule the
|
|
650
|
+
// declared-entity read above applies, extended to the undeclared axis
|
|
651
|
+
// this scan introduces.
|
|
652
|
+
const marker = hasOwnershipMarker(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS);
|
|
653
|
+
if (owned && ownerChain.root !== "declared" && !marker) return;
|
|
654
|
+
|
|
655
|
+
// An undeclared object with NO owner chain at all is not a runtime
|
|
656
|
+
// child — reporting every loose object the cluster serves would turn
|
|
657
|
+
// the sweep into an inventory. Pods keep their pre-#1517 reporting
|
|
658
|
+
// (their chain verdicts, including foreign, were already part of
|
|
659
|
+
// #1077's contract); every other swept kind only reports when the
|
|
660
|
+
// chain reaches a declared entity.
|
|
661
|
+
if (swept.kind !== "Pod" && ownerChain.root !== "declared") return;
|
|
662
|
+
|
|
663
|
+
resources[key] = {
|
|
664
|
+
type: swept.typeName,
|
|
665
|
+
physicalId: uid,
|
|
666
|
+
status: statusFromObject(obj),
|
|
667
|
+
lastUpdated: obj.metadata?.creationTimestamp,
|
|
668
|
+
ownership: classifyOwnership(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS),
|
|
669
|
+
ownerChain,
|
|
670
|
+
attributes: pruneUndefined({
|
|
671
|
+
namespace,
|
|
672
|
+
labels: obj.metadata?.labels,
|
|
673
|
+
resourceVersion: obj.metadata?.resourceVersion,
|
|
674
|
+
conditions: unhappyConditions(obj),
|
|
675
|
+
}),
|
|
676
|
+
};
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
await client.concurrently([...namespaces], async (namespace) => {
|
|
681
|
+
for (const swept of namespacedKinds) await sweep(swept, namespace);
|
|
589
682
|
});
|
|
683
|
+
|
|
684
|
+
// Cluster-scoped kinds are swept once (their lists are cluster-wide by
|
|
685
|
+
// nature), and only when a declared cluster-scoped entity exists for a
|
|
686
|
+
// chain to reach — a cluster-scoped object's owners are cluster-scoped.
|
|
687
|
+
if (clusterScopedDeclared) {
|
|
688
|
+
await client.concurrently(clusterKinds, async (swept) => sweep(swept, undefined));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
for (const [key, failure] of listFailures) {
|
|
692
|
+
unobserved[key] = {
|
|
693
|
+
type: failure.typeName,
|
|
694
|
+
reason: failure.reason,
|
|
695
|
+
detail: `the runtime-children sweep could not list this kind in ${failure.scopes.join(", ")}: ${failure.detail}`,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
590
698
|
}
|
package/src/generated/index.d.ts
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
// --- Kubernetes Resource classes ---
|
|
4
4
|
|
|
5
|
+
export declare class Addon {
|
|
6
|
+
constructor(props: {
|
|
7
|
+
/** Standard object's metadata. */
|
|
8
|
+
metadata?: ObjectMeta;
|
|
9
|
+
/** Spec provides information about the on-disk manifest backing this resource. */
|
|
10
|
+
spec?: Record<string, unknown>;
|
|
11
|
+
});
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly namespace: string;
|
|
14
|
+
readonly uid: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
export declare class Alert {
|
|
6
18
|
constructor(props: {
|
|
7
19
|
/** Standard object's metadata. */
|
|
@@ -920,6 +932,19 @@ export declare class FlowSchemaList {
|
|
|
920
932
|
readonly uid: string;
|
|
921
933
|
}
|
|
922
934
|
|
|
935
|
+
export declare class FluxHelmChart {
|
|
936
|
+
constructor(props: {
|
|
937
|
+
/** Standard object's metadata. */
|
|
938
|
+
metadata?: ObjectMeta;
|
|
939
|
+
/** HelmChartSpec specifies the desired state of a Helm chart. */
|
|
940
|
+
spec?: Record<string, unknown>;
|
|
941
|
+
});
|
|
942
|
+
readonly name: string;
|
|
943
|
+
readonly namespace: string;
|
|
944
|
+
readonly status: Record<string, unknown>;
|
|
945
|
+
readonly uid: string;
|
|
946
|
+
}
|
|
947
|
+
|
|
923
948
|
export declare class FluxInstance {
|
|
924
949
|
constructor(props: {
|
|
925
950
|
/** Standard object's metadata. */
|
|
@@ -999,16 +1024,16 @@ export declare class GRPCRoute {
|
|
|
999
1024
|
readonly uid: string;
|
|
1000
1025
|
}
|
|
1001
1026
|
|
|
1002
|
-
export declare class
|
|
1027
|
+
export declare class HelmChartConfig {
|
|
1003
1028
|
constructor(props: {
|
|
1004
1029
|
/** Standard object's metadata. */
|
|
1005
1030
|
metadata?: ObjectMeta;
|
|
1006
|
-
/**
|
|
1031
|
+
/** HelmChartConfigSpec represents additional user-configurable details of an installed and configured Helm chart release.
|
|
1032
|
+
These fields are merged with or override the corresponding fields on the related HelmChart resource. */
|
|
1007
1033
|
spec?: Record<string, unknown>;
|
|
1008
1034
|
});
|
|
1009
1035
|
readonly name: string;
|
|
1010
1036
|
readonly namespace: string;
|
|
1011
|
-
readonly status: Record<string, unknown>;
|
|
1012
1037
|
readonly uid: string;
|
|
1013
1038
|
}
|
|
1014
1039
|
|
|
@@ -1325,6 +1350,19 @@ export declare class JobList {
|
|
|
1325
1350
|
readonly uid: string;
|
|
1326
1351
|
}
|
|
1327
1352
|
|
|
1353
|
+
export declare class K3sHelmChart {
|
|
1354
|
+
constructor(props: {
|
|
1355
|
+
/** Standard object's metadata. */
|
|
1356
|
+
metadata?: ObjectMeta;
|
|
1357
|
+
/** HelmChartSpec represents the user-configurable details for installation and upgrade of a Helm chart release. */
|
|
1358
|
+
spec?: Record<string, unknown>;
|
|
1359
|
+
});
|
|
1360
|
+
readonly name: string;
|
|
1361
|
+
readonly namespace: string;
|
|
1362
|
+
readonly status: Record<string, unknown>;
|
|
1363
|
+
readonly uid: string;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1328
1366
|
export declare class Kustomization {
|
|
1329
1367
|
constructor(props: {
|
|
1330
1368
|
/** Standard object's metadata. */
|
package/src/generated/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Code generated by chant generate. DO NOT EDIT.
|
|
2
2
|
import { createResource, createProperty } from "./runtime";
|
|
3
3
|
|
|
4
|
+
export const Addon = createResource("K8s::K3s::Addon", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
4
5
|
export const Alert = createResource("K8s::Flux::Alert", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
5
6
|
export const APIGroup = createResource("K8s::Core::APIGroup", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
6
7
|
export const APIGroupList = createResource("K8s::Core::APIGroupList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
@@ -68,13 +69,14 @@ export const EventsEventList = createResource("K8s::Events::EventList", "k8s", {
|
|
|
68
69
|
export const Eviction = createResource("K8s::Policy::Eviction", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
69
70
|
export const FlowSchema = createResource("K8s::Flowcontrol::FlowSchema", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
70
71
|
export const FlowSchemaList = createResource("K8s::Flowcontrol::FlowSchemaList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
72
|
+
export const FluxHelmChart = createResource("K8s::Flux::HelmChart", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
71
73
|
export const FluxInstance = createResource("K8s::Flux::FluxInstance", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
72
74
|
export const FluxReport = createResource("K8s::Flux::FluxReport", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
73
75
|
export const Gateway = createResource("K8s::Gateway::Gateway", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
74
76
|
export const GatewayClass = createResource("K8s::Gateway::GatewayClass", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
75
77
|
export const GitRepository = createResource("K8s::Flux::GitRepository", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
76
78
|
export const GRPCRoute = createResource("K8s::Gateway::GRPCRoute", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
77
|
-
export const
|
|
79
|
+
export const HelmChartConfig = createResource("K8s::K3s::HelmChartConfig", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
78
80
|
export const HelmRelease = createResource("K8s::Flux::HelmRelease", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
79
81
|
export const HelmRepository = createResource("K8s::Flux::HelmRepository", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
80
82
|
export const HorizontalPodAutoscaler = createResource("K8s::Autoscaling::HorizontalPodAutoscaler", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
@@ -100,6 +102,7 @@ export const IPAddressList = createResource("K8s::Networking::IPAddressList", "k
|
|
|
100
102
|
export const Issuer = createResource("K8s::CertManager::Issuer", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
101
103
|
export const Job = createResource("K8s::Batch::Job", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
102
104
|
export const JobList = createResource("K8s::Batch::JobList", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
105
|
+
export const K3sHelmChart = createResource("K8s::K3s::HelmChart", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
103
106
|
export const Kustomization = createResource("K8s::Flux::Kustomization", "k8s", {"name":"name","namespace":"namespace","uid":"uid","status":"status"});
|
|
104
107
|
export const Lease = createResource("K8s::Coordination::Lease", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
105
108
|
export const LeaseCandidate = createResource("K8s::Coordination::LeaseCandidate", "k8s", {"name":"name","namespace":"namespace","uid":"uid"});
|
|
@@ -332,6 +335,9 @@ export const EnvVar = createProperty("K8s::Core::EnvVar", "k8s");
|
|
|
332
335
|
export const EnvVarSource = createProperty("K8s::Core::EnvVarSource", "k8s");
|
|
333
336
|
export const EphemeralContainer = createProperty("K8s::Core::EphemeralContainer", "k8s");
|
|
334
337
|
export const ExecAction = createProperty("K8s::Core::ExecAction", "k8s");
|
|
338
|
+
export const FluxHelmChart_SourceRef = createProperty("K8s::Flux::HelmChart.sourceRef", "k8s");
|
|
339
|
+
export const FluxHelmChart_Status = createProperty("K8s::Flux::HelmChart.status", "k8s");
|
|
340
|
+
export const FluxHelmChart_Verify = createProperty("K8s::Flux::HelmChart.verify", "k8s");
|
|
335
341
|
export const FluxInstance_Cluster = createProperty("K8s::Flux::FluxInstance.cluster", "k8s");
|
|
336
342
|
export const FluxInstance_CommonMetadata = createProperty("K8s::Flux::FluxInstance.commonMetadata", "k8s");
|
|
337
343
|
export const FluxInstance_Distribution = createProperty("K8s::Flux::FluxInstance.distribution", "k8s");
|
|
@@ -362,9 +368,7 @@ export const GitRepository_Verify = createProperty("K8s::Flux::GitRepository.ver
|
|
|
362
368
|
export const GRPCRoute_ParentRef = createProperty("K8s::Gateway::GRPCRoute.parentRefs", "k8s");
|
|
363
369
|
export const GRPCRoute_Rule = createProperty("K8s::Gateway::GRPCRoute.rules", "k8s");
|
|
364
370
|
export const GRPCRoute_Status = createProperty("K8s::Gateway::GRPCRoute.status", "k8s");
|
|
365
|
-
export const
|
|
366
|
-
export const HelmChart_Status = createProperty("K8s::Flux::HelmChart.status", "k8s");
|
|
367
|
-
export const HelmChart_Verify = createProperty("K8s::Flux::HelmChart.verify", "k8s");
|
|
371
|
+
export const HelmChartConfig_ValuesSecret = createProperty("K8s::K3s::HelmChartConfig.valuesSecrets", "k8s");
|
|
368
372
|
export const HelmRelease_Chart = createProperty("K8s::Flux::HelmRelease.chart", "k8s");
|
|
369
373
|
export const HelmRelease_ChartRef = createProperty("K8s::Flux::HelmRelease.chartRef", "k8s");
|
|
370
374
|
export const HelmRelease_CommonMetadata = createProperty("K8s::Flux::HelmRelease.commonMetadata", "k8s");
|
|
@@ -440,6 +444,13 @@ export const Issuer_SelfSigned = createProperty("K8s::CertManager::Issuer.selfSi
|
|
|
440
444
|
export const Issuer_Status = createProperty("K8s::CertManager::Issuer.status", "k8s");
|
|
441
445
|
export const Issuer_Vault = createProperty("K8s::CertManager::Issuer.vault", "k8s");
|
|
442
446
|
export const Issuer_Venafi = createProperty("K8s::CertManager::Issuer.venafi", "k8s");
|
|
447
|
+
export const K3sHelmChart_AuthSecret = createProperty("K8s::K3s::HelmChart.authSecret", "k8s");
|
|
448
|
+
export const K3sHelmChart_DockerRegistrySecret = createProperty("K8s::K3s::HelmChart.dockerRegistrySecret", "k8s");
|
|
449
|
+
export const K3sHelmChart_PodSecurityContext = createProperty("K8s::K3s::HelmChart.podSecurityContext", "k8s");
|
|
450
|
+
export const K3sHelmChart_RepoCAConfigMap = createProperty("K8s::K3s::HelmChart.repoCAConfigMap", "k8s");
|
|
451
|
+
export const K3sHelmChart_SecurityContext = createProperty("K8s::K3s::HelmChart.securityContext", "k8s");
|
|
452
|
+
export const K3sHelmChart_Status = createProperty("K8s::K3s::HelmChart.status", "k8s");
|
|
453
|
+
export const K3sHelmChart_ValuesSecret = createProperty("K8s::K3s::HelmChart.valuesSecrets", "k8s");
|
|
443
454
|
export const KeyToPath = createProperty("K8s::Core::KeyToPath", "k8s");
|
|
444
455
|
export const Kustomization_CommonMetadata = createProperty("K8s::Flux::Kustomization.commonMetadata", "k8s");
|
|
445
456
|
export const Kustomization_Decryption = createProperty("K8s::Flux::Kustomization.decryption", "k8s");
|