mirror of
https://github.com/portainer/portainer.git
synced 2025-07-21 06:19:41 +02:00
feat(k8s/applications): expose applications via ingress (#4136)
* feat(k8s/endpoint): expose ingress controllers on endpoints * feat(k8s/applications): add ability to expose applications over ingress - missing RP and app edits * feat(k8s/application): add validation for ingress routes * feat(k8s/resource-pools): edit available ingress classes * fix(k8s/ingress): var name refactor was partially applied * feat(kubernetes): double validation on RP edit * feat(k8s/application): app edit ingress update + formvalidation + UI rework * feat(k8s/ingress): dictionary for default annotations on ingress creation * fix(k8s/application): temporary fix + TODO dev notice * feat(k8s/application): select default ingress of selected resource pool * feat(k8s/ingress): revert ingressClassName removal * feat(k8s/ingress): admins can now add an host to ingress in a resource pool * feat(k8s/resource-pool): list applications using RP ingresses * feat(k8s/configure): minor UI update * feat(k8s/configure): minor UI update * feat(k8s/configure): minor UI update * feat(k8s/configure): minor UI update * feat(k8s/configure): minor UI update * fix(k8s/ingresses): remove host if undefined * feat(k8s/resource-pool): remove the activate ingresses switch * fix(k8s/resource-pool): edditing an ingress host was deleting all the routes of the ingress * feat(k8s/application): prevent app deploy if no ports to publish and publishing type not internal * feat(k8s/ingress): minor UI update * fix(k8s/ingress): allow routes without prepending / * feat(k8s/application): add form validation on ingress route Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
parent
201c3ac143
commit
f91d3f1ca3
31 changed files with 1595 additions and 443 deletions
|
@ -1,8 +1,8 @@
|
|||
import _ from 'lodash-es';
|
||||
import * as _ from 'lodash-es';
|
||||
import angular from 'angular';
|
||||
import PortainerError from 'Portainer/error';
|
||||
|
||||
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
import { KubernetesApplicationTypes, KubernetesApplicationPublishingTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesApplicationRollbackHelper from 'Kubernetes/helpers/application/rollback';
|
||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||
|
@ -13,8 +13,10 @@ import { KubernetesApplication } from 'Kubernetes/models/application/models';
|
|||
import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
||||
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
||||
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||
|
||||
class KubernetesApplicationService {
|
||||
/* #region CONSTRUCTOR */
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
|
@ -53,10 +55,9 @@ class KubernetesApplicationService {
|
|||
this.rollbackAsync = this.rollbackAsync.bind(this);
|
||||
this.deleteAsync = this.deleteAsync.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/**
|
||||
* UTILS
|
||||
*/
|
||||
/* #region UTILS */
|
||||
_getApplicationApiService(app) {
|
||||
let apiService;
|
||||
if (app instanceof KubernetesDeployment || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DEPLOYMENT)) {
|
||||
|
@ -71,9 +72,15 @@ class KubernetesApplicationService {
|
|||
return apiService;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET
|
||||
*/
|
||||
_generateIngressPatchPromises(oldIngresses, newIngresses) {
|
||||
return _.map(newIngresses, (newIng) => {
|
||||
const oldIng = _.find(oldIngresses, { Name: newIng.Name });
|
||||
return this.KubernetesIngressService.patch(oldIng, newIng);
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET */
|
||||
async getAsync(namespace, name) {
|
||||
try {
|
||||
const [deployment, daemonSet, statefulSet, pods, autoScalers, ingresses] = await Promise.allSettled([
|
||||
|
@ -121,7 +128,7 @@ class KubernetesApplicationService {
|
|||
if (scaler && scaler.Yaml) {
|
||||
application.Yaml += '---\n' + scaler.Yaml;
|
||||
}
|
||||
// TODO: refactor
|
||||
// TODO: refactor @LP
|
||||
// append ingress yaml ?
|
||||
return application;
|
||||
} catch (err) {
|
||||
|
@ -185,10 +192,9 @@ class KubernetesApplicationService {
|
|||
}
|
||||
return this.$async(this.getAllAsync, namespace);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/**
|
||||
* CREATE
|
||||
*/
|
||||
/* #region CREATE */
|
||||
// TODO: review
|
||||
// resource creation flow
|
||||
// should we keep formValues > Resource_1 || Resource_2
|
||||
|
@ -199,6 +205,10 @@ class KubernetesApplicationService {
|
|||
|
||||
if (service) {
|
||||
await this.KubernetesServiceService.create(service);
|
||||
if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
|
||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
|
||||
const apiService = this._getApplicationApiService(app);
|
||||
|
@ -231,10 +241,9 @@ class KubernetesApplicationService {
|
|||
create(formValues) {
|
||||
return this.$async(this.createAsync, formValues);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/**
|
||||
* PATCH
|
||||
*/
|
||||
/* #region PATCH */
|
||||
// this function accepts KubernetesApplicationFormValues as parameters
|
||||
async patchAsync(oldFormValues, newFormValues) {
|
||||
try {
|
||||
|
@ -269,10 +278,23 @@ class KubernetesApplicationService {
|
|||
|
||||
if (oldService && newService) {
|
||||
await this.KubernetesServiceService.patch(oldService, newService);
|
||||
if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
|
||||
const newIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
|
||||
await Promise.all(this._generateIngressPatchPromises(oldIngresses, newIngresses));
|
||||
}
|
||||
} else if (!oldService && newService) {
|
||||
await this.KubernetesServiceService.create(newService);
|
||||
if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
|
||||
await Promise.all(this._generateIngressPatchPromises(newFormValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
} else if (oldService && !newService) {
|
||||
await this.KubernetesServiceService.delete(oldService);
|
||||
if (oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, oldService.Name);
|
||||
await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
|
||||
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
||||
|
@ -327,10 +349,9 @@ class KubernetesApplicationService {
|
|||
}
|
||||
return this.$async(this.patchAsync, oldValues, newValues);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/**
|
||||
* DELETE
|
||||
*/
|
||||
/* #region DELETE */
|
||||
async deleteAsync(application) {
|
||||
try {
|
||||
const payload = {
|
||||
|
@ -351,8 +372,18 @@ class KubernetesApplicationService {
|
|||
|
||||
if (application.ServiceType) {
|
||||
await this.KubernetesServiceService.delete(servicePayload);
|
||||
const isIngress = _.filter(application.PublishedPorts, (p) => p.IngressRules.length).length;
|
||||
if (isIngress) {
|
||||
const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
|
||||
const formValues = {
|
||||
OriginalIngresses: originalIngresses,
|
||||
PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
|
||||
};
|
||||
_.forEach(formValues.PublishedPorts, (p) => (p.NeedsDeletion = true));
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, servicePayload.Name);
|
||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isEmpty(application.AutoScaler)) {
|
||||
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
||||
}
|
||||
|
@ -364,10 +395,9 @@ class KubernetesApplicationService {
|
|||
delete(application) {
|
||||
return this.$async(this.deleteAsync, application);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/**
|
||||
* ROLLBACK
|
||||
*/
|
||||
/* #region ROLLBACK */
|
||||
async rollbackAsync(application, targetRevision) {
|
||||
try {
|
||||
const payload = KubernetesApplicationRollbackHelper.getPatchPayload(application, targetRevision);
|
||||
|
@ -381,6 +411,7 @@ class KubernetesApplicationService {
|
|||
rollback(application, targetRevision) {
|
||||
return this.$async(this.rollbackAsync, application, targetRevision);
|
||||
}
|
||||
/* #endregion */
|
||||
}
|
||||
|
||||
export default KubernetesApplicationService;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue