@controlplane/cli
Version:
Control Plane Corporation CLI
828 lines • 43.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.K8sPodTemplateProcessor = void 0;
const helper_1 = require("../../util/helper");
class K8sPodTemplateProcessor {
constructor(filePath, propertyPath, k8sObject, workload, pod, mapper) {
this.filePath = filePath;
this.propertyPath = propertyPath;
this.k8sObject = k8sObject;
this.workload = workload;
this.pod = pod;
this.mapper = mapper;
this.volumes = {};
}
/*** Public Methods ***/
process() {
// Validate pod
this.validate();
// Process volumes
this.processVolumes();
// Process containers
this.processContainers();
// Process job related properties
this.processJob();
// Process Pull Secrets
this.processImagePullSecrets();
}
/*** Private Methods ***/
// Volumes //
processVolumes() {
if (!this.pod.spec.volumes) {
return;
}
for (const volume of this.pod.spec.volumes) {
this.volumes[volume.name] = volume;
}
}
// Containers //
processContainers() {
// Iterate over each container
for (const k8sContainer of this.pod.spec.containers) {
const container = {
name: k8sContainer.name,
image: k8sContainer.image,
};
// Process resources
this.processContainerResources(container, k8sContainer);
// Process command
if (k8sContainer.command) {
container.command = k8sContainer.command.join(' ');
}
// Process args
if (k8sContainer.args) {
container.args = k8sContainer.args;
}
// Process working directory
if (k8sContainer.workingDir) {
container.workingDir = k8sContainer.workingDir;
}
// Process ports
this.processContainerPorts(container, k8sContainer);
// Process Env
this.processContainerEnv(container, k8sContainer);
// Process Volume Mounts
this.processContainerVolumeMounts(container, k8sContainer);
// Process Lifecycle
this.processContainerLifecycle(container, k8sContainer);
// Process Liveness Probe
if (k8sContainer.livenessProbe) {
container.livenessProbe = this.processContainerProbe(k8sContainer.livenessProbe);
}
// Process Readiness Probe
if (k8sContainer.readinessProbe) {
container.readinessProbe = this.processContainerProbe(k8sContainer.readinessProbe);
}
this.workload.spec.containers.push(container);
}
}
processContainerResources(container, k8sContainer) {
let cpu = '50m';
let memory = '128Mi';
// Extract the resources from the Kubernetes container
if (k8sContainer.resources) {
// If limits is provided and there is at least CPU or memory specified, then let's handle limits as the one that has the resource definitions
if (k8sContainer.resources.limits && (k8sContainer.resources.limits['cpu'] || k8sContainer.resources.limits['memory'])) {
if (k8sContainer.resources.limits['cpu']) {
cpu = k8sContainer.resources.limits['cpu'];
}
if (k8sContainer.resources.limits['memory']) {
memory = k8sContainer.resources.limits['memory'];
}
}
// Otherwise, let's try to extract the resources from the requests property
else if (k8sContainer.resources.requests) {
if (k8sContainer.resources.requests['cpu']) {
cpu = k8sContainer.resources.requests['cpu'];
}
if (k8sContainer.resources.requests['memory']) {
memory = k8sContainer.resources.requests['memory'];
}
}
}
container.cpu = cpu;
container.memory = memory;
}
processContainerPorts(container, k8sContainer) {
if (!k8sContainer.ports) {
return;
}
// Define
container.ports = [];
for (const k8sPort of k8sContainer.ports) {
let protocol = 'http';
if (k8sPort.protocol) {
protocol = k8sPort.protocol;
}
if (this.mapper.overrideProtocol) {
protocol = this.mapper.overrideProtocol;
}
const port = {
protocol: protocol.toLowerCase(),
number: k8sPort.containerPort,
};
container.ports.push(port);
}
}
processContainerEnv(container, k8sContainer) {
if (!k8sContainer.env) {
return;
}
// Define
container.env = [];
for (const k8sEnv of k8sContainer.env) {
const env = {
name: k8sEnv.name,
};
if (k8sEnv.value) {
env.value = k8sEnv.value;
}
if (k8sEnv.valueFrom) {
if (k8sEnv.valueFrom.fieldRef) {
env.value = `cpln://reference/${k8sEnv.valueFrom.fieldRef.fieldPath}`;
}
if (k8sEnv.valueFrom.secretKeyRef) {
let secretLink = `cpln://secret/${k8sEnv.valueFrom.secretKeyRef.name}`;
if (k8sEnv.valueFrom.secretKeyRef.key) {
secretLink = `${secretLink}.${k8sEnv.valueFrom.secretKeyRef.key}`;
}
// Set env value
env.value = secretLink;
// Map secret name to workload name
this.mapper.mapSecretToWorkload(k8sEnv.valueFrom.secretKeyRef.name, this.workload);
}
if (k8sEnv.valueFrom.configMapKeyRef) {
let secretLink = `cpln://secret/${k8sEnv.valueFrom.configMapKeyRef.name}`;
if (k8sEnv.valueFrom.configMapKeyRef.key) {
secretLink = `${secretLink}.${k8sEnv.valueFrom.configMapKeyRef.key}`;
}
// Set env value
env.value = secretLink;
// Map secret name to workload name
this.mapper.mapSecretToWorkload(k8sEnv.valueFrom.configMapKeyRef.name, this.workload);
}
}
container.env.push(env);
}
}
processContainerVolumeMounts(container, k8sContainer) {
var _a;
if (!k8sContainer.volumeMounts || k8sContainer.volumeMounts.length == 0) {
return;
}
// Define
container.volumes = [];
// Iterate over each k8s volume mount
for (const k8sVolumeMount of k8sContainer.volumeMounts) {
if (!this.volumes[k8sVolumeMount.name]) {
// Check if the volume mount is referring to a persistent volume claim template
if (this.k8sObject.kind == 'StatefulSet') {
const k8sWorkload = this.k8sObject;
// Iterate over each persistent volume claim in order to found what the volume mount is referring to
for (const pvc of (_a = k8sWorkload.spec.volumeClaimTemplates) !== null && _a !== void 0 ? _a : []) {
if (pvc.metadata.name == k8sVolumeMount.name) {
const uri = `cpln://volumeset/${pvc.metadata.name}`;
const path = `${k8sVolumeMount.mountPath}`;
container.volumes.push({
uri,
path,
});
// Exit for loop
break;
}
}
}
// Handle next volume mount
continue;
}
const k8sVolume = this.volumes[k8sVolumeMount.name];
// Mount Persistent Volume Claim as a volume
if (k8sVolume.persistentVolumeClaim) {
const uri = `cpln://volumeset/${k8sVolume.persistentVolumeClaim.claimName}`;
const path = `${k8sVolumeMount.mountPath}`;
container.volumes.push({
uri,
path,
});
// Handle next volume mount
continue;
}
// Mount ConfigMap as a volume
if (k8sVolume.configMap) {
this.mountVolume(container.volumes, k8sVolumeMount, k8sVolume.configMap.name, k8sVolume.configMap.items);
// Handle next volume mount
continue;
}
// Mount Secret as a volume
if (k8sVolume.secret) {
this.mountVolume(container.volumes, k8sVolumeMount, k8sVolume.secret.secretName, k8sVolume.secret.items);
// Handle next volume mount
continue;
}
// Mount multiple projected volumes
if (k8sVolume.projected) {
for (const projectedSource of k8sVolume.projected.sources) {
if (projectedSource.configMap) {
this.mountVolume(container.volumes, k8sVolumeMount, projectedSource.configMap.name, projectedSource.configMap.items);
}
if (projectedSource.secret) {
this.mountVolume(container.volumes, k8sVolumeMount, projectedSource.secret.name, projectedSource.secret.items);
}
}
// Handle next volume mount
continue;
}
// Mount a shared volume
if (k8sVolume.emptyDir) {
const uri = `scratch://${k8sVolume.name}`;
let path = k8sVolumeMount.mountPath;
if (k8sVolumeMount.subPath) {
path = `${path}/${k8sVolumeMount.subPath}`;
}
container.volumes.push({
uri,
path,
});
// Handle next volume mount
}
}
}
mountVolume(volumes, k8sVolumeMount, secretName, volumeItems) {
// Map secret name to workload name
this.mapper.mapSecretToWorkload(secretName, this.workload);
// Prepare volume
let uri = `cpln://secret/${secretName}`;
let path = k8sVolumeMount.mountPath;
// Process items and return
if (volumeItems && volumeItems.length != 0) {
// Iterate over each item and add a new volume
for (const item of volumeItems) {
// If a sub path is specified, then we will only pick and handle one item
if (k8sVolumeMount.subPath) {
// Find the path that the sub path is referencing
if (k8sVolumeMount.subPath == item.path) {
// Add new volume
volumes.push({
uri: `${uri}.${item.key}`,
path: `${path}/${item.path}`,
});
// Exit iteration because we have found the item we are looking for
break;
}
// Skip to next iteration in order to find the specified sub path
continue;
}
// Add a new volume for each item
volumes.push({
uri: `${uri}.${item.key}`,
path: `${path}/${item.path}`,
});
}
return;
}
// Reference secret property
if (k8sVolumeMount.subPath) {
uri = `${uri}.${k8sVolumeMount.subPath}`;
}
// Add a new volume
volumes.push({
uri,
path,
});
}
processContainerLifecycle(container, k8sContainer) {
if (!k8sContainer.lifecycle) {
return;
}
container.lifecycle = {};
if (k8sContainer.lifecycle.postStart) {
container.lifecycle.postStart = k8sContainer.lifecycle.postStart;
}
if (k8sContainer.lifecycle.preStop) {
container.lifecycle.preStop = k8sContainer.lifecycle.preStop;
}
}
processContainerProbe(k8sProbe) {
const probe = {
initialDelaySeconds: k8sProbe.initialDelaySeconds || 0,
periodSeconds: k8sProbe.periodSeconds || 10,
timeoutSeconds: k8sProbe.timeoutSeconds || 1,
successThreshold: k8sProbe.successThreshold || 1,
failureThreshold: k8sProbe.failureThreshold || 1,
};
if (k8sProbe.exec) {
probe.exec = k8sProbe.exec;
}
if (k8sProbe.httpGet) {
let httpHeaders = [];
let path = '/';
let port = 8080;
let scheme = 'HTTP';
if (k8sProbe.httpGet.httpHeaders) {
httpHeaders = k8sProbe.httpGet.httpHeaders;
}
if (k8sProbe.httpGet.path) {
path = k8sProbe.httpGet.path;
}
if (k8sProbe.httpGet.port) {
port = (0, helper_1.ensureNumber)(k8sProbe.httpGet.port);
}
if (k8sProbe.httpGet.scheme) {
scheme = k8sProbe.httpGet.scheme;
}
probe.httpGet = {
httpHeaders,
path,
port,
scheme,
};
}
if (k8sProbe.tcpSocket) {
probe.tcpSocket = {
port: (0, helper_1.ensureNumber)(k8sProbe.tcpSocket.port),
};
}
if (k8sProbe.grpc) {
probe.grpc = {
port: (0, helper_1.ensureNumber)(k8sProbe.grpc.port),
};
}
return probe;
}
// Job //
processJob() {
if (!this.workload.spec.job) {
return;
}
// Set Restart Policy
if (this.pod.spec.restartPolicy) {
this.workload.spec.job.restartPolicy = this.pod.spec.restartPolicy;
}
// Set Active Deadline Seconds
if (this.pod.spec.activeDeadlineSeconds) {
this.workload.spec.job.activeDeadlineSeconds = this.pod.spec.activeDeadlineSeconds;
}
}
// Pull Secrets //
processImagePullSecrets() {
var _a;
const alreadyAdded = new Set();
// Read pull secrets straight from the pod
if (this.pod.spec.imagePullSecrets) {
for (const imagePullSecret of this.pod.spec.imagePullSecrets) {
// Skip already added
if (alreadyAdded.has(imagePullSecret.name)) {
continue;
}
this.mapper.imagePullSecrets.push(`//secret/${imagePullSecret.name}`);
alreadyAdded.add(imagePullSecret.name);
}
}
// Read pull secrets from the service account attached to the pod
if (this.pod.spec.serviceAccountName) {
const serviceAccount = this.mapper.serviceAccountNameToObject.get(this.pod.spec.serviceAccountName);
for (const imagePullSecret of (_a = serviceAccount.imagePullSecrets) !== null && _a !== void 0 ? _a : []) {
// Skip already added
if (alreadyAdded.has(imagePullSecret.name)) {
continue;
}
this.mapper.imagePullSecrets.push(`//secret/${imagePullSecret.name}`);
alreadyAdded.add(imagePullSecret.name);
}
}
}
// Validators //
validate() {
var _a;
if (!this.pod.spec) {
this.ensurePropertyPresence('spec');
}
// Create a name to volume map in order to validate a container volume mounts later
const nameToVolume = {};
// Validate volumes if specified
if (this.pod.spec.volumes) {
if (!Array.isArray(this.pod.spec.volumes)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes' must be an array`);
}
// Iterate over each volume and validate it
for (const [volumeIndex, volume] of this.pod.spec.volumes.entries()) {
if (!volume.name) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].name`);
}
// Validate Persistent Volume Claim
if (volume.persistentVolumeClaim) {
if (!volume.persistentVolumeClaim.claimName) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].persistentVolumeClaim.claimName`);
}
// Validate that a persistent volume claim with the given name exists
if (!this.mapper.pvcToWorkload.has(volume.persistentVolumeClaim.claimName)) {
this.raiseCustomK8sError(`The referenced persistent volume claim in 'spec.volumes[${volumeIndex}].persistentVolumeClaim.claimName' was not found`);
}
// Get the workload name that is mapped to the persistent volume claim
const mountedWorkloadName = this.mapper.pvcToWorkload.get(volume.persistentVolumeClaim.claimName);
// Validate that there is no other workload is mounting this volume
if (mountedWorkloadName && this.workload.name != mountedWorkloadName) {
this.raiseCustomK8sError(`The referenced persistent volume claim in 'spec.volumes[${volumeIndex}].persistentVolumeClaim.claimName' is already mounted to a different workload '${mountedWorkloadName}'. Each workload should have unique storage`);
}
}
// Validate ConfigMap if specified
if (volume.configMap) {
if (!volume.configMap.name) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].configMap.name`);
}
if (volume.configMap.items) {
this.validateVolumeItems('configMap', volume.configMap.items, volumeIndex);
}
}
// Validate Secret if specified
if (volume.secret) {
if (!volume.secret.secretName) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].secret.secretName`);
}
if (volume.secret.items) {
this.validateVolumeItems('secret', volume.secret.items, volumeIndex);
}
}
// Validate projected if specified
if (volume.projected) {
if (!volume.projected.sources) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].projected.sources`);
}
if (!Array.isArray(volume.projected.sources)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].projected.sources' must be an array`);
}
for (const [volumeProjectedSourceIndex, volumeProjectedSource] of volume.projected.sources.entries()) {
// Validate ConfigMap
if (volumeProjectedSource.configMap) {
if (!volumeProjectedSource.configMap.name) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].configMap.name`);
}
// Validate ConfigMap items
if (volumeProjectedSource.configMap.items) {
if (!Array.isArray(volumeProjectedSource.configMap.items)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].configMap.items' must be an array`);
}
if (volumeProjectedSource.configMap.items.length == 0) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].configMap.items' must have at least one item`);
}
this.validateVolumeItems(`projected.sources[${volumeProjectedSourceIndex}].configMap`, volumeProjectedSource.configMap.items, volumeIndex);
}
else {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].configMap.items`);
}
}
// Validate Secret
if (volumeProjectedSource.secret) {
if (!volumeProjectedSource.secret.name) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].secret.name`);
}
// Validate Secret items
if (volumeProjectedSource.secret.items) {
if (!Array.isArray(volumeProjectedSource.secret.items)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].secret.items' must be an array`);
}
if (volumeProjectedSource.secret.items.length == 0) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].secret.items' must have at least one item`);
}
this.validateVolumeItems(`projected.sources[${volumeProjectedSourceIndex}].secret`, volumeProjectedSource.secret.items, volumeIndex);
}
else {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].projected.sources[${volumeProjectedSourceIndex}].secret.items`);
}
}
}
}
// Ensure volume names are unique
if (nameToVolume[volume.name]) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes' has a duplicate name: '${volume.name}'`);
}
// Reserve volume name
nameToVolume[volume.name] = volume;
}
}
// Validate containers
if (!this.pod.spec.containers) {
this.ensurePropertyPresence('spec.containers');
}
if (!Array.isArray(this.pod.spec.containers)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers' must be an array`);
}
if (this.pod.spec.containers.length == 0) {
this.raiseCustomK8sError(`There must be at least one container defined in '${this.propertyPath}.spec.containers'`);
}
// Validate each container
for (const [containerIndex, container] of this.pod.spec.containers.entries()) {
if (!container.name) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].name`);
}
if (container.name.startsWith('cpln-')) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].name' failed custom validation because container names cannot start with 'cpln-'`);
}
if (!container.image) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].image`);
}
if (container.command && !Array.isArray(container.command)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].command' must be an array of strings`);
}
if (container.args && !Array.isArray(container.args)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].args' must be an array of strings`);
}
if (container.ports) {
if (!Array.isArray(container.ports)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].ports' must be an array`);
}
// Validate each port
for (const [portIndex, port] of container.ports.entries()) {
if (!port.containerPort) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].ports[${portIndex}].containerPort`);
}
if (port.protocol && port.protocol.toLowerCase() === 'udp') {
this.raiseCustomK8sError(`Protocol 'udp' is unsupported at spec.containers[${containerIndex}].ports[${portIndex}].protocol`);
}
}
}
if (container.env) {
if (!Array.isArray(container.env)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].env' must be an array`);
}
// Validate each env
for (const [envIndex, env] of container.env.entries()) {
if (!env.name) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].env[${envIndex}].name`);
}
if (env.name.startsWith('CPLN_')) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].env[${envIndex}].name' failed custom validation because environment variable names starting with 'CPLN_' are reserved for system use`);
}
if (env.value && env.valueFrom) {
this.raiseCustomK8sError(`should either provide 'value' or 'valueFrom' but not both at '${this.propertyPath}.spec.containers[${containerIndex}].env[${envIndex}]'`);
}
if (env.valueFrom) {
if (env.valueFrom.fieldRef && env.valueFrom.secretKeyRef && env.valueFrom.configMapKeyRef) {
this.raiseCustomK8sError(`ONLY one of 'fieldRef' or 'secretKeyRef' or 'configMapKeyRef' can be provided at 'spec.containers[${containerIndex}].env[${envIndex}].valueFrom'`);
}
if (!env.valueFrom.fieldRef && !env.valueFrom.secretKeyRef && !env.valueFrom.configMapKeyRef) {
this.raiseCustomK8sError(`You must either prvoide one of 'fieldRef' or 'secretKeyRef' or 'configMapKeyRef' at 'spec.containers[${containerIndex}].env[${envIndex}].valueFrom'`);
}
if (env.valueFrom.fieldRef && !env.valueFrom.fieldRef.fieldPath) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].env[${envIndex}].valueFrom.fieldRef.fieldPath`);
}
if (env.valueFrom.secretKeyRef && !env.valueFrom.secretKeyRef.name) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].env[${envIndex}].valueFrom.secretKeyRef.name`);
}
if (env.valueFrom.configMapKeyRef && !env.valueFrom.configMapKeyRef.name) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].env[${envIndex}].valueFrom.configMapKeyRef.name`);
}
if (env.valueFrom.configMapKeyRef &&
env.valueFrom.configMapKeyRef.name &&
!this.mapper.configMapNameToObject.has(env.valueFrom.configMapKeyRef.name)) {
this.raiseCustomK8sError(`'spec.containers[${containerIndex}].env[${envIndex}].valueFrom.configMapKeyRef.name' is referencing a ConfigMap that does not exist. Make sure the ConfigMap '${env.valueFrom.configMapKeyRef.name}' is specified within the convert path`);
}
}
}
}
if (container.volumeMounts) {
const volumeMountIDs = new Set();
if (!Array.isArray(container.volumeMounts)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].volumeMounts' must be an array`);
}
// Iterate over each volume mount and validate it
for (const [containerVolumeMountIndex, containerVolumeMount] of container.volumeMounts.entries()) {
if (!containerVolumeMount.name) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].volumeMounts[${containerVolumeMountIndex}].name`);
}
// Validate that the referenced volume exists
if (!nameToVolume[containerVolumeMount.name]) {
let throwError = true;
// Check if the volume mount is actually referring to a persistent volume mount
if (this.k8sObject.kind == 'StatefulSet') {
const k8sWorkload = this.k8sObject;
for (const pvc of (_a = k8sWorkload.spec.volumeClaimTemplates) !== null && _a !== void 0 ? _a : []) {
if (pvc.metadata.name == containerVolumeMount.name) {
throwError = false;
break;
}
}
}
if (throwError) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].volumeMounts[${containerVolumeMountIndex}].name' is referencing a volume name that does not exist in '${this.propertyPath}.spec.volumes'`);
}
}
if (!containerVolumeMount.mountPath) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].volumeMounts[${containerVolumeMountIndex}].mountPath`);
}
// Define a volume mount id
let id = `${containerVolumeMount.name}@${containerVolumeMount.mountPath}`;
if (containerVolumeMount.subPath) {
id = `${id}@${containerVolumeMount.subPath}`;
}
// Ensure that every volume mount is unique
if (volumeMountIDs.has(id)) {
this.raiseCustomK8sError(`Container has more than one volume mount for the same path, duplicate volume mount: '${this.propertyPath}.spec.containers[${containerIndex}].volumeMounts[${containerVolumeMountIndex}]'`);
}
// Reserve volume mount id
volumeMountIDs.add(id);
// Validate that the specified sub path exists within a volume's items
if (containerVolumeMount.subPath) {
// Get k8s volume object by name
const volume = nameToVolume[containerVolumeMount.name];
// Validate that the specified sub path exists within a ConfigMap items
if (volume.configMap && volume.configMap.items) {
this.validateSubPathToExistInVolumeItems(containerVolumeMount.subPath, volume.configMap.items, volume.name, containerIndex, containerVolumeMountIndex);
}
// Validate that the specified sub path exists within a Secret items
if (volume.secret && volume.secret.items) {
this.validateSubPathToExistInVolumeItems(containerVolumeMount.subPath, volume.secret.items, volume.name, containerIndex, containerVolumeMountIndex);
}
// Validate that the specified sub path exists within a projected source items
if (volume.projected) {
for (const projectedSource of volume.projected.sources) {
if (projectedSource.configMap && projectedSource.configMap.items) {
this.validateSubPathToExistInVolumeItems(containerVolumeMount.subPath, projectedSource.configMap.items, volume.name, containerIndex, containerVolumeMountIndex);
}
if (projectedSource.secret && projectedSource.secret.items) {
this.validateSubPathToExistInVolumeItems(containerVolumeMount.subPath, projectedSource.secret.items, volume.name, containerIndex, containerVolumeMountIndex);
}
}
}
}
}
}
if (container.lifecycle) {
if (container.lifecycle.postStart) {
if (!container.lifecycle.postStart.exec) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].lifecycle.postStart.exec`);
}
if (!container.lifecycle.postStart.exec.command) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].lifecycle.postStart.exec.command`);
}
if (!Array.isArray(container.lifecycle.postStart.exec.command)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].lifecycle.postStart.exec.command' must be an array of strings`);
}
}
if (container.lifecycle.preStop) {
if (!container.lifecycle.preStop.exec) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].lifecycle.preStop.exec`);
}
if (!container.lifecycle.preStop.exec.command) {
this.ensurePropertyPresence(`spec.containers[${containerIndex}].lifecycle.preStop.exec.command`);
}
if (!Array.isArray(container.lifecycle.preStop.exec.command)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].lifecycle.preStop.exec.command' must be an array of strings`);
}
}
}
if (container.livenessProbe) {
this.validateContainerHealthCheck(container.livenessProbe, `spec.containers[${containerIndex}].livenessProbe`);
}
if (container.readinessProbe) {
this.validateContainerHealthCheck(container.readinessProbe, `spec.containers[${containerIndex}].readinessProbe`);
}
}
// Validate job related properties
if (this.workload.spec.job) {
const maxActiveDeadlineSeconds = 86400;
// Validate Restart Policy
if (this.pod.spec.restartPolicy && this.pod.spec.restartPolicy.toLowerCase() === 'always') {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.restartPolicy' must either be 'Never' or 'OnFailure' for workloads of type cron`);
}
// Validate Active Deadline Seconds
if (this.pod.spec.activeDeadlineSeconds && this.pod.spec.activeDeadlineSeconds > maxActiveDeadlineSeconds) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.activeDeadlineSeconds' must be less than or equal to ${maxActiveDeadlineSeconds}`);
}
}
// Validate Pull Secrets
const alreadyValidated = new Set();
if (this.pod.spec.imagePullSecrets) {
if (!Array.isArray(this.pod.spec.imagePullSecrets)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.imagePullSecrets' must be an array`);
}
for (const [index, imagePullSecret] of this.pod.spec.imagePullSecrets.entries()) {
if (!imagePullSecret.name) {
this.ensurePropertyPresence(`spec.imagePullSecrets[${index}].name`);
}
// Skip already validated
if (alreadyValidated.has(imagePullSecret.name)) {
continue;
}
alreadyValidated.add(imagePullSecret.name);
}
}
if (this.pod.spec.serviceAccountName) {
if (!this.mapper.serviceAccountNameToObject.has(this.pod.spec.serviceAccountName)) {
this.raiseCustomK8sError(`The referenced service account '${this.pod.spec.serviceAccountName}' was not found`);
}
// Get the referenced Service Account
const serviceAccount = this.mapper.serviceAccountNameToObject.get(this.pod.spec.serviceAccountName);
if (serviceAccount.imagePullSecrets) {
if (!Array.isArray(serviceAccount.imagePullSecrets)) {
this.raiseCustomK8sError(`The 'imagePullSecrets' property of the referenced service account '${this.pod.spec.serviceAccountName}' must be an array`);
}
for (const [index, imagePullSecret] of serviceAccount.imagePullSecrets.entries()) {
if (!imagePullSecret.name) {
this.ensurePropertyPresence(`The 'imagePullSecrets[${index}].name' property of the referenced service account '${this.pod.spec.serviceAccountName}' is required`);
}
// Skip already validated
if (alreadyValidated.has(imagePullSecret.name)) {
continue;
}
alreadyValidated.add(imagePullSecret.name);
}
}
}
}
validateContainerHealthCheck(probe, property) {
let counter = 0;
const maxInitialDelaySeconds = 600;
const maxPeriodSeconds = 60;
const maxTimeoutSeconds = 60;
const maxSuccessThreshold = 20;
const maxFailureThreshold = 20;
if (probe.exec) {
counter++;
}
if (probe.httpGet) {
if (probe.httpGet.httpHeaders) {
if (!Array.isArray(probe.httpGet.httpHeaders)) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.httpGet.httpHeaders' must be an array`);
}
for (const [index, httpHeader] of probe.httpGet.httpHeaders.entries()) {
if (!httpHeader.name) {
this.ensurePropertyPresence(`${property}.httpGet.httpHeaders[${index}].name`);
}
if (!httpHeader.value) {
this.ensurePropertyPresence(`${property}.httpGet.httpHeaders[${index}].value`);
}
}
}
counter++;
}
if (probe.tcpSocket) {
if (!probe.tcpSocket.port) {
this.ensurePropertyPresence(`${property}.tcpSocket.port`);
}
counter++;
}
if (probe.grpc) {
if (!probe.grpc.port) {
this.ensurePropertyPresence(`${property}.grpc.port`);
}
counter++;
}
if (probe.initialDelaySeconds && probe.initialDelaySeconds > maxInitialDelaySeconds) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.initialDelaySeconds' must be less than or equal to ${maxInitialDelaySeconds}`);
}
if (probe.periodSeconds && probe.periodSeconds > maxPeriodSeconds) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.periodSeconds' must be less than or equal to ${maxPeriodSeconds}`);
}
if (probe.timeoutSeconds && probe.timeoutSeconds > maxTimeoutSeconds) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.timeoutSeconds' must be less than or equal to ${maxTimeoutSeconds}`);
}
if (probe.successThreshold && probe.successThreshold > maxSuccessThreshold) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.successThreshold' must be less than or equal to ${maxSuccessThreshold}`);
}
if (probe.failureThreshold && probe.failureThreshold > maxFailureThreshold) {
this.raiseCustomK8sError(`'${this.propertyPath}.${property}.failureThreshold' must be less than or equal to ${maxFailureThreshold}`);
}
if (counter > 1) {
this.raiseCustomK8sError(`There must be ONLY one of the following defined in a probe: exec, httpGet, tcpSocket or grpc at '${this.propertyPath}.${property}'`);
}
}
validateVolumeItems(propertName, items, volumeIndex) {
if (!Array.isArray(items)) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.volumes[${volumeIndex}].${propertName}.items' must be an array`);
}
for (const [index, item] of items.entries()) {
if (!item.key) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].${propertName}.items[${index}].key`);
}
if (!item.path) {
this.ensurePropertyPresence(`spec.volumes[${volumeIndex}].${propertName}.items[${index}].path`);
}
}
}
validateSubPathToExistInVolumeItems(subPath, items, volumeName, containerIndex, containerVolumeMountIndex) {
let isSubPathFound = false;
// Iterate over each item to find the specified sub path
for (const item of items) {
if (subPath == item.path) {
isSubPathFound = true;
break;
}
}
if (!isSubPathFound) {
this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].volumeMounts[${containerVolumeMountIndex}].subPath' value points to a path '${subPath}' that does not exist in volume '${volumeName}'. Please ensure that the subPath '${subPath}' matches one of the defined 'path' values in the referenced volume`);
}
}
// Validation Helpers //
ensurePropertyPresence(property) {
(0, helper_1.ensurePropertyPresence)(`${this.propertyPath}.${property}`, this.filePath, this.k8sObject);
}
raiseCustomK8sError(message) {
(0, helper_1.raiseCustomK8sError)(message, this.filePath, this.k8sObject);
}
getFormattedWarning(message) {
return (0, helper_1.getFormattedWarning)(message, this.filePath, this.k8sObject);
}
}
exports.K8sPodTemplateProcessor = K8sPodTemplateProcessor;
//# sourceMappingURL=pod-template.js.map