mirror of
https://github.com/portainer/portainer.git
synced 2025-07-22 06:49:40 +02:00
feat(k8s/ingresses): add more granularity to ingress configuration (#4220)
* feat(k8s/configure): separate ingress class name and ingress class type * feat(k8s/resource-pool): ability to add custom annotations to ingress classes on RP create/edit * feat(k8s/ingresses): remove 'allow users to use ingress' switch * feat(k8s/configure): minor UI update * feat(k8s/resource-pool): minor UI update * feat(k8s/application): update ingress route form validation * refactor(k8s/resource-pool): remove console.log statement * feat(k8s/resource-pool): update ingress annotation placeholders * feat(k8s/configure): add pattern form validation on ingress class * fix(k8s/resource-pool): automatically associate ingress class to ingress * fix(k8s/resource-pool): fix invalid ingress when updating a resource pool * fix(k8s/resource-pool): update ingress rewrite target annotation value * feat(k8s/application): ingress form validation * fix(k8s/application): squash ingress rules with empty host inside a single one * feat(k8s/resource-pool): ingress host validation * fix(k8s/resource-pool): rewrite rewrite option and only display it for ingress of type nginx * feat(k8s/application): do not expose ingress applications over node port * feat(k8s/application): add specific notice for ingress Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
parent
68851aada4
commit
d850e18ff0
21 changed files with 699 additions and 220 deletions
|
@ -1,10 +1,15 @@
|
|||
import _ from 'lodash-es';
|
||||
import * as _ from 'lodash-es';
|
||||
import angular from 'angular';
|
||||
import { KubernetesStorageClassAccessPolicies, KubernetesStorageClass } from 'Kubernetes/models/storage-class/models';
|
||||
import { KubernetesStorageClass, KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||
import { KubernetesFormValueDuplicate } from 'Kubernetes/models/application/formValues';
|
||||
import { KubernetesIngressClass } from 'Kubernetes/ingress/models';
|
||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
|
||||
class KubernetesConfigureController {
|
||||
/* #region CONSTRUCTOR */
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $stateParams, Notifications, KubernetesStorageService, EndpointService, EndpointProvider) {
|
||||
constructor($async, $state, $stateParams, Notifications, KubernetesStorageService, EndpointService, EndpointProvider, ModalService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$stateParams = $stateParams;
|
||||
|
@ -12,11 +17,16 @@ class KubernetesConfigureController {
|
|||
this.KubernetesStorageService = KubernetesStorageService;
|
||||
this.EndpointService = EndpointService;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.IngressClassTypes = KubernetesIngressClassTypes;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.configureAsync = this.configureAsync.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region STORAGE CLASSES UI MANAGEMENT */
|
||||
storageClassAvailable() {
|
||||
return this.StorageClasses && this.StorageClasses.length > 0;
|
||||
}
|
||||
|
@ -28,55 +38,99 @@ class KubernetesConfigureController {
|
|||
valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return valid;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region INGRESS CLASSES UI MANAGEMENT */
|
||||
addIngressClass() {
|
||||
this.formValues.IngressClasses.push(new KubernetesIngressClass());
|
||||
this.onChangeIngressClass();
|
||||
}
|
||||
|
||||
restoreIngressClass(index) {
|
||||
this.formValues.IngressClasses[index].NeedsDeletion = false;
|
||||
this.onChangeIngressClass();
|
||||
}
|
||||
|
||||
removeIngressClass(index) {
|
||||
if (!this.formValues.IngressClasses[index].IsNew) {
|
||||
this.formValues.IngressClasses[index].NeedsDeletion = true;
|
||||
} else {
|
||||
this.formValues.IngressClasses.splice(index, 1);
|
||||
}
|
||||
this.onChangeIngressClass();
|
||||
}
|
||||
|
||||
onChangeIngressClass() {
|
||||
const state = this.state.duplicates.ingressClasses;
|
||||
const source = _.map(this.formValues.IngressClasses, (ic) => (ic.NeedsDeletion ? undefined : ic.Name));
|
||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||
state.refs = duplicates;
|
||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
||||
}
|
||||
|
||||
onChangeIngressClassName(index) {
|
||||
const fv = this.formValues.IngressClasses[index];
|
||||
if (_.includes(fv.Name, KubernetesIngressClassTypes.NGINX)) {
|
||||
fv.Type = KubernetesIngressClassTypes.NGINX;
|
||||
} else if (_.includes(fv.Name, KubernetesIngressClassTypes.TRAEFIK)) {
|
||||
fv.Type = KubernetesIngressClassTypes.TRAEFIK;
|
||||
}
|
||||
this.onChangeIngressClass();
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region CONFIGURE */
|
||||
assignFormValuesToEndpoint(endpoint, storageClasses, ingressClasses) {
|
||||
endpoint.Kubernetes.Configuration.StorageClasses = storageClasses;
|
||||
endpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
||||
endpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
|
||||
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
|
||||
}
|
||||
|
||||
transformFormValues() {
|
||||
const storageClasses = _.map(this.StorageClasses, (item) => {
|
||||
if (item.selected) {
|
||||
const res = new KubernetesStorageClass();
|
||||
res.Name = item.Name;
|
||||
res.AccessModes = _.map(item.AccessModes, 'Name');
|
||||
res.Provisioner = item.Provisioner;
|
||||
res.AllowVolumeExpansion = item.AllowVolumeExpansion;
|
||||
return res;
|
||||
}
|
||||
});
|
||||
_.pull(storageClasses, undefined);
|
||||
|
||||
const ingressClasses = _.without(
|
||||
_.map(this.formValues.IngressClasses, (ic) => (ic.NeedsDeletion ? undefined : ic)),
|
||||
undefined
|
||||
);
|
||||
_.pull(ingressClasses, undefined);
|
||||
|
||||
return [storageClasses, ingressClasses];
|
||||
}
|
||||
|
||||
async configureAsync() {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
const classes = _.without(
|
||||
_.map(this.StorageClasses, (item) => {
|
||||
if (item.selected) {
|
||||
const res = new KubernetesStorageClass();
|
||||
res.Name = item.Name;
|
||||
res.AccessModes = _.map(item.AccessModes, 'Name');
|
||||
res.Provisioner = item.Provisioner;
|
||||
res.AllowVolumeExpansion = item.AllowVolumeExpansion;
|
||||
return res;
|
||||
}
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
const [storageClasses, ingressClasses] = this.transformFormValues();
|
||||
|
||||
this.endpoint.Kubernetes.Configuration.StorageClasses = classes;
|
||||
this.endpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
||||
this.endpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
|
||||
this.endpoint.Kubernetes.Configuration.UseIngress = this.formValues.UseIngress;
|
||||
if (this.formValues.UseIngress) {
|
||||
this.endpoint.Kubernetes.Configuration.IngressClasses = _.split(this.formValues.IngressClasses, ',');
|
||||
}
|
||||
this.assignFormValuesToEndpoint(this.endpoint, storageClasses, ingressClasses);
|
||||
await this.EndpointService.updateEndpoint(this.endpoint.Id, this.endpoint);
|
||||
|
||||
const storagePromises = _.map(classes, (storageClass) => {
|
||||
const storagePromises = _.map(storageClasses, (storageClass) => {
|
||||
const oldStorageClass = _.find(this.oldStorageClasses, { Name: storageClass.Name });
|
||||
if (oldStorageClass) {
|
||||
return this.KubernetesStorageService.patch(this.state.endpointId, oldStorageClass, storageClass);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(storagePromises);
|
||||
|
||||
const endpoints = this.EndpointProvider.endpoints();
|
||||
const modifiedEndpoint = _.find(endpoints, (item) => item.Id === this.endpoint.Id);
|
||||
if (modifiedEndpoint) {
|
||||
modifiedEndpoint.Kubernetes.Configuration.StorageClasses = classes;
|
||||
modifiedEndpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
||||
modifiedEndpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
|
||||
modifiedEndpoint.Kubernetes.Configuration.UseIngress = this.formValues.UseIngress;
|
||||
if (this.formValues.UseIngress) {
|
||||
modifiedEndpoint.Kubernetes.Configuration.IngressClasses = _.split(this.formValues.IngressClasses, ',');
|
||||
}
|
||||
this.assignFormValuesToEndpoint(modifiedEndpoint, storageClasses, ingressClasses);
|
||||
this.EndpointProvider.setEndpoints(endpoints);
|
||||
}
|
||||
this.Notifications.success('Configuration successfully applied');
|
||||
|
@ -89,22 +143,38 @@ class KubernetesConfigureController {
|
|||
}
|
||||
|
||||
configure() {
|
||||
return this.$async(this.configureAsync);
|
||||
const toDel = _.filter(this.formValues.IngressClasses, { NeedsDeletion: true });
|
||||
if (toDel.length) {
|
||||
this.ModalService.confirmUpdate(
|
||||
`Removing ingress controllers will make them unavailable for future use.<br/>Existing resources linked to these ingress controllers will continue to live in cluster but you will not be able to remove them from Portainer.<br/><br/>Do you wish to continue?`,
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.configureAsync);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return this.$async(this.configureAsync);
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region ON INIT */
|
||||
async onInit() {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
displayConfigureClassPanel: {},
|
||||
viewReady: false,
|
||||
endpointId: this.$stateParams.id,
|
||||
duplicates: {
|
||||
ingressClasses: new KubernetesFormValueDuplicate(),
|
||||
},
|
||||
};
|
||||
|
||||
this.formValues = {
|
||||
UseLoadBalancer: false,
|
||||
UseServerMetrics: false,
|
||||
UseIngress: false,
|
||||
IngressClasses: '',
|
||||
IngressClasses: [],
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -127,8 +197,11 @@ class KubernetesConfigureController {
|
|||
|
||||
this.formValues.UseLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
||||
this.formValues.UseServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics;
|
||||
this.formValues.UseIngress = this.endpoint.Kubernetes.Configuration.UseIngress;
|
||||
this.formValues.IngressClasses = _.join(this.endpoint.Kubernetes.Configuration.IngressClasses);
|
||||
this.formValues.IngressClasses = _.map(this.endpoint.Kubernetes.Configuration.IngressClasses, (ic) => {
|
||||
ic.IsNew = false;
|
||||
ic.NeedsDeletion = false;
|
||||
return ic;
|
||||
});
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve endpoint configuration');
|
||||
} finally {
|
||||
|
@ -139,6 +212,7 @@ class KubernetesConfigureController {
|
|||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
/* #endregion */
|
||||
}
|
||||
|
||||
export default KubernetesConfigureController;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue