@controlplane/cli
Version:
Control Plane Corporation CLI
415 lines • 20.6 kB
JavaScript
"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