From 1b3e2c8f69fdef2c666636a24ae1e903f7137524 Mon Sep 17 00:00:00 2001 From: xAt0mZ Date: Tue, 14 Jul 2020 22:45:19 +0200 Subject: [PATCH] feat(kubernetes): add ingress details (#4013) * feat(kubernetes): add ingress details * fix(kubernetes): fix broken ingress generated links + ignore IP retrieval/display info on missing LB ingress ip * refactor(kubernetes): each ingress rule in apps port mappings has now its own row * feat(kubernetes): remove protocol column and concat it to container port * feat(kubernetes): edit display of ingress rules in application details * feat(kubernetes): minor UI update Co-authored-by: Anthony Lapenna --- .../applicationsPortsDatatable.html | 102 ++++++++++--- .../applicationsPortsDatatableController.js | 17 ++- app/kubernetes/converters/application.js | 53 ++++--- app/kubernetes/filters/applicationFilters.js | 14 ++ app/kubernetes/helpers/application/index.js | 17 ++- app/kubernetes/helpers/serviceHelper.js | 2 +- app/kubernetes/ingress/converter.js | 19 +++ app/kubernetes/ingress/helper.js | 7 + app/kubernetes/ingress/models.js | 16 ++ app/kubernetes/ingress/rest.js | 50 +++++++ app/kubernetes/ingress/service.js | 54 +++++++ app/kubernetes/models/application/models.js | 17 +++ app/kubernetes/models/port/models.js | 1 + app/kubernetes/models/service/models.js | 1 + app/kubernetes/services/applicationService.js | 59 ++++---- .../applications/applicationsController.js | 4 +- .../views/applications/edit/application.html | 138 ++++++++++-------- .../edit/applicationController.js | 15 ++ jsconfig.json | 1 + 19 files changed, 450 insertions(+), 137 deletions(-) create mode 100644 app/kubernetes/ingress/converter.js create mode 100644 app/kubernetes/ingress/helper.js create mode 100644 app/kubernetes/ingress/models.js create mode 100644 app/kubernetes/ingress/rest.js create mode 100644 app/kubernetes/ingress/service.js diff --git a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html index cf66330d6..814ff9ebf 100644 --- a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html +++ b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html @@ -96,15 +96,13 @@ - - Protocol - - - + HTTP route + + + + {{ item.Name }} system external + - + + Load balancer {{ item.LoadBalancerIPAddress }} pending - Internal - Cluster + + + Internal + + + Cluster + + + + + {{ item.Ports[0].Port }} + + access + + + + + + {{ item.Ports[0].TargetPort }}/{{ item.Ports[0].Protocol }} + + + + + - + + pending + + + + {{ $ctrl.buildIngressRuleURL(item.Ports[0].IngressRules[0]) | stripprotocol }} + + + + - - {{ item.Ports[0].Port }} - + + + + + - + - + + {{ port.Port }} + access - {{ item.Ports[0].TargetPort }} - {{ item.Ports[0].Protocol }} - - - + {{ port.TargetPort }}/{{ port.Protocol }} + - - + - - @@ -155,12 +200,31 @@ access - {{ port.TargetPort }} - {{ port.Protocol }} + {{ port.TargetPort }}/{{ port.Protocol }} + + pending + + + + {{ $ctrl.buildIngressRuleURL(rule) | stripprotocol }} + + + + + Loading... + No application port mapping available. diff --git a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatableController.js b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatableController.js index e7086a40c..848e0ad6b 100644 --- a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatableController.js +++ b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatableController.js @@ -1,6 +1,7 @@ import _ from 'lodash-es'; import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models'; import KubernetesApplicationHelper from 'Kubernetes/helpers/application'; +import { KubernetesServiceTypes } from 'Kubernetes/models/service/models'; angular.module('portainer.docker').controller('KubernetesApplicationsPortsDatatableController', [ '$scope', @@ -16,6 +17,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsPortsDatata }); var ctrl = this; + this.KubernetesServiceTypes = KubernetesServiceTypes; this.settings = Object.assign(this.settings, { showSystem: false, @@ -49,7 +51,20 @@ angular.module('portainer.docker').controller('KubernetesApplicationsPortsDatata }; this.itemCanExpand = function (item) { - return item.Ports.length > 1; + return item.Ports.length > 1 || item.Ports[0].IngressRules.length > 1; + }; + + this.buildIngressRuleURL = function (rule) { + const hostname = rule.Host ? rule.Host : rule.IP; + return 'http://' + hostname + rule.Path; + }; + + this.portHasIngressRules = function (port) { + return port.IngressRules.length > 0; + }; + + this.ruleCanBeDisplayed = function (rule) { + return !rule.Host && !rule.IP ? false : true; }; this.hasExpandableItems = function () { diff --git a/app/kubernetes/converters/application.js b/app/kubernetes/converters/application.js index 5c7cf7476..4491e2e4a 100644 --- a/app/kubernetes/converters/application.js +++ b/app/kubernetes/converters/application.js @@ -1,4 +1,4 @@ -import _ from 'lodash-es'; +import * as _ from 'lodash-es'; import filesizeParser from 'filesize-parser'; import { @@ -25,9 +25,31 @@ import KubernetesStatefulSetConverter from 'Kubernetes/converters/statefulSet'; import KubernetesServiceConverter from 'Kubernetes/converters/service'; import KubernetesPersistentVolumeClaimConverter from 'Kubernetes/converters/persistentVolumeClaim'; import PortainerError from 'Portainer/error'; +import { KubernetesApplicationPort } from 'Kubernetes/models/application/models'; +import { KubernetesIngressHelper } from 'Kubernetes/ingress/helper'; + +function _apiPortsToPublishedPorts(pList, pRefs) { + const ports = _.map(pList, (item) => { + const res = new KubernetesApplicationPort(); + res.Port = item.port; + res.TargetPort = item.targetPort; + res.NodePort = item.nodePort; + res.Protocol = item.protocol; + return res; + }); + _.forEach(ports, (port) => { + if (isNaN(port.TargetPort)) { + const targetPort = _.find(pRefs, { name: port.TargetPort }); + if (targetPort) { + port.TargetPort = targetPort.containerPort; + } + } + }); + return ports; +} class KubernetesApplicationConverter { - static applicationCommon(res, data, service) { + static applicationCommon(res, data, service, ingressRules) { res.Id = data.metadata.uid; res.Name = data.metadata.name; res.StackName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationStackNameLabel] || '-' : '-'; @@ -87,16 +109,11 @@ class KubernetesApplicationConverter { } } - const ports = _.concat(..._.map(data.spec.template.spec.containers, (container) => container.ports)); - res.PublishedPorts = service.spec.ports; - _.forEach(res.PublishedPorts, (publishedPort) => { - if (isNaN(publishedPort.targetPort)) { - const targetPort = _.find(ports, { name: publishedPort.targetPort }); - if (targetPort) { - publishedPort.targetPort = targetPort.containerPort; - } - } - }); + const portsRefs = _.concat(..._.map(data.spec.template.spec.containers, (container) => container.ports)); + const ports = _apiPortsToPublishedPorts(service.spec.ports, portsRefs); + const rules = KubernetesIngressHelper.findSBoundServiceIngressesRules(ingressRules, service); + _.forEach(ports, (port) => (port.IngressRules = _.filter(rules, (rule) => rule.Port === port.Port))); + res.PublishedPorts = ports; } res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : []; @@ -193,9 +210,9 @@ class KubernetesApplicationConverter { ); } - static apiDeploymentToApplication(data, service) { + static apiDeploymentToApplication(data, service, ingressRules) { const res = new KubernetesApplication(); - KubernetesApplicationConverter.applicationCommon(res, data, service); + KubernetesApplicationConverter.applicationCommon(res, data, service, ingressRules); res.ApplicationType = KubernetesApplicationTypes.DEPLOYMENT; res.DeploymentType = KubernetesApplicationDeploymentTypes.REPLICATED; res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED; @@ -204,9 +221,9 @@ class KubernetesApplicationConverter { return res; } - static apiDaemonSetToApplication(data, service) { + static apiDaemonSetToApplication(data, service, ingressRules) { const res = new KubernetesApplication(); - KubernetesApplicationConverter.applicationCommon(res, data, service); + KubernetesApplicationConverter.applicationCommon(res, data, service, ingressRules); res.ApplicationType = KubernetesApplicationTypes.DAEMONSET; res.DeploymentType = KubernetesApplicationDeploymentTypes.GLOBAL; res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED; @@ -215,9 +232,9 @@ class KubernetesApplicationConverter { return res; } - static apiStatefulSetToapplication(data, service) { + static apiStatefulSetToapplication(data, service, ingressRules) { const res = new KubernetesApplication(); - KubernetesApplicationConverter.applicationCommon(res, data, service); + KubernetesApplicationConverter.applicationCommon(res, data, service, ingressRules); res.ApplicationType = KubernetesApplicationTypes.STATEFULSET; res.DeploymentType = KubernetesApplicationDeploymentTypes.REPLICATED; res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.ISOLATED; diff --git a/app/kubernetes/filters/applicationFilters.js b/app/kubernetes/filters/applicationFilters.js index 968218e06..351007566 100644 --- a/app/kubernetes/filters/applicationFilters.js +++ b/app/kubernetes/filters/applicationFilters.js @@ -1,5 +1,6 @@ import _ from 'lodash-es'; import { KubernetesApplicationDataAccessPolicies } from 'Kubernetes/models/application/models'; +import { KubernetesServiceTypes } from 'Kubernetes/models/service/models'; angular .module('portainer.kubernetes') @@ -31,6 +32,19 @@ angular } }; }) + .filter('kubernetesApplicationPortsTableHeaderText', function () { + 'use strict'; + return function (serviceType) { + switch (serviceType) { + case KubernetesServiceTypes.LOAD_BALANCER: + return 'Load balancer'; + case KubernetesServiceTypes.CLUSTER_IP: + return 'Application'; + case KubernetesServiceTypes.NODE_PORT: + return 'Cluster node'; + } + }; + }) .filter('kubernetesApplicationCPUValue', function () { 'use strict'; return function (value) { diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js index ab5c43b78..4075b2e0a 100644 --- a/app/kubernetes/helpers/application/index.js +++ b/app/kubernetes/helpers/application/index.js @@ -41,9 +41,10 @@ class KubernetesApplicationHelper { mapping.Ports = _.map(app.PublishedPorts, (item) => { const port = new KubernetesPortMappingPort(); - port.Port = mapping.ServiceType === KubernetesServiceTypes.NODE_PORT ? item.nodePort : item.port; - port.TargetPort = item.targetPort; - port.Protocol = item.protocol; + port.Port = mapping.ServiceType === KubernetesServiceTypes.NODE_PORT ? item.NodePort : item.Port; + port.TargetPort = item.TargetPort; + port.Protocol = item.Protocol; + port.IngressRules = item.IngressRules; return port; }); acc.push(mapping); @@ -249,13 +250,13 @@ class KubernetesApplicationHelper { static generatePublishedPortsFormValuesFromPublishedPorts(serviceType, publishedPorts) { const finalRes = _.map(publishedPorts, (port) => { const res = new KubernetesApplicationPublishedPortFormValue(); - res.Protocol = port.protocol; - res.ContainerPort = port.targetPort; + res.Protocol = port.Protocol; + res.ContainerPort = port.TargetPort; if (serviceType === KubernetesServiceTypes.LOAD_BALANCER) { - res.LoadBalancerPort = port.port; - res.LoadBalancerNodePort = port.nodePort; + res.LoadBalancerPort = port.Port; + res.LoadBalancerNodePort = port.NodePort; } else if (serviceType === KubernetesServiceTypes.NODE_PORT) { - res.NodePort = port.nodePort; + res.NodePort = port.NodePort; } return res; }); diff --git a/app/kubernetes/helpers/serviceHelper.js b/app/kubernetes/helpers/serviceHelper.js index c4b35050d..c263a3766 100644 --- a/app/kubernetes/helpers/serviceHelper.js +++ b/app/kubernetes/helpers/serviceHelper.js @@ -7,7 +7,7 @@ class KubernetesServiceHelper { } static findApplicationBoundService(services, rawApp) { - return _.find(services, (item) => _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector)); + return _.find(services, (item) => item.spec.selector && _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector)); } } export default KubernetesServiceHelper; diff --git a/app/kubernetes/ingress/converter.js b/app/kubernetes/ingress/converter.js new file mode 100644 index 000000000..f931fd3ab --- /dev/null +++ b/app/kubernetes/ingress/converter.js @@ -0,0 +1,19 @@ +import * as _ from 'lodash-es'; +import { KubernetesIngressRule } from './models'; + +export class KubernetesIngressConverter { + static apiToModel(data) { + const rules = _.flatMap(data.spec.rules, (rule) => { + return _.map(rule.http.paths, (path) => { + const ingRule = new KubernetesIngressRule(); + ingRule.ServiceName = path.backend.serviceName; + ingRule.Host = rule.host; + ingRule.IP = data.status.loadBalancer.ingress ? data.status.loadBalancer.ingress[0].ip : undefined; + ingRule.Port = path.backend.servicePort; + ingRule.Path = path.path; + return ingRule; + }); + }); + return rules; + } +} diff --git a/app/kubernetes/ingress/helper.js b/app/kubernetes/ingress/helper.js new file mode 100644 index 000000000..7090e4596 --- /dev/null +++ b/app/kubernetes/ingress/helper.js @@ -0,0 +1,7 @@ +import * as _ from 'lodash-es'; + +export class KubernetesIngressHelper { + static findSBoundServiceIngressesRules(ingressRules, service) { + return _.filter(ingressRules, (r) => r.ServiceName === service.metadata.name); + } +} diff --git a/app/kubernetes/ingress/models.js b/app/kubernetes/ingress/models.js new file mode 100644 index 000000000..a8486ce94 --- /dev/null +++ b/app/kubernetes/ingress/models.js @@ -0,0 +1,16 @@ +/** + * KubernetesIngressRule Model + */ +const _KubernetesIngressRule = Object.freeze({ + ServiceName: '', + Host: '', + IP: '', + Port: '', + Path: '', +}); + +export class KubernetesIngressRule { + constructor() { + Object.assign(this, JSON.parse(JSON.stringify(_KubernetesIngressRule))); + } +} diff --git a/app/kubernetes/ingress/rest.js b/app/kubernetes/ingress/rest.js new file mode 100644 index 000000000..42b87a80c --- /dev/null +++ b/app/kubernetes/ingress/rest.js @@ -0,0 +1,50 @@ +import { rawResponse } from 'Kubernetes/rest/response/transform'; + +angular.module('portainer.kubernetes').factory('KubernetesIngresses', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function KubernetesIngressesFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return function (namespace) { + const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/apis/networking.k8s.io/v1beta1' + (namespace ? '/namespaces/:namespace' : '') + '/ingresses/:id/:action'; + return $resource( + url, + { + endpointId: EndpointProvider.endpointID, + namespace: namespace, + }, + { + get: { + method: 'GET', + timeout: 15000, + ignoreLoadingBar: true, + }, + getYaml: { + method: 'GET', + headers: { + Accept: 'application/yaml', + }, + transformResponse: rawResponse, + ignoreLoadingBar: true, + }, + create: { method: 'POST' }, + update: { method: 'PUT' }, + patch: { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }, + rollback: { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }, + delete: { method: 'DELETE' }, + } + ); + }; + }, +]); diff --git a/app/kubernetes/ingress/service.js b/app/kubernetes/ingress/service.js new file mode 100644 index 000000000..72ce0ef5d --- /dev/null +++ b/app/kubernetes/ingress/service.js @@ -0,0 +1,54 @@ +import * as _ from 'lodash-es'; +import angular from 'angular'; +import PortainerError from 'Portainer/error'; +import { KubernetesCommonParams } from 'Kubernetes/models/common/params'; +import { KubernetesIngressConverter } from './converter'; + +class KubernetesIngressService { + /* @ngInject */ + constructor($async, KubernetesIngresses) { + this.$async = $async; + this.KubernetesIngresses = KubernetesIngresses; + + this.getAsync = this.getAsync.bind(this); + this.getAllAsync = this.getAllAsync.bind(this); + } + + /** + * GET + */ + async getAsync(namespace, name) { + try { + const params = new KubernetesCommonParams(); + params.id = name; + const [raw, yaml] = await Promise.all([this.KubernetesIngresses(namespace).get(params).$promise, this.KubernetesIngresses(namespace).getYaml(params).$promise]); + const res = { + Raw: KubernetesIngressConverter.apiToModel(raw), + Yaml: yaml.data, + }; + return res; + } catch (err) { + throw new PortainerError('Unable to retrieve Ingress', err); + } + } + + async getAllAsync(namespace) { + try { + const data = await this.KubernetesIngresses(namespace).get().$promise; + const res = _.reduce(data.items, (arr, item) => _.concat(arr, KubernetesIngressConverter.apiToModel(item)), []); + return res; + } catch (err) { + throw new PortainerError('Unable to retrieve Ingresses', err); + } + } + + get(namespace, name) { + if (name) { + return this.$async(this.getAsync, namespace, name); + } + return this.$async(this.getAllAsync, namespace); + } +} + +export default KubernetesIngressService; +angular.module('portainer.kubernetes').service('KubernetesIngressService', KubernetesIngressService); diff --git a/app/kubernetes/models/application/models.js b/app/kubernetes/models/application/models.js index 3589a5ceb..32a953091 100644 --- a/app/kubernetes/models/application/models.js +++ b/app/kubernetes/models/application/models.js @@ -112,3 +112,20 @@ export class KubernetesApplicationConfigurationVolume { Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationConfigurationVolume))); } } + +/** + * KubernetesApplicationPort Model + */ +const _KubernetesApplicationPort = Object.freeze({ + IngressRules: [], // KubernetesIngressRule[] + NodePort: 0, + TargetPort: 0, + Port: 0, + Protocol: '', +}); + +export class KubernetesApplicationPort { + constructor() { + Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationPort))); + } +} diff --git a/app/kubernetes/models/port/models.js b/app/kubernetes/models/port/models.js index 1409a1059..6b100400a 100644 --- a/app/kubernetes/models/port/models.js +++ b/app/kubernetes/models/port/models.js @@ -5,6 +5,7 @@ const _KubernetesPortMappingPort = Object.freeze({ Port: 0, TargetPort: 0, Protocol: '', + IngressRules: [], // KubernetesIngressRule[] }); export class KubernetesPortMappingPort { diff --git a/app/kubernetes/models/service/models.js b/app/kubernetes/models/service/models.js index 5c9abb61c..8dcc36d86 100644 --- a/app/kubernetes/models/service/models.js +++ b/app/kubernetes/models/service/models.js @@ -3,6 +3,7 @@ export const KubernetesServiceHeadlessClusterIP = 'None'; export const KubernetesServiceTypes = Object.freeze({ LOAD_BALANCER: 'LoadBalancer', NODE_PORT: 'NodePort', + CLUSTER_IP: 'ClusterIP', }); /** diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js index ea99d26e1..d4172f606 100644 --- a/app/kubernetes/services/applicationService.js +++ b/app/kubernetes/services/applicationService.js @@ -27,7 +27,8 @@ class KubernetesApplicationService { KubernetesNamespaceService, KubernetesPodService, KubernetesHistoryService, - KubernetesHorizontalPodAutoScalerService + KubernetesHorizontalPodAutoScalerService, + KubernetesIngressService ) { this.$async = $async; this.Authentication = Authentication; @@ -41,6 +42,7 @@ class KubernetesApplicationService { this.KubernetesPodService = KubernetesPodService; this.KubernetesHistoryService = KubernetesHistoryService; this.KubernetesHorizontalPodAutoScalerService = KubernetesHorizontalPodAutoScalerService; + this.KubernetesIngressService = KubernetesIngressService; this.getAsync = this.getAsync.bind(this); this.getAllAsync = this.getAllAsync.bind(this); @@ -73,25 +75,26 @@ class KubernetesApplicationService { */ async getAsync(namespace, name) { try { - const [deployment, daemonSet, statefulSet, pods, autoScalers] = await Promise.allSettled([ + const [deployment, daemonSet, statefulSet, pods, autoScalers, ingresses] = await Promise.allSettled([ this.KubernetesDeploymentService.get(namespace, name), this.KubernetesDaemonSetService.get(namespace, name), this.KubernetesStatefulSetService.get(namespace, name), this.KubernetesPodService.get(namespace), this.KubernetesHorizontalPodAutoScalerService.get(namespace), + this.KubernetesIngressService.get(namespace), ]); let rootItem; - let converterFunction; + let converterFunc; if (deployment.status === 'fulfilled') { rootItem = deployment; - converterFunction = KubernetesApplicationConverter.apiDeploymentToApplication; + converterFunc = KubernetesApplicationConverter.apiDeploymentToApplication; } else if (daemonSet.status === 'fulfilled') { rootItem = daemonSet; - converterFunction = KubernetesApplicationConverter.apiDaemonSetToApplication; + converterFunc = KubernetesApplicationConverter.apiDaemonSetToApplication; } else if (statefulSet.status === 'fulfilled') { rootItem = statefulSet; - converterFunction = KubernetesApplicationConverter.apiStatefulSetToapplication; + converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication; } else { throw new PortainerError('Unable to determine which association to use'); } @@ -100,7 +103,7 @@ class KubernetesApplicationService { const boundService = KubernetesServiceHelper.findApplicationBoundService(services, rootItem.value.Raw); const service = boundService ? await this.KubernetesServiceService.get(namespace, boundService.metadata.name) : {}; - const application = converterFunction(rootItem.value.Raw, service.Raw); + const application = converterFunc(rootItem.value.Raw, service.Raw, ingresses.value); application.Yaml = rootItem.value.Yaml; application.Raw = rootItem.value.Raw; application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods.value, application.Raw); @@ -117,6 +120,8 @@ class KubernetesApplicationService { if (scaler && scaler.Yaml) { application.Yaml += '---\n' + scaler.Yaml; } + // TODO: refactor + // append ingress yaml ? return application; } catch (err) { throw err; @@ -126,33 +131,35 @@ class KubernetesApplicationService { async getAllAsync(namespace) { try { const namespaces = namespace ? [namespace] : _.map(await this.KubernetesNamespaceService.get(), 'Name'); + + const convertToApplication = (item, converterFunc, services, pods, ingresses) => { + const service = KubernetesServiceHelper.findApplicationBoundService(services, item); + const application = converterFunc(item, service, ingresses); + application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item); + return application; + }; + const res = await Promise.all( _.map(namespaces, async (ns) => { - const [deployments, daemonSets, statefulSets, services, pods] = await Promise.all([ + const [deployments, daemonSets, statefulSets, services, pods, ingresses] = await Promise.all([ this.KubernetesDeploymentService.get(ns), this.KubernetesDaemonSetService.get(ns), this.KubernetesStatefulSetService.get(ns), this.KubernetesServiceService.get(ns), this.KubernetesPodService.get(ns), + this.KubernetesIngressService.get(ns), ]); - const deploymentApplications = _.map(deployments, (item) => { - const service = KubernetesServiceHelper.findApplicationBoundService(services, item); - const application = KubernetesApplicationConverter.apiDeploymentToApplication(item, service); - application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item); - return application; - }); - const daemonSetApplications = _.map(daemonSets, (item) => { - const service = KubernetesServiceHelper.findApplicationBoundService(services, item); - const application = KubernetesApplicationConverter.apiDaemonSetToApplication(item, service); - application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item); - return application; - }); - const statefulSetApplications = _.map(statefulSets, (item) => { - const service = KubernetesServiceHelper.findApplicationBoundService(services, item); - const application = KubernetesApplicationConverter.apiStatefulSetToapplication(item, service); - application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item); - return application; - }); + + const deploymentApplications = _.map(deployments, (item) => + convertToApplication(item, KubernetesApplicationConverter.apiDeploymentToApplication, services, pods, ingresses) + ); + const daemonSetApplications = _.map(daemonSets, (item) => + convertToApplication(item, KubernetesApplicationConverter.apiDaemonSetToApplication, services, pods, ingresses) + ); + const statefulSetApplications = _.map(statefulSets, (item) => + convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses) + ); + return _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications); }) ); diff --git a/app/kubernetes/views/applications/applicationsController.js b/app/kubernetes/views/applications/applicationsController.js index 859698a00..07553810e 100644 --- a/app/kubernetes/views/applications/applicationsController.js +++ b/app/kubernetes/views/applications/applicationsController.js @@ -87,9 +87,7 @@ class KubernetesApplicationsController { item.Expanded = false; item.Highlighted = false; if (item.Name === application.Name) { - if (item.Ports.length > 1) { - item.Expanded = true; - } + item.Expanded = true; item.Highlighted = true; } }); diff --git a/app/kubernetes/views/applications/edit/application.html b/app/kubernetes/views/applications/edit/application.html index 0ea9118ba..248fefc0e 100644 --- a/app/kubernetes/views/applications/edit/application.html +++ b/app/kubernetes/views/applications/edit/application.html @@ -180,7 +180,8 @@
-
+ +
This application is exposed through an external load balancer. Use the links below to access the different ports exposed. @@ -210,55 +211,19 @@

-
- - - - - - - - - - - -
Container portLoad balancer port
{{ port.targetPort }} - {{ port.port }} - - access - -
-
-
+ +
This application is exposed globally on all nodes of your cluster. It can be reached using the IP address of any node in your cluster using the port configuration below.
-
- - - - - - - - - - - -
Container portCluster node port
{{ port.targetPort }}{{ port.nodePort }}
-
-
+ +
This application is only available for internal usage inside the cluster via the application name {{ ctrl.application.ServiceName }} @@ -270,22 +235,75 @@

Refer to the below port configuration to access the application.

-
- - - - - - - - - - - - - -
Container portApplication portProtocol
{{ port.targetPort }}{{ port.port }}{{ port.protocol }}
-
+
+ + +
+ + + + + + + + + + + + + + + + + + +
Container port{{ ctrl.application.ServiceType | kubernetesApplicationPortsTableHeaderText }} portHTTP route
{{ port.TargetPort }}/{{ port.Protocol }} + + {{ port.NodePort }} + + + {{ port.Port }} + + + access + + -
{{ port.TargetPort }}/{{ port.Protocol }} + + {{ port.NodePort }} + + + {{ port.Port }} + + + access + + + pending + + + + {{ ctrl.buildIngressRuleURL(rule) | stripprotocol }} + + +
@@ -306,10 +324,8 @@ Maximum instances Target CPU usage - + + diff --git a/app/kubernetes/views/applications/edit/applicationController.js b/app/kubernetes/views/applications/edit/applicationController.js index 0aa896c25..f8706c265 100644 --- a/app/kubernetes/views/applications/edit/applicationController.js +++ b/app/kubernetes/views/applications/edit/applicationController.js @@ -3,6 +3,7 @@ import _ from 'lodash-es'; import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models'; import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper'; import KubernetesApplicationHelper from 'Kubernetes/helpers/application'; +import { KubernetesServiceTypes } from 'Kubernetes/models/service/models'; class KubernetesApplicationController { /* @ngInject */ @@ -34,6 +35,7 @@ class KubernetesApplicationController { this.KubernetesNamespaceHelper = KubernetesNamespaceHelper; this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies; + this.KubernetesServiceTypes = KubernetesServiceTypes; this.onInit = this.onInit.bind(this); this.getApplication = this.getApplication.bind(this); @@ -85,6 +87,19 @@ class KubernetesApplicationController { return this.state.eventWarningCount; } + buildIngressRuleURL(rule) { + const hostname = rule.Host ? rule.Host : rule.IP; + return 'http://' + hostname + rule.Path; + } + + portHasIngressRules(port) { + return port.IngressRules.length > 0; + } + + ruleCanBeDisplayed(rule) { + return !rule.Host && !rule.IP ? false : true; + } + /** * ROLLBACK */ diff --git a/jsconfig.json b/jsconfig.json index c1b062209..493168e31 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -3,6 +3,7 @@ "target": "es2017", "allowSyntheticDefaultImports": false, "baseUrl": "app", + "module": "commonjs", "paths": { "Agent/*": ["agent/*"], "Azure/*": ["azure/*"],