1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

fix(ing): nodeport validate and show errors [EE-4373] (#7801)

This commit is contained in:
Ali 2022-10-12 10:06:57 +13:00 committed by GitHub
parent fd91de3571
commit 7a6ff10268
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 135 additions and 62 deletions

View file

@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { KubernetesServicePort, KubernetesIngressServiceRoute } from 'Kubernetes/models/service/models';
import { KubernetesServicePort } from 'Kubernetes/models/service/models';
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
import { KubernetesApplicationPublishingTypes } from 'Kubernetes/models/application/models/constants';
@ -18,34 +18,17 @@ export default class KubeServicesItemViewController {
port.port = '';
port.targetPort = '';
port.protocol = 'TCP';
if (this.ingressType) {
const route = new KubernetesIngressServiceRoute();
route.ServiceName = this.serviceName;
if (this.serviceType === KubernetesApplicationPublishingTypes.CLUSTER_IP && this.originalIngresses && this.originalIngresses.length > 0) {
if (!route.IngressName) {
route.IngressName = this.originalIngresses[0].Name;
}
if (!route.Host) {
route.Host = this.originalIngresses[0].Hosts[0];
}
}
port.ingress = route;
port.Ingress = true;
}
this.servicePorts.push(port);
this.service.Ports.push(port);
}
removePort(index) {
this.servicePorts.splice(index, 1);
this.service.Ports.splice(index, 1);
}
servicePort(index) {
const targetPort = this.servicePorts[index].targetPort;
this.servicePorts[index].port = targetPort;
const targetPort = this.service.Ports[index].targetPort;
this.service.Ports[index].port = targetPort;
this.onChangeServicePort();
}
isAdmin() {
@ -54,7 +37,7 @@ export default class KubeServicesItemViewController {
onChangeContainerPort() {
const state = this.state.duplicates.targetPort;
const source = _.map(this.servicePorts, (sp) => sp.targetPort);
const source = _.map(this.service.Ports, (sp) => sp.targetPort);
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
state.refs = duplicates;
state.hasRefs = Object.keys(duplicates).length > 0;
@ -62,22 +45,41 @@ export default class KubeServicesItemViewController {
onChangeServicePort() {
const state = this.state.duplicates.servicePort;
const source = _.map(this.servicePorts, (sp) => sp.port);
const source = _.map(this.service.Ports, (sp) => sp.port);
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
state.refs = duplicates;
state.hasRefs = Object.keys(duplicates).length > 0;
this.service.servicePortError = state.hasRefs;
}
onChangeNodePort() {
const state = this.state.duplicates.nodePort;
const source = _.map(this.servicePorts, (sp) => sp.nodePort);
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
// create a list of all the node ports (number[]) in the cluster and current form
const clusterNodePortsWithoutCurrentService = this.nodePortServices
.filter((npService) => npService.Name !== this.service.Name)
.map((npService) => npService.Ports)
.flat()
.map((npServicePorts) => npServicePorts.NodePort);
const formNodePortsWithoutCurrentService = this.formServices
.filter((formService) => formService.Type === KubernetesApplicationPublishingTypes.NODE_PORT && formService.Name !== this.service.Name)
.map((formService) => formService.Ports)
.flat()
.map((formServicePorts) => formServicePorts.nodePort);
const serviceNodePorts = this.service.Ports.map((sp) => sp.nodePort);
// getDuplicates cares about the index, so put the serviceNodePorts at the start
const allNodePortsWithoutCurrentService = [...clusterNodePortsWithoutCurrentService, ...formNodePortsWithoutCurrentService];
const duplicates = KubernetesFormValidationHelper.getDuplicateNodePorts(serviceNodePorts, allNodePortsWithoutCurrentService);
state.refs = duplicates;
state.hasRefs = Object.keys(duplicates).length > 0;
this.service.nodePortError = state.hasRefs;
}
$onInit() {
if (this.servicePorts.length === 0) {
if (this.service.Ports.length === 0) {
this.addPort();
}

View file

@ -1,11 +1,11 @@
<ng-form name="serviceForm">
<div ng-if="$ctrl.isAdmin()" class="small" ng-show="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled">
<div ng-if="$ctrl.isAdmin()" class="small" ng-show="$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled">
<p class="text-warning pt-2 vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> No Load balancer is available in this cluster, click
<a class="hyperlink" ui-sref="kubernetes.cluster.setup">here</a> to configure load balancer.
</p>
</div>
<div ng-if="!$ctrl.isAdmin()" class="small" ng-show="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled">
<div ng-if="!$ctrl.isAdmin()" class="small" ng-show="$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled">
<p class="text-warning pt-2 vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> No Load balancer is available in this cluster, contact your administrator.
</p>
@ -13,9 +13,9 @@
<div
ng-if="
($ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && $ctrl.loadbalancerEnabled) ||
$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.CLUSTER_IP ||
$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.NODE_PORT
($ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && $ctrl.loadbalancerEnabled) ||
$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.CLUSTER_IP ||
$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.NODE_PORT
"
>
<div ng-show="!$ctrl.multiItemDisable" class="mt-5 mb-5 vertical-center">
@ -24,7 +24,7 @@
<pr-icon icon="'plus'" mode="'alt'" size="'sm'" feather="true"></pr-icon> publish a new port
</span>
</div>
<div ng-repeat="servicePort in $ctrl.servicePorts" class="mt-5 service-form row">
<div ng-repeat="servicePort in $ctrl.service.Ports" class="mt-5 service-form row">
<div class="form-group !mx-0 !pl-0 col-sm-3">
<div class="input-group input-group-sm">
<span class="input-group-addon required">Container port</span>
@ -40,7 +40,7 @@
max="65535"
ng-change="$ctrl.servicePort($index)"
required
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
ng-change="$ctrl.onChangeContainerPort()"
data-cy="k8sAppCreate-containerPort_{{ $index }}"
/>
@ -75,7 +75,7 @@
min="1"
max="65535"
required
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
ng-disabled="$ctrl.originalIngresses.length === 0 || ($ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled)"
ng-change="$ctrl.onChangeServicePort()"
data-cy="k8sAppCreate-servicePort_{{ $index }}"
/>
@ -98,7 +98,7 @@
</span>
</div>
<div class="form-group !mx-0 !pl-0 col-sm-3" ng-if="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.NODE_PORT">
<div class="form-group !mx-0 !pl-0 col-sm-3" ng-if="$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.NODE_PORT">
<div class="input-group input-group-sm">
<span class="input-group-addon required">Nodeport</span>
<input
@ -129,12 +129,15 @@
><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Nodeport number must be inside the range 30000-32767 or blank for system
allocated.</p
>
<div class="mt-1 text-warning" ng-if="$ctrl.state.duplicates.nodePort.refs[$index] !== undefined">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This node port is already used.
</div>
</div>
</div>
</span>
</div>
</div>
<div class="form-group !mx-0 !pl-0 col-sm-3" ng-if="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER">
<div class="form-group !mx-0 !pl-0 col-sm-3" ng-if="$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER">
<div class="input-group input-group-sm">
<span class="input-group-addon">Loadbalancer port</span>
<input
@ -148,7 +151,7 @@
min="1"
max="65535"
required
ng-disabled="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled"
ng-disabled="$ctrl.service.Type === $ctrl.KubernetesApplicationPublishingTypes.LOAD_BALANCER && !$ctrl.loadbalancerEnabled"
data-cy="k8sAppCreate-loadbalancerPort_{{ $index }}"
/>
</div>
@ -177,7 +180,7 @@
>
</div>
<button
ng-disabled="$ctrl.servicePorts.length === 1"
ng-disabled="$ctrl.service.Ports.length === 1"
ng-show="!$ctrl.multiItemDisable"
class="btn btn-sm btn-dangerlight btn-only-icon"
type="button"

View file

@ -5,15 +5,10 @@ angular.module('portainer.kubernetes').component('kubeServicesItemView', {
templateUrl: './kube-services-item.html',
controller,
bindings: {
serviceType: '<',
servicePorts: '=',
serviceRoutes: '=',
ingressType: '<',
originalIngresses: '<',
nodePortServices: '<',
formServices: '<',
service: '=',
isEdit: '<',
serviceName: '<',
multiItemDisable: '<',
serviceIndex: '<',
loadbalancerEnabled: '<',
},
});

View file

@ -1,5 +1,7 @@
import { KubernetesService, KubernetesServicePort, KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import { KubernetesApplicationPublishingTypes } from 'Kubernetes/models/application/models/constants';
import { getServices } from 'Kubernetes/react/views/networks/services/service';
import { notifyError } from '@/portainer/services/notifications';
export default class KubeServicesViewController {
/* @ngInject */
@ -7,6 +9,7 @@ export default class KubeServicesViewController {
this.$async = $async;
this.EndpointProvider = EndpointProvider;
this.Authentication = Authentication;
this.asyncOnInit = this.asyncOnInit.bind(this);
}
addEntry(service) {
@ -74,6 +77,21 @@ export default class KubeServicesViewController {
return 'fa fa-project-diagram';
}
}
async asyncOnInit() {
try {
// get all nodeport services in the cluster, to validate unique nodeports in the form
const allSettledServices = await Promise.allSettled(this.namespaces.map((namespace) => getServices(this.state.endpointId, namespace)));
const allServices = allSettledServices
.filter((settledService) => settledService.status === 'fulfilled' && settledService.value)
.map((fulfilledService) => fulfilledService.value)
.flat();
this.nodePortServices = allServices.filter((service) => service.Type === 'NodePort');
} catch (error) {
notifyError('Failure', error, 'Failed getting services');
}
}
$onInit() {
this.state = {
serviceType: [
@ -93,5 +111,6 @@ export default class KubeServicesViewController {
selected: KubernetesApplicationPublishingTypes.CLUSTER_IP,
endpointId: this.EndpointProvider.endpointID(),
};
return this.$async(this.asyncOnInit);
}
}

View file

@ -44,10 +44,9 @@
{{ $ctrl.serviceType(service.Type) }}
</div>
<kube-services-item-view
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"
node-port-services="$ctrl.nodePortServices"
form-services="$ctrl.formValues.Services"
service="$ctrl.formValues.Services[$index]"
is-edit="$ctrl.isEdit"
loadbalancer-enabled="$ctrl.loadbalancerEnabled"
></kube-services-item-view>

View file

@ -7,6 +7,7 @@ angular.module('portainer.kubernetes').component('kubeServicesView', {
bindings: {
formValues: '=',
isEdit: '<',
namespaces: '<',
loadbalancerEnabled: '<',
},
});