UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

1,468 lines • 78.4 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.K8sPodTemplateProcessor = void 0;
const helper_1 = require("../../util/helper");
const constants_1 = require("../../config/constants");
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 = {};
        this.schemeToPort = {};
    }
    /*** Public Methods ***/
    process() {
        // Validate pod
        this.validate();
        // Process volumes
        this.processVolumes();
        // Process containers
        this.processContainers();
        // Process security context
        this.processSecurityContext();
        // Process job related properties
        this.processJob();
        // Process Pull Secrets
        this.processImagePullSecrets();
        // Process firewall
        this.processFirewallConfig();
    }
    /*** 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() {
        // Read the inherit env annotation once; it applies to every container of the workload
        const inheritEnv = this.getInheritEnvAnnotation();
        // Iterate over each container
        for (const k8sContainer of this.pod.spec.containers) {
            const container = {
                name: k8sContainer.name,
                image: k8sContainer.image,
            };
            // Allow GVC-level environment variables to flow into the container when requested
            if (inheritEnv !== undefined) {
                container.inheritEnv = inheritEnv;
            }
            // Process resources
            this.processContainerResources(container, k8sContainer);
            // Process command
            if (k8sContainer.command && k8sContainer.command.length > 0) {
                container.command = k8sContainer.command[0];
                // Any remaining items, add them to the container.args
                if (k8sContainer.command.length > 1) {
                    container.args = k8sContainer.command.slice(1);
                }
            }
            // Process args
            if (k8sContainer.args) {
                if (container.args !== undefined) {
                    container.args.push(...k8sContainer.args);
                }
                else {
                    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) {
        var _a, _b, _c, _d;
        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;
        container.minCpu = (_b = (_a = k8sContainer.resources) === null || _a === void 0 ? void 0 : _a.requests) === null || _b === void 0 ? void 0 : _b.cpu;
        container.minMemory = (_d = (_c = k8sContainer.resources) === null || _c === void 0 ? void 0 : _c.requests) === null || _d === void 0 ? void 0 : _d.memory;
    }
    processContainerPorts(container, k8sContainer) {
        // Return early if the container has no ports defined
        if (!k8sContainer.ports) {
            return;
        }
        // Initialize the list of ports on the container spec
        container.ports = [];
        // Iterate over all declared ports of the container
        for (const k8sPort of k8sContainer.ports) {
            // If the port has a name, map it to its numeric value
            if (k8sPort.name) {
                this.schemeToPort[k8sPort.name] = k8sPort.containerPort;
            }
            // Create a port object with the inferred protocol and port number
            const port = {
                protocol: this.inferProtocolForContainerPort(k8sContainer, k8sPort),
                number: k8sPort.containerPort,
            };
            // Add this processed port to the container spec
            container.ports.push(port);
        }
    }
    inferProtocolForContainerPort(k8sContainer, k8sPort) {
        // Respect a global override when configured
        if (this.mapper.overrideProtocol) {
            // Normalize and return the override as the final protocol
            return this.mapper.overrideProtocol.toLowerCase();
        }
        // Prepare a list to accumulate protocol candidates in precedence order
        const candidates = [];
        // Retrieve all Services associated with this Pod/Workload
        const svcs = this.mapper.getServicesForWorkload(this.pod, this.k8sObject);
        // Compute the Service ports that explicitly target this container port (by name or number)
        const svcPorts = this.matchingServicePortsForContainer(k8sPort, svcs);
        // Iterate over matched service ports to consider explicit appProtocol and port names
        for (const servicePort of svcPorts) {
            // Attempt to resolve protocol from Service port appProtocol
            const pFromAppProtocol = this.protocolFromAppProtocol(servicePort.appProtocol);
            // Prioritize app protocol over any other candidate
            if (pFromAppProtocol) {
                return pFromAppProtocol;
            }
            // Attempt to resolve protocol from Service port name prefix
            const pFromServicePortName = this.protocolFromPortName(servicePort.name);
            // Record the candidate when found
            if (pFromServicePortName) {
                candidates.push(pFromServicePortName);
            }
        }
        // Attempt to resolve protocol from the container port name (prefix-based)
        const pFromContainerPortName = this.protocolFromPortName(k8sPort.name);
        // Record the candidate when found
        if (pFromContainerPortName) {
            candidates.push(pFromContainerPortName);
        }
        // Attempt to resolve protocol from liveness/readiness probes bound to this exact port
        const pFromProbes = this.protocolFromProbesForPort(k8sContainer, k8sPort);
        // Record the candidate when found
        if (pFromProbes) {
            candidates.push(pFromProbes);
        }
        // Attempt to resolve protocol from a well-known numeric port (weak heuristic)
        const pFromNumber = this.protocolFromPortNumber(k8sPort.containerPort);
        // Record the candidate when found
        if (pFromNumber) {
            candidates.push(pFromNumber);
        }
        // Resolve disagreements deterministically according to specificity ranking
        const resolved = this.pickMostSpecificProtocol(candidates);
        // Return the resolved protocol or default to tcp when nothing matched
        return resolved !== null && resolved !== void 0 ? resolved : 'tcp';
    }
    matchingServicePortsForContainer(k8sPort, svcs) {
        var _a, _b;
        // Initialize an accumulator for matched service ports
        const output = [];
        // Normalize the container port name for name-based targetPort matches
        const containerPortName = ((_a = k8sPort.name) !== null && _a !== void 0 ? _a : '').trim().toLowerCase();
        // Extract the numeric container port value for comparisons
        const containerPortNumber = k8sPort.containerPort;
        // Iterate over each candidate Service
        for (const svc of svcs) {
            // Skip if the service has no spec ports
            if (!svc.spec || !svc.spec.ports || !Array.isArray(svc.spec.ports)) {
                continue;
            }
            // Iterate over each declared Service port
            for (const servicePort of svc.spec.ports) {
                // Resolve the effective target port, prefer targetPort, fallback to port if necessary
                const targetPort = ((_b = servicePort.targetPort) !== null && _b !== void 0 ? _b : servicePort.port);
                // Match numeric targetPort directly
                if (typeof targetPort === 'number') {
                    // Record a match when numbers are equal
                    if (targetPort === containerPortNumber) {
                        output.push(servicePort);
                    }
                    // Continue to next service port
                    continue;
                }
                // Match string targetPort which may be a number-like string or a name
                if (typeof targetPort === 'string') {
                    // Normalize the string representation
                    const targetPortString = targetPort.trim().toLowerCase();
                    // Attempt to coerce numeric-looking strings
                    const targetPortAsNumber = Number(targetPortString);
                    // Record a match when the numeric coercion equals the container port
                    if (!Number.isNaN(targetPortAsNumber) && targetPortAsNumber === containerPortNumber) {
                        output.push(servicePort);
                        continue;
                    }
                    // Record a match when the targetPort name equals the container port name
                    if (targetPortString && targetPortString === containerPortName) {
                        output.push(servicePort);
                    }
                }
            }
        }
        // Return all matched service ports
        return output;
    }
    protocolFromAppProtocol(appProtocol) {
        // Normalize the input to a lowercase, trimmed string
        const normalized = this.normalizeProtocol(appProtocol);
        // Return early when the input is empty or missing
        if (!normalized) {
            return undefined;
        }
        // Split into parts on common delimiters: '/', ',', '|', and whitespace
        const parts = normalized.split(/[,\s|/]+/).filter(Boolean);
        // Initialize a list to hold candidate protocols discovered from tokens
        const candidates = [];
        // Iterate over each part extracted from appProtocol
        for (const part of parts) {
            // Attempt to resolve the part into a supported protocol
            const possibleProtocol = this.resolveProtocolToken(part);
            // Record the candidate when found
            if (possibleProtocol) {
                candidates.push(possibleProtocol);
            }
        }
        // Return early when no token produced a mapping
        if (candidates.length === 0) {
            return undefined;
        }
        // Select the most specific protocol among all token-derived candidates
        return this.pickMostSpecificProtocol(candidates);
    }
    resolveProtocolToken(token) {
        // Normalize the token to ensure consistent comparisons
        const possibleProtocol = token.trim().toLowerCase();
        // Return undefined for empty tokens
        if (!possibleProtocol) {
            return undefined;
        }
        // Attempt a direct lookup in the known prefixes table
        const direct = constants_1.K8_NAME_PREFIX_PROTOCOLS[possibleProtocol];
        // Return immediately when a direct match exists
        if (direct) {
            return direct;
        }
        // Extract the prefix before '-', '_' or '.' to follow common naming styles
        const prefix = possibleProtocol.split(/[-_.]/, 1)[0];
        // Attempt a prefix-based lookup as a fallback
        return constants_1.K8_NAME_PREFIX_PROTOCOLS[prefix];
    }
    pickMostSpecificProtocol(protocols) {
        // Initialize a de-duplication set while preserving discovery order
        const seen = new Set(protocols);
        // Prefer gRPC when present
        if (seen.has('grpc')) {
            return 'grpc';
        }
        // Prefer HTTP/2 next
        if (seen.has('http2')) {
            return 'http2';
        }
        // Prefer HTTP next
        if (seen.has('http')) {
            return 'http';
        }
        // Prefer TCP last (still explicit over undefined)
        if (seen.has('tcp')) {
            return 'tcp';
        }
        // Return undefined when no candidates exist
        return undefined;
    }
    protocolFromPortName(name) {
        // Normalize the provided name to a lowercase, trimmed string
        const normalized = this.normalizeProtocol(name);
        // Return early when no usable name is present
        if (!normalized) {
            return undefined;
        }
        // Extract the prefix before '-', '_' or '.' to follow common naming styles
        const prefix = normalized.split(/[-_.]/, 1)[0];
        // Look up the protocol using the extracted prefix
        return constants_1.K8_NAME_PREFIX_PROTOCOLS[prefix];
    }
    protocolFromProbesForPort(k8sContainer, k8sPort) {
        var _a;
        // Cache the numeric container port value
        const containerPortNumber = k8sPort.containerPort;
        // Normalize the container port name for name-based probe matching
        const containerPortName = ((_a = k8sPort.name) !== null && _a !== void 0 ? _a : '').trim().toLowerCase();
        // Check readiness probe when present
        if (k8sContainer.readinessProbe) {
            // Prefer gRPC mapping when the probe targets this port
            if (k8sContainer.readinessProbe.grpc &&
                this.probePortMatchesContainerPort(k8sContainer.readinessProbe.grpc.port, containerPortNumber, containerPortName)) {
                // Return grpc when matched
                return 'grpc';
            }
            // Map HTTP/HTTPS to 'http' when the probe targets this port
            if (k8sContainer.readinessProbe.httpGet &&
                this.probePortMatchesContainerPort(k8sContainer.readinessProbe.httpGet.port, containerPortNumber, containerPortName)) {
                // Return http when matched
                return 'http';
            }
            // Fallback to 'tcp' when the TCP probe targets this port
            if (k8sContainer.readinessProbe.tcpSocket &&
                this.probePortMatchesContainerPort(k8sContainer.readinessProbe.tcpSocket.port, containerPortNumber, containerPortName)) {
                // Return tcp when matched
                return 'tcp';
            }
        }
        // Check liveness probe when present
        if (k8sContainer.livenessProbe) {
            // Prefer gRPC mapping when the probe targets this port
            if (k8sContainer.livenessProbe.grpc &&
                this.probePortMatchesContainerPort(k8sContainer.livenessProbe.grpc.port, containerPortNumber, containerPortName)) {
                // Return grpc when matched
                return 'grpc';
            }
            // Map HTTP/HTTPS to 'http' when the probe targets this port
            if (k8sContainer.livenessProbe.httpGet &&
                this.probePortMatchesContainerPort(k8sContainer.livenessProbe.httpGet.port, containerPortNumber, containerPortName)) {
                // Return http when matched
                return 'http';
            }
            // Fallback to 'tcp' when the TCP probe targets this port
            if (k8sContainer.livenessProbe.tcpSocket &&
                this.probePortMatchesContainerPort(k8sContainer.livenessProbe.tcpSocket.port, containerPortNumber, containerPortName)) {
                // Return tcp when matched
                return 'tcp';
            }
        }
        // Return undefined when no probe maps cleanly to this port
        return undefined;
    }
    probePortMatchesContainerPort(probePort, containerPortNumber, containerPortName) {
        // Short-circuit when probe has no port
        if (probePort === undefined) {
            return false;
        }
        // Compare when probe port is numeric
        if (typeof probePort === 'number') {
            return probePort === containerPortNumber;
        }
        // Normalize the string representation
        const probePortString = probePort.trim().toLowerCase();
        // Attempt to coerce numeric-looking strings
        const probePortAsNumber = Number(probePortString);
        // Compare numerically when coercion succeeds
        if (!Number.isNaN(probePortAsNumber)) {
            return probePortAsNumber === containerPortNumber;
        }
        // Compare by name when a non-empty string name is present
        if (probePortString) {
            return probePortString === containerPortName;
        }
        // Return false when string is empty
        return false;
    }
    protocolFromPortNumber(portNumber) {
        // Return undefined when the input is not a finite number
        if (typeof portNumber !== 'number' || !Number.isFinite(portNumber)) {
            return undefined;
        }
        // Read the protocol mapping from the well-known ports table
        return constants_1.K8S_WELL_KNOWN_PORTS[portNumber];
    }
    normalizeProtocol(value) {
        // Return undefined when the input is missing
        if (!value) {
            return undefined;
        }
        // Trim surrounding whitespace from the input
        const trimmed = value.trim();
        // Return undefined when the trimmed value is empty
        if (!trimmed) {
            return undefined;
        }
        // Convert to lowercase for consistent downstream comparisons
        return trimmed.toLowerCase();
    }
    processContainerEnv(container, k8sContainer) {
        var _a;
        // Expand envFrom sources into individual environment variables
        const expandedEnv = this.expandContainerEnvFrom(k8sContainer);
        // Skip when the container declares no environment variables at all
        if (!k8sContainer.env && expandedEnv.size == 0) {
            return;
        }
        // Accumulate explicitly declared environment variables
        const explicitEnv = [];
        for (const k8sEnv of (_a = k8sContainer.env) !== null && _a !== void 0 ? _a : []) {
            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) {
                    const resolvedName = this.mapper.resolveConfigMapName(k8sEnv.valueFrom.configMapKeyRef.name);
                    let secretLink = `cpln://secret/${resolvedName}`;
                    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(resolvedName, this.workload);
                }
            }
            explicitEnv.push(env);
        }
        // An explicitly declared variable takes precedence over an envFrom expansion with the same name
        for (const env of explicitEnv) {
            expandedEnv.delete(env.name);
        }
        // Define
        container.env = [...expandedEnv.values(), ...explicitEnv];
    }
    /**
     * Expands all envFrom sources of a container into individual environment variables.
     * A key present in multiple sources takes the value of the last source, matching K8s behavior.
     *
     * @param {K8sPodContainer} k8sContainer - The K8s container whose envFrom sources are expanded.
     * @returns {Map<string, EnvVar>} The expanded environment variables keyed by variable name.
     */
    expandContainerEnvFrom(k8sContainer) {
        var _a;
        const expandedEnv = new Map();
        // Skip when the container declares no envFrom sources
        if (!k8sContainer.envFrom) {
            return expandedEnv;
        }
        // Iterate over each envFrom source in declaration order
        for (const source of k8sContainer.envFrom) {
            const prefix = (_a = source.prefix) !== null && _a !== void 0 ? _a : '';
            // Expand every data key of the referenced ConfigMap
            if (source.configMapRef) {
                this.expandConfigMapEnvFrom(expandedEnv, source.configMapRef, prefix, k8sContainer.name);
            }
            // Expand every data key of the referenced Secret
            if (source.secretRef) {
                this.expandSecretEnvFrom(expandedEnv, source.secretRef, prefix, k8sContainer.name);
            }
        }
        return expandedEnv;
    }
    /**
     * Expands an envFrom ConfigMap reference into individual environment variables.
     *
     * @param {Map<string, EnvVar>} expandedEnv - The accumulator of expanded environment variables.
     * @param {K8sEnvFromSourceReference} configMapRef - The envFrom ConfigMap reference to expand.
     * @param {string} prefix - The prefix prepended to every expanded variable name.
     * @param {string} containerName - The name of the container being processed, used in warnings.
     */
    expandConfigMapEnvFrom(expandedEnv, configMapRef, prefix, containerName) {
        var _a;
        const configMap = this.mapper.configMapNameToObject.get(configMapRef.name);
        // A missing non-optional ConfigMap is rejected during validation, so a miss here is an optional source
        if (!configMap) {
            this.mapper.warnings.push(`Skipped optional 'envFrom' ConfigMap '${configMapRef.name}' on container '${containerName}' because the ConfigMap is not part of the conversion input.`);
            return;
        }
        // ConfigMaps may have been renamed to avoid collisions with Secrets
        const resolvedName = this.mapper.resolveConfigMapName(configMapRef.name);
        // Only 'data' entries become environment variables; 'binaryData' is never exposed as env
        for (const key of Object.keys((_a = configMap.data) !== null && _a !== void 0 ? _a : {})) {
            this.addExpandedEnvVar(expandedEnv, `${prefix}${key}`, `cpln://secret/${resolvedName}.${key}`, resolvedName);
        }
    }
    /**
     * Expands an envFrom Secret reference into individual environment variables.
     *
     * @param {Map<string, EnvVar>} expandedEnv - The accumulator of expanded environment variables.
     * @param {K8sEnvFromSourceReference} secretRef - The envFrom Secret reference to expand.
     * @param {string} prefix - The prefix prepended to every expanded variable name.
     * @param {string} containerName - The name of the container being processed, used in warnings.
     */
    expandSecretEnvFrom(expandedEnv, secretRef, prefix, containerName) {
        var _a, _b;
        const k8sSecret = this.mapper.secretNameToObject.get(secretRef.name);
        // The Secret keys are unknown when the Secret is not part of the conversion input, so expansion is impossible
        if (!k8sSecret) {
            this.mapper.warnings.push(`Unable to expand 'envFrom' Secret '${secretRef.name}' on container '${containerName}' because the Secret is not part of the conversion input. Include the Secret in the converted files, or add its keys to the container 'env' manually.`);
            return;
        }
        // Only dictionary secrets expose their keys individually; docker, opaque and userpass convert to a single value
        if (!(0, helper_1.convertsToDictionarySecret)(k8sSecret)) {
            this.mapper.warnings.push(`Unable to expand 'envFrom' Secret '${secretRef.name}' on container '${containerName}' because it does not convert to a dictionary secret whose keys are individually addressable. Add its values to the container 'env' manually.`);
            return;
        }
        // Union of both key sources; K8s merges 'stringData' into 'data' at creation time
        const keys = new Set([...Object.keys((_a = k8sSecret.data) !== null && _a !== void 0 ? _a : {}), ...Object.keys((_b = k8sSecret.stringData) !== null && _b !== void 0 ? _b : {})]);
        for (const key of keys) {
            this.addExpandedEnvVar(expandedEnv, `${prefix}${key}`, `cpln://secret/${secretRef.name}.${key}`, secretRef.name);
        }
    }
    /**
     * Adds a single expanded environment variable to the accumulator and maps its secret to the workload.
     *
     * @param {Map<string, EnvVar>} expandedEnv - The accumulator of expanded environment variables.
     * @param {string} name - The environment variable name, including any envFrom prefix.
     * @param {string} value - The cpln secret reference used as the variable value.
     * @param {string} secretName - The Control Plane secret name backing the variable.
     */
    addExpandedEnvVar(expandedEnv, name, value, secretName) {
        // Environment variable names starting with 'CPLN_' are reserved for system use
        if (name.startsWith('CPLN_')) {
            this.mapper.warnings.push(`Skipped environment variable '${name}' expanded from 'envFrom' because names starting with 'CPLN_' are reserved for system use.`);
            return;
        }
        // A duplicate key keeps its original position but takes the value of the last source
        expandedEnv.set(name, { name, value });
        // Map secret name to workload name
        this.mapper.mapSecretToWorkload(secretName, this.workload);
    }
    /**
     * Reads and validates the inherit env annotation of the workload.
     *
     * @returns {boolean | undefined} The annotation value as a boolean, or undefined when the annotation is absent.
     */
    getInheritEnvAnnotation() {
        const value = (0, helper_1.getCplnAnnotation)(constants_1.K8S_CPLN_ANNOTATION_INHERIT_ENV, this.k8sObject, this.pod.metadata);
        if (value === undefined) {
            return undefined;
        }
        // Only explicit booleans are accepted
        if (value !== 'true' && value !== 'false') {
            this.raiseCustomK8sError(`Annotation '${constants_1.K8S_CPLN_ANNOTATION_INHERIT_ENV}' must be either 'true' or 'false', found '${value}'`);
        }
        return value === 'true';
    }
    processContainerVolumeMounts(container, k8sContainer) {
        var _a, _b;
        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) {
                            // Construct volume set name
                            const volumeSetName = `${this.k8sObject.metadata.name}-${pvc.metadata.name}`;
                            // Construct the cpln container volume
                            const uri = `cpln://volumeset/${volumeSetName}`;
                            const path = `${k8sVolumeMount.mountPath}`;
                            container.volumes.push({
                                uri,
                                path,
                            });
                            // Read the template's access modes directly; its name is unique only within this StatefulSet.
                            // A dedicated (read-write-once) volume set restricts the workload to the stateful type
                            if (!(0, helper_1.isSharedPvc)(pvc)) {
                                this.mapper.workloadsWithDedicatedVolumeSets.add(this.workload);
                            }
                            // 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 claimName = k8sVolume.persistentVolumeClaim.claimName;
                // Assume the volume set name is the claim name of the PVC
                let volumeSetName = claimName;
                // A standalone PVC has a globally unique name, so its shared-ness can be resolved by name
                let isShared = this.mapper.sharedPvcNames.has(claimName);
                // If this is a StatefulSet, then let's explore its defined PVCs and update the volume set accordingly
                if (this.k8sObject.kind == 'StatefulSet') {
                    const k8sWorkload = this.k8sObject;
                    // Attempt to find the PVC within the workload, if found update the volume set name
                    for (const pvc of (_b = k8sWorkload.spec.volumeClaimTemplates) !== null && _b !== void 0 ? _b : []) {
                        if (pvc.metadata.name == claimName) {
                            // Update the volume set name
                            volumeSetName = `${this.k8sObject.metadata.name}-${claimName}`;
                            // A template name is unique only within this StatefulSet, so read its access modes directly
                            isShared = (0, helper_1.isSharedPvc)(pvc);
                            // Stop the loop
                            break;
                        }
                    }
                }
                // Construct the cpln container volume
                const uri = `cpln://volumeset/${volumeSetName}`;
                const path = `${k8sVolumeMount.mountPath}`;
                container.volumes.push({
                    uri,
                    path,
                });
                // A dedicated (read-write-once) volume set restricts the workload to the stateful type
                if (!isShared) {
                    this.mapper.workloadsWithDedicatedVolumeSets.add(this.workload);
                }
                // Handle next volume mount
                continue;
            }
            // Mount ConfigMap as a volume
            if (k8sVolume.configMap) {
                const resolvedName = this.mapper.resolveConfigMapName(k8sVolume.configMap.name);
                this.mountVolume(container.volumes, k8sVolumeMount, resolvedName, 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) {
                        const resolvedName = this.mapper.resolveConfigMapName(projectedSource.configMap.name);
                        this.mountVolume(container.volumes, k8sVolumeMount, resolvedName, 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,
                        });
                        // 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 = this.convertProbePort(k8sProbe.httpGet.port);
            }
            if (k8sProbe.httpGet.scheme) {
                scheme = k8sProbe.httpGet.scheme;
            }
            probe.httpGet = {
                httpHeaders,
                path,
                port,
                scheme,
            };
        }
        if (k8sProbe.tcpSocket) {
            probe.tcpSocket = {
                port: this.convertProbePort(k8sProbe.tcpSocket.port),
            };
        }
        if (k8sProbe.grpc) {
            probe.grpc = {
                port: this.convertProbePort(k8sProbe.grpc.port),
            };
        }
        return probe;
    }
    convertProbePort(port) {
        if (typeof port === 'string') {
            const numericPort = Number(port);
            if (!isNaN(numericPort) && port.trim() !== '') {
                return numericPort;
            }
            return this.schemeToPort[port];
        }
        return port;
    }
    // Security Context //
    processSecurityContext() {
        // Skip if security context is not specified
        if (!this.pod.spec.securityContext) {
            return;
        }
        // Initialize the security options object
        this.workload.spec.securityOptions = {};
        // Use fsGroup if set
        if (this.pod.spec.securityContext.fsGroup !== undefined) {
            this.workload.spec.securityOptions.filesystemGroupId = this.pod.spec.securityContext.fsGroup;
        }
    }
    // 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);
            }
        }
    }
    // Firewall Config //
    processFirewallConfig() {
        var _a, _b;
        // Initialize firewall config with defaults
        this.workload.spec.firewallConfig = {
            external: {
                inboundAllowCIDR: [],
                outboundAllowCIDR: ['0.0.0.0/0'],
            },
            internal: {
                inboundAllowType: 'none', // Internal firewall 'none' by default
                inboundAllowWorkload: [],
            },
        };
        // Process the internal firewall before the public exposure check, which requires pod labels
        this.processInternalFirewall();
        // If the pod has no metadata labels, skip public exposure check
        if (!((_a = this.pod.metadata) === null || _a === void 0 ? void 0 : _a.labels)) {
            return;
        }
        // By default, this workload is not exposed publicly unless proven otherwise
        let isPubliclyExposed = false;
        // Iterate over services and attempt to find the service with type LoadBalancer that is owned by this pod
        for (const service of this.mapper.services) {
            if (!service.spec || !service.spec.selector) {
                continue;
            }
            // A pod is publicly exposed to the internet if it connected to a LoadBalancer service and matches the labels of the pod
            if (service.spec.type === 'LoadBalancer' && this.mapper.isSelectorSubset(service.spec.selector, this.pod.metadata.labels)) {
                isPubliclyExposed = true;
                break;
            }
        }
        // If the service check doesn't indicate public exposure, then attempt to find that in ingresses
        if (!isPubliclyExposed && this.mapper.ingresses.length > 0) {
            // Iterate over each ingress and attempt to determine whether this workload should be exposed to the internet or not
            for (const ing of this.mapper.ingresses) {
                // Skip if required properties were not specified
                if (!ing.spec || !ing.spec.rules) {
                    continue;
                }
                // Flag to determine whether to kill this loop or not
                let killLoop = false;
                // Iterate over spec rules
                for (const rule of ing.spec.rules) {
                    // Skip if required properties were not specified
                    if (!rule.http || !rule.http.paths) {
                        continue;
                    }
                    // Iterate over http paths
                    for (const path of rule.http.paths) {
                        // Skip if required properties were not specified
                        if (!path.backend || !path.backend.service || !path.backend.service.name) {
                            continue;
                        }
                        // Find the target service that this ingress is referring to
                        const targetSvc = this.mapper.services.find((s) => s.metadata.name === path.backend.service.name);
                        // Check if the target service matches the pod labels
                        if (targetSvc && ((_b = targetSvc.spec) === null || _b === void 0 ? void 0 : _b.selector) && this.mapper.isSelectorSubset(targetSvc.spec.selector, this.pod.metadata.labels)) {
                            // An Ingress doesn't itself get a public IP (that's the Ingress Controller's job),
                            // but it sits in front of one or more ClusterIP Services
                            isPubliclyExposed = true;
                            killLoop = true;
                            break;
                        }
                    }
                    // Kill nested loop if requested
                    if (killLoop) {
                        break;
                    }
                }
                // Kill loop if requested
                if (killLoop) {
                    break;
                }
            }
        }
        // If the workload should be exposed publicly, specify that in the workload firewall config
        if (isPubliclyExposed) {
            this.workload.spec.firewallConfig.external.inboundAllowCIDR = ['0.0.0.0/0'];
        }
    }
    /**
     * Determines the internal inbound allow type of the workload from the K8s Services that select it,
     * honoring the internal inbound allow type annotation as an explicit override.
     */
    processInternalFirewall() {
        // A pod selected by a Service is reachable by every other pod in a K8s cluster, so 'same-gvc' preserves that reachability
        const services = this.mapper.getServicesForWorkload(this.pod, this.k8sObject);
        if (services.length > 0) {
            this.workload.spec.firewallConfig.internal.inboundAllowType = 'same-gvc';
        }
        // Honor the annotation as an explicit override of the Service-based inference
        const annotationValue = (0, helper_1.getCplnAnnotation)(constants_1.K8S_CPLN_ANNOTATION_INTERNAL_INBOUND_ALLOW_TYPE, this.k8sObject, this.pod.metadata);
        if (annotationValue === undefined) {
            return;
        }
        // Only a fixed set of inbound allow types can be expressed through the annotation
        if (!constants_1.K8S_CPLN_ANNOTATION_INTERNAL_INBOUND_ALLOW_TYPES.includes(annotationValue)) {
            this.raiseCustomK8sError(`Annotation '${constants_1.K8S_CPLN_ANNOTATION_INTERNAL_INBOUND_ALLOW_TYPE}' must be one of [${constants_1.K8S_CPLN_ANNOTATION_INTERNAL_INBOUND_ALLOW_TYPES.join(', ')}], found '${annotationValue}'`);
        }
        this.workload.spec.firewallConfig.internal.inboundAllowType = annotationValue;
    }
    // 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()) {
            const availableSchemes = [];
            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 (port.name) {
                        if (availableSchemes.includes(port.name)) {
                            this.raiseCustomK8sError(`Port name must be unique, found duplicate name '${port.name}' at spec.containers[${containerIndex}].ports[${portIndex}].name`);
                        }
                        availableSchemes.push(port.name.toLowerCase());
                    }
                }
            }
            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.envFrom) {
                if (!Array.isArray(container.envFrom)) {
                    this.raiseCustomK8sError(`'${this.propertyPath}.spec.containers[${containerIndex}].envFrom' must be an array`);
                }
                // Validate each envFrom source
                for (const [envFromIndex, envFromSource] of container.envFrom.entries()) {
                    if (envFromSource.configMapRef && envFromSource.secretRef) {
                        this.raiseCustomK8sError(`ONLY one of 'configMapRef' or 'secretRef' can be provided at '${this.propertyPath}.spec.containers[${containerIndex}].envFrom[${envFromIndex}]'`);
                    }
                    if (!envFromSource.configMapRef && !envFromSource.secretRef) {
                        this.raiseCustomK8sError(`You must either provide one of 'configMapRef' or 'secretRef' at '${this.propertyPath}.spec.containers[${containerIndex}].envFrom[${envFromIndex}]'`);
                    }
                    if (envFromSource.configMapRef && !envFromSource.configMapRef.name) {
                        this.ensurePropertyPresence(`spec.containers[${containerIndex}].envFrom[${envFromIndex}].configMapRef.name`);
                    }
                    if (envFromSource.secretRef && !envFromSource.secretRef.name) {
                        this.ensurePropertyPresence(`spec.containers[${containerIndex}].envFrom[${envFromIndex}].secretRef.name`);
                    }
                    // A pod cannot start without a non-optional ConfigMap, so its data must be part of the conversion input
                    if (envFromSource.configMapRef &&
                        envFromSource.configMapRef.name &&
                        !envFromSource.configMapRef.optional &&
                        !this.mapper.configMapNameToObject.has(envFromSource.configMapRef.name)) {
                        this.raiseCustomK8sError(`'spec.containers[${containerIndex}].envFrom[${envFromIndex}].configMapRef.name' is referencing a ConfigMap that does not exist. Make sure the ConfigMap '${envFromSource.configMapRef.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`, availableSchemes);
            }
            if (container.readinessProbe) {
                this.validateContainerHealthCheck(container.readinessProbe, `spec.containers[${containerIndex}].readinessProbe`, availableSchemes);
            }
        }
        // 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, availableSchemes) {
        let counter = 0;
        let port = undefined;
        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`);
                    }
                }
            }
            if (probe.httpGet.port) {
                port = probe.httpGet.port;
            }
            counter++;
        }
        if (probe.tcpSocket) {
            if (!probe.tcpSocket.port) {
                this.ensurePropertyPresence(`${property}.tcpSocket.port`);
            }
            port = probe.tcpSocket.port;
            counter++;
        }
        if (probe.grpc) {
            if (!probe.grpc.port) {
                this.ensurePropertyPresence(`${property}.grpc.port`);
            }
            port = probe.grpc.port;
            counter++;
        }
        if (port) {
            if (typeof port === 'string') {
                const numericPort = Number(port);
                if (!isNaN(numericPort) && port.trim() !== '') {
                    this.validatePortNumber(property, numericPort);
                }
                else if (!availableSchemes.includes(port.toLowerCase())) {
                    this.raiseCustomK8sError(`The specified port '${port}' name does not exist within the specified container ports. Supported port names within the contianer are: [${availableSchemes.join(', ')}]`);
                }
            }
            else {
                this.validatePortNumber(property, port);
            }
        }
        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}'`);
        }
    }
    validatePortNumber(property, port) {
        if (port < 1 || port > 65535) {
            this.raiseCustomK8sError(`'${this.propertyPath}.${property}.port' must be a valid port number between 1 and 65535 (inclusive)`);
        }
    }
    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);
    }
}
exports.K8sPodTemplateProcessor = K8sPodTemplateProcessor;
//# sourceMappingURL=pod-template.js.map