hunk.code_window: import { notifyError } from '@/portainer/services/notifications';
   import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
   import { generateNewIngressesFromFormPaths } from '@/react/kubernetes/applications/CreateView/application-services/utils';
   import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
   class KubernetesApplicationService {
     /* #region  CONSTRUCTOR */
     /* @ngInject */
  
hunk.labels: keep keep keep replace keep keep keep keep
hunk.after_edit: import { KubernetesPod } from '../pod/models';
   import { KubernetesApplication } from '../models/application/models';
  
hunk.file_path: app/kubernetes/services/applicationService.js
hunk.type: replace
file: import _ from 'lodash-es';
  import filesizeParser from 'filesize-parser';
  
  import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
  import {
    KubernetesApplication,
    KubernetesApplicationConfigurationVolume,
    KubernetesApplicationPersistedFolder,
    KubernetesApplicationPort,
    KubernetesPortainerApplicationNameLabel,
    KubernetesPortainerApplicationNote,
    KubernetesPortainerApplicationOwnerLabel,
    KubernetesPortainerApplicationStackNameLabel,
    KubernetesPortainerApplicationStackIdLabel,
    KubernetesPortainerApplicationKindLabel,
  } from 'Kubernetes/models/application/models';
  import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
  import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
  import { KubernetesApplicationFormValues } from 'Kubernetes/models/application/formValues';
  import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
  
  import KubernetesDeploymentConverter from 'Kubernetes/converters/deployment';
  import KubernetesDaemonSetConverter from 'Kubernetes/converters/daemonSet';
  import KubernetesStatefulSetConverter from 'Kubernetes/converters/statefulSet';
  import KubernetesPodConverter from 'Kubernetes/pod/converter';
  import KubernetesServiceConverter from 'Kubernetes/converters/service';
  import KubernetesPersistentVolumeClaimConverter from 'Kubernetes/converters/persistentVolumeClaim';
  import PortainerError from 'Portainer/error';
  import { KubernetesIngressHelper } from 'Kubernetes/ingress/helper';
  import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
  import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
  
  function _apiPortsToPublishedPorts(pList, pRefs) {
    const ports = _.map(pList, (item) => {
      const res = new KubernetesApplicationPort();
      res.Port = item.port;
      res.TargetPort = item.targetPort;
      res.NodePort = item.nodePort;
      res.Protocol = item.protocol;
      return res;
    });
    _.forEach(ports, (port) => {
      if (isNaN(port.TargetPort)) {
        const targetPort = _.find(pRefs, { name: port.TargetPort });
        if (targetPort) {
          port.TargetPort = targetPort.containerPort;
        }
      }
    });
    return ports;
  }
  
  class KubernetesApplicationConverter {
    static applicationCommon(res, data, pods, service, ingresses) {
      const containers = data.spec.template ? _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined) : data.spec.containers;
      res.Id = data.metadata.uid;
      res.Name = data.metadata.name;
      res.Metadata = data.metadata;
      res.ApplicationType = data.kind;
      res.Labels = data.metadata.labels || {};
  
      if (data.metadata.labels) {
        const { labels } = data.metadata;
        res.StackId = labels[KubernetesPortainerApplicationStackIdLabel] ? parseInt(labels[KubernetesPortainerApplicationStackIdLabel], 10) : null;
        res.StackName = labels[KubernetesPortainerApplicationStackNameLabel] || '';
        res.ApplicationKind = labels[KubernetesPortainerApplicationKindLabel] || '';
        res.ApplicationOwner = labels[KubernetesPortainerApplicationOwnerLabel] || '';
        res.ApplicationName = labels[KubernetesPortainerApplicationNameLabel] || res.Name;
      }
  
      res.Note = data.metadata.annotations ? data.metadata.annotations[KubernetesPortainerApplicationNote] || '' : '';
      res.ResourcePool = data.metadata.namespace;
      if (containers.length) {
        res.Image = containers[0].image;
      }
      if (data.spec.template && data.spec.template.spec && data.spec.template.spec.imagePullSecrets && data.spec.template.spec.imagePullSecrets.length) {
        res.RegistryId = parseInt(data.spec.template.spec.imagePullSecrets[0].name.replace('registry-', ''), 10);
      }
      res.CreationDate = data.metadata.creationTimestamp;
      res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
      res.Pods = data.spec.selector ? KubernetesApplicationHelper.associatePodsAndApplication(pods, data.spec.selector) : [data];
  
      const limits = {
        Cpu: 0,
        Memory: 0,
      };
      res.Limits = _.reduce(
        containers,
        (acc, item) => {
          if (item.resources.limits && item.resources.limits.cpu) {
            acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.limits.cpu);
          }
          if (item.resources.limits && item.resources.limits.memory) {
            acc.Memory += filesizeParser(item.resources.limits.memory, { base: 10 });
          }
          return acc;
        },
        limits
      );
  
      const requests = {
        Cpu: 0,
        Memory: 0,
      };
      res.Requests = _.reduce(
        containers,
        (acc, item) => {
          if (item.resources.requests && item.resources.requests.cpu) {
            acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.requests.cpu);
          }
          if (item.resources.requests && item.resources.requests.memory) {
            acc.Memory += filesizeParser(item.resources.requests.memory, { base: 10 });
          }
          return acc;
        },
        requests
      );
  
      if (service) {
        const serviceType = service.spec.type;
        res.ServiceType = serviceType;
        res.ServiceId = service.metadata.uid;
        res.ServiceName = service.metadata.name;
        res.ClusterIp = service.spec.clusterIP;
        res.ExternalIp = service.spec.externalIP;
  
        if (serviceType === KubernetesServiceTypes.LOAD_BALANCER) {
          if (service.status.loadBalancer.ingress && service.status.loadBalancer.ingress.length > 0) {
            res.LoadBalancerIPAddress = service.status.loadBalancer.ingress[0].ip || service.status.loadBalancer.ingress[0].hostname;
          }
        }
  
        const portsRefs = _.concat(..._.map(containers, (container) => container.ports));
        const ports = _apiPortsToPublishedPorts(service.spec.ports, portsRefs);
        const rules = KubernetesIngressHelper.findSBoundServiceIngressesRules(ingresses, service.metadata.name);
        _.forEach(ports, (port) => (port.IngressRules = _.filter(rules, (rule) => rule.Port === port.Port)));
        res.PublishedPorts = ports;
      }
  
      if (data.spec.template) {
        res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : [];
      } else {
        res.Volumes = data.spec.volumes;
      }
  
      // TODO: review
      // this if() fixs direct use of PVC reference inside spec.template.spec.containers[0].volumeMounts
      // instead of referencing the PVC the "good way" using spec.template.spec.volumes array
      // Basically it creates an "in-memory" reference for the PVC, as if it was saved in
      // spec.template.spec.volumes and retrieved from here.
      //
      // FIX FOR SFS ONLY ; as far as we know it's not possible to do this with DEPLOYMENTS/DAEMONSETS
      //
      // This may lead to destructing behaviours when we will allow external apps to be edited.
      // E.G. if we try to generate the formValues and patch the app, SFS reference will be created under
      // spec.template.spec.volumes and not be referenced directly inside spec.template.spec.containers[0].volumeMounts
      // As we preserve original SFS name and try to build around it, it SHOULD be fine, but we definitely need to test this
      // before allowing external apps modification
      if (data.spec.volumeClaimTemplates) {
        const vcTemplates = _.map(data.spec.volumeClaimTemplates, (vc) => {
          return {
            name: vc.metadata.name,
            persistentVolumeClaim: { claimName: vc.metadata.name },
          };
        });
        const inexistingPVC = _.filter(vcTemplates, (vc) => {
          return !_.find(res.Volumes, { persistentVolumeClaim: { claimName: vc.persistentVolumeClaim.claimName } });
        });
        res.Volumes = _.concat(res.Volumes, inexistingPVC);
      }
  
      const persistedFolders = _.filter(res.Volumes, (volume) => volume.persistentVolumeClaim || volume.hostPath);
  
      res.PersistedFolders = _.map(persistedFolders, (volume) => {
        const volumeMounts = _.uniq(_.flatMap(_.map(containers, 'volumeMounts')), 'name');
        const matchingVolumeMount = _.find(volumeMounts, { name: volume.name });
  
        if (matchingVolumeMount) {
          const persistedFolder = new KubernetesApplicationPersistedFolder();
          persistedFolder.MountPath = matchingVolumeMount.mountPath;
  
          if (volume.persistentVolumeClaim) {
            persistedFolder.persistentVolumeClaimName = volume.persistentVolumeClaim.claimName;
          } else {
            persistedFolder.HostPath = volume.hostPath.path;
          }
  
          return persistedFolder;
        }
      });
  
      res.PersistedFolders = _.without(res.PersistedFolders, undefined);
  
      res.ConfigurationVolumes = _.reduce(
        res.Volumes,
        (acc, volume) => {
          if (volume.configMap || volume.secret) {
            const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
  
            if (matchingVolumeMount) {
              let items = [];
              let configurationName = '';
  
              if (volume.configMap) {
                items = volume.configMap.items;
                configurationName = volume.configMap.name;
              } else {
                items = volume.secret.items;
                configurationName = volume.secret.secretName;
              }
  
              if (!items) {
                const configurationVolume = new KubernetesApplicationConfigurationVolume();
                configurationVolume.fileMountPath = matchingVolumeMount.mountPath;
                configurationVolume.rootMountPath = matchingVolumeMount.mountPath;
                configurationVolume.configurationName = configurationName;
                configurationVolume.configurationType = volume.configMap ? KubernetesConfigurationKinds.CONFIGMAP : KubernetesConfigurationKinds.SECRET;
  
                acc.push(configurationVolume);
              } else {
                _.forEach(items, (item) => {
                  const configurationVolume = new KubernetesApplicationConfigurationVolume();
                  configurationVolume.fileMountPath = matchingVolumeMount.mountPath + '/' + item.path;
                  configurationVolume.rootMountPath = matchingVolumeMount.mountPath;
                  configurationVolume.configurationKey = item.key;
                  configurationVolume.configurationName = configurationName;
                  configurationVolume.configurationType = volume.configMap ? KubernetesConfigurationKinds.CONFIGMAP : KubernetesConfigurationKinds.SECRET;
  
                  acc.push(configurationVolume);
                });
              }
            }
          }
  
          return acc;
        },
        []
      );
    }
  
    static apiPodToApplication(data, pods, service, ingresses) {
      const res = new KubernetesApplication();
      KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
      res.ApplicationType = KubernetesApplicationTypes.Pod;
      return res;
    }
  
    static apiDeploymentToApplication(data, pods, service, ingresses) {
      const res = new KubernetesApplication();
      KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
      res.ApplicationType = KubernetesApplicationTypes.Deployment;
      res.DeploymentType = KubernetesApplicationDeploymentTypes.Replicated;
      res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.Shared;
      res.RunningPodsCount = data.status.availableReplicas || data.status.replicas - data.status.unavailableReplicas || 0;
      res.TotalPodsCount = data.spec.replicas;
      return res;
    }
  
    static apiDaemonSetToApplication(data, pods, service, ingresses) {
      const res = new KubernetesApplication();
      KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
      res.ApplicationType = KubernetesApplicationTypes.DaemonSet;
      res.DeploymentType = KubernetesApplicationDeploymentTypes.Global;
      res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.Shared;
      res.RunningPodsCount = data.status.numberAvailable || data.status.desiredNumberScheduled - data.status.numberUnavailable || 0;
      res.TotalPodsCount = data.status.desiredNumberScheduled;
      return res;
    }
  
    static apiStatefulSetToapplication(data, pods, service, ingresses) {
      const res = new KubernetesApplication();
      KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
      res.ApplicationType = KubernetesApplicationTypes.StatefulSet;
      res.DeploymentType = KubernetesApplicationDeploymentTypes.Replicated;
      res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.Isolated;
      res.RunningPodsCount = data.status.readyReplicas || 0;
      res.TotalPodsCount = data.spec.replicas;
      res.HeadlessServiceName = data.spec.serviceName;
      return res;
    }
  
    static applicationToFormValues(app, resourcePools, configurations, persistentVolumeClaims, nodesLabels, ingresses) {
      const res = new KubernetesApplicationFormValues();
      res.ApplicationType = app.ApplicationType;
      res.ResourcePool = _.find(resourcePools, ['Namespace.Name', app.ResourcePool]);
      res.Name = app.Name;
      res.Labels = app.Labels;
      res.Services = KubernetesApplicationHelper.generateServicesFormValuesFromServices(app, ingresses);
      res.Selector = KubernetesApplicationHelper.generateSelectorFromService(app);
      res.StackName = app.StackName;
      res.ApplicationOwner = app.ApplicationOwner;
      res.ImageModel.Image = app.Image;
      res.ImageModel.Registry.Id = app.RegistryId;
      res.ReplicaCount = app.TotalPodsCount;
      res.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(app.Limits.Memory);
      res.CpuLimit = app.Limits.Cpu;
      res.DeploymentType = app.DeploymentType;
      res.DataAccessPolicy = app.DataAccessPolicy;
      res.EnvironmentVariables = KubernetesApplicationHelper.generateEnvVariablesFromEnv(app.Env);
      res.PersistedFolders = KubernetesApplicationHelper.generatePersistedFoldersFormValuesFromPersistedFolders(app.PersistedFolders, persistentVolumeClaims); // generate from PVC and app.PersistedFolders
      res.Secrets = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(
        app.Env,
        app.ConfigurationVolumes,
        configurations,
        KubernetesConfigurationKinds.SECRET
      );
      res.ConfigMaps = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(
        app.Env,
        app.ConfigurationVolumes,
        configurations,
        KubernetesConfigurationKinds.CONFIGMAP
      );
      res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler, res.ReplicaCount);
      res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts, ingresses);
      res.Containers = app.Containers;
  
      res.PublishingType = app.ServiceType;
  
      if (app.Pods && app.Pods.length) {
        KubernetesApplicationHelper.generatePlacementsFormValuesFromAffinity(res, app.Pods[0].Affinity);
      }
  
      return res;
    }
  
    static applicationFormValuesToApplication(formValues) {
      formValues.ApplicationOwner = KubernetesCommonHelper.ownerToLabel(formValues.ApplicationOwner);
  
      const claims = KubernetesPersistentVolumeClaimConverter.applicationFormValuesToVolumeClaims(formValues);
      const rwx = KubernetesApplicationHelper.hasRWX(claims);
  
      const deployment =
        (formValues.DeploymentType === KubernetesApplicationDeploymentTypes.Replicated &&
          (claims.length === 0 || (claims.length > 0 && formValues.DataAccessPolicy === KubernetesApplicationDataAccessPolicies.Shared))) ||
        formValues.ApplicationType === KubernetesApplicationTypes.Deployment;
  
      const statefulSet =
        (formValues.DeploymentType === KubernetesApplicationDeploymentTypes.Replicated &&
          claims.length > 0 &&
          formValues.DataAccessPolicy === KubernetesApplicationDataAccessPolicies.Isolated) ||
        formValues.ApplicationType === KubernetesApplicationTypes.StatefulSet;
  
      const daemonSet =
        (formValues.DeploymentType === KubernetesApplicationDeploymentTypes.Global &&
          (claims.length === 0 || (claims.length > 0 && formValues.DataAccessPolicy === KubernetesApplicationDataAccessPolicies.Shared && rwx))) ||
        formValues.ApplicationType === KubernetesApplicationTypes.DaemonSet;
  
      const pod = formValues.ApplicationType === KubernetesApplicationTypes.POD;
  
      let app;
      if (deployment) {
        app = KubernetesDeploymentConverter.applicationFormValuesToDeployment(formValues, claims);
      } else if (statefulSet) {
        app = KubernetesStatefulSetConverter.applicationFormValuesToStatefulSet(formValues, claims);
      } else if (daemonSet) {
        app = KubernetesDaemonSetConverter.applicationFormValuesToDaemonSet(formValues, claims);
      } else if (pod) {
        app = KubernetesPodConverter.applicationFormValuesToPod(formValues, claims);
      } else {
        throw new PortainerError('Unable to determine which association to use to convert form');
      }
      app.ApplicationType = formValues.ApplicationType;
  
      let headlessService;
      if (statefulSet) {
        headlessService = KubernetesServiceConverter.applicationFormValuesToHeadlessService(formValues);
      }
  
      let service = KubernetesServiceConverter.applicationFormValuesToService(formValues);
      if (!service.Ports.length) {
        service = undefined;
      }
  
      let services = KubernetesServiceConverter.applicationFormValuesToServices(formValues);
  
      return [app, headlessService, services, service, claims];
    }
  }
  
  export default KubernetesApplicationConverter;
  