1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-20 05:49:40 +02:00

feat(application): Add the ability to use existing volumes when creating an application (#4044)

* feat(applications): update UI to use existing volumes

* feat(application): Add the ability to use existing volumes when creating an application

* feat(application): Existing persisted folders should default to associated volumes

* feat(application): add form validation to existing volume

* feat(application): remove the ability to use an existing volume with statefulset application

* feat(k8s/applications): minor UI update

* feat(k8s/application): minor UI update

* feat(volume): allow to increase volume size and few other things

* feat(volumes): add the ability to allow volume expansion

* fix(storage): fix the storage patch request

* fix(k8s/applications): remove conflict leftover

* feat(k8s/configure): minor UI update

* feat(k8s/volume): minor UI update

* fix(storage): change few things

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
Maxime Bajeux 2020-08-07 06:40:24 +02:00 committed by GitHub
parent b9c2bf487b
commit 61f97469ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 436 additions and 40 deletions

View file

@ -8,6 +8,7 @@ import {
KubernetesApplicationDeploymentTypes,
KubernetesApplicationPublishingTypes,
KubernetesApplicationQuotaDefaults,
KubernetesApplicationTypes,
} from 'Kubernetes/models/application/models';
import {
KubernetesApplicationConfigurationFormValue,
@ -23,6 +24,7 @@ import KubernetesApplicationConverter from 'Kubernetes/converters/application';
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import KubernetesApplicationHelper from 'Kubernetes/helpers/application/index';
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
class KubernetesCreateApplicationController {
/* @ngInject */
@ -39,7 +41,8 @@ class KubernetesCreateApplicationController {
KubernetesConfigurationService,
KubernetesNodeService,
KubernetesPersistentVolumeClaimService,
KubernetesNamespaceHelper
KubernetesNamespaceHelper,
KubernetesVolumeService
) {
this.$async = $async;
this.$state = $state;
@ -52,12 +55,14 @@ class KubernetesCreateApplicationController {
this.KubernetesStackService = KubernetesStackService;
this.KubernetesConfigurationService = KubernetesConfigurationService;
this.KubernetesNodeService = KubernetesNodeService;
this.KubernetesVolumeService = KubernetesVolumeService;
this.KubernetesPersistentVolumeClaimService = KubernetesPersistentVolumeClaimService;
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
this.ApplicationPublishingTypes = KubernetesApplicationPublishingTypes;
this.ApplicationTypes = KubernetesApplicationTypes;
this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes;
this.ServiceTypes = KubernetesServiceTypes;
@ -73,7 +78,13 @@ class KubernetesCreateApplicationController {
}
isValid() {
return !this.state.alreadyExists && !this.state.hasDuplicateEnvironmentVariables && !this.state.hasDuplicatePersistedFolderPaths && !this.state.hasDuplicateConfigurationPaths;
return (
!this.state.alreadyExists &&
!this.state.hasDuplicateEnvironmentVariables &&
!this.state.hasDuplicatePersistedFolderPaths &&
!this.state.hasDuplicateConfigurationPaths &&
!this.state.hasDuplicateExistingVolumes
);
}
onChangeName() {
@ -197,7 +208,14 @@ class KubernetesCreateApplicationController {
}
onChangePersistedFolderPath() {
this.state.duplicatePersistedFolderPaths = KubernetesFormValidationHelper.getDuplicates(_.map(this.formValues.PersistedFolders, 'ContainerPath'));
this.state.duplicatePersistedFolderPaths = KubernetesFormValidationHelper.getDuplicates(
_.map(this.formValues.PersistedFolders, (persistedFolder) => {
if (persistedFolder.NeedsDeletion) {
return undefined;
}
return persistedFolder.ContainerPath;
})
);
this.state.hasDuplicatePersistedFolderPaths = Object.keys(this.state.duplicatePersistedFolderPaths).length > 0;
}
@ -212,6 +230,47 @@ class KubernetesCreateApplicationController {
this.formValues.PersistedFolders.splice(index, 1);
}
this.onChangePersistedFolderPath();
this.onChangeExistingVolumeSelection();
}
onChangeExistingVolume(index) {
if (this.formValues.PersistedFolders[index].UseNewVolume) {
this.formValues.PersistedFolders[index].ExistingVolume = null;
}
}
useNewVolume(index) {
this.formValues.PersistedFolders[index].UseNewVolume = true;
this.formValues.PersistedFolders[index].ExistingVolume = null;
this.state.PersistedFoldersUseExistingVolumes = !_.reduce(this.formValues.PersistedFolders, (acc, pf) => acc && pf.UseNewVolume, true);
}
useExistingVolume(index) {
this.formValues.PersistedFolders[index].UseNewVolume = false;
this.state.PersistedFoldersUseExistingVolumes = _.find(this.formValues.PersistedFolders, { UseNewVolume: false }) ? true : false;
if (this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED) {
this.formValues.DataAccessPolicy = this.ApplicationDataAccessPolicies.SHARED;
this.resetDeploymentType();
}
}
onChangeExistingVolumeSelection() {
this.state.duplicateExistingVolumes = KubernetesFormValidationHelper.getDuplicates(
_.map(this.formValues.PersistedFolders, (persistedFolder) => {
return persistedFolder.ExistingVolume ? persistedFolder.ExistingVolume.PersistentVolumeClaim.Name : '';
})
);
this.state.hasDuplicateExistingVolumes = Object.keys(this.state.duplicateExistingVolumes).length > 0;
}
filterAvailableVolumes() {
const filteredVolumes = _.filter(this.volumes, (volume) => {
const isSameNamespace = volume.ResourcePool.Namespace.Name === this.formValues.ResourcePool.Namespace.Name;
const isUnused = !KubernetesVolumeHelper.isUsed(volume);
const isRWX = _.find(volume.PersistentVolumeClaim.StorageClass.AccessModes, (am) => am === 'RWX');
return isSameNamespace && (isUnused || isRWX);
});
this.availableVolumes = filteredVolumes;
}
/**
* !PERSISTENT FOLDERS UI MANAGEMENT
@ -500,7 +559,12 @@ class KubernetesCreateApplicationController {
const namespace = this.formValues.ResourcePool.Namespace.Name;
this.updateSliders();
this.refreshStacksConfigsApps(namespace);
this.filterAvailableVolumes();
this.formValues.Configurations = [];
this.formValues.PersistedFolders = _.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
persistedFolder.ExistingVolume = null;
persistedFolder.UseNewVolume = true;
});
}
/**
* !DATA AUTO REFRESH
@ -604,11 +668,14 @@ class KubernetesCreateApplicationController {
hasDuplicatePersistedFolderPaths: false,
duplicateConfigurationPaths: {},
hasDuplicateConfigurationPaths: false,
duplicateExistingVolumes: {},
hasDuplicateExistingVolumes: false,
isEdit: false,
params: {
namespace: this.$transition$.params().namespace,
name: this.$transition$.params().name,
},
PersistedFoldersUseExistingVolumes: false,
};
this.isAdmin = this.Authentication.isAdmin();
@ -627,11 +694,20 @@ class KubernetesCreateApplicationController {
this.formValues = new KubernetesApplicationFormValues();
const [resourcePools, nodes] = await Promise.all([this.KubernetesResourcePoolService.get(), this.KubernetesNodeService.get()]);
const [resourcePools, nodes, volumes, applications] = await Promise.all([
this.KubernetesResourcePoolService.get(),
this.KubernetesNodeService.get(),
this.KubernetesVolumeService.get(),
this.KubernetesApplicationService.get(),
]);
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
this.formValues.ResourcePool = this.resourcePools[0];
this.volumes = volumes;
_.forEach(this.volumes, (volume) => {
volume.Applications = KubernetesVolumeHelper.getUsingApplications(volume, applications);
});
this.filterAvailableVolumes();
_.forEach(nodes, (item) => {
this.state.nodes.memory += filesizeParser(item.Memory);
@ -646,6 +722,16 @@ class KubernetesCreateApplicationController {
this.formValues = KubernetesApplicationConverter.applicationToFormValues(this.application, this.resourcePools, this.configurations, this.persistentVolumeClaims);
this.savedFormValues = angular.copy(this.formValues);
delete this.formValues.ApplicationType;
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
_.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
const volume = _.find(this.availableVolumes, (vol) => vol.PersistentVolumeClaim.Name === persistedFolder.PersistentVolumeClaimName);
if (volume) {
persistedFolder.UseNewVolume = false;
persistedFolder.ExistingVolume = volume;
}
});
}
} else {
this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler();
this.formValues.AutoScaler.MinReplicas = this.formValues.ReplicaCount;