mirror of https://github.com/portainer/portainer
feat(ingress): remove ingresses from add and edit application EE-4206 (#7677)
parent
c384d834f5
commit
fcb76f570e
|
@ -93,8 +93,8 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
|
||||||
// Map all ingress rules in published ports to their respective URLs
|
// Map all ingress rules in published ports to their respective URLs
|
||||||
const ingressUrls = item.PublishedPorts.flatMap((pp) => pp.IngressRules)
|
const ingressUrls = item.PublishedPorts.flatMap((pp) => pp.IngressRules)
|
||||||
.filter(({ Host, IP }) => Host || IP)
|
.filter(({ Host, IP }) => Host || IP)
|
||||||
.map(({ Host, IP, Port, Path }) => {
|
.map(({ Host, IP, Path, TLS }) => {
|
||||||
let scheme = Port === 443 ? 'https' : 'http';
|
let scheme = TLS && TLS.filter((tls) => tls.hosts && tls.hosts.includes(Host)).length > 0 ? 'https' : 'http';
|
||||||
return `${scheme}://${Host || IP}${Path}`;
|
return `${scheme}://${Host || IP}${Path}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,7 @@ export default class KubeServicesViewController {
|
||||||
|
|
||||||
addEntry(service) {
|
addEntry(service) {
|
||||||
const p = new KubernetesService();
|
const p = new KubernetesService();
|
||||||
if (service === KubernetesApplicationPublishingTypes.INGRESS) {
|
p.Type = service;
|
||||||
p.Type = KubernetesApplicationPublishingTypes.CLUSTER_IP;
|
|
||||||
p.Ingress = true;
|
|
||||||
} else {
|
|
||||||
p.Type = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Selector = this.formValues.Selector;
|
p.Selector = this.formValues.Selector;
|
||||||
|
|
||||||
|
@ -62,8 +57,6 @@ export default class KubeServicesViewController {
|
||||||
return KubernetesServiceTypes.NODE_PORT;
|
return KubernetesServiceTypes.NODE_PORT;
|
||||||
case KubernetesApplicationPublishingTypes.LOAD_BALANCER:
|
case KubernetesApplicationPublishingTypes.LOAD_BALANCER:
|
||||||
return KubernetesServiceTypes.LOAD_BALANCER;
|
return KubernetesServiceTypes.LOAD_BALANCER;
|
||||||
case KubernetesApplicationPublishingTypes.INGRESS:
|
|
||||||
return KubernetesServiceTypes.INGRESS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +72,6 @@ export default class KubeServicesViewController {
|
||||||
return 'fa fa-list';
|
return 'fa fa-list';
|
||||||
case KubernetesApplicationPublishingTypes.LOAD_BALANCER:
|
case KubernetesApplicationPublishingTypes.LOAD_BALANCER:
|
||||||
return 'fa fa-project-diagram';
|
return 'fa fa-project-diagram';
|
||||||
case KubernetesApplicationPublishingTypes.INGRESS:
|
|
||||||
return 'fa fa-route';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$onInit() {
|
$onInit() {
|
||||||
|
@ -98,10 +89,6 @@ export default class KubeServicesViewController {
|
||||||
typeName: KubernetesServiceTypes.LOAD_BALANCER,
|
typeName: KubernetesServiceTypes.LOAD_BALANCER,
|
||||||
typeValue: KubernetesApplicationPublishingTypes.LOAD_BALANCER,
|
typeValue: KubernetesApplicationPublishingTypes.LOAD_BALANCER,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
typeName: KubernetesServiceTypes.INGRESS,
|
|
||||||
typeValue: KubernetesApplicationPublishingTypes.INGRESS,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
selected: KubernetesApplicationPublishingTypes.CLUSTER_IP,
|
selected: KubernetesApplicationPublishingTypes.CLUSTER_IP,
|
||||||
endpointId: this.EndpointProvider.endpointID(),
|
endpointId: this.EndpointProvider.endpointID(),
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<div class="col-sm-12 form-section-title"> Publishing the application </div>
|
<div class="col-sm-12 form-section-title"> Publishing the application </div>
|
||||||
|
<div class="col-sm-12 !p-0">
|
||||||
|
<div class="small">
|
||||||
|
<p class="text-muted vertical-center">
|
||||||
|
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
|
||||||
|
<span>Publish your application by creating a ClusterIP service for it, which you may then expose via <a target="_blank" ui-sref="kubernetes.ingresses">an ingress</a>.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12 form-inline">
|
<div class="col-sm-12 form-inline">
|
||||||
<div class="col-sm-5" style="padding-left: 0px">
|
<div class="col-sm-5" style="padding-left: 0px">
|
||||||
|
@ -10,7 +17,18 @@
|
||||||
data-cy="k8sAppCreate-publishingModeDropdown"
|
data-cy="k8sAppCreate-publishingModeDropdown"
|
||||||
></select>
|
></select>
|
||||||
<button type="button" class="btn btn-md btn-default vertical-center !ml-0" ng-click="$ctrl.addEntry( $ctrl.state.selected )" data-cy="k8sAppCreate-createServiceButton">
|
<button type="button" class="btn btn-md btn-default vertical-center !ml-0" ng-click="$ctrl.addEntry( $ctrl.state.selected )" data-cy="k8sAppCreate-createServiceButton">
|
||||||
<pr-icon icon="'plus'" size="'sm'" feather="true"></pr-icon> Create service
|
<span
|
||||||
|
class="interactive vertical-center"
|
||||||
|
tooltip-append-to-body="true"
|
||||||
|
tooltip-placement="top"
|
||||||
|
tooltip-class="portainer-tooltip"
|
||||||
|
uib-tooltip="Different service types expose the deployment in alternate ways.
|
||||||
|
ClusterIP exposes it within the cluster (for internal access only).
|
||||||
|
NodePort exposes it (on a high port) across all nodes.
|
||||||
|
LoadBalancer exposes it via an external load balancer."
|
||||||
|
>
|
||||||
|
<pr-icon icon="'plus'" size="'sm'" feather="true"></pr-icon> Create service
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,30 +89,5 @@
|
||||||
<pr-icon icon="'trash-2'" size="'md'" feather="true"></pr-icon> Remove
|
<pr-icon icon="'trash-2'" size="'md'" feather="true"></pr-icon> Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="$ctrl.formValues.Services[$index].Ingress && $ctrl.formValues.OriginalIngresses.length !== 0">
|
|
||||||
<div class="text-muted">
|
|
||||||
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px"></i>
|
|
||||||
Ingress
|
|
||||||
</div>
|
|
||||||
<kube-services-item-view
|
|
||||||
original-ingresses="$ctrl.formValues.OriginalIngresses"
|
|
||||||
service-routes="$ctrl.formValues.Services[$index].IngressRoute"
|
|
||||||
ingress-type="$ctrl.formValues.Services[$index].Ingress"
|
|
||||||
service-type="$ctrl.formValues.Services[$index].Type"
|
|
||||||
service-ports="$ctrl.formValues.Services[$index].Ports"
|
|
||||||
service-name="$ctrl.formValues.Services[$index].Name"
|
|
||||||
multi-item-disable="true"
|
|
||||||
></kube-services-item-view>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-dangerlight space-right vertical-center"
|
|
||||||
style="margin-left: 0; margin-top: 10px"
|
|
||||||
ng-click="$ctrl.deleteService( $index )"
|
|
||||||
data-cy="k8sConfigCreate-removeButton"
|
|
||||||
>
|
|
||||||
<pr-icon icon="'trash-2'" size="'md'" feather="true"></pr-icon> Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class KubernetesIngressConverter {
|
||||||
ingRule.IP = data.status.loadBalancer.ingress ? data.status.loadBalancer.ingress[0].ip : undefined;
|
ingRule.IP = data.status.loadBalancer.ingress ? data.status.loadBalancer.ingress[0].ip : undefined;
|
||||||
ingRule.Port = path.backend.service.port.number;
|
ingRule.Port = path.backend.service.port.number;
|
||||||
ingRule.Path = path.path;
|
ingRule.Path = path.path;
|
||||||
|
ingRule.TLS = data.spec.tls;
|
||||||
return ingRule;
|
return ingRule;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,12 +2,7 @@ import _ from 'lodash-es';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import PortainerError from 'Portainer/error';
|
import PortainerError from 'Portainer/error';
|
||||||
|
|
||||||
import {
|
import { KubernetesApplication, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||||
KubernetesApplication,
|
|
||||||
KubernetesApplicationDeploymentTypes,
|
|
||||||
KubernetesApplicationPublishingTypes,
|
|
||||||
KubernetesApplicationTypes,
|
|
||||||
} from 'Kubernetes/models/application/models';
|
|
||||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||||
import KubernetesApplicationRollbackHelper from 'Kubernetes/helpers/application/rollback';
|
import KubernetesApplicationRollbackHelper from 'Kubernetes/helpers/application/rollback';
|
||||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||||
|
@ -17,7 +12,6 @@ import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
|
||||||
import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
||||||
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
||||||
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
|
||||||
import KubernetesPodConverter from 'Kubernetes/pod/converter';
|
import KubernetesPodConverter from 'Kubernetes/pod/converter';
|
||||||
|
|
||||||
class KubernetesApplicationService {
|
class KubernetesApplicationService {
|
||||||
|
@ -79,12 +73,6 @@ class KubernetesApplicationService {
|
||||||
return apiService;
|
return apiService;
|
||||||
}
|
}
|
||||||
|
|
||||||
_generateIngressPatchPromises(oldIngresses, newIngresses) {
|
|
||||||
return _.map(newIngresses, (newIng) => {
|
|
||||||
const oldIng = _.find(oldIngresses, { Name: newIng.Name });
|
|
||||||
return this.KubernetesIngressService.patch(oldIng, newIng);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region GET */
|
/* #region GET */
|
||||||
|
@ -221,26 +209,12 @@ class KubernetesApplicationService {
|
||||||
*/
|
*/
|
||||||
async createAsync(formValues) {
|
async createAsync(formValues) {
|
||||||
// formValues -> Application
|
// formValues -> Application
|
||||||
let [app, headlessService, services, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
|
let [app, headlessService, services, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
|
||||||
|
|
||||||
if (services) {
|
if (services) {
|
||||||
services.forEach(async (service) => {
|
services.forEach(async (service) => {
|
||||||
await this.KubernetesServiceService.create(service);
|
await this.KubernetesServiceService.create(service);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Generate all ingresses from current form by passing services object
|
|
||||||
const ingresses = KubernetesIngressConverter.generateNewIngresses(formValues.OriginalIngresses, services);
|
|
||||||
if (ingresses) {
|
|
||||||
//Update original ingress with current ingress
|
|
||||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (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);
|
const apiService = this._getApplicationApiService(app);
|
||||||
|
@ -322,20 +296,10 @@ class KubernetesApplicationService {
|
||||||
newServices.forEach(async (service) => {
|
newServices.forEach(async (service) => {
|
||||||
await this.KubernetesServiceService.create(service);
|
await this.KubernetesServiceService.create(service);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create multiple ingress
|
|
||||||
const ingresses = KubernetesIngressConverter.generateNewIngresses(oldFormValues.OriginalIngresses, newServices);
|
|
||||||
if (ingresses) {
|
|
||||||
await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, ingresses));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete services ( only called when all services been deleted )
|
// Delete services ( only called when all services been deleted )
|
||||||
if (oldServices.length !== 0 && newServices.length === 0) {
|
if (oldServices.length !== 0 && newServices.length === 0) {
|
||||||
const ingresses = KubernetesIngressConverter.removeIngressesPaths(oldFormValues.OriginalIngresses, oldServices);
|
|
||||||
if (ingresses) {
|
|
||||||
await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, ingresses));
|
|
||||||
}
|
|
||||||
await this.KubernetesServiceService.deleteAll(oldServices);
|
await this.KubernetesServiceService.deleteAll(oldServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,15 +320,6 @@ class KubernetesApplicationService {
|
||||||
await this.KubernetesServiceService.create(newService);
|
await this.KubernetesServiceService.create(newService);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear all ingress which is related to services in this application
|
|
||||||
const clearIngress = KubernetesIngressConverter.removeIngressesPaths(oldFormValues.OriginalIngresses, oldServices);
|
|
||||||
|
|
||||||
// Generate all ingress from services in this application
|
|
||||||
const newIngress = KubernetesIngressConverter.generateNewIngresses(clearIngress, newServices);
|
|
||||||
|
|
||||||
// Compare new ingress with old ingress to get api patch
|
|
||||||
await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, newIngress));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
||||||
|
@ -438,17 +393,6 @@ class KubernetesApplicationService {
|
||||||
if (application.ServiceType) {
|
if (application.ServiceType) {
|
||||||
// delete headless service && non-headless service
|
// delete headless service && non-headless service
|
||||||
await this.KubernetesServiceService.delete(application.Services);
|
await this.KubernetesServiceService.delete(application.Services);
|
||||||
|
|
||||||
if (application.Ingresses.length) {
|
|
||||||
const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
|
|
||||||
const formValues = {
|
|
||||||
OriginalIngresses: originalIngresses,
|
|
||||||
PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
|
|
||||||
};
|
|
||||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToDeleteIngresses(formValues, application);
|
|
||||||
|
|
||||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!_.isEmpty(application.AutoScaler)) {
|
if (!_.isEmpty(application.AutoScaler)) {
|
||||||
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default class KubernetesApplicationIngressController {
|
||||||
_.forEach(ingresses, (ingress) => {
|
_.forEach(ingresses, (ingress) => {
|
||||||
_.forEach(ingress.Paths, (path) => {
|
_.forEach(ingress.Paths, (path) => {
|
||||||
if (path.ServiceName === service.metadata.name) {
|
if (path.ServiceName === service.metadata.name) {
|
||||||
|
path.Secure = ingress.TLS && ingress.TLS.filter((tls) => tls.hosts && tls.hosts.includes(path.Host)).length > 0;
|
||||||
this.applicationIngress.push(path);
|
this.applicationIngress.push(path);
|
||||||
this.hasIngress = true;
|
this.hasIngress = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,15 @@
|
||||||
<td style="width: 15%">HTTP Route</td>
|
<td style="width: 15%">HTTP Route</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-repeat="ingress in $ctrl.applicationIngress">
|
<tr ng-repeat="ingress in $ctrl.applicationIngress">
|
||||||
<td>{{ ingress.IngressName }}</td>
|
<td
|
||||||
|
><a ui-sref="kubernetes.ingresses.edit({ name: ingress.IngressName, namespace: $ctrl.application.ResourcePool })">{{ ingress.IngressName }}</a></td
|
||||||
|
>
|
||||||
<td>{{ ingress.ServiceName }}</td>
|
<td>{{ ingress.ServiceName }}</td>
|
||||||
<td>{{ ingress.Host }}</td>
|
<td>{{ ingress.Host }}</td>
|
||||||
<td>{{ ingress.Port }}</td>
|
<td>{{ ingress.Port }}</td>
|
||||||
<td>{{ ingress.Path }}</td>
|
<td>{{ ingress.Path }}</td>
|
||||||
<td
|
<td
|
||||||
><a target="_blank" href="http://{{ ingress.Host }}{{ ingress.Path }}">{{ ingress.Host }}{{ ingress.Path }}</a></td
|
><a target="_blank" href="{{ ingress.Secure ? 'https' : 'http' }}://{{ ingress.Host }}{{ ingress.Path }}">{{ ingress.Host }}{{ ingress.Path }}</a></td
|
||||||
>
|
>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
Loading…
Reference in New Issue