From 46ff8a01bcbae5e0645cfa5be8655c767002f57e Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Fri, 22 Jan 2021 03:08:08 +0200 Subject: [PATCH] fix(kubernetes/pods): save note (#4675) * feat(kubernetes/pods): introduce patch api * feat(k8s/pods): pod converter * feat(kubernetes/pods): introduce patch api * feat(k8s/pod): add annotations only if needed * fix(k8s/pod): replace class with factory function --- app/kubernetes/pod/converter.js | 57 ++++++++++++++++++++++++++- app/kubernetes/pod/payloads/create.js | 45 +++++++++++++++++++++ app/kubernetes/pod/service.js | 25 ++++++++++++ app/kubernetes/rest/pod.js | 6 +++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 app/kubernetes/pod/payloads/create.js diff --git a/app/kubernetes/pod/converter.js b/app/kubernetes/pod/converter.js index 78e2edb8a..127d3760a 100644 --- a/app/kubernetes/pod/converter.js +++ b/app/kubernetes/pod/converter.js @@ -1,5 +1,16 @@ +import * as JsonPatch from 'fast-json-patch'; import _ from 'lodash-es'; -import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes } from 'Kubernetes/pod/models'; + +import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper'; +import { + KubernetesPortainerApplicationStackNameLabel, + KubernetesPortainerApplicationNameLabel, + KubernetesPortainerApplicationOwnerLabel, + KubernetesPortainerApplicationNote, +} from 'Kubernetes/models/application/models'; + +import { createPayloadFactory } from './payloads/create'; +import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes } from './models'; function computeStatus(statuses) { const containerStatuses = _.map(statuses, 'state'); @@ -104,4 +115,48 @@ export default class KubernetesPodConverter { res.Tolerations = computeTolerations(data.spec.tolerations); return res; } + + static patchPayload(oldPod, newPod) { + const oldPayload = createPayload(oldPod); + const newPayload = createPayload(newPod); + const payload = JsonPatch.compare(oldPayload, newPayload); + return payload; + } +} + +function createPayload(pod) { + const payload = createPayloadFactory(); + payload.metadata.name = pod.Name; + payload.metadata.namespace = pod.Namespace; + payload.metadata.labels[KubernetesPortainerApplicationStackNameLabel] = pod.StackName; + payload.metadata.labels[KubernetesPortainerApplicationNameLabel] = pod.ApplicationName; + payload.metadata.labels[KubernetesPortainerApplicationOwnerLabel] = pod.ApplicationOwner; + if (pod.Note) { + payload.metadata.annotations[KubernetesPortainerApplicationNote] = pod.Note; + } else { + payload.metadata.annotations = undefined; + } + + payload.spec.replicas = pod.ReplicaCount; + payload.spec.selector.matchLabels.app = pod.Name; + payload.spec.template.metadata.labels.app = pod.Name; + payload.spec.template.metadata.labels[KubernetesPortainerApplicationNameLabel] = pod.ApplicationName; + payload.spec.template.spec.containers[0].name = pod.Name; + payload.spec.template.spec.containers[0].image = pod.Image; + payload.spec.template.spec.affinity = pod.Affinity; + KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].env', pod.Env); + KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.containers[0].volumeMounts', pod.VolumeMounts); + KubernetesCommonHelper.assignOrDeleteIfEmpty(payload, 'spec.template.spec.volumes', pod.Volumes); + if (pod.MemoryLimit) { + payload.spec.template.spec.containers[0].resources.limits.memory = pod.MemoryLimit; + payload.spec.template.spec.containers[0].resources.requests.memory = pod.MemoryLimit; + } + if (pod.CpuLimit) { + payload.spec.template.spec.containers[0].resources.limits.cpu = pod.CpuLimit; + payload.spec.template.spec.containers[0].resources.requests.cpu = pod.CpuLimit; + } + if (!pod.CpuLimit && !pod.MemoryLimit) { + delete payload.spec.template.spec.containers[0].resources; + } + return payload; } diff --git a/app/kubernetes/pod/payloads/create.js b/app/kubernetes/pod/payloads/create.js new file mode 100644 index 000000000..6f00df970 --- /dev/null +++ b/app/kubernetes/pod/payloads/create.js @@ -0,0 +1,45 @@ +import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloads'; + +export function createPayloadFactory() { + return { + metadata: new KubernetesCommonMetadataPayload(), + spec: { + replicas: 0, + selector: { + matchLabels: { + app: '', + }, + }, + strategy: { + type: 'RollingUpdate', + rollingUpdate: { + maxSurge: 0, + maxUnavailable: '100%', + }, + }, + template: { + metadata: { + labels: { + app: '', + }, + }, + spec: { + affinity: {}, + containers: [ + { + name: '', + image: '', + env: [], + resources: { + limits: {}, + requests: {}, + }, + volumeMounts: [], + }, + ], + volumes: [], + }, + }, + }, + }; +} diff --git a/app/kubernetes/pod/service.js b/app/kubernetes/pod/service.js index 2aa3df981..af6359fb7 100644 --- a/app/kubernetes/pod/service.js +++ b/app/kubernetes/pod/service.js @@ -2,6 +2,7 @@ import angular from 'angular'; import PortainerError from 'Portainer/error'; import { KubernetesCommonParams } from 'Kubernetes/models/common/params'; +import KubernetesPodConverter from './converter'; class KubernetesPodService { /* @ngInject */ @@ -13,6 +14,7 @@ class KubernetesPodService { this.getAllAsync = this.getAllAsync.bind(this); this.logsAsync = this.logsAsync.bind(this); this.deleteAsync = this.deleteAsync.bind(this); + this.patchAsync = this.patchAsync.bind(this); } async getAsync(namespace, name) { @@ -74,6 +76,29 @@ class KubernetesPodService { return this.$async(this.logsAsync, namespace, podName, containerName); } + /** + * PATCH + */ + async patchAsync(oldPod, newPod) { + try { + const params = new KubernetesCommonParams(); + params.id = newPod.Name; + const namespace = newPod.Namespace; + const payload = KubernetesPodConverter.patchPayload(oldPod, newPod); + if (!payload.length) { + return; + } + const data = await this.KubernetesPods(namespace).patch(params, payload).$promise; + return data; + } catch (err) { + throw new PortainerError('Unable to patch pod', err); + } + } + + patch(oldPod, newPod) { + return this.$async(this.patchAsync, oldPod, newPod); + } + /** * DELETE */ diff --git a/app/kubernetes/rest/pod.js b/app/kubernetes/rest/pod.js index 000068e9c..281b698ce 100644 --- a/app/kubernetes/rest/pod.js +++ b/app/kubernetes/rest/pod.js @@ -31,6 +31,12 @@ angular.module('portainer.kubernetes').factory('KubernetesPods', [ create: { method: 'POST' }, update: { method: 'PUT' }, delete: { method: 'DELETE' }, + patch: { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }, logs: { method: 'GET', params: { action: 'log' },