1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-22 23:09:41 +02:00

feat(ingress): autodetect ingress controllers EE-673 (#7712)

This commit is contained in:
Dakota Walsh 2022-09-27 08:43:24 +13:00 committed by GitHub
parent c96551e410
commit 89eda13eb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1252 additions and 1047 deletions

View file

@ -2,12 +2,12 @@ import _ from 'lodash-es';
import angular from 'angular';
import { KubernetesStorageClass, KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
import { KubernetesIngressClass } from 'Kubernetes/ingress/models';
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
import { FeatureId } from '@/portainer/feature-flags/enums';
import { getIngressControllerClassMap, updateIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/utils';
class KubernetesConfigureController {
/* #region CONSTRUCTOR */
@ -41,10 +41,15 @@ class KubernetesConfigureController {
this.onInit = this.onInit.bind(this);
this.configureAsync = this.configureAsync.bind(this);
this.areControllersChanged = this.areControllersChanged.bind(this);
this.areFormValuesChanged = this.areFormValuesChanged.bind(this);
this.onBeforeOnload = this.onBeforeOnload.bind(this);
this.limitedFeature = FeatureId.K8S_SETUP_DEFAULT;
this.limitedFeatureAutoWindow = FeatureId.HIDE_AUTO_UPDATE_WINDOW;
this.onToggleAutoUpdate = this.onToggleAutoUpdate.bind(this);
this.onChangeAvailability = this.onChangeAvailability.bind(this);
this.onChangeEnableResourceOverCommit = this.onChangeEnableResourceOverCommit.bind(this);
this.onToggleIngressAvailabilityPerNamespace = this.onToggleIngressAvailabilityPerNamespace.bind(this);
this.onChangeStorageClassAccessMode = this.onChangeStorageClassAccessMode.bind(this);
}
/* #endregion */
@ -66,48 +71,23 @@ class KubernetesConfigureController {
/* #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.hasRefs = 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();
onChangeAvailability(controllerClassMap) {
this.ingressControllers = controllerClassMap;
}
hasTraefikIngress() {
return _.find(this.formValues.IngressClasses, { Type: this.IngressClassTypes.TRAEFIK });
}
onToggleIngressAvailabilityPerNamespace() {
this.$scope.$evalAsync(() => {
this.formValues.IngressAvailabilityPerNamespace = !this.formValues.IngressAvailabilityPerNamespace;
});
}
/* #endregion */
/* #region RESOURCES AND METRICS */
onChangeEnableResourceOverCommit(enabled) {
this.$scope.$evalAsync(() => {
this.formValues.EnableResourceOverCommit = enabled;
@ -117,13 +97,19 @@ class KubernetesConfigureController {
});
}
/* #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.EnableResourceOverCommit = this.formValues.EnableResourceOverCommit;
endpoint.Kubernetes.Configuration.ResourceOverCommitPercentage = this.formValues.ResourceOverCommitPercentage;
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace;
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = this.formValues.IngressAvailabilityPerNamespace;
endpoint.ChangeWindow = this.state.autoUpdateSettings;
}
transformFormValues() {
@ -150,11 +136,9 @@ class KubernetesConfigureController {
async removeIngressesAcrossNamespaces() {
const ingressesToDel = _.filter(this.formValues.IngressClasses, { NeedsDeletion: true });
if (!ingressesToDel.length) {
return;
}
const promises = [];
const oldEndpointID = this.EndpointProvider.endpointID();
this.EndpointProvider.setEndpointID(this.endpoint.Id);
@ -213,7 +197,9 @@ class KubernetesConfigureController {
this.assignFormValuesToEndpoint(this.endpoint, storageClasses, ingressClasses);
await this.EndpointService.updateEndpoint(this.endpoint.Id, this.endpoint);
// updateIngressControllerClassMap must be done after updateEndpoint, as a hacky workaround. A better solution: saving ingresscontrollers somewhere else, is being discussed
await updateIngressControllerClassMap(this.state.endpointId, this.ingressControllers);
this.state.isSaving = true;
const storagePromises = _.map(storageClasses, (storageClass) => {
const oldStorageClass = _.find(this.oldStorageClasses, { Name: storageClass.Name });
if (oldStorageClass) {
@ -291,19 +277,31 @@ class KubernetesConfigureController {
isServerRunning: false,
userClick: false,
},
timeZone: '',
isSaving: false,
};
this.formValues = {
UseLoadBalancer: false,
UseServerMetrics: false,
EnableResourceOverCommit: true,
ResourceOverCommitPercentage: 20,
IngressClasses: [],
RestrictDefaultNamespace: false,
enableAutoUpdateTimeWindow: false,
IngressAvailabilityPerNamespace: false,
};
try {
this.availableAccessModes = new KubernetesStorageClassAccessPolicies();
[this.StorageClasses, this.endpoint] = await Promise.all([this.KubernetesStorageService.get(this.state.endpointId), this.EndpointService.endpoint(this.state.endpointId)]);
this.ingressControllers = await getIngressControllerClassMap({ environmentId: this.state.endpointId });
this.originalIngressControllers = structuredClone(this.ingressControllers);
this.state.autoUpdateSettings = this.endpoint.ChangeWindow;
_.forEach(this.StorageClasses, (item) => {
const storage = _.find(this.endpoint.Kubernetes.Configuration.StorageClasses, (sc) => sc.Name === item.Name);
if (storage) {
@ -316,12 +314,15 @@ class KubernetesConfigureController {
this.formValues.UseLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
this.formValues.UseServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics;
this.formValues.EnableResourceOverCommit = this.endpoint.Kubernetes.Configuration.EnableResourceOverCommit;
this.formValues.ResourceOverCommitPercentage = this.endpoint.Kubernetes.Configuration.ResourceOverCommitPercentage;
this.formValues.RestrictDefaultNamespace = this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace;
this.formValues.IngressClasses = _.map(this.endpoint.Kubernetes.Configuration.IngressClasses, (ic) => {
ic.IsNew = false;
ic.NeedsDeletion = false;
return ic;
});
this.formValues.IngressAvailabilityPerNamespace = this.endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace;
this.oldFormValues = Object.assign({}, this.formValues);
} catch (err) {
@ -329,12 +330,48 @@ class KubernetesConfigureController {
} finally {
this.state.viewReady = true;
}
window.addEventListener('beforeunload', this.onBeforeOnload);
}
$onInit() {
return this.$async(this.onInit);
}
/* #endregion */
$onDestroy() {
window.removeEventListener('beforeunload', this.onBeforeOnload);
}
areControllersChanged() {
return !_.isEqual(this.ingressControllers, this.originalIngressControllers);
}
areFormValuesChanged() {
return !_.isEqual(this.formValues, this.oldFormValues);
}
onBeforeOnload(event) {
if (!this.state.isSaving && (this.areControllersChanged() || this.areFormValuesChanged())) {
event.preventDefault();
event.returnValue = '';
}
}
uiCanExit() {
if (!this.state.isSaving && (this.areControllersChanged() || this.areFormValuesChanged())) {
return this.ModalService.confirmAsync({
title: 'Are you sure?',
message: 'You currently have unsaved changes in the cluster setup view. Are you sure you want to leave?',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-danger',
},
},
});
}
}
}
export default KubernetesConfigureController;