@codiac.io/codiac-cli 1.3.229 → 1.3.232

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.
@@ -0,0 +1,799 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const k8s = require("@kubernetes/client-node");
6
+ const zlib = require("zlib");
7
+ const YAML = require("yaml");
8
+ const contracts_1 = require("../../apis/codiac-api/contracts");
9
+ const EXCLUDED_NAMESPACES = new Set([
10
+ "kube-system",
11
+ "kube-public",
12
+ "kube-node-lease",
13
+ // Azure-managed
14
+ "gatekeeper-system",
15
+ "calico-system",
16
+ "cert-manager",
17
+ "ingress-nginx",
18
+ "keda",
19
+ "azure-arc",
20
+ "tigera-operator",
21
+ // AWS-managed
22
+ "amazon-cloudwatch",
23
+ "aws-observability",
24
+ ]);
25
+ class ImportCluster extends core_1.Command {
26
+ constructor() {
27
+ super(...arguments);
28
+ /** Accumulates unique secret store names discovered during the scan. */
29
+ this.discoveredStores = new Set();
30
+ /** Accumulates enterprise-scoped `codiac pvc create` lines keyed by their prefixed FileStoreDef name (`<namespace>-<pvc>`).
31
+ * Map preserves insertion order; the prefix guarantees cross-namespace uniqueness so no dedupe logic is needed beyond key collision. */
32
+ this.discoveredPvcs = new Map();
33
+ /** Count of `# TODO:` comment lines emitted across all generated commands; surfaced as a summary banner at top of output. */
34
+ this.todoCount = 0;
35
+ /** Codiac registry codes that must already exist in the tenant for the generated `cod asset create` lines to succeed.
36
+ * Keyed by code, value is the cloud provider hint used to render the `cod imageRegistry capture` suggestion in the banner.
37
+ * `DockerHub|official` is intentionally excluded to keep the banner focused on registries the user must capture themselves. */
38
+ this.discoveredRegistries = new Map();
39
+ }
40
+ run() {
41
+ var _a, _b, _c, _d;
42
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
43
+ const { flags } = yield this.parse(ImportCluster);
44
+ if (!flags["run-in-cluster"]) {
45
+ this.log("The --run-in-cluster flag is required. Interactive mode is not yet supported.");
46
+ this.log("Usage: codiac import cluster --run-in-cluster [--enterprise <name>] [--environment <name>] [--cluster <name>] [--subscription-id <id>] [--generate-cabinet] [--generate-secret-store] [--generate-pvcs] [--generate-volume-mounts] [--keyvault-name <name>] [--include-default]");
47
+ return;
48
+ }
49
+ // The generated commands all rely on --enterprise. Catch the omission up front rather than emitting broken script lines.
50
+ const generatesAnything = Boolean(flags["generate-cabinet"] || flags["generate-secret-store"] || flags["generate-pvcs"] || flags["generate-volume-mounts"]);
51
+ if (generatesAnything && !flags.enterprise) {
52
+ throw new Error("--enterprise is required when using --generate-cabinet / --generate-secret-store / --generate-pvcs / --generate-volume-mounts.");
53
+ }
54
+ // `cabinet create --silent` requires environment AND cluster (see src/commands/cabinet/create.ts validatePartial).
55
+ if (flags["generate-cabinet"] && (!flags.environment || !flags.cluster)) {
56
+ throw new Error("--generate-cabinet requires both --environment and --cluster.");
57
+ }
58
+ const kubeConfig = new k8s.KubeConfig();
59
+ kubeConfig.loadFromCluster();
60
+ const coreApi = kubeConfig.makeApiClient(k8s.CoreV1Api);
61
+ const appsApi = kubeConfig.makeApiClient(k8s.AppsV1Api);
62
+ const networkingApi = kubeConfig.makeApiClient(k8s.NetworkingV1Api);
63
+ const storageApi = kubeConfig.makeApiClient(k8s.StorageV1Api);
64
+ const namespaces = yield this.listUserNamespaces(coreApi, (_a = flags["include-default"]) !== null && _a !== void 0 ? _a : false);
65
+ if (namespaces.length === 0) {
66
+ this.log("# No user namespaces found in cluster.");
67
+ return;
68
+ }
69
+ this.log(`# Found ${namespaces.length} namespace(s) to import.`);
70
+ // Fetch StorageClass list once so each PVC can resolve its FileStoreType via the className -> CSI driver map.
71
+ // Tolerate failure (e.g. RBAC denial) by falling back to an empty map; PVCs that can't resolve emit a TODO.
72
+ let storageClassByName = new Map();
73
+ if (flags["generate-pvcs"]) {
74
+ try {
75
+ const scList = yield storageApi.listStorageClass();
76
+ for (const sc of (_b = scList.items) !== null && _b !== void 0 ? _b : []) {
77
+ const name = (_c = sc.metadata) === null || _c === void 0 ? void 0 : _c.name;
78
+ if (name && sc.provisioner)
79
+ storageClassByName.set(name, sc.provisioner);
80
+ }
81
+ }
82
+ catch (err) {
83
+ this.log(`# WARNING: Failed to list StorageClasses (${(_d = err.message) !== null && _d !== void 0 ? _d : err}). PVC --type inference will fall back to PV CSI driver / TODO comments.`);
84
+ }
85
+ }
86
+ const allGroups = [];
87
+ const allHelmGroups = [];
88
+ for (const namespace of namespaces) {
89
+ const { groups, helmGroups } = yield this.scanNamespace(namespace, coreApi, appsApi, networkingApi, storageClassByName, flags);
90
+ allGroups.push(...groups);
91
+ allHelmGroups.push(...helmGroups);
92
+ }
93
+ this.outputCommands(allGroups, allHelmGroups, flags);
94
+ });
95
+ }
96
+ // --------------------------------------------------------------------
97
+ // Namespace Discovery
98
+ // --------------------------------------------------------------------
99
+ listUserNamespaces(coreApi, includeDefault) {
100
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
101
+ const result = yield coreApi.listNamespace();
102
+ return result.items
103
+ .map(ns => { var _a; return (_a = ns.metadata) === null || _a === void 0 ? void 0 : _a.name; })
104
+ .filter((name) => {
105
+ if (!name)
106
+ return false;
107
+ if (EXCLUDED_NAMESPACES.has(name))
108
+ return false;
109
+ if (name === "default" && !includeDefault)
110
+ return false;
111
+ return true;
112
+ })
113
+ .sort();
114
+ });
115
+ }
116
+ // --------------------------------------------------------------------
117
+ // Per-Namespace Scanning
118
+ // --------------------------------------------------------------------
119
+ scanNamespace(namespace, coreApi, appsApi, networkingApi, storageClassByName, flags) {
120
+ var _a, _b;
121
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
122
+ // Discover Helm releases first so their owned Deployments can be deduped out of the raw asset path.
123
+ const helmReleases = yield this.scanNamespaceHelmReleases(namespace, coreApi);
124
+ const releaseNames = new Set(helmReleases.map(r => r.name));
125
+ const helmGroups = helmReleases.map(r => ({
126
+ namespace,
127
+ releaseName: r.name,
128
+ lines: this.buildHelmReleaseCommands(r, flags),
129
+ }));
130
+ const deploymentResult = yield appsApi.listNamespacedDeployment({ namespace });
131
+ // Skip Deployments owned by a Helm release we successfully imported; they are represented by the helm asset instead.
132
+ const deployments = deploymentResult.items.filter(d => { var _a; return !!((_a = d.metadata) === null || _a === void 0 ? void 0 : _a.name) && !this.isHelmOwned(d, releaseNames); });
133
+ if (deployments.length === 0)
134
+ return { groups: [], helmGroups };
135
+ const services = (yield coreApi.listNamespacedService({ namespace })).items;
136
+ const ingresses = (yield networkingApi.listNamespacedIngress({ namespace })).items;
137
+ // Only list PVCs when at least one PVC-related emission flag is set; the API call is per-namespace.
138
+ const needsPvcs = flags["generate-pvcs"] || flags["generate-volume-mounts"];
139
+ const pvcsInNs = needsPvcs ? yield this.scanNamespacePvcs(namespace, coreApi) : new Map();
140
+ const groups = [];
141
+ for (let i = 0; i < deployments.length; i++) {
142
+ const deployment = deployments[i];
143
+ const matchingServices = this.findServicesForDeployment(deployment, services);
144
+ const matchingIngresses = this.findIngressesForServices(matchingServices, ingresses);
145
+ const group = {
146
+ namespace,
147
+ assetCommand: this.buildAssetCreateCommand(deployment, matchingIngresses, flags),
148
+ configCommands: this.buildConfigCommands(deployment, namespace, flags),
149
+ volumeMountCommands: [],
150
+ filestoreBindCommands: [],
151
+ };
152
+ if (needsPvcs && pvcsInNs.size > 0) {
153
+ const assetName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
154
+ const mounts = this.findPvcMountsForDeployment(deployment, pvcsInNs);
155
+ for (const mount of mounts) {
156
+ if (flags["generate-volume-mounts"]) {
157
+ group.volumeMountCommands.push(this.buildAssetVolumeCreateCommand(assetName, mount.volName, mount.mountPath, flags));
158
+ }
159
+ if (flags["generate-pvcs"]) {
160
+ const prefixedPvcName = `${namespace}-${mount.k8sPvcName}`;
161
+ const pvc = pvcsInNs.get(mount.k8sPvcName);
162
+ // Resolve the FileStoreType, with PV fallback for statically-bound PVCs.
163
+ const type = yield this.resolveFileStoreType(pvc, storageClassByName, coreApi);
164
+ if (!this.discoveredPvcs.has(prefixedPvcName)) {
165
+ this.discoveredPvcs.set(prefixedPvcName, this.buildPvcCreateCommand(prefixedPvcName, pvc, type, flags));
166
+ }
167
+ group.filestoreBindCommands.push(this.buildFilestoreBindCommand(assetName, namespace, mount.volName, prefixedPvcName, flags));
168
+ }
169
+ }
170
+ }
171
+ groups.push(group);
172
+ }
173
+ return { groups, helmGroups };
174
+ });
175
+ }
176
+ /** True when the Deployment is owned by one of the named Helm releases (so it must not be imported as a raw asset). */
177
+ isHelmOwned(deployment, releaseNames) {
178
+ var _a, _b;
179
+ const releaseName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.annotations) === null || _b === void 0 ? void 0 : _b["meta.helm.sh/release-name"];
180
+ return releaseName != undefined && releaseNames.has(releaseName);
181
+ }
182
+ // --------------------------------------------------------------------
183
+ // Helm Release Discovery
184
+ // --------------------------------------------------------------------
185
+ /** Lists the Helm v3 release storage Secrets in a namespace, keeps the latest revision per release, and decodes
186
+ * each into a HelmRelease. Listing failures (typically missing `secrets` read RBAC) are non-fatal: a warning is
187
+ * emitted and an empty list returned, so the affected Deployments fall back to the raw asset-import path. */
188
+ scanNamespaceHelmReleases(namespace, coreApi) {
189
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
190
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
191
+ let secrets;
192
+ try {
193
+ const list = yield coreApi.listNamespacedSecret({ namespace, fieldSelector: "type=helm.sh/release.v1" });
194
+ secrets = (_a = list.items) !== null && _a !== void 0 ? _a : [];
195
+ }
196
+ catch (err) {
197
+ this.log(`# WARNING: Failed to list Helm release secrets in namespace [${namespace}] (${(_b = err.message) !== null && _b !== void 0 ? _b : err}). Helm releases here will not be imported; their Deployments will be imported individually instead.`);
198
+ return [];
199
+ }
200
+ // Group by release name, keeping only the highest revision Secret per release.
201
+ const latestByName = new Map();
202
+ for (const secret of secrets) {
203
+ const releaseName = (_d = (_c = secret.metadata) === null || _c === void 0 ? void 0 : _c.labels) === null || _d === void 0 ? void 0 : _d["name"];
204
+ if (!releaseName)
205
+ continue;
206
+ const revision = parseInt((_g = (_f = (_e = secret.metadata) === null || _e === void 0 ? void 0 : _e.labels) === null || _f === void 0 ? void 0 : _f["version"]) !== null && _g !== void 0 ? _g : "0", 10) || 0;
207
+ const existing = latestByName.get(releaseName);
208
+ if (!existing || revision > existing.revision)
209
+ latestByName.set(releaseName, { revision, secret });
210
+ }
211
+ const releases = [];
212
+ for (const [releaseName, { secret }] of latestByName) {
213
+ const encoded = (_h = secret.data) === null || _h === void 0 ? void 0 : _h["release"];
214
+ if (!encoded)
215
+ continue;
216
+ try {
217
+ const release = this.decodeHelmRelease(encoded);
218
+ const metadata = (_k = (_j = release === null || release === void 0 ? void 0 : release.chart) === null || _j === void 0 ? void 0 : _j.metadata) !== null && _k !== void 0 ? _k : {};
219
+ releases.push({
220
+ name: (_l = release === null || release === void 0 ? void 0 : release.name) !== null && _l !== void 0 ? _l : releaseName,
221
+ namespace: (_m = release === null || release === void 0 ? void 0 : release.namespace) !== null && _m !== void 0 ? _m : namespace,
222
+ chartName: (_o = metadata.name) !== null && _o !== void 0 ? _o : releaseName,
223
+ chartVersion: metadata.version,
224
+ defaults: (_q = (_p = release === null || release === void 0 ? void 0 : release.chart) === null || _p === void 0 ? void 0 : _p.values) !== null && _q !== void 0 ? _q : {},
225
+ overrides: (_r = release === null || release === void 0 ? void 0 : release.config) !== null && _r !== void 0 ? _r : {},
226
+ repoGuess: this.sniffChartRepo(metadata),
227
+ repoHint: this.buildRepoHint(metadata),
228
+ });
229
+ }
230
+ catch (err) {
231
+ this.log(`# WARNING: Failed to decode Helm release [${namespace}/${releaseName}] (${(_s = err.message) !== null && _s !== void 0 ? _s : err}); skipping.`);
232
+ }
233
+ }
234
+ releases.sort((a, b) => a.name.localeCompare(b.name));
235
+ return releases;
236
+ });
237
+ }
238
+ /** Decodes a Helm v3 release storage payload. Helm stores the release as base64(gzip(json)); the Kubernetes API
239
+ * then base64-encodes that string again as the Secret's `release` data value, so two base64 decodes precede the
240
+ * gunzip. Older releases may be stored without gzip, so the gzip magic bytes are checked before inflating. */
241
+ decodeHelmRelease(b64FromK8s) {
242
+ const helmStored = Buffer.from(b64FromK8s, "base64");
243
+ const payload = Buffer.from(helmStored.toString("utf8"), "base64");
244
+ let json;
245
+ if (payload.length >= 2 && payload[0] === 0x1f && payload[1] === 0x8b) {
246
+ json = zlib.gunzipSync(payload);
247
+ }
248
+ else {
249
+ json = payload.length > 0 ? payload : helmStored;
250
+ }
251
+ return JSON.parse(json.toString("utf8"));
252
+ }
253
+ /** Best-effort recovery of a usable chart repository reference from chart metadata. The Helm release Secret does
254
+ * not record where a chart came from, so only an explicit OCI source can be returned with confidence. */
255
+ sniffChartRepo(metadata) {
256
+ const sources = Array.isArray(metadata === null || metadata === void 0 ? void 0 : metadata.sources) ? metadata.sources : [];
257
+ const ociRef = sources.find(s => typeof s === "string" && s.startsWith("oci://"));
258
+ return ociRef;
259
+ }
260
+ /** Renders a human hint (chart home / sources) shown in a comment when the chart repository could not be resolved. */
261
+ buildRepoHint(metadata) {
262
+ const hints = [];
263
+ if (typeof (metadata === null || metadata === void 0 ? void 0 : metadata.home) === "string" && metadata.home)
264
+ hints.push(metadata.home);
265
+ if (Array.isArray(metadata === null || metadata === void 0 ? void 0 : metadata.sources))
266
+ hints.push(...metadata.sources.filter((s) => typeof s === "string"));
267
+ return hints.length > 0 ? hints.join(", ") : undefined;
268
+ }
269
+ // --------------------------------------------------------------------
270
+ // Helm Command Generation
271
+ // --------------------------------------------------------------------
272
+ /** Builds the full block of generated commands for one Helm release: the asset create line, plus enterprise-scoped
273
+ * default values and (when present) cabinet-scoped override values, each delivered via a heredoc to keep the output
274
+ * a single self-contained script stream. */
275
+ buildHelmReleaseCommands(release, flags) {
276
+ const lines = [];
277
+ const versionSuffix = release.chartVersion ? `:${release.chartVersion}` : "";
278
+ lines.push(`# --- Helm release: ${release.namespace}/${release.name} (chart ${release.chartName}${versionSuffix}) ---`);
279
+ // The Codiac asset model has no chart-version field; version is a deploy-time concern, so surface it for the operator.
280
+ if (release.chartVersion) {
281
+ lines.push(`# Chart version at import time: ${release.chartVersion} (set explicitly at deploy time; not stored on the asset).`);
282
+ }
283
+ let repo = release.repoGuess;
284
+ if (!repo) {
285
+ this.todoCount++;
286
+ lines.push(`# TODO: the Helm release secret does not record its chart repository; set --chart-repo to the Codiac registry code for the repo that publishes [${release.chartName}].`);
287
+ if (release.repoHint)
288
+ lines.push(`# hint - chart home/sources: ${release.repoHint}`);
289
+ repo = "<CHART_REPO>";
290
+ }
291
+ const createParts = ["codiac asset create --helm", `--chart ${release.chartName}`, `--chart-repo ${repo}`];
292
+ if (flags.enterprise)
293
+ createParts.push(`--enterprise ${flags.enterprise}`);
294
+ createParts.push(`--name ${release.name}`, "--silent");
295
+ lines.push(createParts.join(" "));
296
+ // Chart defaults define the asset baseline -> enterprise-scoped helm config.
297
+ if (release.defaults && Object.keys(release.defaults).length > 0) {
298
+ lines.push(`# Chart default values for ${release.name}:`);
299
+ lines.push(this.buildHelmValuesConfigBlock(release.name, release.defaults, "enterprise", release.namespace, flags));
300
+ }
301
+ // Operator overrides define per-deployment behavior -> cabinet-scoped helm config.
302
+ if (release.overrides && Object.keys(release.overrides).length > 0) {
303
+ lines.push(`# Release override values for ${release.name} (captured verbatim from the live release; MAY CONTAIN SECRETS - review before running):`);
304
+ lines.push(this.buildHelmValuesConfigBlock(release.name, release.overrides, "cabinet", release.namespace, flags));
305
+ }
306
+ return lines;
307
+ }
308
+ /** Builds a single `codiac config set -t helm` command whose value is the entire values document, supplied via a
309
+ * quoted heredoc so the YAML passes through the shell literally. `--escape-expressions` ensures any third-party
310
+ * `@{...}` tokens in the values are stored as literals rather than evaluated by Codiac at deploy time. */
311
+ buildHelmValuesConfigBlock(assetName, values, scope, namespace, flags) {
312
+ const parts = ["codiac config set --silent"];
313
+ if (flags.enterprise)
314
+ parts.push(`--enterprise ${flags.enterprise}`);
315
+ parts.push(`-a ${assetName}`);
316
+ if (scope === "enterprise")
317
+ parts.push("--enterprise-scope");
318
+ else
319
+ parts.push(`-c ${namespace}`);
320
+ parts.push("-t helm", "--entire-file", "--value-stdin", "--escape-expressions");
321
+ const yamlText = YAML.stringify(values).replace(/\n+$/, "");
322
+ return `${parts.join(" ")} <<'EOF'\n${yamlText}\nEOF`;
323
+ }
324
+ scanNamespacePvcs(namespace, coreApi) {
325
+ var _a, _b, _c;
326
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
327
+ const out = new Map();
328
+ try {
329
+ const list = yield coreApi.listNamespacedPersistentVolumeClaim({ namespace });
330
+ for (const pvc of (_a = list.items) !== null && _a !== void 0 ? _a : []) {
331
+ const name = (_b = pvc.metadata) === null || _b === void 0 ? void 0 : _b.name;
332
+ if (name)
333
+ out.set(name, pvc);
334
+ }
335
+ }
336
+ catch (err) {
337
+ this.log(`# WARNING: Failed to list PVCs in namespace [${namespace}] (${(_c = err.message) !== null && _c !== void 0 ? _c : err}).`);
338
+ }
339
+ return out;
340
+ });
341
+ }
342
+ /** Resolves a PVC's FileStoreType using the cluster-wide StorageClass map, falling back to the PV's CSI driver
343
+ * for statically-bound PVCs (storageClassName empty / spec.volumeName set). Returns undefined when no path resolves. */
344
+ resolveFileStoreType(pvc, storageClassByName, coreApi) {
345
+ var _a, _b, _c, _d;
346
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
347
+ const sc = (_a = pvc.spec) === null || _a === void 0 ? void 0 : _a.storageClassName;
348
+ if (sc && storageClassByName.has(sc)) {
349
+ const t = this.csiDriverToFileStoreType(storageClassByName.get(sc));
350
+ if (t)
351
+ return t;
352
+ }
353
+ const pvName = (_b = pvc.spec) === null || _b === void 0 ? void 0 : _b.volumeName;
354
+ if (pvName) {
355
+ try {
356
+ const pv = yield coreApi.readPersistentVolume({ name: pvName });
357
+ return this.csiDriverToFileStoreType((_d = (_c = pv.spec) === null || _c === void 0 ? void 0 : _c.csi) === null || _d === void 0 ? void 0 : _d.driver);
358
+ }
359
+ catch (_e) {
360
+ // PV may not exist yet, or RBAC may deny; fall through to undefined.
361
+ }
362
+ }
363
+ return undefined;
364
+ });
365
+ }
366
+ // --------------------------------------------------------------------
367
+ // Command Generation
368
+ // --------------------------------------------------------------------
369
+ buildCabinetCreateCommand(namespace, flags) {
370
+ const parts = [`codiac cabinet create ${namespace}`];
371
+ if (flags.enterprise)
372
+ parts.push(`--enterprise ${flags.enterprise}`);
373
+ if (flags.environment)
374
+ parts.push(`--environment ${flags.environment}`);
375
+ if (flags.cluster)
376
+ parts.push(`--cluster ${flags.cluster}`);
377
+ parts.push("--silent");
378
+ return parts.join(" ");
379
+ }
380
+ buildAssetCreateCommand(deployment, ingresses, flags) {
381
+ var _a, _b, _c, _d, _e, _f, _g, _h;
382
+ const deploymentName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
383
+ const containers = (_f = (_e = (_d = (_c = deployment.spec) === null || _c === void 0 ? void 0 : _c.template) === null || _d === void 0 ? void 0 : _d.spec) === null || _e === void 0 ? void 0 : _e.containers) !== null && _f !== void 0 ? _f : [];
384
+ const firstContainer = containers[0];
385
+ const hasIngress = ingresses.length > 0;
386
+ const { registry: registryHost, imageName } = this.parseContainerImage(firstContainer === null || firstContainer === void 0 ? void 0 : firstContainer.image);
387
+ const firstPort = (_h = (_g = firstContainer === null || firstContainer === void 0 ? void 0 : firstContainer.ports) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.containerPort;
388
+ // `--registry` on `cod asset create` expects the Codiac ImageRegistry CODE (e.g. `devcodiaccontainers`),
389
+ // not the URL the container puller uses. Map known hostname patterns to codes; unknown patterns become a TODO.
390
+ const registryInfo = this.toRegistryCode(registryHost);
391
+ const todoLines = [];
392
+ if (imageName === "<unknown>") {
393
+ todoLines.push(`# TODO: container image could not be inferred from the K8s deployment spec for [${deploymentName}]; set --image manually before running.`);
394
+ this.todoCount++;
395
+ }
396
+ if (registryInfo === undefined) {
397
+ todoLines.push(`# TODO: could not derive a Codiac registry code from container image registry [${registryHost}] for [${deploymentName}]; capture it via \`cod imageRegistry capture\` and add --registry <code> below.`);
398
+ this.todoCount++;
399
+ }
400
+ else if (registryInfo.code !== "DockerHub|official") {
401
+ // Track non-DockerHub registries so the discovered-registries banner can remind the user to capture them.
402
+ this.discoveredRegistries.set(registryInfo.code, registryInfo.provider);
403
+ }
404
+ const parts = [
405
+ "codiac asset create",
406
+ `--name ${deploymentName}`,
407
+ `--image ${imageName}`,
408
+ ];
409
+ if (registryInfo !== undefined) {
410
+ parts.push(`--registry ${registryInfo.code}`);
411
+ }
412
+ if (flags.enterprise)
413
+ parts.push(`--enterprise ${flags.enterprise}`);
414
+ if (hasIngress) {
415
+ parts.push(`--code ${deploymentName}`);
416
+ }
417
+ if (firstPort !== undefined) {
418
+ parts.push(`--port ${firstPort}`);
419
+ }
420
+ if (hasIngress) {
421
+ parts.push("--hasIngress");
422
+ }
423
+ parts.push("--silent");
424
+ const commandLine = parts.join(" ");
425
+ return todoLines.length > 0 ? `${todoLines.join("\n")}\n${commandLine}` : commandLine;
426
+ }
427
+ /** Maps a container-image hostname to the Codiac `ImageRegistry.code` recognised by `cod asset create --registry`.
428
+ * Returns `undefined` when the hostname can't be resolved so the caller can emit a TODO comment instead of
429
+ * producing a broken command line. Provider hint is paired with the code so the discovered-registries banner
430
+ * can render an accurate `cod imageRegistry capture --provider <p> --name <code>` suggestion. */
431
+ toRegistryCode(host) {
432
+ if (!host || host === "<unknown>")
433
+ return undefined;
434
+ if (host === "docker.io")
435
+ return { code: "DockerHub|official", provider: "dockerHub" };
436
+ if (host.endsWith(".azurecr.io")) {
437
+ const code = host.slice(0, -".azurecr.io".length);
438
+ return code ? { code, provider: "azure" } : undefined;
439
+ }
440
+ return undefined;
441
+ }
442
+ buildConfigCommands(deployment, namespace, flags) {
443
+ var _a, _b, _c, _d, _e, _f, _g;
444
+ const deploymentName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
445
+ const containers = (_f = (_e = (_d = (_c = deployment.spec) === null || _c === void 0 ? void 0 : _c.template) === null || _d === void 0 ? void 0 : _d.spec) === null || _e === void 0 ? void 0 : _e.containers) !== null && _f !== void 0 ? _f : [];
446
+ const commands = [];
447
+ for (const container of containers) {
448
+ for (const envVar of (_g = container.env) !== null && _g !== void 0 ? _g : []) {
449
+ const cmd = this.buildConfigCommand(deploymentName, namespace, envVar, flags);
450
+ commands.push(cmd);
451
+ }
452
+ }
453
+ return commands;
454
+ }
455
+ buildConfigCommand(assetName, namespace, envVar, flags) {
456
+ const settingName = envVar.name;
457
+ // Direct value
458
+ if (envVar.value !== undefined) {
459
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, `"${envVar.value.replace(/"/g, '\\"')}"`, flags);
460
+ }
461
+ if (envVar.valueFrom) {
462
+ // Secret reference -> #REF syntax
463
+ if (envVar.valueFrom.secretKeyRef) {
464
+ return this.buildSecretRefCommand(assetName, namespace, settingName, envVar.valueFrom.secretKeyRef, flags);
465
+ }
466
+ // Field reference -> k8spatch
467
+ if (envVar.valueFrom.fieldRef) {
468
+ return this.buildFieldRefPatchCommand(assetName, namespace, settingName, envVar.valueFrom.fieldRef, flags);
469
+ }
470
+ // configMapKeyRef / resourceFieldRef -> TODO comments (out of scope)
471
+ const ref = this.describeEnvVarReference(envVar.valueFrom);
472
+ const base = this.buildConfigAddEnvBase(assetName, namespace, settingName, flags);
473
+ this.todoCount++;
474
+ return `# TODO: ${settingName} - ${ref}\n# ${base} --value "<MANUAL_ENTRY_REQUIRED>"`;
475
+ }
476
+ // Empty value
477
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, '""', flags);
478
+ }
479
+ /** Builds a standard codiac config add for a direct env value. */
480
+ buildConfigAddEnvCommand(assetName, namespace, settingName, value, flags) {
481
+ return `${this.buildConfigAddEnvBase(assetName, namespace, settingName, flags)} --value ${value}`;
482
+ }
483
+ /** Builds the base portion of a codiac config add command (without --value). */
484
+ buildConfigAddEnvBase(assetName, namespace, settingName, flags) {
485
+ const parts = ["codiac config add --silent"];
486
+ if (flags.enterprise)
487
+ parts.push(`--enterprise ${flags.enterprise}`);
488
+ parts.push(`-a ${assetName}`, "-t env", `--cabinet=${namespace}`, `--setting ${settingName}`);
489
+ return parts.join(" ");
490
+ }
491
+ /** Builds a codiac config add with #REF|azKeyVault|... for a secretKeyRef env var. */
492
+ buildSecretRefCommand(assetName, namespace, settingName, secretKeyRef, flags) {
493
+ var _a, _b, _c;
494
+ const secretName = (_a = secretKeyRef.name) !== null && _a !== void 0 ? _a : "<unknown>";
495
+ const secretKey = (_b = secretKeyRef.key) !== null && _b !== void 0 ? _b : "<unknown>";
496
+ const storeName = (_c = flags["keyvault-name"]) !== null && _c !== void 0 ? _c : secretName;
497
+ this.discoveredStores.add(storeName);
498
+ const refValue = `'#REF|azKeyVault|${storeName}|${secretKey}'`;
499
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, refValue, flags);
500
+ }
501
+ /** Builds a codiac config set with -t k8spatch for a fieldRef (Downward API) env var. */
502
+ buildFieldRefPatchCommand(assetName, namespace, settingName, fieldRef, flags) {
503
+ const parts = ["codiac config set --silent"];
504
+ if (flags.enterprise)
505
+ parts.push(`--enterprise ${flags.enterprise}`);
506
+ parts.push(`-a ${assetName}`, `-c ${namespace}`, "-t k8spatch", "--setting 'deployment'");
507
+ const fieldRefObj = {};
508
+ if (fieldRef.apiVersion)
509
+ fieldRefObj.apiVersion = fieldRef.apiVersion;
510
+ fieldRefObj.fieldPath = fieldRef.fieldPath;
511
+ const patchOp = {
512
+ op: "add",
513
+ path: "/spec/template/spec/containers/0/env/-",
514
+ value: {
515
+ name: settingName,
516
+ valueFrom: { fieldRef: fieldRefObj },
517
+ },
518
+ };
519
+ const patchJson = JSON.stringify([patchOp]);
520
+ const shellEscaped = patchJson.replace(/'/g, "'\\''");
521
+ parts.push(`--value '${shellEscaped}'`);
522
+ return parts.join(" ");
523
+ }
524
+ /** Builds a codiac secretStore:capture command. When the import was run with --subscription-id, threads
525
+ * it through so the generated line resolves the Azure tenant context unambiguously when the operator
526
+ * has multiple cached azure subscriptions. */
527
+ buildSecretStoreCaptureCommand(storeName, flags) {
528
+ const parts = [`codiac secretStore:capture --silent --name=${storeName} --provider=azure`];
529
+ if (flags["subscription-id"])
530
+ parts.push(`--subscription-id=${flags["subscription-id"]}`);
531
+ return parts.join(" ");
532
+ }
533
+ // --------------------------------------------------------------------
534
+ // Output
535
+ // --------------------------------------------------------------------
536
+ outputCommands(groups, helmGroups, flags) {
537
+ var _a, _b, _c, _d;
538
+ if (groups.length === 0 && helmGroups.length === 0) {
539
+ this.log("# No deployments or Helm releases found to import.");
540
+ return;
541
+ }
542
+ this.log("");
543
+ this.log("# " + "=".repeat(58));
544
+ this.log("# Generated Import Commands");
545
+ this.log("# " + "=".repeat(58));
546
+ if (this.todoCount > 0) {
547
+ this.log("");
548
+ this.log("# " + "=".repeat(58));
549
+ this.log(`# WARNING: ${this.todoCount} TODO item(s) below require manual review before running this script.`);
550
+ this.log(`# Search for "# TODO:" to find them.`);
551
+ this.log("# " + "=".repeat(58));
552
+ }
553
+ if (this.discoveredRegistries.size > 0) {
554
+ this.log("");
555
+ this.log("# --- Image Registries (must already exist in Codiac) ---");
556
+ this.log("# The asset create commands below reference these registries by code.");
557
+ this.log("# If any have not yet been captured, run: cod imageRegistry capture --provider <provider> --name <code>");
558
+ this.log("# Discovered:");
559
+ const sortedCodes = [...this.discoveredRegistries.keys()].sort();
560
+ for (const code of sortedCodes) {
561
+ this.log(`# - ${code} (provider: ${this.discoveredRegistries.get(code)})`);
562
+ }
563
+ }
564
+ if (flags["generate-secret-store"] && this.discoveredStores.size > 0) {
565
+ this.log("");
566
+ this.log("# --- Secret Store Capture ---");
567
+ for (const store of [...this.discoveredStores].sort()) {
568
+ this.log(this.buildSecretStoreCaptureCommand(store, flags));
569
+ }
570
+ }
571
+ if (flags["generate-pvcs"] && this.discoveredPvcs.size > 0) {
572
+ this.log("");
573
+ this.log("# --- PVC Filestores ---");
574
+ const sortedNames = [...this.discoveredPvcs.keys()].sort();
575
+ for (const name of sortedNames) {
576
+ this.log(this.discoveredPvcs.get(name));
577
+ }
578
+ }
579
+ // Group both raw and helm commands by namespace, then iterate the union so a namespace that contains only
580
+ // Helm releases (and no raw Deployments) still gets its own section and cabinet line.
581
+ const groupsByNs = new Map();
582
+ for (const group of groups) {
583
+ const list = (_a = groupsByNs.get(group.namespace)) !== null && _a !== void 0 ? _a : [];
584
+ list.push(group);
585
+ groupsByNs.set(group.namespace, list);
586
+ }
587
+ const helmByNs = new Map();
588
+ for (const helmGroup of helmGroups) {
589
+ const list = (_b = helmByNs.get(helmGroup.namespace)) !== null && _b !== void 0 ? _b : [];
590
+ list.push(helmGroup);
591
+ helmByNs.set(helmGroup.namespace, list);
592
+ }
593
+ const allNamespaces = [...new Set([...groupsByNs.keys(), ...helmByNs.keys()])].sort();
594
+ for (const namespace of allNamespaces) {
595
+ this.log("");
596
+ this.log(`# --- Namespace: ${namespace} ---`);
597
+ if (flags["generate-cabinet"]) {
598
+ this.log("");
599
+ this.log(this.buildCabinetCreateCommand(namespace, flags));
600
+ }
601
+ for (const helmGroup of (_c = helmByNs.get(namespace)) !== null && _c !== void 0 ? _c : []) {
602
+ this.log("");
603
+ for (const line of helmGroup.lines) {
604
+ this.log(line);
605
+ }
606
+ }
607
+ for (const group of (_d = groupsByNs.get(namespace)) !== null && _d !== void 0 ? _d : []) {
608
+ this.log("");
609
+ this.log(group.assetCommand);
610
+ for (const cmd of group.configCommands) {
611
+ this.log(cmd);
612
+ }
613
+ for (const cmd of group.volumeMountCommands) {
614
+ this.log(cmd);
615
+ }
616
+ for (const cmd of group.filestoreBindCommands) {
617
+ this.log(cmd);
618
+ }
619
+ }
620
+ }
621
+ this.log("");
622
+ }
623
+ // --------------------------------------------------------------------
624
+ // K8s Matching Logic
625
+ // --------------------------------------------------------------------
626
+ findServicesForDeployment(deployment, services) {
627
+ var _a, _b, _c, _d;
628
+ const podLabels = (_d = (_c = (_b = (_a = deployment.spec) === null || _a === void 0 ? void 0 : _a.template) === null || _b === void 0 ? void 0 : _b.metadata) === null || _c === void 0 ? void 0 : _c.labels) !== null && _d !== void 0 ? _d : {};
629
+ return services.filter(service => {
630
+ var _a, _b;
631
+ const selector = (_b = (_a = service.spec) === null || _a === void 0 ? void 0 : _a.selector) !== null && _b !== void 0 ? _b : {};
632
+ if (Object.keys(selector).length === 0)
633
+ return false;
634
+ return Object.entries(selector).every(([key, value]) => podLabels[key] === value);
635
+ });
636
+ }
637
+ findIngressesForServices(services, ingresses) {
638
+ const serviceNames = new Set(services.map(s => { var _a; return (_a = s.metadata) === null || _a === void 0 ? void 0 : _a.name; }).filter(Boolean));
639
+ return ingresses.filter(ingress => {
640
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
641
+ const defaultBackend = (_c = (_b = (_a = ingress.spec) === null || _a === void 0 ? void 0 : _a.defaultBackend) === null || _b === void 0 ? void 0 : _b.service) === null || _c === void 0 ? void 0 : _c.name;
642
+ if (defaultBackend && serviceNames.has(defaultBackend))
643
+ return true;
644
+ for (const rule of (_e = (_d = ingress.spec) === null || _d === void 0 ? void 0 : _d.rules) !== null && _e !== void 0 ? _e : []) {
645
+ for (const path of (_g = (_f = rule.http) === null || _f === void 0 ? void 0 : _f.paths) !== null && _g !== void 0 ? _g : []) {
646
+ if (((_j = (_h = path.backend) === null || _h === void 0 ? void 0 : _h.service) === null || _j === void 0 ? void 0 : _j.name) && serviceNames.has(path.backend.service.name))
647
+ return true;
648
+ }
649
+ }
650
+ return false;
651
+ });
652
+ }
653
+ // --------------------------------------------------------------------
654
+ // Image Parsing
655
+ // --------------------------------------------------------------------
656
+ parseContainerImage(image) {
657
+ if (!image) {
658
+ return { registry: "<unknown>", imageName: "<unknown>" };
659
+ }
660
+ const imageWithoutTag = image.split(":")[0];
661
+ const parts = imageWithoutTag.split("/");
662
+ if (parts.length === 1) {
663
+ return { registry: "docker.io", imageName: parts[0] };
664
+ }
665
+ else if (parts.length === 2) {
666
+ if (parts[0].includes(".") || parts[0].includes(":")) {
667
+ return { registry: parts[0], imageName: parts[1] };
668
+ }
669
+ return { registry: "docker.io", imageName: parts[1] };
670
+ }
671
+ return { registry: parts[0], imageName: parts[parts.length - 1] };
672
+ }
673
+ // --------------------------------------------------------------------
674
+ // Env Var Reference Description (for unsupported types)
675
+ // --------------------------------------------------------------------
676
+ describeEnvVarReference(valueFrom) {
677
+ var _a, _b, _c, _d;
678
+ if (valueFrom.configMapKeyRef) {
679
+ return `Value from ConfigMap "${(_a = valueFrom.configMapKeyRef.name) !== null && _a !== void 0 ? _a : "<unknown>"}", key "${(_b = valueFrom.configMapKeyRef.key) !== null && _b !== void 0 ? _b : "<unknown>"}"`;
680
+ }
681
+ if (valueFrom.resourceFieldRef) {
682
+ return `Value from container "${(_c = valueFrom.resourceFieldRef.containerName) !== null && _c !== void 0 ? _c : "<current>"}" resource "${(_d = valueFrom.resourceFieldRef.resource) !== null && _d !== void 0 ? _d : "<unknown>"}"`;
683
+ }
684
+ return "Value from unknown reference type";
685
+ }
686
+ // --------------------------------------------------------------------
687
+ // PVC Discovery & Emission
688
+ // --------------------------------------------------------------------
689
+ /** Maps a CSI driver name (or legacy in-tree provisioner) to the Codiac FileStoreType
690
+ * recognised by `cod pvc create`. Returns undefined when the driver is unknown so the
691
+ * caller can emit a TODO comment instead of guessing. */
692
+ csiDriverToFileStoreType(driver) {
693
+ switch (driver) {
694
+ case "file.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureFileStorage;
695
+ case "disk.csi.azure.com":
696
+ case "kubernetes.io/azure-disk": return contracts_1.FileStoreTypeEnum.azureDisk;
697
+ case "blob.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureBlobStorage;
698
+ case "netapp.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureNetAppFiles;
699
+ default: return undefined;
700
+ }
701
+ }
702
+ /** Walks a deployment's pod template to find every container volumeMount backed by a PVC
703
+ * in `pvcsInNs`. Returns one entry per matched (volume, container) pair. */
704
+ findPvcMountsForDeployment(deployment, pvcsInNs) {
705
+ var _a, _b, _c, _d, _e, _f;
706
+ const podSpec = (_b = (_a = deployment.spec) === null || _a === void 0 ? void 0 : _a.template) === null || _b === void 0 ? void 0 : _b.spec;
707
+ const volumes = (_c = podSpec === null || podSpec === void 0 ? void 0 : podSpec.volumes) !== null && _c !== void 0 ? _c : [];
708
+ const containers = (_d = podSpec === null || podSpec === void 0 ? void 0 : podSpec.containers) !== null && _d !== void 0 ? _d : [];
709
+ const out = [];
710
+ for (const vol of volumes) {
711
+ const claimName = (_e = vol.persistentVolumeClaim) === null || _e === void 0 ? void 0 : _e.claimName;
712
+ if (!claimName || !pvcsInNs.has(claimName))
713
+ continue;
714
+ for (const container of containers) {
715
+ for (const vm of (_f = container.volumeMounts) !== null && _f !== void 0 ? _f : []) {
716
+ if (vm.name === vol.name && vm.mountPath) {
717
+ out.push({ volName: vol.name, mountPath: vm.mountPath, k8sPvcName: claimName });
718
+ }
719
+ }
720
+ }
721
+ }
722
+ return out;
723
+ }
724
+ /** Builds the enterprise-scoped `codiac pvc create` line. When the FileStoreType could not
725
+ * be inferred, prepends a TODO comment line so the user is forced to set `--type` manually. */
726
+ buildPvcCreateCommand(prefixedPvcName, pvc, type, flags) {
727
+ var _a, _b, _c, _d, _e, _f, _g;
728
+ const parts = ["codiac pvc create --silent"];
729
+ if (flags.enterprise)
730
+ parts.push(`--enterprise ${flags.enterprise}`);
731
+ parts.push(`--name ${prefixedPvcName}`);
732
+ parts.push(`--provider azure`);
733
+ if (type)
734
+ parts.push(`--type ${type}`);
735
+ const storageClassName = (_a = pvc.spec) === null || _a === void 0 ? void 0 : _a.storageClassName;
736
+ if (storageClassName)
737
+ parts.push(`--storage-class ${storageClassName}`);
738
+ const modes = (_c = (_b = pvc.spec) === null || _b === void 0 ? void 0 : _b.accessModes) !== null && _c !== void 0 ? _c : [];
739
+ if (modes.length > 0)
740
+ parts.push(`--access-modes ${modes.join(",")}`);
741
+ const size = (_f = (_e = (_d = pvc.spec) === null || _d === void 0 ? void 0 : _d.resources) === null || _e === void 0 ? void 0 : _e.requests) === null || _f === void 0 ? void 0 : _f.storage;
742
+ if (size)
743
+ parts.push(`--size ${size}`);
744
+ if ((_g = pvc.spec) === null || _g === void 0 ? void 0 : _g.volumeName)
745
+ parts.push(`--volume-name ${pvc.spec.volumeName}`);
746
+ const line = parts.join(" ");
747
+ if (!type) {
748
+ const sc = storageClassName !== null && storageClassName !== void 0 ? storageClassName : "<none>";
749
+ this.todoCount++;
750
+ return `# TODO: --type could not be inferred (storageClass=${sc}); set it manually before running.\n${line}`;
751
+ }
752
+ return line;
753
+ }
754
+ /** Builds the asset-scoped `codiac asset volume create` line. The mountPath is a positional arg. */
755
+ buildAssetVolumeCreateCommand(assetName, volName, mountPath, flags) {
756
+ const parts = ["codiac asset volume create --silent"];
757
+ if (flags.enterprise)
758
+ parts.push(`--enterprise ${flags.enterprise}`);
759
+ parts.push(`--asset ${assetName}`);
760
+ parts.push(`--name ${volName}`);
761
+ parts.push(mountPath);
762
+ // Auto-description so reviewers can later audit where each volume mount originated.
763
+ // Defensive escape of any embedded double-quotes (K8s names are DNS-safe so this is belt-and-suspenders).
764
+ const description = `Imported from K8s deployment ${assetName}, volume ${volName}, mount ${mountPath}`.replace(/"/g, '\\"');
765
+ parts.push(`--description "${description}"`);
766
+ return parts.join(" ");
767
+ }
768
+ /** Builds the cabinet-scoped `codiac config add -t filestore` line that binds an asset's reserved
769
+ * folder (declared by `asset volume create`) to the FileStoreDef (created by `pvc create`). */
770
+ buildFilestoreBindCommand(assetName, namespace, volName, prefixedPvcName, flags) {
771
+ const parts = ["codiac config add --silent"];
772
+ if (flags.enterprise)
773
+ parts.push(`--enterprise ${flags.enterprise}`);
774
+ parts.push(`-a ${assetName}`, `-c ${namespace}`, "-t filestore", `--setting ${volName}`, `--value ${prefixedPvcName}`);
775
+ return parts.join(" ");
776
+ }
777
+ }
778
+ exports.default = ImportCluster;
779
+ ImportCluster.description = 'Scans all deployments in the current Kubernetes cluster and generates the Codiac CLI commands to import them.';
780
+ ImportCluster.examples = [
781
+ '<%= config.bin %> <%= command.id %> --run-in-cluster',
782
+ '<%= config.bin %> <%= command.id %> --run-in-cluster --enterprise myco --environment dev --cluster mycluster --generate-cabinet --generate-secret-store',
783
+ '<%= config.bin %> <%= command.id %> --run-in-cluster --enterprise myco --environment dev --cluster mycluster --generate-pvcs --generate-volume-mounts',
784
+ '<%= config.bin %> <%= command.id %> --run-in-cluster --enterprise myco --environment dev --cluster mycluster --generate-cabinet --generate-secret-store --subscription-id 00000000-0000-0000-0000-000000000000',
785
+ ];
786
+ ImportCluster.flags = {
787
+ "run-in-cluster": core_1.Flags.boolean({ description: 'Run using in-cluster Kubernetes config (service account). Required for headless/pod execution.' }),
788
+ enterprise: core_1.Flags.string({ description: 'Enterprise code to include in generated commands.' }),
789
+ environment: core_1.Flags.string({ description: 'Environment name to include in generated commands (required by --generate-cabinet).' }),
790
+ cluster: core_1.Flags.string({ description: 'Cluster name to include in generated cabinet create commands (required by --generate-cabinet).' }),
791
+ "subscription-id": core_1.Flags.string({ char: 's', description: 'Cloud subscription containing the source cluster; threaded into generated secretStore:capture commands so they run silently in multi-subscription tenants.' }),
792
+ "generate-cabinet": core_1.Flags.boolean({ description: 'Include codiac cabinet create commands in the output (one per namespace). Requires --enterprise, --environment, and --cluster.' }),
793
+ "generate-secret-store": core_1.Flags.boolean({ description: 'Include codiac secretStore:capture commands in the output for discovered Azure Key Vaults.' }),
794
+ "generate-pvcs": core_1.Flags.boolean({ description: 'Include codiac pvc create commands (enterprise-scope) plus codiac config add -t filestore bindings for every PersistentVolumeClaim mounted by a discovered deployment.' }),
795
+ "generate-volume-mounts": core_1.Flags.boolean({ description: 'Include codiac asset volume create commands for every container volumeMount backed by a PVC on a discovered deployment.' }),
796
+ "include-default": core_1.Flags.boolean({ description: 'Include the "default" namespace (excluded by default).' }),
797
+ "keyvault-name": core_1.Flags.string({ description: 'Azure Key Vault name to use in generated secret references. When omitted, the K8s Secret name is used as a placeholder.' }),
798
+ };
799
+ //# sourceMappingURL=cluster.js.map