@codiac.io/codiac-cli 1.3.229 → 1.3.230

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,620 @@
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 contracts_1 = require("../../apis/codiac-api/contracts");
7
+ const EXCLUDED_NAMESPACES = new Set([
8
+ "kube-system",
9
+ "kube-public",
10
+ "kube-node-lease",
11
+ // Azure-managed
12
+ "gatekeeper-system",
13
+ "calico-system",
14
+ "cert-manager",
15
+ "ingress-nginx",
16
+ "keda",
17
+ "azure-arc",
18
+ "tigera-operator",
19
+ // AWS-managed
20
+ "amazon-cloudwatch",
21
+ "aws-observability",
22
+ ]);
23
+ class ImportCluster extends core_1.Command {
24
+ constructor() {
25
+ super(...arguments);
26
+ /** Accumulates unique secret store names discovered during the scan. */
27
+ this.discoveredStores = new Set();
28
+ /** Accumulates enterprise-scoped `codiac pvc create` lines keyed by their prefixed FileStoreDef name (`<namespace>-<pvc>`).
29
+ * Map preserves insertion order; the prefix guarantees cross-namespace uniqueness so no dedupe logic is needed beyond key collision. */
30
+ this.discoveredPvcs = new Map();
31
+ /** Count of `# TODO:` comment lines emitted across all generated commands; surfaced as a summary banner at top of output. */
32
+ this.todoCount = 0;
33
+ /** Codiac registry codes that must already exist in the tenant for the generated `cod asset create` lines to succeed.
34
+ * Keyed by code, value is the cloud provider hint used to render the `cod imageRegistry capture` suggestion in the banner.
35
+ * `DockerHub|official` is intentionally excluded to keep the banner focused on registries the user must capture themselves. */
36
+ this.discoveredRegistries = new Map();
37
+ }
38
+ run() {
39
+ var _a, _b, _c, _d;
40
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
41
+ const { flags } = yield this.parse(ImportCluster);
42
+ if (!flags["run-in-cluster"]) {
43
+ this.log("The --run-in-cluster flag is required. Interactive mode is not yet supported.");
44
+ this.log("Usage: codiac import cluster --run-in-cluster [--enterprise <name>] [--environment <name>] [--cluster <name>] [--generate-cabinet] [--generate-secret-store] [--generate-pvcs] [--generate-volume-mounts] [--keyvault-name <name>] [--include-default]");
45
+ return;
46
+ }
47
+ // The generated commands all rely on --enterprise. Catch the omission up front rather than emitting broken script lines.
48
+ const generatesAnything = Boolean(flags["generate-cabinet"] || flags["generate-secret-store"] || flags["generate-pvcs"] || flags["generate-volume-mounts"]);
49
+ if (generatesAnything && !flags.enterprise) {
50
+ throw new Error("--enterprise is required when using --generate-cabinet / --generate-secret-store / --generate-pvcs / --generate-volume-mounts.");
51
+ }
52
+ // `cabinet create --silent` requires environment AND cluster (see src/commands/cabinet/create.ts validatePartial).
53
+ if (flags["generate-cabinet"] && (!flags.environment || !flags.cluster)) {
54
+ throw new Error("--generate-cabinet requires both --environment and --cluster.");
55
+ }
56
+ const kubeConfig = new k8s.KubeConfig();
57
+ kubeConfig.loadFromCluster();
58
+ const coreApi = kubeConfig.makeApiClient(k8s.CoreV1Api);
59
+ const appsApi = kubeConfig.makeApiClient(k8s.AppsV1Api);
60
+ const networkingApi = kubeConfig.makeApiClient(k8s.NetworkingV1Api);
61
+ const storageApi = kubeConfig.makeApiClient(k8s.StorageV1Api);
62
+ const namespaces = yield this.listUserNamespaces(coreApi, (_a = flags["include-default"]) !== null && _a !== void 0 ? _a : false);
63
+ if (namespaces.length === 0) {
64
+ this.log("# No user namespaces found in cluster.");
65
+ return;
66
+ }
67
+ this.log(`# Found ${namespaces.length} namespace(s) to import.`);
68
+ // Fetch StorageClass list once so each PVC can resolve its FileStoreType via the className -> CSI driver map.
69
+ // Tolerate failure (e.g. RBAC denial) by falling back to an empty map; PVCs that can't resolve emit a TODO.
70
+ let storageClassByName = new Map();
71
+ if (flags["generate-pvcs"]) {
72
+ try {
73
+ const scList = yield storageApi.listStorageClass();
74
+ for (const sc of (_b = scList.items) !== null && _b !== void 0 ? _b : []) {
75
+ const name = (_c = sc.metadata) === null || _c === void 0 ? void 0 : _c.name;
76
+ if (name && sc.provisioner)
77
+ storageClassByName.set(name, sc.provisioner);
78
+ }
79
+ }
80
+ catch (err) {
81
+ 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.`);
82
+ }
83
+ }
84
+ const allGroups = [];
85
+ for (const namespace of namespaces) {
86
+ const groups = yield this.scanNamespace(namespace, coreApi, appsApi, networkingApi, storageClassByName, flags);
87
+ allGroups.push(...groups);
88
+ }
89
+ this.outputCommands(allGroups, flags);
90
+ });
91
+ }
92
+ // --------------------------------------------------------------------
93
+ // Namespace Discovery
94
+ // --------------------------------------------------------------------
95
+ listUserNamespaces(coreApi, includeDefault) {
96
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
97
+ const result = yield coreApi.listNamespace();
98
+ return result.items
99
+ .map(ns => { var _a; return (_a = ns.metadata) === null || _a === void 0 ? void 0 : _a.name; })
100
+ .filter((name) => {
101
+ if (!name)
102
+ return false;
103
+ if (EXCLUDED_NAMESPACES.has(name))
104
+ return false;
105
+ if (name === "default" && !includeDefault)
106
+ return false;
107
+ return true;
108
+ })
109
+ .sort();
110
+ });
111
+ }
112
+ // --------------------------------------------------------------------
113
+ // Per-Namespace Scanning
114
+ // --------------------------------------------------------------------
115
+ scanNamespace(namespace, coreApi, appsApi, networkingApi, storageClassByName, flags) {
116
+ var _a, _b;
117
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
118
+ const deploymentResult = yield appsApi.listNamespacedDeployment({ namespace });
119
+ const deployments = deploymentResult.items.filter(d => { var _a; return !!((_a = d.metadata) === null || _a === void 0 ? void 0 : _a.name); });
120
+ if (deployments.length === 0)
121
+ return [];
122
+ const services = (yield coreApi.listNamespacedService({ namespace })).items;
123
+ const ingresses = (yield networkingApi.listNamespacedIngress({ namespace })).items;
124
+ // Only list PVCs when at least one PVC-related emission flag is set; the API call is per-namespace.
125
+ const needsPvcs = flags["generate-pvcs"] || flags["generate-volume-mounts"];
126
+ const pvcsInNs = needsPvcs ? yield this.scanNamespacePvcs(namespace, coreApi) : new Map();
127
+ const groups = [];
128
+ for (let i = 0; i < deployments.length; i++) {
129
+ const deployment = deployments[i];
130
+ const matchingServices = this.findServicesForDeployment(deployment, services);
131
+ const matchingIngresses = this.findIngressesForServices(matchingServices, ingresses);
132
+ const group = {
133
+ namespace,
134
+ assetCommand: this.buildAssetCreateCommand(deployment, matchingIngresses, flags),
135
+ configCommands: this.buildConfigCommands(deployment, namespace, flags),
136
+ volumeMountCommands: [],
137
+ filestoreBindCommands: [],
138
+ };
139
+ if (i === 0 && flags["generate-cabinet"]) {
140
+ group.cabinetCommand = this.buildCabinetCreateCommand(namespace, flags);
141
+ }
142
+ if (needsPvcs && pvcsInNs.size > 0) {
143
+ const assetName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
144
+ const mounts = this.findPvcMountsForDeployment(deployment, pvcsInNs);
145
+ for (const mount of mounts) {
146
+ if (flags["generate-volume-mounts"]) {
147
+ group.volumeMountCommands.push(this.buildAssetVolumeCreateCommand(assetName, mount.volName, mount.mountPath, flags));
148
+ }
149
+ if (flags["generate-pvcs"]) {
150
+ const prefixedPvcName = `${namespace}-${mount.k8sPvcName}`;
151
+ const pvc = pvcsInNs.get(mount.k8sPvcName);
152
+ // Resolve the FileStoreType, with PV fallback for statically-bound PVCs.
153
+ const type = yield this.resolveFileStoreType(pvc, storageClassByName, coreApi);
154
+ if (!this.discoveredPvcs.has(prefixedPvcName)) {
155
+ this.discoveredPvcs.set(prefixedPvcName, this.buildPvcCreateCommand(prefixedPvcName, pvc, type, flags));
156
+ }
157
+ group.filestoreBindCommands.push(this.buildFilestoreBindCommand(assetName, namespace, mount.volName, prefixedPvcName, flags));
158
+ }
159
+ }
160
+ }
161
+ groups.push(group);
162
+ }
163
+ return groups;
164
+ });
165
+ }
166
+ scanNamespacePvcs(namespace, coreApi) {
167
+ var _a, _b, _c;
168
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
169
+ const out = new Map();
170
+ try {
171
+ const list = yield coreApi.listNamespacedPersistentVolumeClaim({ namespace });
172
+ for (const pvc of (_a = list.items) !== null && _a !== void 0 ? _a : []) {
173
+ const name = (_b = pvc.metadata) === null || _b === void 0 ? void 0 : _b.name;
174
+ if (name)
175
+ out.set(name, pvc);
176
+ }
177
+ }
178
+ catch (err) {
179
+ this.log(`# WARNING: Failed to list PVCs in namespace [${namespace}] (${(_c = err.message) !== null && _c !== void 0 ? _c : err}).`);
180
+ }
181
+ return out;
182
+ });
183
+ }
184
+ /** Resolves a PVC's FileStoreType using the cluster-wide StorageClass map, falling back to the PV's CSI driver
185
+ * for statically-bound PVCs (storageClassName empty / spec.volumeName set). Returns undefined when no path resolves. */
186
+ resolveFileStoreType(pvc, storageClassByName, coreApi) {
187
+ var _a, _b, _c, _d;
188
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
189
+ const sc = (_a = pvc.spec) === null || _a === void 0 ? void 0 : _a.storageClassName;
190
+ if (sc && storageClassByName.has(sc)) {
191
+ const t = this.csiDriverToFileStoreType(storageClassByName.get(sc));
192
+ if (t)
193
+ return t;
194
+ }
195
+ const pvName = (_b = pvc.spec) === null || _b === void 0 ? void 0 : _b.volumeName;
196
+ if (pvName) {
197
+ try {
198
+ const pv = yield coreApi.readPersistentVolume({ name: pvName });
199
+ return this.csiDriverToFileStoreType((_d = (_c = pv.spec) === null || _c === void 0 ? void 0 : _c.csi) === null || _d === void 0 ? void 0 : _d.driver);
200
+ }
201
+ catch (_e) {
202
+ // PV may not exist yet, or RBAC may deny; fall through to undefined.
203
+ }
204
+ }
205
+ return undefined;
206
+ });
207
+ }
208
+ // --------------------------------------------------------------------
209
+ // Command Generation
210
+ // --------------------------------------------------------------------
211
+ buildCabinetCreateCommand(namespace, flags) {
212
+ const parts = [`codiac cabinet create ${namespace}`];
213
+ if (flags.enterprise)
214
+ parts.push(`--enterprise ${flags.enterprise}`);
215
+ if (flags.environment)
216
+ parts.push(`--environment ${flags.environment}`);
217
+ if (flags.cluster)
218
+ parts.push(`--cluster ${flags.cluster}`);
219
+ parts.push("--silent");
220
+ return parts.join(" ");
221
+ }
222
+ buildAssetCreateCommand(deployment, ingresses, flags) {
223
+ var _a, _b, _c, _d, _e, _f, _g, _h;
224
+ const deploymentName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
225
+ 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 : [];
226
+ const firstContainer = containers[0];
227
+ const hasIngress = ingresses.length > 0;
228
+ const { registry: registryHost, imageName } = this.parseContainerImage(firstContainer === null || firstContainer === void 0 ? void 0 : firstContainer.image);
229
+ 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;
230
+ // `--registry` on `cod asset create` expects the Codiac ImageRegistry CODE (e.g. `devcodiaccontainers`),
231
+ // not the URL the container puller uses. Map known hostname patterns to codes; unknown patterns become a TODO.
232
+ const registryInfo = this.toRegistryCode(registryHost);
233
+ const todoLines = [];
234
+ if (imageName === "<unknown>") {
235
+ todoLines.push(`# TODO: container image could not be inferred from the K8s deployment spec for [${deploymentName}]; set --image manually before running.`);
236
+ this.todoCount++;
237
+ }
238
+ if (registryInfo === undefined) {
239
+ 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.`);
240
+ this.todoCount++;
241
+ }
242
+ else if (registryInfo.code !== "DockerHub|official") {
243
+ // Track non-DockerHub registries so the discovered-registries banner can remind the user to capture them.
244
+ this.discoveredRegistries.set(registryInfo.code, registryInfo.provider);
245
+ }
246
+ const parts = [
247
+ "codiac asset create",
248
+ `--name ${deploymentName}`,
249
+ `--image ${imageName}`,
250
+ ];
251
+ if (registryInfo !== undefined) {
252
+ parts.push(`--registry ${registryInfo.code}`);
253
+ }
254
+ if (flags.enterprise)
255
+ parts.push(`--enterprise ${flags.enterprise}`);
256
+ if (hasIngress) {
257
+ parts.push(`--code ${deploymentName}`);
258
+ }
259
+ if (firstPort !== undefined) {
260
+ parts.push(`--port ${firstPort}`);
261
+ }
262
+ if (hasIngress) {
263
+ parts.push("--hasIngress");
264
+ }
265
+ parts.push("--silent");
266
+ const commandLine = parts.join(" ");
267
+ return todoLines.length > 0 ? `${todoLines.join("\n")}\n${commandLine}` : commandLine;
268
+ }
269
+ /** Maps a container-image hostname to the Codiac `ImageRegistry.code` recognised by `cod asset create --registry`.
270
+ * Returns `undefined` when the hostname can't be resolved so the caller can emit a TODO comment instead of
271
+ * producing a broken command line. Provider hint is paired with the code so the discovered-registries banner
272
+ * can render an accurate `cod imageRegistry capture --provider <p> --name <code>` suggestion. */
273
+ toRegistryCode(host) {
274
+ if (!host || host === "<unknown>")
275
+ return undefined;
276
+ if (host === "docker.io")
277
+ return { code: "DockerHub|official", provider: "dockerHub" };
278
+ if (host.endsWith(".azurecr.io")) {
279
+ const code = host.slice(0, -".azurecr.io".length);
280
+ return code ? { code, provider: "azure" } : undefined;
281
+ }
282
+ return undefined;
283
+ }
284
+ buildConfigCommands(deployment, namespace, flags) {
285
+ var _a, _b, _c, _d, _e, _f, _g;
286
+ const deploymentName = (_b = (_a = deployment.metadata) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unnamed";
287
+ 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 : [];
288
+ const commands = [];
289
+ for (const container of containers) {
290
+ for (const envVar of (_g = container.env) !== null && _g !== void 0 ? _g : []) {
291
+ const cmd = this.buildConfigCommand(deploymentName, namespace, envVar, flags);
292
+ commands.push(cmd);
293
+ }
294
+ }
295
+ return commands;
296
+ }
297
+ buildConfigCommand(assetName, namespace, envVar, flags) {
298
+ const settingName = envVar.name;
299
+ // Direct value
300
+ if (envVar.value !== undefined) {
301
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, `"${envVar.value.replace(/"/g, '\\"')}"`, flags);
302
+ }
303
+ if (envVar.valueFrom) {
304
+ // Secret reference -> #REF syntax
305
+ if (envVar.valueFrom.secretKeyRef) {
306
+ return this.buildSecretRefCommand(assetName, namespace, settingName, envVar.valueFrom.secretKeyRef, flags);
307
+ }
308
+ // Field reference -> k8spatch
309
+ if (envVar.valueFrom.fieldRef) {
310
+ return this.buildFieldRefPatchCommand(assetName, namespace, settingName, envVar.valueFrom.fieldRef, flags);
311
+ }
312
+ // configMapKeyRef / resourceFieldRef -> TODO comments (out of scope)
313
+ const ref = this.describeEnvVarReference(envVar.valueFrom);
314
+ const base = this.buildConfigAddEnvBase(assetName, namespace, settingName, flags);
315
+ this.todoCount++;
316
+ return `# TODO: ${settingName} - ${ref}\n# ${base} --value "<MANUAL_ENTRY_REQUIRED>"`;
317
+ }
318
+ // Empty value
319
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, '""', flags);
320
+ }
321
+ /** Builds a standard codiac config add for a direct env value. */
322
+ buildConfigAddEnvCommand(assetName, namespace, settingName, value, flags) {
323
+ return `${this.buildConfigAddEnvBase(assetName, namespace, settingName, flags)} --value ${value}`;
324
+ }
325
+ /** Builds the base portion of a codiac config add command (without --value). */
326
+ buildConfigAddEnvBase(assetName, namespace, settingName, flags) {
327
+ const parts = ["codiac config add --silent"];
328
+ if (flags.enterprise)
329
+ parts.push(`--enterprise ${flags.enterprise}`);
330
+ parts.push(`-a ${assetName}`, "-t env", `--cabinet=${namespace}`, `--setting ${settingName}`);
331
+ return parts.join(" ");
332
+ }
333
+ /** Builds a codiac config add with #REF|azKeyVault|... for a secretKeyRef env var. */
334
+ buildSecretRefCommand(assetName, namespace, settingName, secretKeyRef, flags) {
335
+ var _a, _b, _c;
336
+ const secretName = (_a = secretKeyRef.name) !== null && _a !== void 0 ? _a : "<unknown>";
337
+ const secretKey = (_b = secretKeyRef.key) !== null && _b !== void 0 ? _b : "<unknown>";
338
+ const storeName = (_c = flags["keyvault-name"]) !== null && _c !== void 0 ? _c : secretName;
339
+ this.discoveredStores.add(storeName);
340
+ const refValue = `'#REF|azKeyVault|${storeName}|${secretKey}'`;
341
+ return this.buildConfigAddEnvCommand(assetName, namespace, settingName, refValue, flags);
342
+ }
343
+ /** Builds a codiac config set with -t k8spatch for a fieldRef (Downward API) env var. */
344
+ buildFieldRefPatchCommand(assetName, namespace, settingName, fieldRef, flags) {
345
+ const parts = ["codiac config set --silent"];
346
+ if (flags.enterprise)
347
+ parts.push(`--enterprise ${flags.enterprise}`);
348
+ parts.push(`-a ${assetName}`, `-c ${namespace}`, "-t k8spatch", "--setting 'deployment'");
349
+ const fieldRefObj = {};
350
+ if (fieldRef.apiVersion)
351
+ fieldRefObj.apiVersion = fieldRef.apiVersion;
352
+ fieldRefObj.fieldPath = fieldRef.fieldPath;
353
+ const patchOp = {
354
+ op: "add",
355
+ path: "/spec/template/spec/containers/0/env/-",
356
+ value: {
357
+ name: settingName,
358
+ valueFrom: { fieldRef: fieldRefObj },
359
+ },
360
+ };
361
+ const patchJson = JSON.stringify([patchOp]);
362
+ const shellEscaped = patchJson.replace(/'/g, "'\\''");
363
+ parts.push(`--value '${shellEscaped}'`);
364
+ return parts.join(" ");
365
+ }
366
+ /** Builds a codiac secretStore:capture command. */
367
+ buildSecretStoreCaptureCommand(storeName) {
368
+ return `codiac secretStore:capture --silent --name=${storeName} --provider=azure`;
369
+ }
370
+ // --------------------------------------------------------------------
371
+ // Output
372
+ // --------------------------------------------------------------------
373
+ outputCommands(groups, flags) {
374
+ if (groups.length === 0) {
375
+ this.log("# No deployments found to import.");
376
+ return;
377
+ }
378
+ this.log("");
379
+ this.log("# " + "=".repeat(58));
380
+ this.log("# Generated Import Commands");
381
+ this.log("# " + "=".repeat(58));
382
+ if (this.todoCount > 0) {
383
+ this.log("");
384
+ this.log("# " + "=".repeat(58));
385
+ this.log(`# WARNING: ${this.todoCount} TODO item(s) below require manual review before running this script.`);
386
+ this.log(`# Search for "# TODO:" to find them.`);
387
+ this.log("# " + "=".repeat(58));
388
+ }
389
+ if (this.discoveredRegistries.size > 0) {
390
+ this.log("");
391
+ this.log("# --- Image Registries (must already exist in Codiac) ---");
392
+ this.log("# The asset create commands below reference these registries by code.");
393
+ this.log("# If any have not yet been captured, run: cod imageRegistry capture --provider <provider> --name <code>");
394
+ this.log("# Discovered:");
395
+ const sortedCodes = [...this.discoveredRegistries.keys()].sort();
396
+ for (const code of sortedCodes) {
397
+ this.log(`# - ${code} (provider: ${this.discoveredRegistries.get(code)})`);
398
+ }
399
+ }
400
+ if (flags["generate-secret-store"] && this.discoveredStores.size > 0) {
401
+ this.log("");
402
+ this.log("# --- Secret Store Capture ---");
403
+ for (const store of [...this.discoveredStores].sort()) {
404
+ this.log(this.buildSecretStoreCaptureCommand(store));
405
+ }
406
+ }
407
+ if (flags["generate-pvcs"] && this.discoveredPvcs.size > 0) {
408
+ this.log("");
409
+ this.log("# --- PVC Filestores ---");
410
+ const sortedNames = [...this.discoveredPvcs.keys()].sort();
411
+ for (const name of sortedNames) {
412
+ this.log(this.discoveredPvcs.get(name));
413
+ }
414
+ }
415
+ let lastNamespace = "";
416
+ for (const group of groups) {
417
+ if (group.namespace !== lastNamespace) {
418
+ this.log("");
419
+ this.log(`# --- Namespace: ${group.namespace} ---`);
420
+ lastNamespace = group.namespace;
421
+ }
422
+ if (group.cabinetCommand) {
423
+ this.log("");
424
+ this.log(group.cabinetCommand);
425
+ }
426
+ this.log("");
427
+ this.log(group.assetCommand);
428
+ if (group.configCommands.length > 0) {
429
+ for (const cmd of group.configCommands) {
430
+ this.log(cmd);
431
+ }
432
+ }
433
+ if (group.volumeMountCommands.length > 0) {
434
+ for (const cmd of group.volumeMountCommands) {
435
+ this.log(cmd);
436
+ }
437
+ }
438
+ if (group.filestoreBindCommands.length > 0) {
439
+ for (const cmd of group.filestoreBindCommands) {
440
+ this.log(cmd);
441
+ }
442
+ }
443
+ }
444
+ this.log("");
445
+ }
446
+ // --------------------------------------------------------------------
447
+ // K8s Matching Logic
448
+ // --------------------------------------------------------------------
449
+ findServicesForDeployment(deployment, services) {
450
+ var _a, _b, _c, _d;
451
+ 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 : {};
452
+ return services.filter(service => {
453
+ var _a, _b;
454
+ const selector = (_b = (_a = service.spec) === null || _a === void 0 ? void 0 : _a.selector) !== null && _b !== void 0 ? _b : {};
455
+ if (Object.keys(selector).length === 0)
456
+ return false;
457
+ return Object.entries(selector).every(([key, value]) => podLabels[key] === value);
458
+ });
459
+ }
460
+ findIngressesForServices(services, ingresses) {
461
+ const serviceNames = new Set(services.map(s => { var _a; return (_a = s.metadata) === null || _a === void 0 ? void 0 : _a.name; }).filter(Boolean));
462
+ return ingresses.filter(ingress => {
463
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
464
+ 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;
465
+ if (defaultBackend && serviceNames.has(defaultBackend))
466
+ return true;
467
+ for (const rule of (_e = (_d = ingress.spec) === null || _d === void 0 ? void 0 : _d.rules) !== null && _e !== void 0 ? _e : []) {
468
+ for (const path of (_g = (_f = rule.http) === null || _f === void 0 ? void 0 : _f.paths) !== null && _g !== void 0 ? _g : []) {
469
+ 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))
470
+ return true;
471
+ }
472
+ }
473
+ return false;
474
+ });
475
+ }
476
+ // --------------------------------------------------------------------
477
+ // Image Parsing
478
+ // --------------------------------------------------------------------
479
+ parseContainerImage(image) {
480
+ if (!image) {
481
+ return { registry: "<unknown>", imageName: "<unknown>" };
482
+ }
483
+ const imageWithoutTag = image.split(":")[0];
484
+ const parts = imageWithoutTag.split("/");
485
+ if (parts.length === 1) {
486
+ return { registry: "docker.io", imageName: parts[0] };
487
+ }
488
+ else if (parts.length === 2) {
489
+ if (parts[0].includes(".") || parts[0].includes(":")) {
490
+ return { registry: parts[0], imageName: parts[1] };
491
+ }
492
+ return { registry: "docker.io", imageName: parts[1] };
493
+ }
494
+ return { registry: parts[0], imageName: parts[parts.length - 1] };
495
+ }
496
+ // --------------------------------------------------------------------
497
+ // Env Var Reference Description (for unsupported types)
498
+ // --------------------------------------------------------------------
499
+ describeEnvVarReference(valueFrom) {
500
+ var _a, _b, _c, _d;
501
+ if (valueFrom.configMapKeyRef) {
502
+ 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>"}"`;
503
+ }
504
+ if (valueFrom.resourceFieldRef) {
505
+ 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>"}"`;
506
+ }
507
+ return "Value from unknown reference type";
508
+ }
509
+ // --------------------------------------------------------------------
510
+ // PVC Discovery & Emission
511
+ // --------------------------------------------------------------------
512
+ /** Maps a CSI driver name (or legacy in-tree provisioner) to the Codiac FileStoreType
513
+ * recognised by `cod pvc create`. Returns undefined when the driver is unknown so the
514
+ * caller can emit a TODO comment instead of guessing. */
515
+ csiDriverToFileStoreType(driver) {
516
+ switch (driver) {
517
+ case "file.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureFileStorage;
518
+ case "disk.csi.azure.com":
519
+ case "kubernetes.io/azure-disk": return contracts_1.FileStoreTypeEnum.azureDisk;
520
+ case "blob.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureBlobStorage;
521
+ case "netapp.csi.azure.com": return contracts_1.FileStoreTypeEnum.azureNetAppFiles;
522
+ default: return undefined;
523
+ }
524
+ }
525
+ /** Walks a deployment's pod template to find every container volumeMount backed by a PVC
526
+ * in `pvcsInNs`. Returns one entry per matched (volume, container) pair. */
527
+ findPvcMountsForDeployment(deployment, pvcsInNs) {
528
+ var _a, _b, _c, _d, _e, _f;
529
+ const podSpec = (_b = (_a = deployment.spec) === null || _a === void 0 ? void 0 : _a.template) === null || _b === void 0 ? void 0 : _b.spec;
530
+ const volumes = (_c = podSpec === null || podSpec === void 0 ? void 0 : podSpec.volumes) !== null && _c !== void 0 ? _c : [];
531
+ const containers = (_d = podSpec === null || podSpec === void 0 ? void 0 : podSpec.containers) !== null && _d !== void 0 ? _d : [];
532
+ const out = [];
533
+ for (const vol of volumes) {
534
+ const claimName = (_e = vol.persistentVolumeClaim) === null || _e === void 0 ? void 0 : _e.claimName;
535
+ if (!claimName || !pvcsInNs.has(claimName))
536
+ continue;
537
+ for (const container of containers) {
538
+ for (const vm of (_f = container.volumeMounts) !== null && _f !== void 0 ? _f : []) {
539
+ if (vm.name === vol.name && vm.mountPath) {
540
+ out.push({ volName: vol.name, mountPath: vm.mountPath, k8sPvcName: claimName });
541
+ }
542
+ }
543
+ }
544
+ }
545
+ return out;
546
+ }
547
+ /** Builds the enterprise-scoped `codiac pvc create` line. When the FileStoreType could not
548
+ * be inferred, prepends a TODO comment line so the user is forced to set `--type` manually. */
549
+ buildPvcCreateCommand(prefixedPvcName, pvc, type, flags) {
550
+ var _a, _b, _c, _d, _e, _f, _g;
551
+ const parts = ["codiac pvc create --silent"];
552
+ if (flags.enterprise)
553
+ parts.push(`--enterprise ${flags.enterprise}`);
554
+ parts.push(`--name ${prefixedPvcName}`);
555
+ parts.push(`--provider azure`);
556
+ if (type)
557
+ parts.push(`--type ${type}`);
558
+ const storageClassName = (_a = pvc.spec) === null || _a === void 0 ? void 0 : _a.storageClassName;
559
+ if (storageClassName)
560
+ parts.push(`--storage-class ${storageClassName}`);
561
+ const modes = (_c = (_b = pvc.spec) === null || _b === void 0 ? void 0 : _b.accessModes) !== null && _c !== void 0 ? _c : [];
562
+ if (modes.length > 0)
563
+ parts.push(`--access-modes ${modes.join(",")}`);
564
+ 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;
565
+ if (size)
566
+ parts.push(`--size ${size}`);
567
+ if ((_g = pvc.spec) === null || _g === void 0 ? void 0 : _g.volumeName)
568
+ parts.push(`--volume-name ${pvc.spec.volumeName}`);
569
+ const line = parts.join(" ");
570
+ if (!type) {
571
+ const sc = storageClassName !== null && storageClassName !== void 0 ? storageClassName : "<none>";
572
+ this.todoCount++;
573
+ return `# TODO: --type could not be inferred (storageClass=${sc}); set it manually before running.\n${line}`;
574
+ }
575
+ return line;
576
+ }
577
+ /** Builds the asset-scoped `codiac asset volume create` line. The mountPath is a positional arg. */
578
+ buildAssetVolumeCreateCommand(assetName, volName, mountPath, flags) {
579
+ const parts = ["codiac asset volume create --silent"];
580
+ if (flags.enterprise)
581
+ parts.push(`--enterprise ${flags.enterprise}`);
582
+ parts.push(`--asset ${assetName}`);
583
+ parts.push(`--name ${volName}`);
584
+ parts.push(mountPath);
585
+ // Auto-description so reviewers can later audit where each volume mount originated.
586
+ // Defensive escape of any embedded double-quotes (K8s names are DNS-safe so this is belt-and-suspenders).
587
+ const description = `Imported from K8s deployment ${assetName}, volume ${volName}, mount ${mountPath}`.replace(/"/g, '\\"');
588
+ parts.push(`--description "${description}"`);
589
+ return parts.join(" ");
590
+ }
591
+ /** Builds the cabinet-scoped `codiac config add -t filestore` line that binds an asset's reserved
592
+ * folder (declared by `asset volume create`) to the FileStoreDef (created by `pvc create`). */
593
+ buildFilestoreBindCommand(assetName, namespace, volName, prefixedPvcName, flags) {
594
+ const parts = ["codiac config add --silent"];
595
+ if (flags.enterprise)
596
+ parts.push(`--enterprise ${flags.enterprise}`);
597
+ parts.push(`-a ${assetName}`, `-c ${namespace}`, "-t filestore", `--setting ${volName}`, `--value ${prefixedPvcName}`);
598
+ return parts.join(" ");
599
+ }
600
+ }
601
+ exports.default = ImportCluster;
602
+ ImportCluster.description = 'Scans all deployments in the current Kubernetes cluster and generates the Codiac CLI commands to import them.';
603
+ ImportCluster.examples = [
604
+ '<%= config.bin %> <%= command.id %> --run-in-cluster',
605
+ '<%= config.bin %> <%= command.id %> --run-in-cluster --enterprise myco --environment dev --cluster mycluster --generate-cabinet --generate-secret-store',
606
+ '<%= config.bin %> <%= command.id %> --run-in-cluster --enterprise myco --environment dev --cluster mycluster --generate-pvcs --generate-volume-mounts',
607
+ ];
608
+ ImportCluster.flags = {
609
+ "run-in-cluster": core_1.Flags.boolean({ description: 'Run using in-cluster Kubernetes config (service account). Required for headless/pod execution.' }),
610
+ enterprise: core_1.Flags.string({ description: 'Enterprise code to include in generated commands.' }),
611
+ environment: core_1.Flags.string({ description: 'Environment name to include in generated commands (required by --generate-cabinet).' }),
612
+ cluster: core_1.Flags.string({ description: 'Cluster name to include in generated cabinet create commands (required by --generate-cabinet).' }),
613
+ "generate-cabinet": core_1.Flags.boolean({ description: 'Include codiac cabinet create commands in the output (one per namespace). Requires --enterprise, --environment, and --cluster.' }),
614
+ "generate-secret-store": core_1.Flags.boolean({ description: 'Include codiac secretStore:capture commands in the output for discovered Azure Key Vaults.' }),
615
+ "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.' }),
616
+ "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.' }),
617
+ "include-default": core_1.Flags.boolean({ description: 'Include the "default" namespace (excluded by default).' }),
618
+ "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.' }),
619
+ };
620
+ //# sourceMappingURL=cluster.js.map