diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
index 5d0ece02c..e90ef8362 100644
--- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
+++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
@@ -151,11 +151,14 @@
>{{ item.Image }} + {{ item.Containers.length - 1 }}
{{ item.ApplicationType | kubernetesApplicationTypeText }} |
-
+ |
Replicated
Global
- {{ item.RunningPodsCount }} / {{ item.TotalPodsCount }} |
+ {{ item.RunningPodsCount }}
/ {{ item.TotalPodsCount }}
+
+
+ {{ item.Pods[0].Status }}
+ |
diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js
index aaee33363..f195c2bc0 100644
--- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js
+++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatableController.js
@@ -1,4 +1,4 @@
-import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
+import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
@@ -42,6 +42,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
this.$onInit = function () {
this.isAdmin = Authentication.isAdmin();
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
+ this.KubernetesApplicationTypes = KubernetesApplicationTypes;
this.setDefaults();
this.prepareTableFromDataset();
diff --git a/app/kubernetes/converters/application.js b/app/kubernetes/converters/application.js
index 56cfdf2bb..8e603cdf0 100644
--- a/app/kubernetes/converters/application.js
+++ b/app/kubernetes/converters/application.js
@@ -50,7 +50,7 @@ function _apiPortsToPublishedPorts(pList, pRefs) {
class KubernetesApplicationConverter {
static applicationCommon(res, data, pods, service, ingresses) {
- const containers = _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined);
+ const containers = data.spec.template ? _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined) : data.spec.containers;
res.Id = data.metadata.uid;
res.Name = data.metadata.name;
res.StackName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationStackNameLabel] || '-' : '-';
@@ -61,7 +61,7 @@ class KubernetesApplicationConverter {
res.Image = containers[0].image;
res.CreationDate = data.metadata.creationTimestamp;
res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
- res.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, data);
+ res.Pods = data.spec.selector ? KubernetesApplicationHelper.associatePodsAndApplication(pods, data.spec.selector) : [data];
const limits = {
Cpu: 0,
@@ -118,7 +118,11 @@ class KubernetesApplicationConverter {
res.PublishedPorts = ports;
}
- res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : [];
+ if (data.spec.templates) {
+ res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : [];
+ } else {
+ res.Volumes = data.spec.volumes;
+ }
// TODO: review
// this if() fixs direct use of PVC reference inside spec.template.spec.containers[0].volumeMounts
@@ -169,7 +173,7 @@ class KubernetesApplicationConverter {
res.PersistedFolders = _.without(res.PersistedFolders, undefined);
res.ConfigurationVolumes = _.reduce(
- data.spec.template.spec.volumes,
+ res.Volumes,
(acc, volume) => {
if (volume.configMap || volume.secret) {
const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
@@ -213,6 +217,17 @@ class KubernetesApplicationConverter {
);
}
+ static apiPodToApplication(data, pods, service, ingresses) {
+ const res = new KubernetesApplication();
+ KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
+ res.ApplicationType = KubernetesApplicationTypes.POD;
+ // res.DeploymentType = KubernetesApplicationDeploymentTypes.REPLICATED;
+ // res.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED;
+ // res.RunningPodsCount = data.status.availableReplicas || data.status.replicas - data.status.unavailableReplicas || 0;
+ // res.TotalPodsCount = data.spec.replicas;
+ return res;
+ }
+
static apiDeploymentToApplication(data, pods, service, ingresses) {
const res = new KubernetesApplication();
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
@@ -310,7 +325,7 @@ class KubernetesApplicationConverter {
} else if (daemonSet) {
app = KubernetesDaemonSetConverter.applicationFormValuesToDaemonSet(formValues, claims);
} else {
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use to convert form');
}
let headlessService;
diff --git a/app/kubernetes/filters/applicationFilters.js b/app/kubernetes/filters/applicationFilters.js
index 0629c12ff..0ce2d3831 100644
--- a/app/kubernetes/filters/applicationFilters.js
+++ b/app/kubernetes/filters/applicationFilters.js
@@ -57,6 +57,8 @@ angular
return KubernetesApplicationTypeStrings.DAEMONSET;
case KubernetesApplicationTypes.STATEFULSET:
return KubernetesApplicationTypeStrings.STATEFULSET;
+ case KubernetesApplicationTypes.POD:
+ return KubernetesApplicationTypeStrings.POD;
default:
return '-';
}
diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js
index 9bc898fca..3124f4247 100644
--- a/app/kubernetes/helpers/application/index.js
+++ b/app/kubernetes/helpers/application/index.js
@@ -38,8 +38,8 @@ class KubernetesApplicationHelper {
return !application.ApplicationOwner;
}
- static associatePodsAndApplication(pods, app) {
- return _.filter(pods, { Labels: app.spec.selector.matchLabels });
+ static associatePodsAndApplication(pods, selector) {
+ return _.filter(pods, ['metadata.labels', selector.matchLabels]);
}
static associateContainerPersistedFoldersAndConfigurations(app, containers) {
diff --git a/app/kubernetes/helpers/application/rollback.js b/app/kubernetes/helpers/application/rollback.js
index f593e0cf5..7f110e8f8 100644
--- a/app/kubernetes/helpers/application/rollback.js
+++ b/app/kubernetes/helpers/application/rollback.js
@@ -20,7 +20,7 @@ class KubernetesApplicationRollbackHelper {
result = KubernetesApplicationRollbackHelper._getStatefulSetPayload(application, targetRevision);
break;
default:
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use to convert patch');
}
return result;
}
diff --git a/app/kubernetes/helpers/history/index.js b/app/kubernetes/helpers/history/index.js
index d611644a0..5f8851ab4 100644
--- a/app/kubernetes/helpers/history/index.js
+++ b/app/kubernetes/helpers/history/index.js
@@ -21,7 +21,7 @@ class KubernetesHistoryHelper {
[currentRevision, revisionsList] = KubernetesHistoryHelper._getStatefulSetRevisions(rawRevisions, application.Raw);
break;
default:
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use to get revisions');
}
revisionsList = _.sortBy(revisionsList, 'revision');
return [currentRevision, revisionsList];
diff --git a/app/kubernetes/helpers/serviceHelper.js b/app/kubernetes/helpers/serviceHelper.js
index c263a3766..247e4a441 100644
--- a/app/kubernetes/helpers/serviceHelper.js
+++ b/app/kubernetes/helpers/serviceHelper.js
@@ -7,6 +7,9 @@ class KubernetesServiceHelper {
}
static findApplicationBoundService(services, rawApp) {
+ if (!rawApp.spec.template) {
+ return undefined;
+ }
return _.find(services, (item) => item.spec.selector && _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector));
}
}
diff --git a/app/kubernetes/horizontal-pod-auto-scaler/helper.js b/app/kubernetes/horizontal-pod-auto-scaler/helper.js
index 663c3baf8..44bc10c49 100644
--- a/app/kubernetes/horizontal-pod-auto-scaler/helper.js
+++ b/app/kubernetes/horizontal-pod-auto-scaler/helper.js
@@ -18,7 +18,8 @@ export class KubernetesHorizontalPodAutoScalerHelper {
return KubernetesApplicationTypeStrings.DAEMONSET;
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET) || app instanceof KubernetesStatefulSet) {
return KubernetesApplicationTypeStrings.STATEFULSET;
- // } else if () { ---> TODO: refactor - handle bare pod type !
+ } else if (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.POD) {
+ return KubernetesApplicationTypeStrings.POD;
} else {
throw new PortainerError('Unable to determine application type');
}
diff --git a/app/kubernetes/models/application/models/constants.js b/app/kubernetes/models/application/models/constants.js
index 2d42bba05..970cb3557 100644
--- a/app/kubernetes/models/application/models/constants.js
+++ b/app/kubernetes/models/application/models/constants.js
@@ -12,12 +12,14 @@ export const KubernetesApplicationTypes = Object.freeze({
DEPLOYMENT: 1,
DAEMONSET: 2,
STATEFULSET: 3,
+ POD: 4,
});
export const KubernetesApplicationTypeStrings = Object.freeze({
DEPLOYMENT: 'Deployment',
DAEMONSET: 'DaemonSet',
STATEFULSET: 'StatefulSet',
+ POD: 'Pod',
});
export const KubernetesApplicationPublishingTypes = Object.freeze({
diff --git a/app/kubernetes/models/common/params.js b/app/kubernetes/models/common/params.js
index fd37b8764..5c7de8d34 100644
--- a/app/kubernetes/models/common/params.js
+++ b/app/kubernetes/models/common/params.js
@@ -1,11 +1,8 @@
/**
* Generic params
*/
-const _KubernetesCommonParams = Object.freeze({
- id: '',
-});
-export class KubernetesCommonParams {
- constructor() {
- Object.assign(this, JSON.parse(JSON.stringify(_KubernetesCommonParams)));
- }
+export function KubernetesCommonParams() {
+ return {
+ id: '',
+ };
}
diff --git a/app/kubernetes/pod/service.js b/app/kubernetes/pod/service.js
index c2de34a2e..2aa3df981 100644
--- a/app/kubernetes/pod/service.js
+++ b/app/kubernetes/pod/service.js
@@ -1,9 +1,7 @@
-import _ from 'lodash-es';
import angular from 'angular';
import PortainerError from 'Portainer/error';
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
-import KubernetesPodConverter from 'Kubernetes/pod/converter';
class KubernetesPodService {
/* @ngInject */
@@ -11,23 +9,43 @@ class KubernetesPodService {
this.$async = $async;
this.KubernetesPods = KubernetesPods;
+ this.getAsync = this.getAsync.bind(this);
this.getAllAsync = this.getAllAsync.bind(this);
this.logsAsync = this.logsAsync.bind(this);
this.deleteAsync = this.deleteAsync.bind(this);
}
+
+ async getAsync(namespace, name) {
+ try {
+ const params = new KubernetesCommonParams();
+ params.id = name;
+ const [raw, yaml] = await Promise.all([this.KubernetesPods(namespace).get(params).$promise, this.KubernetesPods(namespace).getYaml(params).$promise]);
+ const res = {
+ Raw: raw,
+ Yaml: yaml.data,
+ };
+ return res;
+ } catch (err) {
+ throw new PortainerError('Unable to retrieve pod', err);
+ }
+ }
+
/**
* GET ALL
*/
async getAllAsync(namespace) {
try {
const data = await this.KubernetesPods(namespace).get().$promise;
- return _.map(data.items, (item) => KubernetesPodConverter.apiToModel(item));
+ return data.items;
} catch (err) {
throw new PortainerError('Unable to retrieve pods', err);
}
}
- get(namespace) {
+ get(namespace, name) {
+ if (name) {
+ return this.$async(this.getAsync, namespace, name);
+ }
return this.$async(this.getAllAsync, namespace);
}
diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js
index 55bfa9908..075adb91f 100644
--- a/app/kubernetes/services/applicationService.js
+++ b/app/kubernetes/services/applicationService.js
@@ -18,6 +18,7 @@ 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';
+import KubernetesPodConverter from 'Kubernetes/pod/converter';
class KubernetesApplicationService {
/* #region CONSTRUCTOR */
@@ -71,7 +72,7 @@ class KubernetesApplicationService {
} else if (app instanceof KubernetesStatefulSet || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET)) {
apiService = this.KubernetesStatefulSetService;
} else {
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use to retrieve API Service');
}
return apiService;
}
@@ -87,15 +88,18 @@ class KubernetesApplicationService {
/* #region GET */
async getAsync(namespace, name) {
try {
- const [deployment, daemonSet, statefulSet, pods, autoScalers, ingresses] = await Promise.allSettled([
+ const [deployment, daemonSet, statefulSet, pod, 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, name),
this.KubernetesPodService.get(namespace),
this.KubernetesHorizontalPodAutoScalerService.get(namespace),
this.KubernetesIngressService.get(namespace),
]);
+ // const pod = _.find(pods.value, ['metadata.namespace', namespace, 'metadata.name', name]);
+
let rootItem;
let converterFunc;
if (deployment.status === 'fulfilled') {
@@ -107,8 +111,11 @@ class KubernetesApplicationService {
} else if (statefulSet.status === 'fulfilled') {
rootItem = statefulSet;
converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
+ } else if (pod.status === 'fulfilled') {
+ rootItem = pod;
+ converterFunc = KubernetesApplicationConverter.apiPodToApplication;
} else {
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use to convert application');
}
const services = await this.KubernetesServiceService.get(namespace);
@@ -118,6 +125,7 @@ class KubernetesApplicationService {
const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
application.Yaml = rootItem.value.Yaml;
application.Raw = rootItem.value.Raw;
+ application.Pods = _.map(application.Pods, (item) => KubernetesPodConverter.apiToModel(item));
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
@@ -173,7 +181,14 @@ class KubernetesApplicationService {
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
);
- const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications);
+ const boundPods = _.concat(_.flatMap(deploymentApplications, 'Pods'), _.flatMap(daemonSetApplications, 'Pods'), _.flatMap(statefulSetApplications, 'Pods'));
+ const unboundPods = _.without(pods, ...boundPods);
+ const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
+
+ const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
+ _.forEach(applications, (app) => {
+ app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
+ });
await Promise.all(
_.forEach(applications, async (application) => {
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
diff --git a/app/kubernetes/services/historyService.js b/app/kubernetes/services/historyService.js
index df0284166..b00bf7f3e 100644
--- a/app/kubernetes/services/historyService.js
+++ b/app/kubernetes/services/historyService.js
@@ -32,13 +32,17 @@ class KubernetesHistoryService {
case KubernetesApplicationTypes.STATEFULSET:
rawRevisions = await this.KubernetesControllerRevisionService.get(namespace);
break;
+ case KubernetesApplicationTypes.POD:
+ rawRevisions = [];
+ break;
default:
- throw new PortainerError('Unable to determine which association to use');
+ throw new PortainerError('Unable to determine which association to use for history');
+ }
+ if (rawRevisions.length) {
+ const [currentRevision, revisionsList] = KubernetesHistoryHelper.getRevisions(rawRevisions, application);
+ application.CurrentRevision = currentRevision;
+ application.Revisions = revisionsList;
}
-
- const [currentRevision, revisionsList] = KubernetesHistoryHelper.getRevisions(rawRevisions, application);
- application.CurrentRevision = currentRevision;
- application.Revisions = revisionsList;
return application;
} catch (err) {
throw new PortainerError('', err);
diff --git a/app/portainer/views/init/endpoint/initEndpointController.js b/app/portainer/views/init/endpoint/initEndpointController.js
index 15a2faa87..c5a39a737 100644
--- a/app/portainer/views/init/endpoint/initEndpointController.js
+++ b/app/portainer/views/init/endpoint/initEndpointController.js
@@ -61,7 +61,7 @@ class InitEndpointController {
case PortainerEndpointConnectionTypes.AGENT:
return this.createAgentEndpoint();
default:
- this.Notifications.error('Failure', 'Unable to determine which action to do');
+ this.Notifications.error('Failure', 'Unable to determine which action to do to create endpoint');
}
}
|