fix(app): allow editing pod services [EE-6480] (#10875)

* fix(app): allow editing pod services [EE-6480]
* address review comment

---------

Co-authored-by: testa113 <testa113>
Co-authored-by: prabhat khera <prabhat.khera@portainer.io>
pull/10731/merge
Ali 2024-01-23 10:10:16 +13:00 committed by GitHub
parent 7cba02226e
commit 4c0049edbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 11 deletions

View File

@ -22,6 +22,7 @@ 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';
@ -55,6 +56,8 @@ class KubernetesApplicationConverter {
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;
@ -281,6 +284,7 @@ class KubernetesApplicationConverter {
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;
@ -341,6 +345,8 @@ class KubernetesApplicationConverter {
(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);
@ -348,6 +354,8 @@ class KubernetesApplicationConverter {
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');
}

View File

@ -315,7 +315,12 @@ class KubernetesApplicationHelper {
ports.push(svcport);
});
svc.Ports = ports;
svc.Selector = app.Raw.spec.selector.matchLabels;
// if the app is a pod (doesn't have a selector), then get the pod labels
if (app.Raw.spec.selector) {
svc.Selector = app.Raw.spec.selector.matchLabels;
} else {
svc.Selector = app.Raw.metadata.labels || { app: app.Name };
}
services.push(svc);
}
});

View File

@ -14,10 +14,12 @@ class KubernetesServiceHelper {
}
static findApplicationBoundServices(services, rawApp) {
if (!rawApp.spec.template) {
// if the app is a naked pod (doesn't have a template), then get the pod labels
const appLabels = rawApp.spec.template ? rawApp.spec.template.metadata.labels : rawApp.metadata.labels;
if (!appLabels) {
return undefined;
}
return _.filter(services, (item) => item.spec.selector && _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector));
return _.filter(services, (item) => item.spec.selector && _.isMatch(appLabels, item.spec.selector));
}
}
export default KubernetesServiceHelper;

View File

@ -9,7 +9,9 @@ import {
KubernetesPortainerApplicationNote,
} from 'Kubernetes/models/application/models';
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes, KubernetesPodEviction } from 'Kubernetes/pod/models';
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
import { createPayloadFactory } from './payloads/create';
function computeStatus(statuses) {
@ -100,6 +102,32 @@ function computeContainers(data) {
}
export default class KubernetesPodConverter {
static applicationFormValuesToPod(formValues, volumeClaims) {
let serviceSelector = {};
if (formValues.Services.length) {
serviceSelector = formValues.Services[0].Selector || { app: formValues.Name };
}
const res = new KubernetesPod();
res.Namespace = formValues.ResourcePool.Namespace.Name;
res.Name = formValues.Name;
res.StackName = formValues.StackName ? formValues.StackName : formValues.Name;
res.ApplicationOwner = formValues.ApplicationOwner;
res.ApplicationName = formValues.Name;
res.ImageModel = formValues.ImageModel;
res.CpuLimit = formValues.CpuLimit;
res.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
res.Containers = formValues.Containers;
res.ApplicationType = formValues.ApplicationType;
res.ServiceSelector = serviceSelector;
res.Labels = formValues.Labels;
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.ConfigMaps, formValues.Secrets);
KubernetesApplicationHelper.generateAffinityFromPlacements(res, formValues);
return res;
}
static apiToModel(data) {
const res = new KubernetesPod();
res.Id = data.metadata.uid;
@ -115,6 +143,7 @@ export default class KubernetesPodConverter {
res.Affinity = computeAffinity(data.spec.affinity);
res.NodeSelector = data.spec.nodeSelector;
res.Tolerations = computeTolerations(data.spec.tolerations);
res.Labels = data.metadata.labels;
return res;
}
@ -140,6 +169,7 @@ function createPayload(pod) {
payload.metadata.labels[KubernetesPortainerApplicationStackNameLabel] = pod.StackName;
payload.metadata.labels[KubernetesPortainerApplicationNameLabel] = pod.ApplicationName;
payload.metadata.labels[KubernetesPortainerApplicationOwnerLabel] = pod.ApplicationOwner;
payload.metadata.labels = { ...(pod.Labels || {}), ...(pod.ServiceSelector || {}), ...payload.metadata.labels };
if (pod.Note) {
payload.metadata.annotations[KubernetesPortainerApplicationNote] = pod.Note;
} else {

View File

@ -199,13 +199,6 @@ class KubernetesCreateApplicationController {
this.updateApplicationType();
}
updateApplicationType() {
return this.$scope.$evalAsync(() => {
this.formValues.ApplicationType = this.getAppType();
this.validatePersistedFolders();
});
}
getAppType() {
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Global) {
return this.ApplicationTypes.DaemonSet;
@ -216,6 +209,14 @@ class KubernetesCreateApplicationController {
return this.ApplicationTypes.Deployment;
}
// keep the application type up to date
updateApplicationType() {
return this.$scope.$evalAsync(() => {
this.formValues.ApplicationType = this.getAppType();
this.validatePersistedFolders();
});
}
onChangeFileContent(value) {
this.$scope.$evalAsync(() => {
if (this.oldStackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== value.replace(/(\r\n|\n|\r)/gm, '')) {
@ -273,7 +274,9 @@ class KubernetesCreateApplicationController {
}
imageValidityIsValid() {
return this.state.pullImageValidity || (this.formValues.registryDetails && this.formValues.registryDetails.Registry.Type !== RegistryTypes.DOCKERHUB);
return (
this.isExternalApplication() || this.state.pullImageValidity || (this.formValues.registryDetails && this.formValues.registryDetails.Registry.Type !== RegistryTypes.DOCKERHUB)
);
}
onChangeAppName(appName) {
@ -349,6 +352,7 @@ class KubernetesCreateApplicationController {
persistedFolder.useNewVolume = true;
});
this.validatePersistedFolders();
this.updateApplicationType();
}
/* #endregion */