diff --git a/api/portainer.go b/api/portainer.go index 06f133957..f6f960380 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -344,9 +344,10 @@ type ( // KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration KubernetesStorageClassConfig struct { - Name string `json:"Name"` - AccessModes []string `json:"AccessModes"` - Provisioner string `json:"Provisioner"` + Name string `json:"Name"` + AccessModes []string `json:"AccessModes"` + Provisioner string `json:"Provisioner"` + AllowVolumeExpansion bool `json:"AllowVolumeExpansion"` } // LDAPGroupSearchSettings represents settings used to search for groups in a LDAP server diff --git a/app/kubernetes/converters/persistentVolumeClaim.js b/app/kubernetes/converters/persistentVolumeClaim.js index 8a7a76060..60ea28c58 100644 --- a/app/kubernetes/converters/persistentVolumeClaim.js +++ b/app/kubernetes/converters/persistentVolumeClaim.js @@ -28,16 +28,28 @@ class KubernetesPersistentVolumeClaimConverter { _.remove(formValues.PersistedFolders, (item) => item.NeedsDeletion); const res = _.map(formValues.PersistedFolders, (item) => { const pvc = new KubernetesPersistentVolumeClaim(); - if (item.PersistentVolumeClaimName) { - pvc.Name = item.PersistentVolumeClaimName; - pvc.PreviousName = item.PersistentVolumeClaimName; + if (!_.isEmpty(item.ExistingVolume)) { + const existantPVC = item.ExistingVolume.PersistentVolumeClaim; + pvc.Name = existantPVC.Name; + if (item.PersistentVolumeClaimName) { + pvc.PreviousName = item.PersistentVolumeClaimName; + } + pvc.StorageClass = existantPVC.StorageClass; + pvc.Storage = existantPVC.Storage.charAt(0) + 'i'; + pvc.CreationDate = existantPVC.CreationDate; + pvc.Id = existantPVC.Id; } else { - pvc.Name = formValues.Name + '-' + pvc.Name; + if (item.PersistentVolumeClaimName) { + pvc.Name = item.PersistentVolumeClaimName; + pvc.PreviousName = item.PersistentVolumeClaimName; + } else { + pvc.Name = formValues.Name + '-' + pvc.Name; + } + pvc.Storage = '' + item.Size + item.SizeUnit.charAt(0) + 'i'; + pvc.StorageClass = item.StorageClass; } pvc.MountPath = item.ContainerPath; pvc.Namespace = formValues.ResourcePool.Namespace.Name; - pvc.Storage = '' + item.Size + item.SizeUnit.charAt(0) + 'i'; - pvc.StorageClass = item.StorageClass; pvc.ApplicationOwner = formValues.ApplicationOwner; pvc.ApplicationName = formValues.Name; return pvc; diff --git a/app/kubernetes/converters/storageClass.js b/app/kubernetes/converters/storageClass.js index 773c96862..7491c32e7 100644 --- a/app/kubernetes/converters/storageClass.js +++ b/app/kubernetes/converters/storageClass.js @@ -1,4 +1,6 @@ import { KubernetesStorageClass } from 'Kubernetes/models/storage-class/models'; +import { KubernetesStorageClassCreatePayload } from 'Kubernetes/models/storage-class/payload'; +import * as JsonPatch from 'fast-json-patch'; class KubernetesStorageClassConverter { /** @@ -8,8 +10,24 @@ class KubernetesStorageClassConverter { const res = new KubernetesStorageClass(); res.Name = data.metadata.name; res.Provisioner = data.provisioner; + res.AllowVolumeExpansion = data.allowVolumeExpansion; return res; } + + static createPayload(storageClass) { + const res = new KubernetesStorageClassCreatePayload(); + res.metadata.name = storageClass.Name; + res.provisioner = storageClass.Provisioner; + res.allowVolumeExpansion = storageClass.AllowVolumeExpansion; + return res; + } + + static patchPayload(oldStorageClass, newStorageClass) { + const oldPayload = KubernetesStorageClassConverter.createPayload(oldStorageClass); + const newPayload = KubernetesStorageClassConverter.createPayload(newStorageClass); + const payload = JsonPatch.compare(oldPayload, newPayload); + return payload; + } } export default KubernetesStorageClassConverter; diff --git a/app/kubernetes/models/application/formValues.js b/app/kubernetes/models/application/formValues.js index 49d3cc827..7ca9ca1b9 100644 --- a/app/kubernetes/models/application/formValues.js +++ b/app/kubernetes/models/application/formValues.js @@ -92,6 +92,8 @@ const _KubernetesApplicationPersistedFolderFormValue = Object.freeze({ Size: '', SizeUnit: 'GB', StorageClass: {}, + ExistingVolume: null, + UseNewVolume: true, }); export class KubernetesApplicationPersistedFolderFormValue { diff --git a/app/kubernetes/models/storage-class/models.js b/app/kubernetes/models/storage-class/models.js index 153976f64..beb5bb3d5 100644 --- a/app/kubernetes/models/storage-class/models.js +++ b/app/kubernetes/models/storage-class/models.js @@ -25,6 +25,7 @@ const _KubernetesStorageClass = Object.freeze({ Name: '', AccessModes: [], Provisioner: '', + AllowVolumeExpansion: false, }); export class KubernetesStorageClass { diff --git a/app/kubernetes/models/storage-class/payload.js b/app/kubernetes/models/storage-class/payload.js new file mode 100644 index 000000000..47e183669 --- /dev/null +++ b/app/kubernetes/models/storage-class/payload.js @@ -0,0 +1,16 @@ +import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloads'; + +/** + * KubernetesStorageClassCreatePayload Model + */ +const _KubernetesStorageClassCreatePayload = Object.freeze({ + metadata: new KubernetesCommonMetadataPayload(), + provisioner: '', + allowVolumeExpansion: false, +}); + +export class KubernetesStorageClassCreatePayload { + constructor() { + Object.assign(this, JSON.parse(JSON.stringify(_KubernetesStorageClassCreatePayload))); + } +} diff --git a/app/kubernetes/rest/storage.js b/app/kubernetes/rest/storage.js index 44e6406bf..d3ef80d68 100644 --- a/app/kubernetes/rest/storage.js +++ b/app/kubernetes/rest/storage.js @@ -29,6 +29,12 @@ angular.module('portainer.kubernetes').factory('KubernetesStorage', [ }, create: { method: 'POST' }, update: { method: 'PUT' }, + patch: { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }, delete: { method: 'DELETE' }, } ); diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js index e3c549de8..614267068 100644 --- a/app/kubernetes/services/applicationService.js +++ b/app/kubernetes/services/applicationService.js @@ -209,7 +209,7 @@ class KubernetesApplicationService { app.ServiceName = headlessService.metadata.name; } else { const claimPromises = _.map(claims, (item) => { - if (!item.PreviousName) { + if (!item.PreviousName && !item.Id) { return this.KubernetesPersistentVolumeClaimService.create(item); } }); @@ -255,11 +255,12 @@ class KubernetesApplicationService { await this.KubernetesServiceService.patch(oldHeadlessService, newHeadlessService); } else { const claimPromises = _.map(newClaims, (newClaim) => { - if (!newClaim.PreviousName) { + if (!newClaim.PreviousName && !newClaim.Id) { return this.KubernetesPersistentVolumeClaimService.create(newClaim); + } else if (!newClaim.Id) { + const oldClaim = _.find(oldClaims, { Name: newClaim.PreviousName }); + return this.KubernetesPersistentVolumeClaimService.patch(oldClaim, newClaim); } - const oldClaim = _.find(oldClaims, { Name: newClaim.PreviousName }); - return this.KubernetesPersistentVolumeClaimService.patch(oldClaim, newClaim); }); await Promise.all(claimPromises); } diff --git a/app/kubernetes/services/storageService.js b/app/kubernetes/services/storageService.js index ea2a5f053..550a8f5c9 100644 --- a/app/kubernetes/services/storageService.js +++ b/app/kubernetes/services/storageService.js @@ -2,6 +2,7 @@ import angular from 'angular'; import _ from 'lodash-es'; import PortainerError from 'Portainer/error'; import KubernetesStorageClassConverter from 'Kubernetes/converters/storageClass'; +import { KubernetesCommonParams } from 'Kubernetes/models/common/params'; class KubernetesStorageService { /* @ngInject */ @@ -10,6 +11,7 @@ class KubernetesStorageService { this.KubernetesStorage = KubernetesStorage; this.getAsync = this.getAsync.bind(this); + this.patchAsync = this.patchAsync.bind(this); } /** @@ -31,6 +33,24 @@ class KubernetesStorageService { get(endpointId) { return this.$async(this.getAsync, endpointId); } + + /** + * PATCH + */ + async patchAsync(oldStorageClass, newStorageClass) { + try { + const params = new KubernetesCommonParams(); + params.id = newStorageClass.Name; + const payload = KubernetesStorageClassConverter.patchPayload(oldStorageClass, newStorageClass); + await this.KubernetesStorage().patch(params, payload).$promise; + } catch (err) { + throw new PortainerError('Unable to patch storage class', err); + } + } + + patch(oldStorageClass, newStorageClass) { + return this.$async(this.patchAsync, oldStorageClass, newStorageClass); + } } export default KubernetesStorageService; diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index 4dfaf0d00..57315ce2f 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -319,8 +319,8 @@ -
Size is required.
This value must be greater than zero.
Volume is required.
+This volume is already used.
+All the instances of this application will use the same data