@controlplane/cli
Version:
Control Plane Corporation CLI
207 lines • 9.93 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.K8sMapper = void 0;
const types_1 = require("../types");
const hpa_1 = require("./handlers/hpa");
class K8sMapper {
constructor(overrideProtocol) {
this.overrideProtocol = overrideProtocol;
this.k8sWorkloadNameToHpa = {};
this.workloadToSecretNames = new Map();
this.pvcToWorkload = new Map();
this.pvNameToObject = new Map();
this.storageClassNameToObject = new Map();
this.serviceAccountNameToObject = new Map();
this.configMapNameToObject = new Map();
this.imagePullSecrets = [];
this.warnings = [];
this.kindToK8sObject = {};
this.kindToEquivalent = new Map();
// Fill kind to equivalent
this.kindToEquivalent.set('ConfigMap', 'Secret');
this.kindToEquivalent.set('ReplicationController', 'Deployment');
this.kindToEquivalent.set('ReplicaSet', 'Deployment');
this.kindToEquivalent.set('StatefulSet', 'Deployment');
this.kindToEquivalent.set('Job', 'Deployment');
this.kindToEquivalent.set('CronJob', 'Deployment');
this.kindToEquivalent.set('DaemonSet', 'Deployment');
}
/*** Public Methods ***/
initialize(objects) {
// Map objects to their respective data structures
for (const object of objects) {
// Ensure that no object of the same kind has the same name
this.ensureUniqueNames(object);
// Assume this object is being mapped until proven otherwise
let isMapped = true;
// Start mapping objects to their respective data structures
switch (object.resource.kind) {
case 'StatefulSet':
this.handleK8sStatefulWorkload(object.resource);
break;
case 'StatefulSetList':
const k8sWorkloadList = object.resource;
for (const k8sWorkload of k8sWorkloadList.items) {
this.handleK8sStatefulWorkload(k8sWorkload);
}
break;
case 'ConfigMap':
const configMap = object.resource;
this.configMapNameToObject.set(configMap.metadata.name, configMap);
break;
case 'ConfigMapList':
const configMapList = object.resource;
for (const configMap of configMapList.items) {
this.configMapNameToObject.set(configMap.metadata.name, configMap);
}
break;
case 'PersistentVolumeClaim':
this.pvcToWorkload.set(object.resource.metadata.name, '');
break;
case 'PersistentVolumeClaimList':
const pvcList = object.resource;
for (const pvc of pvcList.items) {
this.pvcToWorkload.set(pvc.metadata.name, '');
}
break;
case 'PersistentVolume':
const pv = object.resource;
this.pvNameToObject.set(pv.metadata.name, pv);
break;
case 'PersistentVolumeList':
const pvList = object.resource;
for (const pv of pvList.items) {
this.pvNameToObject.set(pv.metadata.name, pv);
}
break;
case 'StorageClass':
const storageClass = object.resource;
this.storageClassNameToObject.set(storageClass.metadata.name, storageClass);
break;
case 'StorageClassList':
const storageClassList = object.resource;
for (const storageClass of storageClassList.items) {
this.storageClassNameToObject.set(storageClass.metadata.name, storageClass);
}
break;
case 'HorizontalPodAutoscaler':
this.handleHpa(object.filePath, object.resource);
break;
case 'HorizontalPodAutoscalerList':
const hpaList = object.resource;
for (const hpa of hpaList.items) {
this.handleHpa(object.filePath, hpa);
}
break;
case 'ServiceAccount':
const serviceAccount = object.resource;
this.serviceAccountNameToObject.set(serviceAccount.metadata.name, serviceAccount);
break;
case 'ServiceAccountList':
const serviceAccountList = object.resource;
for (const serviceAccount of serviceAccountList.items) {
this.serviceAccountNameToObject.set(serviceAccount.metadata.name, serviceAccount);
}
break;
default:
isMapped = false;
break;
}
// Don't worry about this, this is put in place so we can avoid developer error;
// It helps sync between the kinds being mapped and the mappable kinds list
if (isMapped && !types_1.K8sMappableKinds.includes(object.resource.kind)) {
// As I said, don't worry about it; This exception will never fire on production because we will catch it in test
throw new Error(`DEVELOPER ERROR: K8s Kind: '${object.resource.kind}' needs to be added to the K8sMappableKinds array.`);
}
}
}
mapSecretToWorkload(secretName, workload) {
if (!this.workloadToSecretNames.has(workload)) {
this.workloadToSecretNames.set(workload, new Set([secretName]));
return;
}
// Get secret names that this workload is referring to
const secretNames = this.workloadToSecretNames.get(workload);
// Don't add the same secret twice to the map
if (secretNames.has(secretName)) {
return;
}
// Map secret to workload
secretNames.add(secretName);
}
/*** Private Methods ***/
ensureUniqueNames(object) {
let name = object.resource.metadata.name;
let kind = object.resource.kind;
let kindToReplace;
let replacedWithKind;
// Handle lists
if (object.resource.hasOwnProperty('items')) {
const list = object.resource;
for (const item of list.items) {
this.ensureUniqueNames({ filePath: object.filePath, resource: item });
}
return;
}
// Process persistent volume claims of a K8s stateful workload
if (object.resource.kind == 'StatefulSet') {
const k8sWorkload = object.resource;
// Ensure that each persistent volume claim name is not creating a conflict
if (k8sWorkload.hasOwnProperty('spec') && k8sWorkload.spec.volumeClaimTemplates) {
for (const pvc of k8sWorkload.spec.volumeClaimTemplates) {
this.ensureUniqueNames({ resource: pvc, filePath: object.filePath });
}
}
}
// Replace kinds with their equivalent
if (this.kindToEquivalent.has(kind)) {
kindToReplace = kind;
replacedWithKind = this.kindToEquivalent.get(kind);
kind = replacedWithKind;
}
// Register kind as a key
if (!this.kindToK8sObject[kind]) {
this.kindToK8sObject[kind] = {};
}
// Check if the name already exists for this kind, if so, throw an error accordingly
if (this.kindToK8sObject[kind][name]) {
const existingObject = this.kindToK8sObject[kind][name];
let fileMessage = object.filePath == existingObject.filePath
? `File path: ${object.filePath}`
: `File paths: '${object.filePath}' and '${existingObject.filePath}'`;
let message = `ERROR: Duplicate name '${name}' found for kind '${kind}'. ${fileMessage}.`;
// The following cases might occur:
// - ConfigMap == ConfigMap || Secret != Secret => A Secret already registered this name, so a ConfigMap cannot
// - Secret == Undefined || ConfigMap != Secret => A ConfigMap already registered this name, so a Secret cannot
if (existingObject.resource.kind == kindToReplace || existingObject.resource.kind != kind) {
message = `Error: A '${existingObject.resource.kind}' cannot share the same name as a '${kind}'. Name '${name}', ${fileMessage}.`;
}
throw new Error(message);
}
// Reserve this name for this kind
this.kindToK8sObject[kind][name] = object;
}
handleK8sStatefulWorkload(object) {
const k8sWorkload = object;
if (!k8sWorkload.hasOwnProperty('spec') || !k8sWorkload.spec.volumeClaimTemplates) {
return;
}
// Map persistent volume claim name to workload name
for (const pvc of k8sWorkload.spec.volumeClaimTemplates) {
this.pvcToWorkload.set(pvc.metadata.name, k8sWorkload.metadata.name);
}
}
handleHpa(filePath, hpa) {
// Handle Horizontal Pod Autoscaler
const hpaHandler = new hpa_1.K8sHorizontalPodAutoscalerHandler(filePath, hpa);
hpaHandler.handle();
// Only handle auto-scalable kinds
if (!types_1.K8sAutoscalableKinds.includes(hpa.spec.scaleTargetRef.kind)) {
return;
}
// Map the Horizontal Pod Autoscaler to workload
this.k8sWorkloadNameToHpa[hpa.spec.scaleTargetRef.name] = hpa;
}
}
exports.K8sMapper = K8sMapper;
//# sourceMappingURL=mapper.js.map