-
-
-
-
- This application is exposed through a service of type {{ ctrl.application.ServiceType }}. Refer to the port configuration below to access it.
-
-
-
- Load balancer status: pending
-
- what does the "pending" status means?
-
-
-
-
-
- Load balancer status: available
-
- Load balancer IP address: {{ ctrl.application.LoadBalancerIPAddress }}
-
- Copy
-
-
- copied
-
-
-
-
-
-
-
-
-
-
- This application is exposed through a service of type {{ ctrl.application.ServiceType }}. It can be reached using the IP address of any node in your cluster using the port configuration below.
-
-
-
-
-
-
-
- This application is exposed through a service of type {{ ctrl.application.ServiceType }}. It can be reached via the application name {{ ctrl.application.ServiceName }}
and the port configuration below.
- Copy
- copied
-
-
-
-
-
+
+
- This application is exposed through a service of type {{ ctrl.application.ServiceType }}. It can be reached via the application name {{ ctrl.application.ServiceName }}
and the port configuration below.
- Copy
- copied
+ This application is exposed through service(s) as below:
-
It is also associated to an Ingress and can be accessed via specific HTTP route(s).
-
+
+
+
+
+
+
diff --git a/app/kubernetes/views/applications/edit/applicationController.js b/app/kubernetes/views/applications/edit/applicationController.js
index 06ec3f02d..c41950860 100644
--- a/app/kubernetes/views/applications/edit/applicationController.js
+++ b/app/kubernetes/views/applications/edit/applicationController.js
@@ -1,6 +1,7 @@
import angular from 'angular';
import _ from 'lodash-es';
import * as JsonPatch from 'fast-json-patch';
+
import {
KubernetesApplicationDataAccessPolicies,
KubernetesApplicationDeploymentTypes,
@@ -112,7 +113,6 @@ class KubernetesApplicationController {
KubernetesStackService,
KubernetesPodService,
KubernetesNodeService,
-
StackService
) {
this.$async = $async;
@@ -317,6 +317,7 @@ class KubernetesApplicationController {
this.application = application;
this.allContainers = KubernetesApplicationHelper.associateAllContainersAndApplication(application);
this.formValues.Note = this.application.Note;
+ this.formValues.Services = this.application.Services;
if (this.application.Note) {
this.state.expandedNote = true;
}
@@ -347,6 +348,12 @@ class KubernetesApplicationController {
}
async onInit() {
+ const endpointId = this.LocalStorage.getEndpointID();
+ const endpoints = this.LocalStorage.getEndpoints();
+ const endpoint = _.find(endpoints, function (item) {
+ return item.Id === endpointId;
+ });
+
this.state = {
activeTab: 0,
currentName: this.$state.$current.name,
@@ -365,6 +372,7 @@ class KubernetesApplicationController {
expandedNote: false,
useIngress: false,
useServerMetrics: this.endpoint.Kubernetes.Configuration.UseServerMetrics,
+ publicUrl: endpoint.PublicURL,
};
this.state.activeTab = this.LocalStorage.getActiveTab('application');
diff --git a/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.controller.js b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.controller.js
new file mode 100644
index 000000000..d09d9f6c8
--- /dev/null
+++ b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.controller.js
@@ -0,0 +1,29 @@
+import _ from 'lodash-es';
+
+export default class KubernetesApplicationIngressController {
+ /* @ngInject */
+ constructor($async, KubernetesIngressService) {
+ this.$async = $async;
+ this.KubernetesIngressService = KubernetesIngressService;
+ }
+
+ $onInit() {
+ return this.$async(async () => {
+ this.hasIngress;
+ this.applicationIngress = [];
+ const ingresses = await this.KubernetesIngressService.get(this.application.ResourcePool);
+ const services = this.application.Services;
+
+ _.forEach(services, (service) => {
+ _.forEach(ingresses, (ingress) => {
+ _.forEach(ingress.Paths, (path) => {
+ if (path.ServiceName === service.metadata.name) {
+ this.applicationIngress.push(path);
+ this.hasIngress = true;
+ }
+ });
+ });
+ });
+ });
+ }
+}
diff --git a/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.html b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.html
new file mode 100644
index 000000000..76b0e8216
--- /dev/null
+++ b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Ingress name |
+ Service name |
+ Host |
+ Port |
+ Path |
+ HTTP Route |
+
+
+ {{ ingress.IngressName }} |
+ {{ ingress.ServiceName }} |
+ {{ ingress.Host }} |
+ {{ ingress.Port }} |
+ {{ ingress.Path }} |
+ {{ ingress.Host }}{{ ingress.Path }} |
+
+
+
+
diff --git a/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.js b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.js
new file mode 100644
index 000000000..d9da327b4
--- /dev/null
+++ b/app/kubernetes/views/applications/edit/components/ingress-table/ingress-table.js
@@ -0,0 +1,11 @@
+import angular from 'angular';
+import controller from './ingress-table.controller';
+
+angular.module('portainer.kubernetes').component('kubernetesApplicationIngressTable', {
+ templateUrl: './ingress-table.html',
+ controller,
+ bindings: {
+ application: '<',
+ publicUrl: '<',
+ },
+});
diff --git a/app/kubernetes/views/applications/edit/components/services-table/services-table.html b/app/kubernetes/views/applications/edit/components/services-table/services-table.html
new file mode 100644
index 000000000..9bbc1e99a
--- /dev/null
+++ b/app/kubernetes/views/applications/edit/components/services-table/services-table.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ Service name |
+ Type |
+ Cluster IP |
+ External IP |
+ Container port |
+ Service port(s) |
+
+
+ {{ service.metadata.name }} |
+ {{ service.spec.type }} |
+ {{ service.spec.clusterIP }} |
+
+
+
+ {{ service.spec.externalIP ? service.spec.externalIP : 'pending...' }}
+
+ |
+ {{ service.spec.externalIP ? service.spec.externalIP : '-' }} |
+
+
+ {{ port.targetPort }}
+ |
+
+
+ |
+
+
+
+
diff --git a/app/kubernetes/views/applications/edit/components/services-table/services-table.js b/app/kubernetes/views/applications/edit/components/services-table/services-table.js
new file mode 100644
index 000000000..4b82cfbb3
--- /dev/null
+++ b/app/kubernetes/views/applications/edit/components/services-table/services-table.js
@@ -0,0 +1,10 @@
+import angular from 'angular';
+
+angular.module('portainer.kubernetes').component('kubernetesApplicationServicesTable', {
+ templateUrl: './services-table.html',
+ bindings: {
+ services: '<',
+ application: '<',
+ publicUrl: '<',
+ },
+});
diff --git a/app/kubernetes/views/summary/resources/applicationResources.js b/app/kubernetes/views/summary/resources/applicationResources.js
index 4ea6cbf9b..f25264b37 100644
--- a/app/kubernetes/views/summary/resources/applicationResources.js
+++ b/app/kubernetes/views/summary/resources/applicationResources.js
@@ -4,7 +4,7 @@ import { KubernetesApplicationFormValues } from 'Kubernetes/models/application/f
import { KubernetesDeployment } from 'Kubernetes/models/deployment/models';
import { KubernetesStatefulSet } from 'Kubernetes/models/stateful-set/models';
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
-import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
+import { KubernetesService, KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import {
KubernetesApplication,
KubernetesApplicationDeploymentTypes,
@@ -40,7 +40,17 @@ export default function (formValues, oldFormValues = {}) {
function getCreatedApplicationResources(formValues) {
const resources = [];
- let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
+ let [app, headlessService, services, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
+
+ if (services) {
+ services.forEach((service) => {
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: service.Name, type: service.Type || KubernetesServiceTypes.CLUSTER_IP });
+ if (formValues.OriginalIngresses.length !== 0) {
+ const ingresses = KubernetesIngressConverter.newApplicationFormValuesToIngresses(formValues, service.Name, service.Ports);
+ resources.push(...getIngressUpdateSummary(formValues.OriginalIngresses, ingresses));
+ }
+ });
+ }
if (service) {
// Service
@@ -87,16 +97,15 @@ function getCreatedApplicationResources(formValues) {
function getUpdatedApplicationResources(oldFormValues, newFormValues) {
const resources = [];
- const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
- const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
-
+ const [oldApp, oldHeadlessService, oldServices, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
+ const [newApp, newHeadlessService, newServices, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
const oldAppResourceType = getApplicationResourceType(oldApp);
const newAppResourceType = getApplicationResourceType(newApp);
if (oldAppResourceType !== newAppResourceType) {
// Deployment
resources.push({ action: DELETE, kind: oldAppResourceType, name: oldApp.Name });
- if (oldService) {
+ if (oldService && oldServices) {
// Service
resources.push({ action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP });
}
@@ -129,11 +138,13 @@ function getUpdatedApplicationResources(oldFormValues, newFormValues) {
// Deployment
resources.push({ action: UPDATE, kind: oldAppResourceType, name: oldApp.Name });
- if (oldService && newService) {
+ if (oldServices && newServices) {
// Service
- const serviceUpdateResourceSummary = getServiceUpdateResourceSummary(oldService, newService);
- if (serviceUpdateResourceSummary) {
- resources.push(serviceUpdateResourceSummary);
+ const serviceUpdateResourceSummary = getServiceUpdateResourceSummary(oldServices, newServices);
+ if (serviceUpdateResourceSummary !== null) {
+ serviceUpdateResourceSummary.forEach((updateSummary) => {
+ resources.push(updateSummary);
+ });
}
if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
@@ -224,10 +235,41 @@ function getVolumeClaimUpdateResourceSummary(oldPVC, newPVC) {
}
// getServiceUpdateResourceSummary replicates KubernetesServiceService.patch
-function getServiceUpdateResourceSummary(oldService, newService) {
- const payload = KubernetesServiceConverter.patchPayload(oldService, newService);
- if (payload.length) {
- return { action: UPDATE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP };
+function getServiceUpdateResourceSummary(oldServices, newServices) {
+ let summary = [];
+ newServices.forEach((newService) => {
+ const oldServiceMatched = _.find(oldServices, { Name: newService.Name });
+ if (oldServiceMatched) {
+ const payload = KubernetesServiceConverter.patchPayload(oldServiceMatched, newService);
+ if (payload.length) {
+ const serviceUpdate = {
+ action: UPDATE,
+ kind: KubernetesResourceTypes.SERVICE,
+ name: oldServiceMatched.Name,
+ type: oldServiceMatched.Type || KubernetesServiceTypes.CLUSTER_IP,
+ };
+ summary.push(serviceUpdate);
+ }
+ } else {
+ const emptyService = new KubernetesService();
+ const payload = KubernetesServiceConverter.patchPayload(emptyService, newService);
+ if (payload.length) {
+ const serviceCreate = { action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: newService.Name, type: newService.Type || KubernetesServiceTypes.CLUSTER_IP };
+ summary.push(serviceCreate);
+ }
+ }
+ });
+
+ oldServices.forEach((oldService) => {
+ const newServiceMatched = _.find(newServices, { Name: oldService.Name });
+ if (!newServiceMatched) {
+ const serviceDelete = { action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP };
+ summary.push(serviceDelete);
+ }
+ });
+
+ if (summary.length !== 0) {
+ return summary;
}
return null;
}