/** * Copyright (c) 2021 OpenLens Authors * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import moment from "moment"; import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; import { autoBind } from "../../utils"; import { KubeApi } from "../kube-api"; import { metricsApi } from "./metrics.api"; import type { IPodMetrics } from "./pods.api"; import type { KubeJsonApiData } from "../kube-json-api"; export class DeploymentApi extends KubeApi { protected getScaleApiUrl(params: { namespace: string; name: string }) { return `${this.getUrl(params)}/scale`; } getReplicas(params: { namespace: string; name: string }): Promise { return this.request .get(this.getScaleApiUrl(params)) .then(({ status }: any) => status?.replicas); } scale(params: { namespace: string; name: string }, replicas: number) { return this.request.put(this.getScaleApiUrl(params), { data: { metadata: params, spec: { replicas } } }); } restart(params: { namespace: string; name: string }) { return this.request.patch(this.getUrl(params), { data: { spec: { template: { metadata: { annotations: {"kubectl.kubernetes.io/restartedAt" : moment.utc().format()} } } } } }, { headers: { "content-type": "application/strategic-merge-patch+json" } }); } } export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise { const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|"); const opts = { category: "pods", pods: podSelector, namespace, selector }; return metricsApi.getMetrics({ cpuUsage: opts, memoryUsage: opts, fsUsage: opts, networkReceive: opts, networkTransmit: opts, }, { namespace, }); } interface IContainerProbe { httpGet?: { path?: string; port: number; scheme: string; host?: string; }; exec?: { command: string[]; }; tcpSocket?: { port: number; }; initialDelaySeconds?: number; timeoutSeconds?: number; periodSeconds?: number; successThreshold?: number; failureThreshold?: number; } export class Deployment extends WorkloadKubeObject { static kind = "Deployment"; static namespaced = true; static apiBase = "/apis/apps/v1/deployments"; constructor(data: KubeJsonApiData) { super(data); autoBind(this); } declare spec: { replicas: number; selector: { matchLabels: { [app: string]: string } }; template: { metadata: { creationTimestamp?: string; labels: { [app: string]: string }; annotations?: { [app: string]: string }; }; spec: { containers: { name: string; image: string; args?: string[]; ports?: { name: string; containerPort: number; protocol: string; }[]; env?: { name: string; value: string; }[]; resources: { limits?: { cpu: string; memory: string; }; requests: { cpu: string; memory: string; }; }; volumeMounts?: { name: string; mountPath: string; }[]; livenessProbe?: IContainerProbe; readinessProbe?: IContainerProbe; startupProbe?: IContainerProbe; terminationMessagePath: string; terminationMessagePolicy: string; imagePullPolicy: string; }[]; restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; affinity?: IAffinity; nodeSelector?: { [selector: string]: string; }; serviceAccountName: string; serviceAccount: string; securityContext: {}; schedulerName: string; tolerations?: { key: string; operator: string; effect: string; tolerationSeconds: number; }[]; volumes?: { name: string; configMap: { name: string; defaultMode: number; optional: boolean; }; }[]; }; }; strategy: { type: string; rollingUpdate: { maxUnavailable: number; maxSurge: number; }; }; }; declare status: { observedGeneration: number; replicas: number; updatedReplicas: number; readyReplicas: number; availableReplicas?: number; unavailableReplicas?: number; conditions: { type: string; status: string; lastUpdateTime: string; lastTransitionTime: string; reason: string; message: string; }[]; }; getConditions(activeOnly = false) { const { conditions } = this.status; if (!conditions) return []; if (activeOnly) { return conditions.filter(c => c.status === "True"); } return conditions; } getConditionsText(activeOnly = true) { return this.getConditions(activeOnly).map(({ type }) => type).join(" "); } getReplicas() { return this.spec.replicas || 0; } } export const deploymentApi = new DeploymentApi({ objectConstructor: Deployment, });