@intentius/chant-lexicon-k8s 0.30.0 → 0.32.0
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 +65 -0
- package/dist/api/classify.d.ts.map +1 -0
- package/dist/api/connect.d.ts +58 -0
- package/dist/api/connect.d.ts.map +1 -0
- package/dist/api/fake-cluster.d.ts +55 -0
- package/dist/api/fake-cluster.d.ts.map +1 -0
- package/dist/api/operation-surface.d.ts +64 -0
- package/dist/api/operation-surface.d.ts.map +1 -0
- package/dist/api/owner-chain.d.ts +46 -0
- package/dist/api/owner-chain.d.ts.map +1 -0
- package/dist/api/sweep-types.d.ts +17 -0
- package/dist/api/sweep-types.d.ts.map +1 -0
- package/dist/codegen/generate-operations.d.ts +29 -0
- package/dist/codegen/generate-operations.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/crd/parser.d.ts.map +1 -1
- package/dist/crd/types.d.ts +7 -0
- package/dist/crd/types.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +86 -0
- package/dist/deep-observe-hooks.d.ts.map +1 -0
- package/dist/deep-observe.d.ts +146 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +58 -26
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/export-resources.d.ts +31 -1
- package/dist/export-resources.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/kube/apply.d.ts +26 -0
- package/dist/kube/apply.d.ts.map +1 -0
- package/dist/kube/connect.d.ts +30 -0
- package/dist/kube/connect.d.ts.map +1 -0
- package/dist/kube/delete.d.ts +27 -0
- package/dist/kube/delete.d.ts.map +1 -0
- package/dist/kube/describe.d.ts +19 -0
- package/dist/kube/describe.d.ts.map +1 -0
- package/dist/kube/events.d.ts +34 -0
- package/dist/kube/events.d.ts.map +1 -0
- package/dist/kube/flags.d.ts +40 -0
- package/dist/kube/flags.d.ts.map +1 -0
- package/dist/kube/get.d.ts +21 -0
- package/dist/kube/get.d.ts.map +1 -0
- package/dist/kube/group.d.ts +19 -0
- package/dist/kube/group.d.ts.map +1 -0
- package/dist/kube/logs.d.ts +16 -0
- package/dist/kube/logs.d.ts.map +1 -0
- package/dist/kube/project.d.ts +59 -0
- package/dist/kube/project.d.ts.map +1 -0
- package/dist/kube/render.d.ts +75 -0
- package/dist/kube/render.d.ts.map +1 -0
- package/dist/kube/source.d.ts +19 -0
- package/dist/kube/source.d.ts.map +1 -0
- package/dist/kube/target.d.ts +52 -0
- package/dist/kube/target.d.ts.map +1 -0
- package/dist/kube/testing.d.ts +21 -0
- package/dist/kube/testing.d.ts.map +1 -0
- package/dist/kube/top.d.ts +23 -0
- package/dist/kube/top.d.ts.map +1 -0
- package/dist/kube/verdict.d.ts +45 -0
- package/dist/kube/verdict.d.ts.map +1 -0
- package/dist/kube/wait.d.ts +23 -0
- package/dist/kube/wait.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/index.d.ts +7 -4
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/kubectl.d.ts +120 -2
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/wait-for-ready.d.ts +30 -3
- package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/spec/parse.d.ts +42 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/api/classify.test.ts +133 -0
- package/src/api/classify.ts +144 -0
- package/src/api/connect.ts +104 -0
- package/src/api/fake-cluster.ts +218 -0
- package/src/api/operation-surface.test.ts +116 -0
- package/src/api/operation-surface.ts +129 -0
- package/src/api/owner-chain.test.ts +171 -0
- package/src/api/owner-chain.ts +144 -0
- package/src/api/sweep-types.ts +36 -0
- package/src/codegen/generate-operations.ts +56 -0
- package/src/codegen/generate.ts +9 -0
- package/src/config.ts +17 -0
- package/src/crd/parser.ts +8 -0
- package/src/crd/types.ts +7 -0
- package/src/deep-observe-hooks.ts +125 -0
- package/src/deep-observe.test.ts +668 -0
- package/src/deep-observe.ts +299 -0
- package/src/describe-resources.test.ts +619 -191
- package/src/describe-resources.ts +218 -117
- package/src/export-resources-io.test.ts +76 -51
- package/src/export-resources.ts +47 -35
- package/src/generated/operations.json +2156 -0
- package/src/kube/apply.test.ts +105 -0
- package/src/kube/apply.ts +101 -0
- package/src/kube/connect.ts +54 -0
- package/src/kube/delete.test.ts +123 -0
- package/src/kube/delete.ts +174 -0
- package/src/kube/describe.test.ts +106 -0
- package/src/kube/describe.ts +195 -0
- package/src/kube/events.test.ts +115 -0
- package/src/kube/events.ts +160 -0
- package/src/kube/flags.test.ts +65 -0
- package/src/kube/flags.ts +114 -0
- package/src/kube/get.test.ts +195 -0
- package/src/kube/get.ts +190 -0
- package/src/kube/group.test.ts +33 -0
- package/src/kube/group.ts +125 -0
- package/src/kube/logs.test.ts +126 -0
- package/src/kube/logs.ts +96 -0
- package/src/kube/project.test.ts +57 -0
- package/src/kube/project.ts +106 -0
- package/src/kube/render.test.ts +142 -0
- package/src/kube/render.ts +252 -0
- package/src/kube/source.test.ts +138 -0
- package/src/kube/source.ts +139 -0
- package/src/kube/target.test.ts +67 -0
- package/src/kube/target.ts +89 -0
- package/src/kube/testing.ts +42 -0
- package/src/kube/top.test.ts +149 -0
- package/src/kube/top.ts +120 -0
- package/src/kube/verdict.test.ts +48 -0
- package/src/kube/verdict.ts +77 -0
- package/src/kube/wait.test.ts +125 -0
- package/src/kube/wait.ts +160 -0
- package/src/lifecycle-integration.test.ts +132 -92
- package/src/op/activities/index.ts +7 -3
- package/src/op/activities/kubectl.test.ts +420 -0
- package/src/op/activities/kubectl.ts +379 -13
- package/src/op/activities/wait-for-ready.test.ts +94 -0
- package/src/op/activities/wait-for-ready.ts +66 -15
- package/src/plugin.test.ts +58 -1
- package/src/plugin.ts +29 -0
- package/src/spec/parse.ts +93 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes deep observation (#1076) — the k8s row of the deep-observe
|
|
3
|
+
* contract (#1014), and the epic's (#1073) thesis made real: chant compiled
|
|
4
|
+
* the manifest, so it knows which fields it authored; the API server records,
|
|
5
|
+
* per field, which manager last wrote it (`metadata.managedFields`, chant
|
|
6
|
+
* #1075/#1178). Intersecting those two makes Kubernetes drift *derived*
|
|
7
|
+
* rather than the hand-maintained `ignoreDifferences` list every Argo user
|
|
8
|
+
* keeps.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is not "the AWS reader, with `kubectl get` instead of Cloud
|
|
11
|
+
* Control"
|
|
12
|
+
*
|
|
13
|
+
* AWS, Azure and Temporal's rows all prune by a **static, entityType-keyed**
|
|
14
|
+
* table: an ARN always looks like an ARN, `provisioningState` is always
|
|
15
|
+
* server-populated, a namespace's retention default is always the same
|
|
16
|
+
* value. None of that needs the specific live object in hand — it is exactly
|
|
17
|
+
* what `./deep-observe-hooks.ts`'s `k8sDeepNormalizationHooks` is, and it
|
|
18
|
+
* covers Kubernetes' *equivalent* static noise (`status`,
|
|
19
|
+
* `metadata.{uid,resourceVersion,generation,creationTimestamp}`, a handful of
|
|
20
|
+
* server-defaulted pod-spec fields).
|
|
21
|
+
*
|
|
22
|
+
* managedFields is not that kind of rule. Whether `spec.replicas` is noise on
|
|
23
|
+
* *this* Deployment depends on whether *this* Deployment's
|
|
24
|
+
* `metadata.managedFields` says a controller owns it — a fact that differs
|
|
25
|
+
* between two Deployments of the same type, and that the declared tree never
|
|
26
|
+
* carries at all (chant's source has no `managedFields` key to normalize).
|
|
27
|
+
* `DeepNormalizationHooks.prune` cannot see the object it is walking, only a
|
|
28
|
+
* path and a value, so this cannot be expressed as a fixed hook the way the
|
|
29
|
+
* other three rows' entire contribution is. It has to be computed once per
|
|
30
|
+
* resource, here, and layered on top of the static rules before the tree is
|
|
31
|
+
* normalized.
|
|
32
|
+
*
|
|
33
|
+
* ## The contested-field rule, precisely
|
|
34
|
+
*
|
|
35
|
+
* For every path on the live tree, three questions, each independent:
|
|
36
|
+
*
|
|
37
|
+
* 1. **Does any chant field manager (`chant`, `chant:<stack>` — chant #1075,
|
|
38
|
+
* matched on the family so a stack rename does not stop recognizing its
|
|
39
|
+
* own history) own this path?** If so it is chant's business regardless of
|
|
40
|
+
* what else is true — always diffable, never pruned by this rule.
|
|
41
|
+
* 2. **Does some *other* manager own this path, and chant does not?** That is
|
|
42
|
+
* controller-managed noise — HPA rewriting `spec.replicas`, a mutating
|
|
43
|
+
* webhook defaulting a field, `kubectl-client-side-apply` from a human
|
|
44
|
+
* operator — *provided* nobody declared it (next question). Prune it.
|
|
45
|
+
* 3. **Does chant's own manifest *also* set this path?** — independent of
|
|
46
|
+
* who currently owns it live. This is the case the issue calls out by
|
|
47
|
+
* name: **a contested field chant declared is drift-relevant.** The
|
|
48
|
+
* reasoning: chant's source is a statement of intent regardless of
|
|
49
|
+
* whether a previous apply is what currently holds the field, and a
|
|
50
|
+
* foreign write that overrides a value chant's manifest asks for is
|
|
51
|
+
* exactly the thing `lifecycle diff --live` exists to surface — silencing
|
|
52
|
+
* it because *something else* currently owns the field would hide the one
|
|
53
|
+
* case where chant and the cluster disagree about a property chant is
|
|
54
|
+
* actively declaring. So: foreign-owned *and* declared is never pruned by
|
|
55
|
+
* this rule, whatever #2 would otherwise do.
|
|
56
|
+
*
|
|
57
|
+
* `counterpart: "unknown"` and why it does not help here: core's own
|
|
58
|
+
* counterpart tri-state (`side === "live" && counterpart === "absent"`, what
|
|
59
|
+
* {@link K8S_SERVICE_DEFAULTS} is gated on) answers "did the *declared tree*
|
|
60
|
+
* carry this path at all" — which is exactly question 3, but it is computed
|
|
61
|
+
* by *core*, from the raw declared and raw live trees, only on the **second**
|
|
62
|
+
* normalization pass (`packages/core/src/lifecycle/deep-observe.ts`'s
|
|
63
|
+
* `diffDeepObservation`). This reader's own call to
|
|
64
|
+
* {@link normalizeDeepProperties} runs first, with no `counterpartPaths`
|
|
65
|
+
* supplied — same as the other three rows — so `counterpart` is `"unknown"`
|
|
66
|
+
* for every node here, and a rule gated on `"absent"` would never fire during
|
|
67
|
+
* this read at all. That is fine for a table keyed only by entityType (AWS's
|
|
68
|
+
* service defaults are subtracted on core's later pass, not this one), but it
|
|
69
|
+
* is *not* fine for the managed-fields rule: if this reader left
|
|
70
|
+
* `spec.replicas` in the tree waiting for core's second pass to prune it,
|
|
71
|
+
* core would be normalizing with the *static* `k8sDeepNormalizationHooks`,
|
|
72
|
+
* which has no managedFields for this object in hand either (this reader
|
|
73
|
+
* already computed its ownership sets from data it read once and does not
|
|
74
|
+
* return). So question 3 is answered **here**, directly against the declared
|
|
75
|
+
* `props` this reader was handed — not via `counterpart` — and the result is
|
|
76
|
+
* baked into what this reader returns: a foreign-owned, undeclared field is
|
|
77
|
+
* gone from the tree before it ever reaches core, and a contested one is
|
|
78
|
+
* still there for the ordinary declared-vs-live comparison to catch.
|
|
79
|
+
*
|
|
80
|
+
* ## Resolving a managedFields entry against a live array
|
|
81
|
+
*
|
|
82
|
+
* `metadata.managedFields[].fieldsV1` encodes a list item three ways —
|
|
83
|
+
* `k:{"name":"web"}` (by key), `v:"blue"` (by value), `i:3` (by index) — and
|
|
84
|
+
* chant #1178's `managed-fields.ts` renders all three to a path *string*
|
|
85
|
+
* (`.spec.containers[name="web"]`) for display. Parsing that string back
|
|
86
|
+
* would be ambiguous in general (a label key may itself contain `.`, e.g.
|
|
87
|
+
* `app.kubernetes.io/name`, indistinguishable in the rendered form from
|
|
88
|
+
* nested fields), so this reader does not parse strings at all: it walks the
|
|
89
|
+
* *structured* `fieldsV1` tree directly (`walkOwnership`, below), matching
|
|
90
|
+
* each `k:`/`v:` entry against the actual live array to find the concrete
|
|
91
|
+
* index, and against the actual declared array (by the same key or value) to
|
|
92
|
+
* answer question 3 for that element specifically — a declared container
|
|
93
|
+
* named `app` at index 0 is still "declared" if a sidecar gets injected in
|
|
94
|
+
* front of it and shifts it to live index 1.
|
|
95
|
+
*
|
|
96
|
+
* ## What is deliberately not modeled
|
|
97
|
+
*
|
|
98
|
+
* A manager's `.` entry (SSA's "I own this element's presence") on a
|
|
99
|
+
* container that also sets no scalar sub-fields (unusual — a real apply
|
|
100
|
+
* setting a container almost always sets `image`/`name` too) would not confer
|
|
101
|
+
* ownership onto a sibling field nobody's `fieldsV1` entry lists explicitly.
|
|
102
|
+
* That is the conservative direction: a path absent from every manager's
|
|
103
|
+
* fields is treated as "no ownership information", which leaves it diffable
|
|
104
|
+
* rather than silently pruned. Reported drift that turns out to be more
|
|
105
|
+
* noise than expected is a tuning problem; drift silently dropped is not.
|
|
106
|
+
*
|
|
107
|
+
* ## `buildOwnershipSets` lives in core now
|
|
108
|
+
*
|
|
109
|
+
* The ownership walk itself (`walkOwnership`, resolving a `fieldsV1` tree
|
|
110
|
+
* against the live and declared trees) has nothing k8s-*lexicon*-specific
|
|
111
|
+
* about it — it is generic Kubernetes SSA machinery. Chant #1087 (GCP's row,
|
|
112
|
+
* reusing this one, since a Config Connector CR is a Kubernetes object too)
|
|
113
|
+
* moved it to `@intentius/chant/managed-fields` rather than have gcp depend
|
|
114
|
+
* on this lexicon's package, the same reason chant #1100's
|
|
115
|
+
* `resolveClusterTarget` lives in core. This module re-exports
|
|
116
|
+
* `buildOwnershipSets`/`OwnershipSets` from there so nothing here changes for
|
|
117
|
+
* an existing importer of this file.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
import type { K8sObject } from "@intentius/chant-k8s-client";
|
|
121
|
+
import type {
|
|
122
|
+
DeepNormalizationHooks,
|
|
123
|
+
DeepObservationResult,
|
|
124
|
+
DeepResourceObservation,
|
|
125
|
+
UnobservedEntity,
|
|
126
|
+
} from "@intentius/chant/lexicon";
|
|
127
|
+
import { deepObservation, normalizeDeepProperties } from "@intentius/chant/deep-observation";
|
|
128
|
+
import { unobservedAll } from "@intentius/chant/observation";
|
|
129
|
+
import { hasOwnershipMarker, LABEL_OWNERSHIP_KEYS } from "@intentius/chant/ownership";
|
|
130
|
+
import { buildOwnershipSets, pruneByOwnership, type OwnershipSets } from "@intentius/chant/managed-fields";
|
|
131
|
+
import { defaultK8sConnector, type K8sConnector } from "./api/connect";
|
|
132
|
+
import {
|
|
133
|
+
classifyApiFailure,
|
|
134
|
+
isMissingClientPackage,
|
|
135
|
+
isWholeLexiconFailure,
|
|
136
|
+
MISSING_CLIENT_DETAIL,
|
|
137
|
+
} from "./api/classify";
|
|
138
|
+
import { operationFor } from "./api/operation-surface";
|
|
139
|
+
import { k8sDeepNormalizationHooks } from "./deep-observe-hooks";
|
|
140
|
+
|
|
141
|
+
// Re-exported so a dynamic importer of this module (plugin.ts's
|
|
142
|
+
// `observeResourcesDeep`, a test) can get the reader and its hooks from one
|
|
143
|
+
// place, the same shape AWS/Azure/Temporal's single deep-observe.ts offers.
|
|
144
|
+
// `plugin.ts`'s own `deepNormalizationHooks` field imports the hooks
|
|
145
|
+
// separately, directly from `./deep-observe-hooks` — that file has no
|
|
146
|
+
// dependency on `@intentius/chant-k8s-client`, so it is safe to import
|
|
147
|
+
// statically; this module is not (see the module doc).
|
|
148
|
+
export { k8sDeepNormalizationHooks, buildOwnershipSets, type OwnershipSets };
|
|
149
|
+
|
|
150
|
+
export interface K8sDeepObserveOptions {
|
|
151
|
+
environment: string;
|
|
152
|
+
buildOutput?: string;
|
|
153
|
+
entityNames: string[];
|
|
154
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
155
|
+
stack?: string;
|
|
156
|
+
owned?: boolean;
|
|
157
|
+
/** Directory whose `chant.config.ts` carries the cluster binding. Defaults to cwd. */
|
|
158
|
+
cwd?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The managed-fields prune, composed with the static rules, for one
|
|
163
|
+
* resource's normalization call. See the module doc for the three-question
|
|
164
|
+
* rule this encodes (`@intentius/chant/managed-fields`'s `pruneByOwnership`).
|
|
165
|
+
*/
|
|
166
|
+
function perResourceHooks(sets: OwnershipSets): DeepNormalizationHooks {
|
|
167
|
+
return {
|
|
168
|
+
prune(node) {
|
|
169
|
+
if (k8sDeepNormalizationHooks.prune?.(node)) return true;
|
|
170
|
+
return pruneByOwnership(node, sets);
|
|
171
|
+
},
|
|
172
|
+
orderKey: k8sDeepNormalizationHooks.orderKey,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The live object minus the envelope fields that live outside `properties` on {@link DeepResourceObservation} (mirrors `type`/`physicalId`). */
|
|
177
|
+
function propertiesTreeOf(obj: K8sObject): Record<string, unknown> {
|
|
178
|
+
const { apiVersion: _apiVersion, kind: _kind, ...rest } = obj;
|
|
179
|
+
return rest;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Read the live property tree for each declared entity, pruning by
|
|
184
|
+
* `metadata.managedFields` (see the module doc). Reuses the same connector,
|
|
185
|
+
* discovery/caching client and tri-state failure classification as the thin
|
|
186
|
+
* read (`./describe-resources.ts`) — the binding check (chant #1100/#1155)
|
|
187
|
+
* still refuses before any resource is touched, and a connect failure still
|
|
188
|
+
* becomes NOT-OBSERVED for every declared entity rather than an empty result.
|
|
189
|
+
*/
|
|
190
|
+
export async function observeResourcesDeepK8s(
|
|
191
|
+
options: K8sDeepObserveOptions,
|
|
192
|
+
connect: K8sConnector = defaultK8sConnector,
|
|
193
|
+
): Promise<DeepObservationResult> {
|
|
194
|
+
const { managedFieldsOf, isChantFieldManager } = await import("@intentius/chant-k8s-client");
|
|
195
|
+
|
|
196
|
+
const declared = [...options.entities].map(([entityName, entity]) => ({
|
|
197
|
+
entityName,
|
|
198
|
+
entityType: entity.entityType,
|
|
199
|
+
props: entity.props,
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
let client;
|
|
203
|
+
try {
|
|
204
|
+
({ client } = await connect({ environment: options.environment, cwd: options.cwd }));
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (isMissingClientPackage(err)) {
|
|
207
|
+
return deepObservation(
|
|
208
|
+
{},
|
|
209
|
+
unobservedAll(
|
|
210
|
+
declared.map((d) => d.entityName),
|
|
211
|
+
"read-failed",
|
|
212
|
+
MISSING_CLIENT_DETAIL,
|
|
213
|
+
options.entities,
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (isWholeLexiconFailure(err)) {
|
|
218
|
+
const outcome = classifyApiFailure(err);
|
|
219
|
+
return deepObservation(
|
|
220
|
+
{},
|
|
221
|
+
unobservedAll(
|
|
222
|
+
declared.map((d) => d.entityName),
|
|
223
|
+
outcome.kind === "unobserved" ? outcome.reason : "read-failed",
|
|
224
|
+
outcome.kind === "unobserved" ? outcome.detail : undefined,
|
|
225
|
+
options.entities,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
// A cluster-binding mismatch (chant #1100) — refuse loudly, same as the
|
|
230
|
+
// thin path; core turns the throw into NOT-OBSERVED for every entity.
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const resources: Record<string, DeepResourceObservation> = {};
|
|
235
|
+
const unobserved: Record<string, UnobservedEntity> = {};
|
|
236
|
+
|
|
237
|
+
await client.concurrently(declared, async ({ entityName, entityType, props }) => {
|
|
238
|
+
const operation = operationFor(entityType);
|
|
239
|
+
if (!operation) {
|
|
240
|
+
unobserved[entityName] = {
|
|
241
|
+
type: entityType,
|
|
242
|
+
reason: "unsupported-kind",
|
|
243
|
+
detail: `no generated operation surface for ${entityType} — run \`chant generate\` in the k8s lexicon, or declare the CRD as a codegen source`,
|
|
244
|
+
};
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const metadata = props.metadata as { name?: string; namespace?: string } | undefined;
|
|
249
|
+
const name = metadata?.name;
|
|
250
|
+
if (!name) {
|
|
251
|
+
unobserved[entityName] = {
|
|
252
|
+
type: entityType,
|
|
253
|
+
reason: "read-failed",
|
|
254
|
+
detail: "declared entity has no metadata.name to query by",
|
|
255
|
+
};
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const obj = await client.read({
|
|
261
|
+
apiVersion: operation.apiVersion,
|
|
262
|
+
kind: operation.kind,
|
|
263
|
+
name,
|
|
264
|
+
...(metadata?.namespace ? { namespace: metadata.namespace } : {}),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (options.owned && !hasOwnershipMarker(obj.metadata?.labels, LABEL_OWNERSHIP_KEYS)) {
|
|
268
|
+
unobserved[entityName] = {
|
|
269
|
+
type: entityType,
|
|
270
|
+
reason: "filtered",
|
|
271
|
+
detail: "live object carries no chant ownership marker and --owned was requested",
|
|
272
|
+
};
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const liveRoot = propertiesTreeOf(obj);
|
|
277
|
+
const sets = buildOwnershipSets(managedFieldsOf(obj), liveRoot, props, isChantFieldManager);
|
|
278
|
+
|
|
279
|
+
resources[entityName] = {
|
|
280
|
+
type: entityType,
|
|
281
|
+
physicalId: obj.metadata?.uid,
|
|
282
|
+
properties: normalizeDeepProperties(liveRoot, {
|
|
283
|
+
entityType,
|
|
284
|
+
side: "live",
|
|
285
|
+
hooks: perResourceHooks(sets),
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
} catch (err) {
|
|
289
|
+
const outcome = classifyApiFailure(err);
|
|
290
|
+
if (outcome.kind === "unobserved") {
|
|
291
|
+
unobserved[entityName] = { type: entityType, reason: outcome.reason, detail: outcome.detail };
|
|
292
|
+
}
|
|
293
|
+
// `absent` records nothing — the thin read already reports the
|
|
294
|
+
// non-existence; restating it here would turn one finding into two.
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return deepObservation(resources, unobserved);
|
|
299
|
+
}
|