UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

415 lines 20.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.K8sConverter = void 0; const mapper_1 = require("./classes/mapper"); const types_1 = require("./types"); const workload_1 = require("./types/workload"); const workload_2 = require("./classes/handlers/workload"); const cron_job_1 = require("./classes/handlers/cron-job"); const cron_job_2 = require("./types/cron-job"); const io_1 = require("../util/io"); const helper_1 = require("./util/helper"); const secret_1 = require("./types/secret"); const secret_2 = require("./classes/handlers/secret"); const configmap_1 = require("./types/configmap"); const configmap_2 = require("./classes/handlers/configmap"); const identity_manager_1 = require("./classes/identity-manager"); const pvc_1 = require("./types/pvc"); const pvc_2 = require("./classes/handlers/pvc"); const pv_1 = require("./types/pv"); const job_1 = require("./types/job"); const job_2 = require("./classes/handlers/job"); const constants_1 = require("./config/constants"); const client_1 = require("../session/client"); const logger_1 = require("../util/logger"); const archive_manager_1 = require("../archive-manager"); const conversion_1 = require("../util/conversion"); const api_1 = require("../rest/api"); const shape_printer_1 = require("./classes/shape-printer"); const storage_class_1 = require("./types/storage-class"); const hpa_1 = require("./types/hpa"); const service_account_1 = require("./types/service-account"); class K8sConverter { constructor(filePaths, ctx, session, overrideProtocol) { this.pullSecrets = []; this.warnings = []; this.overrideProtocol = overrideProtocol; this.convertedObjects = []; this.unconvertedObjects = []; this.filePaths = filePaths; this.ctx = ctx; this.session = session; } /*** Public Methods ***/ async convert() { const resources = []; const objects = []; // Initialize the K8s mapper that will later be used to help convert K8s objects const mapper = new mapper_1.K8sMapper(this.overrideProtocol); // Read file paths into k8s objects for (const filePath of this.filePaths) { // Extract content from the file const fileContent = await (0, io_1.readTextFile)(filePath); // Load into a K8s object list const loadedK8sResources = (0, io_1.loadAllObject)(fileContent, filePath); // Iterate over each K8s resource, validate and add it to the 'objects' array for (const loadedK8sObject of loadedK8sResources) { const objectFile = { resource: loadedK8sObject, filePath: filePath, }; // Validate K8s object this.validateK8sObject(objectFile); // Handle kind 'List' differently if (objectFile.resource.kind === 'List') { // Cast to K8sObjectList const list = objectFile.resource; // Iterate over each K8s object item and add it to the 'objects' array for (const item of list.items) { const itemObjectFile = { resource: item, filePath: filePath, }; // Add the list item to the 'objects' array objects.push(itemObjectFile); } // Avoid adding the object of kind 'List' to the 'objects' array continue; } // Add to the 'objects' array objects.push(objectFile); } } // Initialize mapper mapper.initialize(objects); // Iterate over all captured objects and convert for (const object of objects) { // Assume this object is converted until proven otherwise let isConverted = true; // Convert the K8s object based on its kind switch (object.resource.kind) { case 'ReplicationController': case 'ReplicaSet': case 'Deployment': case 'StatefulSet': case 'DaemonSet': const k8sWorkload = object.resource; const k8sWorkloadHandler = new workload_2.K8sWorkloadHandler(object.filePath, k8sWorkload, mapper); resources.push(k8sWorkloadHandler.handle()); // Handle persistent volume claims if (k8sWorkload.spec.volumeClaimTemplates) { for (const pvc of k8sWorkload.spec.volumeClaimTemplates) { const pvcHandler = new pvc_2.K8sPersistentVolumeClaimHandler(object.filePath, pvc, mapper); resources.push(pvcHandler.handle()); } } break; case 'ReplicationControllerList': case 'ReplicaSetList': case 'DeploymentList': case 'StatefulSetList': case 'DaemonSetList': const k8sWorkloadList = object.resource; for (const k8sWorkload of k8sWorkloadList.items) { const k8sWorkloadHandler = new workload_2.K8sWorkloadHandler(object.filePath, k8sWorkload, mapper); resources.push(k8sWorkloadHandler.handle()); // Handle persistent volume claims if (k8sWorkload.spec.volumeClaimTemplates) { for (const pvc of k8sWorkload.spec.volumeClaimTemplates) { const pvcHandler = new pvc_2.K8sPersistentVolumeClaimHandler(object.filePath, pvc, mapper); resources.push(pvcHandler.handle()); } } } break; case 'Job': const job = object.resource; const jobHandler = new job_2.K8sJobHandler(object.filePath, job, mapper); resources.push(jobHandler.handle()); break; case 'JobList': const jobList = object.resource; for (const job of jobList.items) { const jobHandler = new job_2.K8sJobHandler(object.filePath, job, mapper); resources.push(jobHandler.handle()); } break; case 'CronJob': const cronJob = object.resource; const cronJobHandler = new cron_job_1.K8sCronJobHandler(object.filePath, cronJob, mapper); resources.push(cronJobHandler.handle()); break; case 'CronJobList': const cronJobList = object.resource; for (const cronJob of cronJobList.items) { const cronJobHandler = new cron_job_1.K8sCronJobHandler(object.filePath, cronJob, mapper); resources.push(cronJobHandler.handle()); } break; case 'ConfigMap': const configMap = object.resource; const configMapHandler = new configmap_2.K8sConfigMapHandler(object.filePath, configMap); resources.push(configMapHandler.handle()); break; case 'ConfigMapList': const configMapList = object.resource; for (const configMap of configMapList.items) { const configMapHandler = new configmap_2.K8sConfigMapHandler(object.filePath, configMap); resources.push(configMapHandler.handle()); } break; case 'Secret': const secret = object.resource; const secretHandler = new secret_2.K8sSecretHandler(object.filePath, secret); resources.push(secretHandler.handle()); break; case 'SecretList': const secretList = object.resource; for (const secret of secretList.items) { const secretHandler = new secret_2.K8sSecretHandler(object.filePath, secret); resources.push(secretHandler.handle()); } break; case 'PersistentVolumeClaim': const pvc = object.resource; const pvcHandler = new pvc_2.K8sPersistentVolumeClaimHandler(object.filePath, pvc, mapper); resources.push(pvcHandler.handle()); break; case 'PersistentVolumeClaimList': const pvcList = object.resource; for (const pvc of pvcList.items) { const pvcHandler = new pvc_2.K8sPersistentVolumeClaimHandler(object.filePath, pvc, mapper); resources.push(pvcHandler.handle()); } break; default: // If none of the cases were executed and this is not a mapped object, then it means we couldn't convert it isConverted = types_1.K8sMappableKinds.includes(object.resource.kind); break; } // Print and stringify object's shape const stringifiedObject = this.printAndStringifyShape(object, isConverted); // Only track objects that are not in the logging ignore list if (!types_1.K8sLoggingIgnore.includes(object.resource.kind)) { // Add the object to either converted or unconverted list based on conversion status if (isConverted) { // Store the stringified shape of successfully converted objects this.convertedObjects.push(stringifiedObject); } else { // Store the raw JSON of objects that couldn't be converted this.unconvertedObjects.push(JSON.stringify(object.resource)); } } } // Add reliant resources resources.push(...this.getReliantResources(mapper)); // Update pull secrets this.pullSecrets.push(...mapper.imagePullSecrets); // Let's save ourselves the effort and avoid submitting if there were no converted/unconverted objects if (this.convertedObjects.length != 0 || this.unconvertedObjects.length != 0) { // Submit conversion data to K8s converter logging await this.submitConversion(); } // This will return all necessary Control Plane objects converted from K8s objects return resources; } /*** Private Methods ***/ printAndStringifyShape(object, isConverted) { let shape = undefined; if (isConverted) { switch (object.resource.kind) { case 'ReplicationController': case 'ReplicaSet': case 'Deployment': case 'StatefulSet': case 'DaemonSet': shape = workload_1.K8sWorkloadShape; break; case 'ReplicationControllerList': case 'ReplicaSetList': case 'DeploymentList': case 'StatefulSetList': case 'DaemonSetList': shape = workload_1.K8sWorkloadListShape; break; case 'Job': shape = job_1.K8sJobShape; break; case 'JobList': shape = job_1.K8sJobListShape; break; case 'CronJob': shape = cron_job_2.K8sCronJobShape; break; case 'CronJobList': shape = cron_job_2.K8sCronJobListShape; break; case 'ConfigMap': shape = configmap_1.K8sConfigMapShape; break; case 'ConfigMapList': shape = configmap_1.K8sConfigMapListShape; break; case 'Secret': shape = secret_1.K8sSecretShape; break; case 'SecretList': shape = secret_1.K8sSecretListShape; break; case 'PersistentVolumeClaim': shape = pvc_1.K8sPersistentVolumeClaimShape; break; case 'PersistentVolumeClaimList': shape = pvc_1.K8sPersistentVolumeClaimListShape; break; case 'PersistentVolume': shape = pv_1.K8sPersistentVolumeShape; break; case 'PersistentVolumeList': shape = pv_1.K8sPersistentVolumeListShape; break; case 'StorageClass': shape = storage_class_1.K8sStorageClassShape; break; case 'StorageClassList': shape = storage_class_1.K8sStorageClassListShape; break; case 'HorizontalPodAutoscaler': shape = hpa_1.K8sHorizontalPodAutoscalerShape; break; case 'HorizontalPodAutoscalerList': shape = hpa_1.K8sHorizontalPodAutoscalerListShape; break; case 'ServiceAccount': shape = service_account_1.K8sServiceAccountShape; break; case 'ServiceAccountList': shape = service_account_1.K8sServiceAccountListShape; break; default: const errorMessage = `DEVELOPER ERROR: Shape for K8s kind '${object.resource.kind}' is not implemented or specified in the print method.`; logger_1.logger.error(errorMessage); return errorMessage; } } return new shape_printer_1.K8sShapePrinter(object, shape).printAndStringify(); } getReliantResources(mapper) { const resources = []; // Create identity manager const identityManager = new identity_manager_1.IdentityManager(this.ctx); // Assign identities to workloads if applicable identityManager.assignIdentitiesToWorkloads(mapper.workloadToSecretNames); // Add policies and identities to reliant resources resources.push(...identityManager.identities); resources.push(...identityManager.policies); return resources; } async submitConversion() { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; // Avoid submitting if there is no session specified if (!this.session || process.env.SKIP_K8S_CONVERTER_LOGGING == 'true') { return; } try { // Initialize the archive manager so we can compress K8s files const archiveManager = new archive_manager_1.default(); // ZIP the K8s specified files const zipBuffer = await archiveManager.compress(this.filePaths); // Convert to base64 const zipBase64 = conversion_1.default.bufferToBase64(zipBuffer); // Prepare the body request const body = { org_id: this.session.context.org || '', user_id: ((_b = (_a = this.session.profile) === null || _a === void 0 ? void 0 : _a.authInfo) === null || _b === void 0 ? void 0 : _b.email) || 'unknown-user', original_yaml: zipBase64, converted_objects: this.convertedObjects, unconverted_objects: this.unconvertedObjects, endpoint: ((_d = (_c = this.session.profile) === null || _c === void 0 ? void 0 : _c.request) === null || _d === void 0 ? void 0 : _d.endpoint) || api_1.DATA_SERVICE_BASE_URL, }; // Create a new client for the conversion service endpoint const client = (0, client_1.makeClient)(this.session.request, constants_1.K8S_CONVERTER_LOGGING_BASE_URL); // Make the submission request const response = await client.post(constants_1.K8S_CONVERTER_LOGGING_CONVERSIONS_PATH, body); // This will only be printed if the user specified --verbose logger_1.logger.debug('K8s Conversion record created successfully. Response Object:'); logger_1.logger.debug(JSON.stringify(response, null, 2)); } catch (e) { if ((_f = (_e = e.response) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.message) { logger_1.logger.debug(`ERROR: Unable to submit K8s conversion result to the K8s Converter Logging service. Status: ${((_h = (_g = e.response) === null || _g === void 0 ? void 0 : _g.data) === null || _h === void 0 ? void 0 : _h.status) || ((_j = e.response) === null || _j === void 0 ? void 0 : _j.status)}, Message: ${(_l = (_k = e.response) === null || _k === void 0 ? void 0 : _k.data) === null || _l === void 0 ? void 0 : _l.message}. Entire error object below:`); } else { logger_1.logger.debug(`ERROR: An unexpected error occurred during K8s conversion submission to K8s Converter Logging service. Message: ${e.message}`); } } } // Validators // validateK8sObject(object) { if (!object.resource.metadata) { (0, helper_1.ensurePropertyPresence)('metadata', object.filePath); } if (!object.resource.metadata.name && object.resource.kind != 'List') { (0, helper_1.ensurePropertyPresence)('metadata.name', object.filePath); } if (!object.resource.apiVersion) { (0, helper_1.ensurePropertyPresence)('apiVersion', object.filePath, object.resource); } if (!object.resource.kind) { (0, helper_1.ensurePropertyPresence)('kind', object.filePath, object.resource); } // Validate that each persistent volume claim that is attached to a K8s stateful workload is a K8s object if (object.resource.kind == 'StatefulSet') { const k8sWorkload = object.resource; if (k8sWorkload.hasOwnProperty('spec') && k8sWorkload.spec.volumeClaimTemplates) { for (const pvc of k8sWorkload.spec.volumeClaimTemplates) { if (!pvc.apiVersion) { pvc.apiVersion = 'v1'; } if (!pvc.kind) { pvc.kind = 'PersistentVolumeClaim'; } this.validateK8sObject({ resource: pvc, filePath: object.filePath }); } } } // Ensure every persistent volume has property spec defined if (object.resource.kind == 'PersistentVolume') { const pv = object.resource; if (!pv.spec) { (0, helper_1.ensurePropertyPresence)('spec', object.filePath, pv); } } switch (object.resource.kind) { case 'ReplicationControllerList': case 'ReplicaSetList': case 'DeploymentList': case 'StatefulSetList': case 'DaemonSetList': case 'JobList': case 'CronJobList': case 'ConfigMapList': case 'SecretList': case 'StorageClassList': case 'PersistentVolumeClaimList': case 'PersistentVolumeList': case 'HorizontalPodAutoscalerList': case 'ServiceAccountList': case 'List': this.validateK8sObjectList(object); break; } } validateK8sObjectList(object) { const list = object.resource; if (!list.items) { (0, helper_1.ensurePropertyPresence)('items', object.filePath, object.resource); } for (const item of list.items) { this.validateK8sObject({ resource: item, filePath: object.filePath }); } } } exports.K8sConverter = K8sConverter; //# sourceMappingURL=converter.js.map