@intentius/chant-lexicon-gcp 0.124.31 → 0.124.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/index.js +45 -0
- package/dist/api/read-client.js +105 -0
- package/dist/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +115 -0
- package/dist/codegen/generate-cli.js +17 -0
- package/dist/codegen/generate.js +214 -0
- package/dist/codegen/naming.js +164 -0
- package/dist/codegen/package.js +43 -0
- package/dist/composites/catalog.js +1245 -0
- package/dist/composites/cloud-function.js +65 -0
- package/dist/composites/cloud-run-service.js +71 -0
- package/dist/composites/cloud-sql-instance.js +85 -0
- package/dist/composites/gcs-bucket.js +64 -0
- package/dist/composites/gke-cluster.js +113 -0
- package/dist/composites/gke-crdb-region.js +137 -0
- package/dist/composites/gpu-node-pool.js +74 -0
- package/dist/composites/index.js +15 -0
- package/dist/composites/landing-zone.js +89 -0
- package/dist/composites/managed-certificate.js +46 -0
- package/dist/composites/memorystore-redis.js +52 -0
- package/dist/composites/multi-region-vpc.js +108 -0
- package/dist/composites/private-service.js +50 -0
- package/dist/composites/pubsub-pipeline.js +63 -0
- package/dist/composites/secure-project.js +85 -0
- package/dist/composites/vpc-network.js +113 -0
- package/dist/coverage.js +28 -0
- package/dist/deep-observe-hooks.js +152 -0
- package/dist/deep-observe.js +273 -0
- package/dist/default-labels.js +67 -0
- package/dist/describe-resources.js +264 -0
- package/dist/detect.js +16 -0
- package/dist/export-resources.js +59 -0
- package/dist/generated/index.js +1804 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +98 -0
- package/dist/import/generator.js +68 -0
- package/dist/import/live-export.js +73 -0
- package/dist/import/parser.js +61 -0
- package/dist/index.js +36 -0
- package/dist/integrity.json +3 -3
- package/dist/lint/audit-catalog.js +38 -0
- package/dist/lint/audit-lineage.js +63 -0
- package/dist/lint/post-synth/gcp-helpers.js +91 -0
- package/dist/lint/post-synth/index.js +54 -0
- package/dist/lint/post-synth/schema-registry.d.ts.map +1 -1
- package/dist/lint/post-synth/schema-registry.js +69 -0
- package/dist/lint/post-synth/wgc101.js +47 -0
- package/dist/lint/post-synth/wgc102.js +29 -0
- package/dist/lint/post-synth/wgc103.js +37 -0
- package/dist/lint/post-synth/wgc104.js +34 -0
- package/dist/lint/post-synth/wgc105.js +39 -0
- package/dist/lint/post-synth/wgc106.js +30 -0
- package/dist/lint/post-synth/wgc107.js +33 -0
- package/dist/lint/post-synth/wgc108.js +34 -0
- package/dist/lint/post-synth/wgc109.js +33 -0
- package/dist/lint/post-synth/wgc110.js +32 -0
- package/dist/lint/post-synth/wgc111.js +47 -0
- package/dist/lint/post-synth/wgc112.js +46 -0
- package/dist/lint/post-synth/wgc113.js +35 -0
- package/dist/lint/post-synth/wgc201.js +30 -0
- package/dist/lint/post-synth/wgc202.js +33 -0
- package/dist/lint/post-synth/wgc203.js +35 -0
- package/dist/lint/post-synth/wgc204.js +33 -0
- package/dist/lint/post-synth/wgc301.js +28 -0
- package/dist/lint/post-synth/wgc302.js +28 -0
- package/dist/lint/post-synth/wgc303.js +29 -0
- package/dist/lint/post-synth/wgc401.js +50 -0
- package/dist/lint/post-synth/wgc402.js +46 -0
- package/dist/lint/post-synth/wgc403.js +74 -0
- package/dist/lint/post-synth/wgc501.js +63 -0
- package/dist/lint/post-synth/wgc502.js +37 -0
- package/dist/lint/post-synth/wgc503.js +49 -0
- package/dist/lint/rules/hardcoded-project.js +51 -0
- package/dist/lint/rules/hardcoded-region.js +51 -0
- package/dist/lint/rules/index.js +3 -0
- package/dist/lint/rules/public-iam.js +37 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +15 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +46 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/floci-gcp.js +49 -0
- package/dist/op/activities/gcp-apply.js +618 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +50 -0
- package/dist/ownership.js +38 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +371 -0
- package/dist/pseudo.js +15 -0
- package/dist/rules/schema-registry.ts +2 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +233 -0
- package/dist/spec/fetch.js +107 -0
- package/dist/spec/parse.js +323 -0
- package/dist/validate-cli.js +11 -0
- package/dist/validate.js +24 -0
- package/dist/variables.js +15 -0
- package/package.json +8 -8
- package/src/codegen/package.ts +2 -2
- package/src/lint/post-synth/schema-registry.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP thin observation (#1209) — presence and scrubbed outputs, read over the
|
|
3
|
+
* applier's own REST transport.
|
|
4
|
+
*
|
|
5
|
+
* chant applies GCP cluster-free over direct REST (#706). Until this, it
|
|
6
|
+
* observed through a Config Connector cluster — `kubectl get <cnrm-gvk> -o
|
|
7
|
+
* json` per entity — which is the split #1085's principle forbids: a lexicon
|
|
8
|
+
* observed on a different transport than it is applied with can disagree with
|
|
9
|
+
* itself about what a resource even is, and needs a GKE cluster to answer a
|
|
10
|
+
* question about a bucket.
|
|
11
|
+
*
|
|
12
|
+
* Every GET here is built by the same `ResourceMapper` the applier uses (see
|
|
13
|
+
* ./api/read-client.ts), so reader and applier cannot diverge about where a
|
|
14
|
+
* resource lives.
|
|
15
|
+
*
|
|
16
|
+
* ## What changed in the answers, not just the transport
|
|
17
|
+
*
|
|
18
|
+
* **Status.** Config Connector encodes state as a `Ready` condition, which a
|
|
19
|
+
* GCP REST payload has no equivalent of — a bucket simply exists. So a
|
|
20
|
+
* successful GET is `PRESENT` unless the body carries a recognisable state of
|
|
21
|
+
* its own (Cloud Run's `status.conditions`, an explicit `state`). `PRESENT` is
|
|
22
|
+
* the same sentinel the Azure reader emits for the same reason.
|
|
23
|
+
*
|
|
24
|
+
* **Ownership.** CNRM carried chant's marker as a k8s label. The REST payloads
|
|
25
|
+
* carry `labels` only on kinds that have them (a bucket does; a Pub/Sub topic
|
|
26
|
+
* does not), so ownership is `unknown` where there is nothing to read — and
|
|
27
|
+
* `--owned` degrades to detect-only rather than withholding everything it
|
|
28
|
+
* cannot prove, the same posture the AWS thin path takes when
|
|
29
|
+
* `describe-stack-resources` returns no tags.
|
|
30
|
+
*
|
|
31
|
+
* **Coverage.** kubectl could fetch any CNRM kind the cluster knew about; REST
|
|
32
|
+
* reaches the kinds the applier has a mapper for. That is narrower on paper and
|
|
33
|
+
* not in practice: a kind with no mapper cannot be applied either, so observing
|
|
34
|
+
* it produced a live tree nothing could reconcile against. Anything outside the
|
|
35
|
+
* table reports NOT-OBSERVED with `unsupported-kind` (#1089) rather than being
|
|
36
|
+
* dropped.
|
|
37
|
+
*/
|
|
38
|
+
import { observation } from "@intentius/chant/observation";
|
|
39
|
+
import { hasOwnershipMarker, classifyOwnership, readOwnership } from "@intentius/chant/ownership";
|
|
40
|
+
import { getResource, mapperForKind, GcpReadError, isNotFound } from "./api/read-client.js";
|
|
41
|
+
import { resolveGcpProject, parseManifest } from "./op/activities/gcp-apply.js";
|
|
42
|
+
import { resolveGVK } from "./serializer.js";
|
|
43
|
+
/**
|
|
44
|
+
* chant's ownership marker as GCP labels.
|
|
45
|
+
*
|
|
46
|
+
* The applier stamps `managed-by: chant` (gcp-apply.ts) because GCP label keys
|
|
47
|
+
* cannot hold the k8s `app.kubernetes.io/managed-by` slash/dot form that
|
|
48
|
+
* `LABEL_OWNERSHIP_KEYS` uses — so the reader has to look for what the applier
|
|
49
|
+
* actually wrote, not for the CNRM label the kubectl path read. The stack/env
|
|
50
|
+
* keys follow the same flattening.
|
|
51
|
+
*/
|
|
52
|
+
const GCP_OWNERSHIP_LABEL_KEYS = {
|
|
53
|
+
managedBy: "managed-by",
|
|
54
|
+
stack: "chant-stack",
|
|
55
|
+
env: "chant-env",
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The CNRM group/kind for an entity type, resolved through the serializer's
|
|
59
|
+
* own GVK map so the reader and the manifest agree on the kind's casing —
|
|
60
|
+
* `GCP::Pubsub::Topic` is `PubSubTopic` in CNRM and in the applier's mapper
|
|
61
|
+
* table, while a naive `${service}${shortKind}` concatenation would produce
|
|
62
|
+
* `PubsubTopic` and silently miss the mapper for 4 of the 6 appliable kinds.
|
|
63
|
+
*/
|
|
64
|
+
export function deriveGVK(entityType) {
|
|
65
|
+
const resolved = resolveGVK(entityType);
|
|
66
|
+
if (!resolved)
|
|
67
|
+
return null;
|
|
68
|
+
return { group: resolved.apiVersion.split("/")[0], kind: resolved.kind };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The `cnrm.cloud.google.com/project-id` annotations in the built manifest,
|
|
72
|
+
* keyed `kind/name`.
|
|
73
|
+
*
|
|
74
|
+
* The declared entities the observe paths receive are discovery output, and a
|
|
75
|
+
* project-wide `defaultAnnotations({"cnrm.cloud.google.com/project-id": …})`
|
|
76
|
+
* is a separate declarable the serializer merges into each document at
|
|
77
|
+
* synthesis — the entity's own props never see it. The applier resolves its
|
|
78
|
+
* project from the manifest for exactly that reason, so the readers do too:
|
|
79
|
+
* without this, every entity relying on the default annotation reports
|
|
80
|
+
* `no-binding` unless the shell happens to export GOOGLE_CLOUD_PROJECT.
|
|
81
|
+
*/
|
|
82
|
+
export function manifestProjectAnnotations(buildOutput) {
|
|
83
|
+
const out = new Map();
|
|
84
|
+
if (!buildOutput)
|
|
85
|
+
return out;
|
|
86
|
+
try {
|
|
87
|
+
for (const doc of parseManifest(buildOutput, "<build output>")) {
|
|
88
|
+
const project = doc.metadata?.annotations?.["cnrm.cloud.google.com/project-id"];
|
|
89
|
+
if (doc.kind && doc.metadata?.name && typeof project === "string") {
|
|
90
|
+
out.set(`${doc.kind}/${doc.metadata.name}`, project);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// An unparsable build output resolves nothing — per-entity annotations and
|
|
96
|
+
// GOOGLE_CLOUD_PROJECT still apply, and a truly unresolvable project is
|
|
97
|
+
// still reported as `no-binding` per entity.
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The project a read should target: `GOOGLE_CLOUD_PROJECT` / the entity's own
|
|
103
|
+
* annotation (via `resolveGcpProject`, the applier's rule), then the built
|
|
104
|
+
* manifest's merged annotation. Throws the applier's own error when nothing
|
|
105
|
+
* resolves, so the `no-binding` detail names both knobs.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveReadProject(kind, name, metadata, manifestProjects) {
|
|
108
|
+
try {
|
|
109
|
+
return resolveGcpProject({ kind, metadata });
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const fromManifest = manifestProjects.get(`${kind}/${name}`);
|
|
113
|
+
if (fromManifest)
|
|
114
|
+
return fromManifest;
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function pruneUndefined(obj) {
|
|
119
|
+
const out = {};
|
|
120
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
121
|
+
if (v !== undefined)
|
|
122
|
+
out[k] = v;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Status from a REST payload.
|
|
128
|
+
*
|
|
129
|
+
* Most GCP resources have no status at all — a bucket that answers a GET simply
|
|
130
|
+
* exists — so `PRESENT` is the honest answer and the common one. Where a kind
|
|
131
|
+
* does carry state (Cloud Run's `status.conditions`, a `state` enum), that is
|
|
132
|
+
* reported instead.
|
|
133
|
+
*
|
|
134
|
+
* `PRESENT` is deliberately the same sentinel the Azure reader emits for a
|
|
135
|
+
* resource with no `provisioningState`: one word, meaning "read it back, it is
|
|
136
|
+
* there, there is nothing richer to say".
|
|
137
|
+
*/
|
|
138
|
+
export function statusFromRest(obj) {
|
|
139
|
+
if (typeof obj.state === "string" && obj.state.length > 0)
|
|
140
|
+
return obj.state;
|
|
141
|
+
const conditions = obj.status?.conditions ?? [];
|
|
142
|
+
const ready = conditions.find((c) => c.type === "Ready");
|
|
143
|
+
if (ready) {
|
|
144
|
+
if (ready.status === "True" || ready.state === "CONDITION_SUCCEEDED")
|
|
145
|
+
return "READY";
|
|
146
|
+
return ready.reason ?? "NOT_READY";
|
|
147
|
+
}
|
|
148
|
+
if (conditions.length > 0) {
|
|
149
|
+
return conditions.map((c) => `${c.type}=${c.status ?? c.state}`).join(",");
|
|
150
|
+
}
|
|
151
|
+
return "PRESENT";
|
|
152
|
+
}
|
|
153
|
+
export async function describeResources(options) {
|
|
154
|
+
const result = {};
|
|
155
|
+
const unobserved = {};
|
|
156
|
+
// The endpoint override is the emulator tell (floci-gcp :4588); unset means
|
|
157
|
+
// real GCP, exactly as it does for the applier.
|
|
158
|
+
const endpoint = process.env.GCP_ENDPOINT_URL;
|
|
159
|
+
// `--owned` needs labels to filter on, and only some kinds carry them. Rather
|
|
160
|
+
// than withhold everything it cannot prove, this warns once and degrades to
|
|
161
|
+
// detect-only — the same posture the AWS thin path takes when
|
|
162
|
+
// `describe-stack-resources` returns no tags.
|
|
163
|
+
let warnedOwnership = false;
|
|
164
|
+
const manifestProjects = manifestProjectAnnotations(options.buildOutput);
|
|
165
|
+
const reads = [...options.entities].map(async ([entityName, { entityType, props }]) => {
|
|
166
|
+
const gvk = deriveGVK(entityType);
|
|
167
|
+
if (!gvk) {
|
|
168
|
+
unobserved[entityName] = {
|
|
169
|
+
type: entityType,
|
|
170
|
+
reason: "unsupported-kind",
|
|
171
|
+
detail: `cannot derive a GCP kind from ${entityType}`,
|
|
172
|
+
};
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!mapperForKind(gvk.kind)) {
|
|
176
|
+
// Outside the applier's dispatch table: chant cannot write this kind, so
|
|
177
|
+
// it does not claim to have read it either (#1089).
|
|
178
|
+
unobserved[entityName] = {
|
|
179
|
+
type: entityType,
|
|
180
|
+
reason: "unsupported-kind",
|
|
181
|
+
detail: `no REST mapper for ${gvk.kind} — chant cannot apply this kind either`,
|
|
182
|
+
};
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const metadata = props.metadata;
|
|
186
|
+
const name = metadata?.name;
|
|
187
|
+
if (!name) {
|
|
188
|
+
unobserved[entityName] = {
|
|
189
|
+
type: entityType,
|
|
190
|
+
reason: "read-failed",
|
|
191
|
+
detail: "declared entity has no metadata.name to query by",
|
|
192
|
+
};
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
let client;
|
|
196
|
+
try {
|
|
197
|
+
client = {
|
|
198
|
+
project: resolveReadProject(gvk.kind, name, metadata, manifestProjects),
|
|
199
|
+
...(endpoint ? { endpoint } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
// No project resolvable — nothing was queried, so nothing is known.
|
|
204
|
+
unobserved[entityName] = {
|
|
205
|
+
type: entityType,
|
|
206
|
+
reason: "no-binding",
|
|
207
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
208
|
+
};
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const obj = (await getResource(client, gvk.kind, name, props));
|
|
213
|
+
if (options.owned) {
|
|
214
|
+
if (obj.labels == null) {
|
|
215
|
+
if (!warnedOwnership) {
|
|
216
|
+
// eslint-disable-next-line no-console
|
|
217
|
+
console.warn(`[gcp] ownership filter unavailable for ${gvk.kind} (the REST payload carries no labels) — returning it with an \`unknown\` verdict rather than withholding it`);
|
|
218
|
+
warnedOwnership = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (!hasOwnershipMarker(obj.labels, GCP_OWNERSHIP_LABEL_KEYS)) {
|
|
222
|
+
// Withheld is not absent (#1089) — the resource exists, it just isn't chant's.
|
|
223
|
+
unobserved[entityName] = {
|
|
224
|
+
type: entityType,
|
|
225
|
+
reason: "filtered",
|
|
226
|
+
detail: "live resource carries no chant ownership marker and --owned was requested",
|
|
227
|
+
};
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
result[entityName] = {
|
|
232
|
+
type: entityType,
|
|
233
|
+
physicalId: obj.id ?? obj.selfLink ?? obj.name,
|
|
234
|
+
status: statusFromRest(obj),
|
|
235
|
+
lastUpdated: obj.updated ?? obj.timeCreated,
|
|
236
|
+
ownership: obj.labels == null ? "unknown" : classifyOwnership(obj.labels, GCP_OWNERSHIP_LABEL_KEYS),
|
|
237
|
+
// Marker identity (#1222): stack/env from the resource's own labels.
|
|
238
|
+
// Undefined when the payload carries no labels or no managed-by marker.
|
|
239
|
+
marker: readOwnership(obj.labels ?? undefined, GCP_OWNERSHIP_LABEL_KEYS),
|
|
240
|
+
attributes: pruneUndefined({
|
|
241
|
+
labels: obj.labels ?? undefined,
|
|
242
|
+
selfLink: obj.selfLink,
|
|
243
|
+
}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
// A 404 is a real absence. Anything else — no credentials, an
|
|
248
|
+
// unreachable endpoint, a body that will not parse — proves nothing and
|
|
249
|
+
// is a hole rather than an absence (#1089).
|
|
250
|
+
if (isNotFound(err))
|
|
251
|
+
return;
|
|
252
|
+
const status = err instanceof GcpReadError ? err.status : undefined;
|
|
253
|
+
const noCreds = status === 401 || status === 403;
|
|
254
|
+
unobserved[entityName] = {
|
|
255
|
+
type: entityType,
|
|
256
|
+
reason: noCreds ? "no-credentials" : "read-failed",
|
|
257
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Concurrent, where the kubectl path was one spawn after another (#1201/#1209).
|
|
262
|
+
await Promise.all(reads);
|
|
263
|
+
return observation(result, unobserved);
|
|
264
|
+
}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Edge-safe template detection for gcp (Config Connector) — see #426. */
|
|
2
|
+
export function detectTemplate(data) {
|
|
3
|
+
// Handle raw string input (unparsed YAML)
|
|
4
|
+
if (typeof data === "string") {
|
|
5
|
+
return data.includes("cnrm.cloud.google.com");
|
|
6
|
+
}
|
|
7
|
+
// Handle parsed YAML objects
|
|
8
|
+
if (typeof data !== "object" || data === null)
|
|
9
|
+
return false;
|
|
10
|
+
const obj = data;
|
|
11
|
+
const apiVersion = obj.apiVersion;
|
|
12
|
+
if (typeof apiVersion === "string" && apiVersion.includes("cnrm.cloud.google.com")) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live export for the GCP Config Connector lexicon — implements
|
|
3
|
+
* LexiconPlugin.exportResources() so `chant import --from <gcp-env>`
|
|
4
|
+
* regenerates GCP resources as chant TypeScript.
|
|
5
|
+
*
|
|
6
|
+
* Config Connector encodes each GCP resource as a Kubernetes CRD on a
|
|
7
|
+
* management cluster, so export reads live CC objects with kubectl and maps
|
|
8
|
+
* them to the import IR. A type selector targets one kind; otherwise every
|
|
9
|
+
* `*.cnrm.cloud.google.com` resource kind is discovered and swept. All I/O
|
|
10
|
+
* lives here; cleaning and IR-building is pure in `./import/live-export`.
|
|
11
|
+
*/
|
|
12
|
+
import { exec } from "node:child_process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { deriveGVK } from "./describe-resources.js";
|
|
15
|
+
import { buildExportFromObjects } from "./import/live-export.js";
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
/** Discover all Config Connector resource kinds present in the cluster. */
|
|
18
|
+
async function discoverCnrmResources() {
|
|
19
|
+
try {
|
|
20
|
+
const { stdout } = await execAsync("kubectl api-resources -o name");
|
|
21
|
+
return stdout
|
|
22
|
+
.split("\n")
|
|
23
|
+
.map((l) => l.trim())
|
|
24
|
+
.filter((l) => l.endsWith(".cnrm.cloud.google.com"));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function exportResources(options) {
|
|
31
|
+
let resources;
|
|
32
|
+
if (options.selector?.type) {
|
|
33
|
+
const gvk = deriveGVK(options.selector.type);
|
|
34
|
+
resources = gvk ? [`${gvk.kind.toLowerCase()}.${gvk.group}`] : [];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
resources = await discoverCnrmResources();
|
|
38
|
+
}
|
|
39
|
+
if (resources.length === 0) {
|
|
40
|
+
return { resources: [], parameters: [] };
|
|
41
|
+
}
|
|
42
|
+
const objects = [];
|
|
43
|
+
for (const resource of resources) {
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execAsync(["kubectl", "get", resource, "-A", "-o", "json"].join(" "));
|
|
46
|
+
const list = JSON.parse(stdout);
|
|
47
|
+
if (Array.isArray(list.items))
|
|
48
|
+
objects.push(...list.items);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Kind absent / RBAC denied — skip, don't fail the whole export.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return buildExportFromObjects(objects, {
|
|
55
|
+
verbatim: options.verbatim,
|
|
56
|
+
selector: options.selector,
|
|
57
|
+
owned: options.owned,
|
|
58
|
+
});
|
|
59
|
+
}
|