chore: Extract @k8slens/kube-object package

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2023-05-02 10:11:52 -07:00
1 parent 576373b16b
commit 1bf24db797
466 files changed
+6838 -6375

No files matched your search

@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import replicationControllersRouteInjectable from "./replicationcontrollers-route.injectable";
import replicationControllersRouteInjectable from "./route.injectable";
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
const navigateToReplicationControllersInjectable = getInjectable({
@@ -7,10 +7,10 @@ import { shouldShowResourceInjectionToken } from "../../../../../../features/clu
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
const replicationControllersRouteInjectable = getInjectable({
id: "replicationcontrollers-route",
id: "replication-controllers-route",
instantiate: (di) => ({
path: "/replicationcontrollers",
path: "/replication-controllers",
clusterFrame: true,
isEnabled: di.inject(shouldShowResourceInjectionToken, {
apiName: "replicationcontrollers",
@@ -15,7 +15,7 @@ import loggerInjectable from "../../logger.injectable";
import type { ApiManager } from "../api-manager";
import apiManagerInjectable from "../api-manager/manager.injectable";
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectStore } from "../kube-object.store";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
@@ -27,8 +27,8 @@ import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-ap
import assert from "assert";
class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() {
return;
protected checkPreferredVersion() {
return Promise.resolve();
}
}
@@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { CustomResourceDefinitionSpec } from "../endpoints";
import { CustomResourceDefinition } from "../endpoints";
import type { CustomResourceDefinitionSpec } from "@k8slens/kube-object";
import { CustomResourceDefinition } from "@k8slens/kube-object";
describe("Crds", () => {
describe("getVersion()", () => {
@@ -33,7 +33,7 @@ describe("DeploymentApi", () => {
describe("scale", () => {
it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => {
deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5);
void deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5);
expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/deployments/deployment-1/scale", {
data: {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { formatEndpointSubset } from "../endpoints";
import { formatEndpointSubset } from "@k8slens/kube-object";
describe("endpoint tests", () => {
describe("EndpointSubset", () => {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { computeRuleDeclarations, Ingress } from "../endpoints";
import { computeRuleDeclarations, Ingress } from "@k8slens/kube-object";
describe("Ingress", () => {
it("given no loadbalancer ingresses in status property, loadbalancers should be an empty array", () => {
@@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
jest.mock("../kube-object");
jest.mock("../kube-api");
jest.mock("../api-manager", () => ({
apiManager() {
@@ -4,7 +4,8 @@
*/
import type { ApiManager } from "../api-manager";
import type { IngressApi } from "../endpoints";
import { Ingress, HorizontalPodAutoscalerApi } from "../endpoints";
import { HorizontalPodAutoscalerApi } from "../endpoints";
import { Ingress } from "@k8slens/kube-object";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable";
@@ -4,38 +4,27 @@
*/
import type { KubeApiWatchCallback } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import { PassThrough } from "stream";
import type { DeploymentApi, NamespaceApi } from "../endpoints";
import { Deployment, Pod, PodApi } from "../endpoints";
import { PodApi } from "../endpoints";
import type { DeploymentApi, NamespaceApi } from "../endpoints";
import { Deployment, Pod } from "@k8slens/kube-object";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable";
import type {
CreateKubeApiForRemoteCluster,
} from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable
from "../create-kube-api-for-remote-cluster.injectable";
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "@k8slens/test-utils";
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
import type { IKubeWatchEvent } from "../kube-watch-event";
import type { KubeJsonApiDataFor, KubeStatusData } from "../kube-object";
import setupAutoRegistrationInjectable
from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
import {
createMockResponseFromStream,
createMockResponseFromString,
} from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable
from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable
from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import hostedClusterInjectable
from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable
from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import type { KubeJsonApiDataFor, KubeStatusData, KubeJsonApiData } from "@k8slens/kube-object";
import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import deploymentApiInjectable from "../endpoints/deployment.api.injectable";
@@ -51,7 +40,7 @@ describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
let fetchMock: AsyncFnMock<Fetch>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
@@ -70,7 +59,7 @@ describe("createKubeApiForRemoteCluster", () => {
createKubeApiForRemoteCluster = di.inject(createKubeApiForRemoteClusterInjectable);
});
it("builds api client for KubeObject", async () => {
it("builds api client for KubeObject", () => {
const api = createKubeApiForRemoteCluster({
cluster: {
server: "https://127.0.0.1:6443",
@@ -150,7 +139,7 @@ describe("KubeApi", () => {
let fetchMock: AsyncFnMock<Fetch>;
let di: DiContainer;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
@@ -374,7 +363,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
@@ -410,7 +399,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
@@ -446,7 +435,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background", "{}"),
);
@@ -461,7 +450,7 @@ describe("KubeApi", () => {
describe("eviction-api as better replacement for pod.delete() request", () => {
let evictRequest: Promise<string>;
beforeEach(async () => {
beforeEach(() => {
evictRequest = api.evict({ name: "foo", namespace: "test" });
});
@@ -478,7 +467,7 @@ describe("KubeApi", () => {
});
it("should resolve the call with >=200 <300 http code", async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({
apiVersion: "policy/v1",
@@ -492,7 +481,7 @@ describe("KubeApi", () => {
});
it("should throw in case of error", async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({
apiVersion: "policy/v1",
@@ -502,12 +491,12 @@ describe("KubeApi", () => {
} as KubeStatusData)),
);
expect(async () => evictRequest).rejects.toBe("500: something went wrong");
await expect(async () => evictRequest).rejects.toBe("500: something went wrong");
});
});
});
describe("deleting namespaces (cluser scoped resource)", () => {
describe("deleting namespaces (cluster scoped resource)", () => {
let api: NamespaceApi;
beforeEach(() => {
@@ -538,7 +527,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
@@ -574,7 +563,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
@@ -587,8 +576,8 @@ describe("KubeApi", () => {
});
describe("when deleting by name and namespace", () => {
it("rejects request", () => {
expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined();
it("rejects request", async () => {
await expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined();
});
});
});
@@ -5,7 +5,7 @@
import { noop } from "@k8slens/utilities";
import type { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
import type { KubeObjectStoreLoadingParams } from "../kube-object.store";
import { KubeObjectStore } from "../kube-object.store";
@@ -30,7 +30,7 @@ class FakeKubeObjectStore extends KubeObjectStore<KubeObject> {
}
async loadItems(params: KubeObjectStoreLoadingParams) {
return this._loadItems(params);
return Promise.resolve(this._loadItems(params));
}
}
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
describe("KubeObject", () => {
describe("isJsonApiData", () => {
@@ -1,192 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Node } from "../endpoints";
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
describe("Node tests", () => {
describe("isMasterNode()", () => {
it("given a master node labelled before kubernetes 1.20, should return true", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/master": "NoSchedule",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(true);
});
it("given a master node labelled after kubernetes 1.20, should return true", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/control-plane": "NoSchedule",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(true);
});
it("given a non master node, should return false", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.isMasterNode()).toBe(false);
});
});
describe("getRoleLabels()", () => {
it("should return empty string if labels is not present", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("");
});
it("should return empty string if labels is empty object", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("");
});
it("should return rest of keys with substring node-role.kubernetes.io/", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/foobar": "bat",
"hellonode-role.kubernetes.io/foobar1": "bat",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("foobar, foobar1");
});
it("should return rest of keys with substring node-role.kubernetes.io/ after last /", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node-role.kubernetes.io/foobar": "bat",
"hellonode-role.kubernetes.io//////foobar1": "bat",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("foobar, foobar1");
});
it("should return value of label kubernetes.io/role if present", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"kubernetes.io/role": "master",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("master");
});
it("should return value of label node.kubernetes.io/role if present", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"node.kubernetes.io/role": "master",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("master");
});
it("all sources should be joined together", () => {
const node = new Node({
apiVersion: "foo",
kind: "Node",
metadata: {
name: "bar",
resourceVersion: "1",
uid: "bat",
labels: {
"aksjhdkjahsdnode-role.kubernetes.io/foobar": "bat",
"kubernetes.io/role": "master",
"node.kubernetes.io/role": "master-v2-max",
},
selfLink: "/api/v1/nodes/bar",
},
});
expect(node.getRoleLabels()).toBe("foobar, master, master-v2-max");
});
});
});
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Pod } from "../endpoints";
import { Pod } from "@k8slens/kube-object";
describe("Pod tests", () => {
it("getAllContainers() should never throw", () => {
@@ -1,318 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import assert from "assert";
import type { Container, PodContainerStatus } from "../endpoints";
import { Pod } from "../endpoints";
interface GetDummyPodOptions {
running?: number;
dead?: number;
initRunning?: number;
initDead?: number;
}
function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
const {
running = 0,
dead = 0,
initDead = 0,
initRunning = 0,
} = rawOpts;
const containers: Container[] = [];
const initContainers: Container[] = [];
const containerStatuses: PodContainerStatus[] = [];
const initContainerStatuses: PodContainerStatus[] = [];
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
uid: "1",
name: "test",
resourceVersion: "v1",
namespace: "default",
selfLink: "/api/v1/pods/default/test",
},
spec: {
containers,
initContainers,
serviceAccount: "dummy",
serviceAccountName: "dummy",
},
status: {
phase: "Running",
conditions: [],
hostIP: "10.0.0.1",
podIP: "10.0.0.1",
startTime: "now",
containerStatuses,
initContainerStatuses,
},
});
for (let i = 0; i < running; i += 1) {
const name = `container_running_${i}`;
containers.push({
image: "dummy",
imagePullPolicy: "Always",
name,
});
containerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: true,
restartCount: i,
state: {
running: {
startedAt: "before",
},
},
});
}
for (let i = 0; i < dead; i += 1) {
const name = `container_dead_${i}`;
containers.push({
image: "dummy",
imagePullPolicy: "Always",
name,
});
containerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: false,
restartCount: i,
state: {
terminated: {
startedAt: "before",
exitCode: i+1,
finishedAt: "later",
reason: `reason_${i}`,
},
},
});
}
for (let i = 0; i < initRunning; i += 1) {
const name = `container_init-running_${i}`;
initContainers.push({
image: "dummy",
imagePullPolicy: "Always",
name,
});
initContainerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: true,
restartCount: i,
state: {
running: {
startedAt: "before",
},
},
});
}
for (let i = 0; i < initDead; i += 1) {
const name = `container_init-dead_${i}`;
initContainers.push({
image: "dummy",
imagePullPolicy: "Always",
name,
});
initContainerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: false,
restartCount: i,
state: {
terminated: {
startedAt: "before",
exitCode: i+1,
finishedAt: "later",
reason: `reason_${i}`,
},
},
});
}
return pod;
}
describe("Pods", () => {
const podTests = [];
for (let r = 0; r < 3; r += 1) {
for (let d = 0; d < 3; d += 1) {
for (let ir = 0; ir < 3; ir += 1) {
for (let id = 0; id < 3; id += 1) {
podTests.push([r, d, ir, id]);
}
}
}
}
describe.each(podTests)("for [%d running, %d dead] & initial [%d running, %d dead]", (running, dead, initRunning, initDead) => {
const pod = getDummyPod({ running, dead, initRunning, initDead });
function getNamedContainer(name: string) {
return {
image: "dummy",
imagePullPolicy: "Always",
name,
};
}
it("getRunningContainers should return only running and init running", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
];
expect(pod.getRunningContainers()).toStrictEqual(res);
});
it("getAllContainers should return all containers", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_dead_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_init-dead_${index}`)),
];
expect(pod.getAllContainers()).toStrictEqual(res);
});
it("getRestartsCount should return total restart counts", () => {
function sum(len: number): number {
let res = 0;
for (let i = 0; i < len; i += 1) {
res += i;
}
return res;
}
expect(pod.getRestartsCount()).toStrictEqual(sum(running) + sum(dead));
});
it("hasIssues should return false", () => {
expect(pod.hasIssues()).toStrictEqual(false);
});
});
describe("getSelectedNodeOs", () => {
it("should return stable", () => {
const pod = getDummyPod();
pod.spec.nodeSelector = {
"kubernetes.io/os": "foobar",
};
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar");
});
it("should return beta", () => {
const pod = getDummyPod();
pod.spec.nodeSelector = {
"beta.kubernetes.io/os": "foobar1",
};
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar1");
});
it("should return stable over beta", () => {
const pod = getDummyPod();
pod.spec.nodeSelector = {
"kubernetes.io/os": "foobar2",
"beta.kubernetes.io/os": "foobar3",
};
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar2");
});
it("should return undefined if none set", () => {
const pod = getDummyPod();
expect(pod.getSelectedNodeOs()).toStrictEqual(undefined);
});
});
describe("hasIssues", () => {
it("should return true if a condition isn't ready", () => {
const pod = getDummyPod({ running: 1 });
pod.status?.conditions.push({
type: "Ready",
status: "foobar",
lastProbeTime: 1,
lastTransitionTime: "longer ago",
});
expect(pod.hasIssues()).toStrictEqual(true);
});
it("should return false if a condition is non-ready", () => {
const pod = getDummyPod({ running: 1 });
pod.status?.conditions.push({
type: "dummy",
status: "foobar",
lastProbeTime: 1,
lastTransitionTime: "longer ago",
});
expect(pod.hasIssues()).toStrictEqual(false);
});
it("should return true if a current container is in a crash loop back off", () => {
const pod = getDummyPod({ running: 1 });
const firstStatus = pod.status?.containerStatuses?.[0];
assert(firstStatus);
firstStatus.state = {
waiting: {
reason: "CrashLookBackOff",
message: "too much foobar",
},
};
expect(pod.hasIssues()).toStrictEqual(true);
});
it("should return true if a current phase isn't running", () => {
const pod = getDummyPod({ running: 1 });
assert(pod.status);
pod.status.phase = "not running";
expect(pod.hasIssues()).toStrictEqual(true);
});
it("should return false if a current phase is running", () => {
const pod = getDummyPod({ running: 1 });
assert(pod.status);
pod.status.phase = "Running";
expect(pod.hasIssues()).toStrictEqual(false);
});
});
});
@@ -8,7 +8,7 @@ import type { KubeObjectStore } from "../kube-object.store";
import type { IComputedValue } from "mobx";
import { autorun, action, observable } from "mobx";
import type { KubeApi } from "../kube-api";
import type { KubeObject, ObjectReference } from "../kube-object";
import type { KubeObject, ObjectReference } from "@k8slens/kube-object";
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
import { getOrInsertWith, iter } from "@k8slens/utilities";
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";
@@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
import loggerInjectable from "../../logger.injectable";
import type { KubeApi } from "../kube-api";
import type { KubeObject } from "../kube-object";
import type { KubeObject } from "@k8slens/kube-object";
import type { KubeObjectStoreDependencies } from "../kube-object.store";
import { CustomResourceStore } from "./resource.store";
@@ -6,7 +6,7 @@
import type { KubeApi } from "../kube-api";
import type { KubeObjectStoreDependencies } from "../kube-object.store";
import { KubeObjectStore } from "../kube-object.store";
import type { KubeObject } from "../kube-object";
import type { KubeObject } from "@k8slens/kube-object";
export class CustomResourceStore<K extends KubeObject> extends KubeObjectStore<K, KubeApi<K>> {
constructor(deps: KubeObjectStoreDependencies, api: KubeApi<K>) {
@@ -10,7 +10,7 @@ import apiBaseInjectable from "./api-base.injectable";
import type { KubeApiConstructor } from "./create-kube-api-for-remote-cluster.injectable";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object";
export interface CreateKubeApiForLocalClusterConfig {
metadata: {
@@ -11,7 +11,7 @@ import isDevelopmentInjectable from "../vars/is-development.injectable";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import type { KubeApiOptions } from "./kube-api";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object";
export interface CreateKubeApiForRemoteClusterConfig {
cluster: {
@@ -4,47 +4,8 @@
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleRef } from "./types/role-ref";
import type { Subject } from "./types/subject";
export interface ClusterRoleBindingData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
subjects?: Subject[];
roleRef: RoleRef;
}
export class ClusterRoleBinding extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static kind = "ClusterRoleBinding";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings";
subjects?: Subject[];
roleRef: RoleRef;
constructor({
subjects,
roleRef,
...rest
}: ClusterRoleBindingData) {
super(rest);
this.subjects = subjects;
this.roleRef = roleRef;
}
getSubjects() {
return this.subjects ?? [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
import type { ClusterRoleBindingData } from "@k8slens/kube-object";
import { ClusterRoleBinding } from "@k8slens/kube-object";
export class ClusterRoleBindingApi extends KubeApi<ClusterRoleBinding, ClusterRoleBindingData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -5,39 +5,8 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { AggregationRule } from "./types/aggregation-rule";
import type { PolicyRule } from "./types/policy-rule";
export interface ClusterRoleData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
}
export class ClusterRole extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static kind = "ClusterRole";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles";
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
constructor({ rules, aggregationRule, ...rest }: ClusterRoleData) {
super(rest);
this.rules = rules;
this.aggregationRule = aggregationRule;
}
getRules() {
return this.rules || [];
}
}
import type { ClusterRoleData } from "@k8slens/kube-object";
import { ClusterRole } from "@k8slens/kube-object";
export class ClusterRoleApi extends KubeApi<ClusterRole, ClusterRoleData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { Cluster } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
@@ -25,58 +25,3 @@ export class ClusterApi extends KubeApi<Cluster> {
});
}
}
export enum ClusterStatus {
ACTIVE = "Active",
CREATING = "Creating",
REMOVING = "Removing",
ERROR = "Error",
}
export interface Cluster {
spec: {
clusterNetwork?: {
serviceDomain?: string;
pods?: {
cidrBlocks?: string[];
};
services?: {
cidrBlocks?: string[];
};
};
providerSpec: {
value: {
profile: string;
};
};
};
status?: {
apiEndpoints: {
host: string;
port: string;
}[];
providerStatus: {
adminUser?: string;
adminPassword?: string;
kubeconfig?: string;
processState?: string;
lensAddress?: string;
};
errorMessage?: string;
errorReason?: string;
};
}
export class Cluster extends KubeObject {
static kind = "Cluster";
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
static namespaced = true;
getStatus() {
if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING;
if (!this.status || !this.status) return ClusterStatus.CREATING;
if (this.status.errorMessage) return ClusterStatus.ERROR;
return ClusterStatus.ACTIVE;
}
}
@@ -3,30 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { ComponentStatus } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface ComponentStatusCondition {
type: string;
status: string;
message: string;
}
export interface ComponentStatus {
conditions: ComponentStatusCondition[];
}
export class ComponentStatus extends KubeObject {
static kind = "ComponentStatus";
static namespaced = false;
static apiBase = "/api/v1/componentstatuses";
getTruthyConditions() {
return this.conditions.filter(c => c.status === "True");
}
}
export class ComponentStatusApi extends KubeApi<ComponentStatus> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {
@@ -3,45 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ConfigMapData } from "@k8slens/kube-object";
import { ConfigMap } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import autoBind from "auto-bind";
export interface ConfigMapData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
data?: Partial<Record<string, string>>;
binaryData?: Partial<Record<string, string>>;
immutable?: boolean;
}
export class ConfigMap extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "ConfigMap";
static namespaced = true;
static apiBase = "/api/v1/configmaps";
data: Partial<Record<string, string>>;
binaryData: Partial<Record<string, string>>;
immutable?: boolean;
constructor({ data, binaryData, immutable, ...rest }: ConfigMapData) {
super(rest);
autoBind(this);
this.data = data ?? {};
this.binaryData = binaryData ?? {};
this.immutable = immutable;
}
getKeys(): string[] {
return Object.keys(this.data);
}
}
export class ConfigMapApi extends KubeApi<ConfigMap, ConfigMapData> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -3,13 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import type { NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { formatDuration } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { CronJob } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { JobTemplateSpec } from "./types/job-template-spec";
export class CronJobApi extends KubeApi<CronJob> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions) {
@@ -19,90 +15,19 @@ export class CronJobApi extends KubeApi<CronJob> {
});
}
suspend(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
suspend: true,
},
private requestSetSuspend(params: NamespacedResourceDescriptor, suspend: boolean) {
return this.patch(params, {
spec: {
suspend,
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}, "strategic");
}
resume(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
suspend: false,
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
suspend(params: NamespacedResourceDescriptor) {
return this.requestSetSuspend(params, true);
}
}
export interface CronJobSpec {
concurrencyPolicy?: string;
failedJobsHistoryLimit?: number;
jobTemplate?: JobTemplateSpec;
schedule: string;
startingDeadlineSeconds?: number;
successfulJobsHistoryLimit?: number;
suspend?: boolean;
}
export interface CronJobStatus {
lastScheduleTime?: string;
lastSuccessfulTime?: string;
active?: ObjectReference[];
}
export class CronJob extends KubeObject<
NamespaceScopedMetadata,
CronJobStatus,
CronJobSpec
> {
static readonly kind = "CronJob";
static readonly namespaced = true;
static readonly apiBase = "/apis/batch/v1/cronjobs";
getSuspendFlag() {
return (this.spec.suspend ?? false).toString();
}
getLastScheduleTime() {
if (!this.status?.lastScheduleTime) return "-";
const diff = moment().diff(this.status.lastScheduleTime);
return formatDuration(diff, true);
}
getSchedule() {
return this.spec.schedule;
}
isNeverRun() {
const schedule = this.getSchedule();
const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const stamps = schedule.split(" ");
const day = Number(stamps[stamps.length - 3]); // 1-31
const month = Number(stamps[stamps.length - 2]); // 1-12
if (schedule.startsWith("@")) return false;
return day > daysInMonth[month - 1];
}
isSuspend() {
return this.spec.suspend;
resume(params: NamespacedResourceDescriptor) {
return this.requestSetSuspend(params, false);
}
}
@@ -3,235 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di";
import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
import { buildURL } from "@k8slens/utilities";
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { CustomResourceDefinition } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { JSONSchemaProps } from "./types/json-schema-props";
interface AdditionalPrinterColumnsCommon {
name: string;
type: "integer" | "number" | "string" | "boolean" | "date";
priority?: number;
format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password";
description?: string;
}
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
jsonPath: string;
};
type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
JSONPath: string;
};
export interface CustomResourceValidation {
openAPIV3Schema?: JSONSchemaProps;
}
export interface CustomResourceDefinitionVersion {
name: string;
served: boolean;
storage: boolean;
schema?: CustomResourceValidation; // required in v1 but not present in v1beta
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
}
export interface CustomResourceDefinitionNames {
categories?: string[];
kind: string;
listKind?: string;
plural: string;
shortNames?: string[];
singular?: string;
}
export interface CustomResourceConversion {
strategy?: string;
webhook?: WebhookConversion;
}
export interface WebhookConversion {
clientConfig?: WebhookClientConfig[];
conversionReviewVersions: string[];
}
export interface WebhookClientConfig {
caBundle?: string;
url?: string;
service?: ServiceReference;
}
export interface ServiceReference {
name: string;
namespace: string;
path?: string;
port?: number;
}
export interface CustomResourceDefinitionSpec {
group: string;
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
version?: string;
names: CustomResourceDefinitionNames;
scope: "Namespaced" | "Cluster";
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
validation?: object;
versions?: CustomResourceDefinitionVersion[];
conversion?: CustomResourceConversion;
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[];
preserveUnknownFields?: boolean;
}
export interface CustomResourceDefinitionConditionAcceptedNames {
plural: string;
singular: string;
kind: string;
shortNames: string[];
listKind: string;
}
export interface CustomResourceDefinitionStatus {
conditions?: BaseKubeObjectCondition[];
acceptedNames: CustomResourceDefinitionConditionAcceptedNames;
storedVersions: string[];
}
export class CustomResourceDefinition extends KubeObject<
ClusterScopedMetadata,
CustomResourceDefinitionStatus,
CustomResourceDefinitionSpec
> {
static kind = "CustomResourceDefinition";
static namespaced = false;
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
getResourceUrl() {
const di = getLegacyGlobalDiForExtensionApi();
const customResourcesRoute = di.inject(customResourcesRouteInjectable);
return buildURL(customResourcesRoute.path, {
params: {
group: this.getGroup(),
name: this.getPluralName(),
},
});
}
getResourceApiBase() {
const { group } = this.spec;
return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}`;
}
getPluralName() {
return this.getNames().plural;
}
getResourceKind() {
return this.spec.names.kind;
}
getResourceTitle() {
const name = this.getPluralName();
return name[0].toUpperCase() + name.slice(1);
}
getGroup() {
return this.spec.group;
}
getScope() {
return this.spec.scope;
}
getPreferedVersion(): CustomResourceDefinitionVersion {
const { apiVersion } = this;
switch (apiVersion) {
case "apiextensions.k8s.io/v1":
for (const version of this.spec.versions ?? []) {
if (version.storage) {
return version;
}
}
break;
case "apiextensions.k8s.io/v1beta1": {
const { additionalPrinterColumns: apc } = this.spec;
const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath }));
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
name: this.spec.version!,
served: true,
storage: true,
schema: this.spec.validation,
additionalPrinterColumns,
};
}
}
throw new Error(`Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`);
}
getVersion() {
return this.getPreferedVersion().name;
}
isNamespaced() {
return this.getScope() === "Namespaced";
}
getStoredVersions() {
return this.status?.storedVersions.join(", ") ?? "";
}
getNames() {
return this.spec.names;
}
getConversion() {
return JSON.stringify(this.spec.conversion);
}
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
const columns = this.getPreferedVersion().additionalPrinterColumns ?? [];
return columns
.filter(column => column.name.toLowerCase() != "age" && (ignorePriority || !column.priority));
}
getValidation() {
return JSON.stringify(this.getPreferedVersion().schema, null, 2);
}
getConditions() {
if (!this.status?.conditions) return [];
return this.status.conditions.map(condition => {
const { message, reason, lastTransitionTime, status } = condition;
return {
...condition,
isReady: status === "True",
tooltip: `${message || reason} (${lastTransitionTime})`,
};
});
}
}
export class CustomResourceDefinitionApi extends KubeApi<CustomResourceDefinition> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -13,7 +13,7 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
const daemonSetApiInjectable = getInjectable({
id: "daemon-set-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environements");
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environments");
return new DaemonSetApi({
logger: di.inject(loggerInjectable),
@@ -4,83 +4,9 @@
*/
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
export interface RollingUpdateDaemonSet {
maxUnavailable?: number | string;
maxSurge?: number | string;
}
export interface DaemonSetUpdateStrategy {
type: string;
rollingUpdate: RollingUpdateDaemonSet;
}
export interface DaemonSetSpec {
selector: LabelSelector;
template: PodTemplateSpec;
updateStrategy: DaemonSetUpdateStrategy;
minReadySeconds?: number;
revisionHistoryLimit?: number;
}
export interface DaemonSetStatus extends KubeObjectStatus {
collisionCount?: number;
currentNumberScheduled: number;
desiredNumberScheduled: number;
numberAvailable?: number;
numberMisscheduled: number;
numberReady: number;
numberUnavailable?: number;
observedGeneration?: number;
updatedNumberScheduled?: number;
}
export class DaemonSet extends KubeObject<
NamespaceScopedMetadata,
DaemonSetStatus,
DaemonSetSpec
> {
static kind = "DaemonSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/daemonsets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata?.labels);
}
getTolerations() {
return this.spec.template.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec?.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
const initContainers = this.spec.template?.spec?.initContainers ?? [];
return [...containers, ...initContainers].map(container => container.image);
}
}
import { DaemonSet } from "@k8slens/kube-object";
export class DaemonSetApi extends KubeApi<DaemonSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -90,22 +16,15 @@ export class DaemonSetApi extends KubeApi<DaemonSet> {
});
}
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() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}, "strategic");
}
}
@@ -5,11 +5,9 @@
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { PodSpec } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { Deployment } from "@k8slens/kube-object";
import { hasTypedProperty, isNumber, isObject } from "@k8slens/utilities";
export class DeploymentApi extends KubeApi<Deployment> {
@@ -20,11 +18,11 @@ export class DeploymentApi extends KubeApi<Deployment> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
if (isObject(status) && hasTypedProperty(status, "replicas", isNumber)) {
@@ -34,7 +32,7 @@ export class DeploymentApi extends KubeApi<Deployment> {
return 0;
}
scale(params: { namespace: string; name: string }, replicas: number) {
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
spec: {
@@ -49,105 +47,15 @@ export class DeploymentApi extends KubeApi<Deployment> {
});
}
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() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
export interface DeploymentSpec {
replicas: number;
selector: LabelSelector;
template: {
metadata: {
creationTimestamp?: string;
labels: Partial<Record<string, string>>;
annotations?: Partial<Record<string, string>>;
};
spec: PodSpec;
};
strategy: {
type: string;
rollingUpdate: {
maxUnavailable: number;
maxSurge: number;
};
};
}
export interface DeploymentStatus extends KubeObjectStatus {
observedGeneration: number;
replicas: number;
updatedReplicas: number;
readyReplicas: number;
availableReplicas?: number;
unavailableReplicas?: number;
}
export class Deployment extends KubeObject<
NamespaceScopedMetadata,
DeploymentStatus,
DeploymentSpec
> {
static kind = "Deployment";
static namespaced = true;
static apiBase = "/apis/apps/v1/deployments";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata.labels);
}
getTolerations() {
return this.spec.template.spec.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getConditions(activeOnly = false) {
const { conditions = [] } = this.status ?? {};
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;
}, "strategic");
}
}
@@ -3,113 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import autoBind from "auto-bind";
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { EndpointsData } from "@k8slens/kube-object";
import { Endpoints } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export function formatEndpointSubset(subset: EndpointSubset): string {
const { addresses, ports } = subset;
if (!addresses || !ports) {
return "";
}
return addresses
.map(address => (
ports
.map(port => `${address.ip}:${port.port}`)
.join(", ")
))
.join(", ");
}
export interface ForZone {
name: string;
}
export interface EndpointHints {
forZones?: ForZone[];
}
export interface EndpointConditions {
ready?: boolean;
serving?: boolean;
terminating?: boolean;
}
export interface EndpointData {
addresses: string[];
conditions?: EndpointConditions;
hints?: EndpointHints;
hostname?: string;
nodeName?: string;
targetRef?: ObjectReference;
zone?: string;
}
export interface EndpointPort {
appProtocol?: string;
name?: string;
protocol?: string;
port: number;
}
export interface EndpointAddress {
hostname?: string;
ip: string;
nodeName?: string;
targetRef?: ObjectReference;
}
export interface EndpointSubset {
addresses?: EndpointAddress[];
notReadyAddresses?: EndpointAddress[];
ports?: EndpointPort[];
}
export interface EndpointsData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
subsets?: EndpointSubset[];
}
export class Endpoints extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "Endpoints";
static namespaced = true;
static apiBase = "/api/v1/endpoints";
subsets?: EndpointSubset[];
constructor({ subsets, ...rest }: EndpointsData) {
super(rest);
autoBind(this);
this.subsets = subsets;
}
getEndpointSubsets(): Required<EndpointSubset>[] {
return this.subsets?.map(({
addresses = [],
notReadyAddresses = [],
ports = [],
}) => ({
addresses,
notReadyAddresses,
ports,
})) ?? [];
}
toString(): string {
return this.getEndpointSubsets()
.map(formatEndpointSubset)
.join(", ") || "<none>";
}
}
export class EndpointsApi extends KubeApi<Endpoints, EndpointsData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,133 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import type { KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { formatDuration } from "@k8slens/utilities";
import type { KubeEventData } from "@k8slens/kube-object";
import { KubeEvent } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface EventSeries {
count?: number;
lastObservedTime?: string;
}
export interface EventSource {
component?: string;
host?: string;
}
export interface KubeEventData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
action?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: Required<ObjectReference>;
lastTimestamp?: string;
message?: string;
reason?: string;
related?: ObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: EventSeries;
source?: EventSource;
type?: string;
}
export class KubeEvent extends KubeObject<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
static kind = "Event";
static namespaced = true;
static apiBase = "/api/v1/events";
action?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: Required<ObjectReference>;
lastTimestamp?: string;
message?: string;
reason?: string;
related?: ObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: EventSeries;
source?: EventSource;
/**
* Current supported values are:
* - "Normal"
* - "Warning"
*/
type?: string;
constructor({
action,
count,
eventTime,
firstTimestamp,
involvedObject,
lastTimestamp,
message,
reason,
related,
reportingComponent,
reportingInstance,
series,
source,
type,
...rest
}: KubeEventData) {
super(rest);
this.action = action;
this.count = count;
this.eventTime = eventTime;
this.firstTimestamp = firstTimestamp;
this.involvedObject = involvedObject;
this.lastTimestamp = lastTimestamp;
this.message = message;
this.reason = reason;
this.related = related;
this.reportingComponent = reportingComponent;
this.reportingInstance = reportingInstance;
this.series = series;
this.source = source;
this.type = type;
}
isWarning() {
return this.type === "Warning";
}
getSource() {
if (!this.source?.component) {
return "<unknown>";
}
const { component, host = "" } = this.source;
return `${component} ${host}`;
}
/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getFirstSeenTime() {
const diff = moment().diff(this.firstTimestamp);
return formatDuration(diff, true);
}
/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getLastSeenTime() {
const diff = moment().diff(this.lastTimestamp);
return formatDuration(diff, true);
}
}
export class KubeEventApi extends KubeApi<KubeEvent, KubeEventData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable";
@@ -26,7 +26,7 @@ export interface HelmReleaseDetails {
export type CallForHelmReleaseDetails = (name: string, namespace: string) => Promise<HelmReleaseDetails>;
const requestDetailsEnpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestDetailsEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestHelmReleaseDetailsInjectable = getInjectable({
id: "call-for-helm-release-details",
@@ -34,7 +34,7 @@ const requestHelmReleaseDetailsInjectable = getInjectable({
instantiate: (di): CallForHelmReleaseDetails => {
const apiBase = di.inject(apiBaseInjectable);
return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace }));
return (name, namespace) => apiBase.get(requestDetailsEndpoint.compile({ name, namespace }));
},
});
@@ -3,365 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OptionVariant } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { CrossVersionObjectReference } from "./types/cross-version-object-reference";
export enum HpaMetricType {
Resource = "Resource",
Pods = "Pods",
Object = "Object",
External = "External",
ContainerResource = "ContainerResource",
}
export interface MetricCurrentTarget {
current?: string;
target?: string;
}
export interface HorizontalPodAutoscalerMetricTarget {
kind: string;
name: string;
apiVersion: string;
}
export interface V2ContainerResourceMetricSource {
container: string;
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ContainerResourceMetricSource {
container: string;
name: string;
targetAverageUtilization?: number;
targetAverageValue?: string;
}
export type ContainerResourceMetricSource =
| V2ContainerResourceMetricSource
| V2Beta1ContainerResourceMetricSource;
export interface V2ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type: string;
value?: string;
averageValue?: string;
};
}
export interface V2Beta1ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector;
targetAverageValue?: string;
targetValue?: string;
metric?: {
selector?: LabelSelector;
};
}
export type ExternalMetricSource =
| V2Beta1ExternalMetricSource
| V2ExternalMetricSource;
export interface V2ObjectMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricSource {
averageValue?: string;
metricName?: string;
selector?: LabelSelector;
targetValue?: string;
describedObject?: CrossVersionObjectReference;
}
export type ObjectMetricSource =
| V2ObjectMetricSource
| V2Beta1ObjectMetricSource;
export interface V2PodsMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
averageValue?: string;
type?: string;
};
}
export interface V2Beta1PodsMetricSource {
metricName?: string;
selector?: LabelSelector;
targetAverageValue?: string;
}
export type PodsMetricSource =
| V2PodsMetricSource
| V2Beta1PodsMetricSource;
export interface V2ResourceMetricSource {
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ResourceMetricSource {
name: string;
targetAverageUtilization?: number;
targetAverageValue?: string;
}
export type ResourceMetricSource =
| V2ResourceMetricSource
| V2Beta1ResourceMetricSource;
export interface BaseHorizontalPodAutoscalerMetricSpec {
containerResource: ContainerResourceMetricSource;
external: ExternalMetricSource;
object: ObjectMetricSource;
pods: PodsMetricSource;
resource: ResourceMetricSource;
}
export type HorizontalPodAutoscalerMetricSpec =
| OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricSpec, "resource">
| OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricSpec, "external">
| OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricSpec, "object">
| OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
| OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">;
interface HorizontalPodAutoscalerBehavior {
scaleUp?: HPAScalingRules;
scaleDown?: HPAScalingRules;
}
interface HPAScalingRules {
stabilizationWindowSecond?: number;
selectPolicy?: ScalingPolicySelect;
policies?: HPAScalingPolicy[];
}
type ScalingPolicySelect = string;
interface HPAScalingPolicy {
type: HPAScalingPolicyType;
value: number;
periodSeconds: number;
}
type HPAScalingPolicyType = string;
export interface V2ContainerResourceMetricStatus {
container?: string;
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ContainerResourceMetricStatus {
container?: string;
currentAverageUtilization?: number;
currentAverageValue?: string;
name: string;
}
export type ContainerResourceMetricStatus =
| V2ContainerResourceMetricStatus
| V2Beta1ContainerResourceMetricStatus;
export interface V2ExternalMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
value?: string;
};
}
export interface V2Beta1ExternalMetricStatus {
currentAverageValue?: string;
currentValue?: string;
metricName?: string;
metricSelector?: LabelSelector;
}
export type ExternalMetricStatus =
| V2Beta1ExternalMetricStatus
| V2ExternalMetricStatus;
export interface V2ObjectMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricStatus {
averageValue?: string;
currentValue?: string;
metricName?: string;
selector?: LabelSelector;
describedObject?: CrossVersionObjectReference;
}
export type ObjectMetricStatus =
| V2Beta1ObjectMetricStatus
| V2ObjectMetricStatus;
export interface V2PodsMetricStatus {
selector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
};
}
export interface V2Beta1PodsMetricStatus {
currentAverageValue?: string;
metricName?: string;
selector?: LabelSelector;
}
export type PodsMetricStatus =
| V2Beta1PodsMetricStatus
| V2PodsMetricStatus;
export interface V2ResourceMetricStatus {
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ResourceMetricStatus {
currentAverageUtilization?: number;
currentAverageValue?: string;
name: string;
}
export type ResourceMetricStatus =
| V2Beta1ResourceMetricStatus
| V2ResourceMetricStatus;
export interface BaseHorizontalPodAutoscalerMetricStatus {
containerResource: ContainerResourceMetricStatus;
external: ExternalMetricStatus;
object: ObjectMetricStatus;
pods: PodsMetricStatus;
resource: ResourceMetricStatus;
}
export type HorizontalPodAutoscalerMetricStatus =
| OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricStatus, "resource">
| OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricStatus, "external">
| OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricStatus, "object">
| OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricStatus, "pods">
| OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricStatus, "containerResource">;
export interface HorizontalPodAutoscalerSpec {
scaleTargetRef: CrossVersionObjectReference;
minReplicas?: number;
maxReplicas: number;
metrics?: HorizontalPodAutoscalerMetricSpec[];
behavior?: HorizontalPodAutoscalerBehavior;
targetCPUUtilizationPercentage?: number; // used only in autoscaling/v1
}
export interface HorizontalPodAutoscalerStatus {
conditions?: BaseKubeObjectCondition[];
currentReplicas: number;
desiredReplicas: number;
currentMetrics?: HorizontalPodAutoscalerMetricStatus[];
currentCPUUtilizationPercentage?: number; // used only in autoscaling/v1
}
export class HorizontalPodAutoscaler extends KubeObject<
NamespaceScopedMetadata,
HorizontalPodAutoscalerStatus,
HorizontalPodAutoscalerSpec
> {
static readonly kind = "HorizontalPodAutoscaler";
static readonly namespaced = true;
static readonly apiBase = "/apis/autoscaling/v2/horizontalpodautoscalers";
getMaxPods() {
return this.spec.maxReplicas ?? 0;
}
getMinPods() {
return this.spec.minReplicas ?? 0;
}
getReplicas() {
return this.status?.currentReplicas ?? 0;
}
getReadyConditions() {
return this.getConditions().filter(({ isReady }) => isReady);
}
getConditions() {
return this.status?.conditions?.map(condition => {
const { message, reason, lastTransitionTime, status } = condition;
return {
...condition,
isReady: status === "True",
tooltip: `${message || reason} (${lastTransitionTime})`,
};
}) ?? [];
}
getMetrics() {
return this.spec.metrics ?? [];
}
getCurrentMetrics() {
return this.status?.currentMetrics ?? [];
}
}
import { HorizontalPodAutoscaler } from "@k8slens/kube-object";
export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -46,5 +46,4 @@ export * from "./service.api";
export * from "./service-account.api";
export * from "./stateful-set.api";
export * from "./storage-class.api";
export * from "./types";
export * from "./vertical-pod-autoscaler.api";
@@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { IngressClass } from "@k8slens/kube-object";
import type { KubeApiDependencies, ResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
@@ -18,84 +17,12 @@ export class IngressClassApi extends KubeApi<IngressClass> {
}
setAsDefault({ name }: ResourceDescriptor, isDefault = true) {
const reqUrl = this.formatUrlForNotListing({ name });
return this.request.patch(reqUrl, {
data: {
metadata: {
annotations: {
[IngressClass.ANNOTATION_IS_DEFAULT]: JSON.stringify(isDefault),
},
return this.patch({ name }, {
metadata: {
annotations: {
[IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault),
},
},
}, {
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
// API docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#ingressclass-v1-networking-k8s-io
export type IngressClassMetadata = KubeObjectMetadata<KubeObjectScope.Cluster> & {
"name": string;
"labels"?: {
[name: string]: string | undefined;
"app.kubernetes.io/component"?: "controller";
};
"annotations"?: {
[name: string]: string | undefined;
"ingressclass.kubernetes.io/is-default-class"?: "true";
};
};
export interface IngressClassParametersReference {
"apiGroup": string; // k8s.example.net
"scope": "Namespace" | "Cluster";
"kind": "ClusterIngressParameter" | "IngressParameter";
"name": string; // external-config-1
"namespace"?: string; // namespaced for IngressClass must be defined in `spec.parameters.namespace` instead of `metadata.namespace` (!)
}
export interface IngressClassSpec {
controller: string; // "example.com/ingress-controller"
parameters?: IngressClassParametersReference;
}
export interface IngressClassStatus {
}
export class IngressClass extends KubeObject<IngressClassMetadata, IngressClassStatus, IngressClassSpec> {
static readonly kind = "IngressClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/networking.k8s.io/v1/ingressclasses";
static readonly ANNOTATION_IS_DEFAULT = "ingressclass.kubernetes.io/is-default-class";
getController(): string {
return this.spec.controller;
}
getCtrlApiGroup() {
return this.spec?.parameters?.apiGroup;
}
getCtrlScope() {
return this.spec?.parameters?.scope;
}
getCtrlNs() {
return this.spec?.parameters?.namespace;
}
getCtrlKind() {
return this.spec?.parameters?.kind;
}
getCtrlName() {
return this.spec?.parameters?.name as string;
}
get isDefault() {
return this.metadata.annotations?.[IngressClass.ANNOTATION_IS_DEFAULT] === "true";
}, "strategic");
}
}
@@ -3,12 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isString, iter } from "@k8slens/utilities";
import { Ingress } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
export class IngressApi extends KubeApi<Ingress> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -21,193 +18,3 @@ export class IngressApi extends KubeApi<Ingress> {
});
}
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;
}
// extensions/v1beta1
export interface ExtensionsBackend {
serviceName?: string;
servicePort?: number | string;
}
// networking.k8s.io/v1
export interface NetworkingBackend {
service?: IngressService;
}
export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & {
resource?: TypedLocalObjectReference;
};
export interface IngressService {
name: string;
port: RequireExactlyOne<{
name: string;
number: number;
}>;
}
function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend {
return hasTypedProperty(backend, "serviceName", isString);
}
/**
* Format an ingress backend into the name of the service and port
* @param backend The ingress target
*/
export function getBackendServiceNamePort(backend: IngressBackend | undefined): string {
if (!backend) {
return "<unknown>";
}
if (isExtensionsBackend(backend)) {
return `${backend.serviceName}:${backend.servicePort}`;
}
if (backend.service) {
const { name, port } = backend.service;
return `${name}:${port.number ?? port.name}`;
}
return "<unknown>";
}
export interface HTTPIngressPath {
pathType: "Exact" | "Prefix" | "ImplementationSpecific";
path?: string;
backend?: IngressBackend;
}
export interface HTTPIngressRuleValue {
paths: HTTPIngressPath[];
}
export interface IngressRule {
host?: string;
http?: HTTPIngressRuleValue;
}
export interface IngressSpec {
tls: {
secretName: string;
}[];
rules?: IngressRule[];
// extensions/v1beta1
backend?: ExtensionsBackend;
/**
* The default backend which is exactly on of:
* - service
* - resource
*/
defaultBackend?: RequireExactlyOne<NetworkingBackend & {
resource: {
apiGroup: string;
kind: string;
name: string;
};
}>;
}
export interface IngressStatus {
loadBalancer: {
ingress?: ILoadBalancerIngress[];
};
}
export class Ingress extends KubeObject<
NamespaceScopedMetadata,
IngressStatus,
IngressSpec
> {
static readonly kind = "Ingress";
static readonly namespaced = true;
static readonly apiBase = "/apis/networking.k8s.io/v1/ingresses";
getRules() {
return this.spec.rules ?? [];
}
getRoutes(): string[] {
return computeRouteDeclarations(this).map(({ url, service }) => `${url} ⇢ ${service}`);
}
getServiceNamePort(): ExtensionsBackend | undefined {
const { spec: { backend, defaultBackend } = {}} = this;
const serviceName = defaultBackend?.service?.name ?? backend?.serviceName;
const servicePort = defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort;
if (!serviceName || !servicePort) {
return undefined;
}
return {
serviceName,
servicePort,
};
}
getHosts() {
const { spec: { rules = [] }} = this;
return [...iter.filterMap(rules, rule => rule.host)];
}
getPorts() {
const ports: number[] = [];
const { spec: { tls, rules = [], backend, defaultBackend }} = this;
const httpPort = 80;
const tlsPort = 443;
// Note: not using the port name (string)
const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort;
if (rules.length > 0) {
if (rules.some(rule => rule.http)) {
ports.push(httpPort);
}
} else if (servicePort !== undefined) {
ports.push(Number(servicePort));
}
if (tls && tls.length > 0) {
ports.push(tlsPort);
}
return ports.join(", ");
}
getLoadBalancers() {
return this.status?.loadBalancer?.ingress?.map(address => (
address.hostname || address.ip
)) ?? [];
}
}
export interface ComputedIngressRoute {
displayAsLink: boolean;
pathname: string;
url: string;
service: string;
}
export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] {
const { host = "*", http: { paths } = { paths: [] }} = rule;
const protocol = (ingress.spec?.tls?.length ?? 0) === 0
? "http"
: "https";
return paths.map(({ path = "/", backend }) => ({
displayAsLink: !host.includes("*"),
pathname: path,
url: `${protocol}://${host}${path}`,
service: getBackendServiceNamePort(backend),
}));
}
export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] {
return ingress.getRules().flatMap(rule => computeRuleDeclarations(ingress, rule));
}
@@ -5,94 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { PodSpec } from "./pod.api";
import type { Container } from "./types/container";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface JobSpec {
parallelism?: number;
completions?: number;
backoffLimit?: number;
selector?: LabelSelector;
template: {
metadata: {
creationTimestamp?: string;
labels?: Partial<Record<string, string>>;
annotations?: Partial<Record<string, string>>;
};
spec: PodSpec;
};
containers?: Container[];
restartPolicy?: string;
terminationGracePeriodSeconds?: number;
dnsPolicy?: string;
serviceAccountName?: string;
serviceAccount?: string;
schedulerName?: string;
}
export interface JobStatus extends KubeObjectStatus {
startTime: string;
completionTime: string;
succeeded: number;
}
export class Job extends KubeObject<
NamespaceScopedMetadata,
JobStatus,
JobSpec
> {
static readonly kind = "Job";
static readonly namespaced = true;
static readonly apiBase = "/apis/batch/v1/jobs";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector?.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata.labels);
}
getTolerations() {
return this.spec.template.spec.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getDesiredCompletions() {
return this.spec.completions ?? 0;
}
getCompletions() {
return this.status?.succeeded ?? 0;
}
getParallelism() {
return this.spec.parallelism;
}
getCondition() {
// Type of Job condition could be only Complete or Failed
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#jobcondition-v1-batch
return this.status?.conditions?.find(({ status }) => status === "True");
}
getImages() {
return this.spec.template.spec.containers?.map(container => container.image) ?? [];
}
}
import { Job } from "@k8slens/kube-object";
export class JobApi extends KubeApi<Job> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -5,46 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface LeaseSpec {
acquireTime?: string;
holderIdentity: string;
leaseDurationSeconds: number;
leaseTransitions?: number;
renewTime: string;
}
export class Lease extends KubeObject<
NamespaceScopedMetadata,
void,
LeaseSpec
> {
static readonly kind = "Lease";
static readonly namespaced = true;
static readonly apiBase = "/apis/coordination.k8s.io/v1/leases";
getAcquireTime(): string {
return this.spec.acquireTime || "";
}
getHolderIdentity(): string {
return this.spec.holderIdentity;
}
getLeaseDurationSeconds(): number {
return this.spec.leaseDurationSeconds;
}
getLeaseTransitions(): number | undefined {
return this.spec.leaseTransitions;
}
getRenewTime(): string {
return this.spec.renewTime;
}
}
import { Lease } from "@k8slens/kube-object";
export class LeaseApi extends KubeApi<Lease> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -3,63 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { LimitRange } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export enum LimitType {
CONTAINER = "Container",
POD = "Pod",
PVC = "PersistentVolumeClaim",
}
export enum Resource {
MEMORY = "memory",
CPU = "cpu",
STORAGE = "storage",
EPHEMERAL_STORAGE = "ephemeral-storage",
}
export enum LimitPart {
MAX = "max",
MIN = "min",
DEFAULT = "default",
DEFAULT_REQUEST = "defaultRequest",
MAX_LIMIT_REQUEST_RATIO = "maxLimitRequestRatio",
}
type LimitRangeParts = Partial<Record<LimitPart, Record<string, string>>>;
export interface LimitRangeItem extends LimitRangeParts {
type: string;
}
export interface LimitRangeSpec {
limits: LimitRangeItem[];
}
export class LimitRange extends KubeObject<
NamespaceScopedMetadata,
void,
LimitRangeSpec
> {
static readonly kind = "LimitRange";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/limitranges";
getContainerLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.CONTAINER);
}
getPodLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.POD);
}
getPVCLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.PVC);
}
}
export class LimitRangeApi extends KubeApi<LimitRange> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -7,7 +7,6 @@ import { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-ti
import apiBaseInjectable from "../../api-base.injectable";
import type { MetricData } from "../metrics.api";
export interface RequestMetricsParams {
/**
* timestamp in seconds or valid date-string
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { PersistentVolumeClaim } from "../persistent-volume-claim.api";
import type { PersistentVolumeClaim } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PersistentVolumeClaimMetricData {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { DaemonSet } from "../daemon-set.api";
import type { DaemonSet } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Deployment } from "../deployment.api";
import type { Deployment } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Job } from "../job.api";
import type { Job } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { ReplicaSet } from "../replica-set.api";
import type { ReplicaSet } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface ReplicaSetPodMetricData {
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { StatefulSet } from "../stateful-set.api";
import type { StatefulSet } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface StatefulSetPodMetricData {
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { Pod } from "../pod.api";
import type { Pod } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PodMetricData {
@@ -2,151 +2,9 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { MutatingWebhookConfiguration } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
interface WebhookClientConfig {
// `url` gives the location of the webhook
url?: string;
// `service` is a reference to the service for this webhook. Either `service` or `url` must be specified.
service?: ServiceReference;
// `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
// If unspecified, system trust roots on the apiserver are used.
caBundle?: string;
}
interface RuleWithOperations {
// APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one.
apiGroups: string[];
// APIVersions is the API versions the resources belong to. '*' is all versions.
apiVersions?: string[];
// Resources is a list of resources this rule applies to.
// For example: 'pods' means pods.
// '*' means all resources, but not subresources.
// 'pods/' means all subresources of pods.
// '*/scale' means all scale subresources. Allowed values are "Resource" / "Resource/Scale" / "Resource/Status".
resources: string[];
// Operations is a list of operations this rule applies to.
// The valid values are: "CREATE" / "UPDATE" / "DELETE" / "CONNECT".
operations: string[];
// Scope specifies the scope of this rule. Valid values are "Cluster" / "Namespace".
// Default is "Cluster".
scope?: string;
}
export interface Webhook {
// The name of the webhook configuration.
name: string;
// ClientConfig defines how to communicate with the hook.
clientConfig: WebhookClientConfig;
// Rules describes what operations on what resources/subresources the webhook cares about.
// The webhook cares about an operation if it matches _any_ Rule.
rules?: RuleWithOperations[];
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions
// the webhook expects. API server will try to use first version in the list which it
// supports. If none of the versions specified in this list supported by API server,
// validation will fail for this object.
admissionReviewVersions?: string[];
// TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored
// or the API call will fail depending on the failure policy.
timeoutSeconds?: number;
// FailurePolicy specifies how unrecognized errors from the webhook are handled - allowed values are Ignore or Fail.
// Defaults to Fail.
failurePolicy?: string;
// matchPolicy defines how the "rules" list is used to match incoming requests. Allowed values are "Exact" or "Equivalent".
// - Exact: match a request only if it exactly matches a specified rule.
// - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version.
// Defaults to "Equivalent".
matchPolicy?: string;
// NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object
// matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels.
// If both the object and the webhook configuration specify namespaceSelector, they must match.
namespaceSelector?: LabelSelector;
// ObjectSelector decides whether to run the webhook based on if the object has matching labels.
// objectSelector and namespaceSelector are ANDed. An empty objectSelector matches all objects.
// A null objectSelector matches no objects.
objectSelector?: LabelSelector;
// SideEffects states whether this webhookk should run when no mutating or validating webhook
// needs to run. This should be false when the webhook only applies to resources that have
// the sideEffects field set to None. Defaults to true.
sideEffects?: string;
// reinvocationPolicy indicates whether this webhook should be called multiple times as part of a
// single admission evaluation. Allowed values are "Never" and "IfNeeded"
reinvocationPolicy?: "Never" | "IfNeeded";
}
export interface MutatingWebhook extends Webhook {
}
interface ServiceReference {
// `namespace` is the namespace of the service.
namespace: string;
// `name` is the name of the service.
name: string;
// `path` is an optional URL path which will be sent in any request to this service.
path?: string;
// `port` is an optional service port which will be used when accessing the service.
port?: number | string;
}
interface MutatingWebhookConfigurationData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
webhooks?: MutatingWebhook[];
}
export class MutatingWebhookConfiguration extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "MutatingWebhookConfiguration";
static namespaced = false;
static apiBase = "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations";
webhooks?: MutatingWebhook[];
constructor({ webhooks, ...rest }: MutatingWebhookConfigurationData) {
super(rest);
this.webhooks = webhooks;
}
getWebhooks(): MutatingWebhook[] {
return this.webhooks ?? [];
}
getClientConfig(serviceName: string, serviceNamespace: string): WebhookClientConfig | undefined {
const webhooks = this.getWebhooks();
for (const webhook of webhooks) {
if (webhook.clientConfig.service?.name === serviceName && webhook.clientConfig.service?.namespace === serviceNamespace) {
return webhook.clientConfig;
}
}
return undefined;
}
}
export class MutatingWebhookConfigurationApi extends KubeApi<MutatingWebhookConfiguration> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -5,47 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object";
import { KubeObject } from "../kube-object";
export enum NamespaceStatusKind {
ACTIVE = "Active",
TERMINATING = "Terminating",
}
export interface NamespaceSpec {
finalizers?: string[];
}
export interface NamespaceStatus extends KubeObjectStatus {
phase?: string;
}
export class Namespace extends KubeObject<
ClusterScopedMetadata,
NamespaceStatus,
NamespaceSpec
> {
static readonly kind = "Namespace";
static readonly namespaced = false;
static readonly apiBase = "/api/v1/namespaces";
getStatus() {
return this.status?.phase ?? "-";
}
isSubnamespace() {
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of"));
}
isChildOf(parentName: string) {
return this.getLabels().find(label => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`);
}
isControlledByHNC() {
return this.getLabels().includes("hnc.x-k8s.io/included-namespace=true");
}
}
import { Namespace } from "@k8slens/kube-object";
export class NamespaceApi extends KubeApi<Namespace> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -3,126 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { NetworkPolicy } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface IPolicyIpBlock {
cidr: string;
except?: string[];
}
/**
* @deprecated Use `LabelSelector` instead
*/
export type IPolicySelector = LabelSelector;
export interface NetworkPolicyPort {
/**
* The protocol which network traffic must match.
*
* One of:
* - `"TCP"`
* - `"UDP"`
* - `"SCTP"`
*
* @default "TCP"
*/
protocol?: string;
/**
* The port on the given protocol. This can either be a numerical or named
* port on a pod. If this field is not provided, this matches all port names and
* numbers.
*
* If present, only traffic on the specified protocol AND port will be matched.
*/
port?: number | string;
/**
* If set, indicates that the range of ports from port to endPort, inclusive,
* should be allowed by the policy. This field cannot be defined if the port field
* is not defined or if the port field is defined as a named (string) port.
*
* The endPort must be equal or greater than port.
*/
endPort?: number;
}
export interface NetworkPolicyPeer {
/**
* IPBlock defines policy on a particular IPBlock. If this field is set then
* neither of the other fields can be.
*/
ipBlock?: IPolicyIpBlock;
/**
* Selects Namespaces using cluster-scoped labels. This field follows standard label
* selector semantics; if present but empty, it selects all namespaces.
*
* If PodSelector is also set, then the NetworkPolicyPeer as a whole selects
* the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
*
* Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
*/
namespaceSelector?: LabelSelector;
/**
* This is a label selector which selects Pods. This field follows standard label
* selector semantics; if present but empty, it selects all pods.
*
* If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects
* the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
*
* Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
*/
podSelector?: LabelSelector;
}
export interface IPolicyIngress {
from?: NetworkPolicyPeer[];
ports?: NetworkPolicyPort[];
}
export interface IPolicyEgress {
to?: NetworkPolicyPeer[];
ports?: NetworkPolicyPort[];
}
export type PolicyType = "Ingress" | "Egress";
export interface NetworkPolicySpec {
podSelector: LabelSelector;
policyTypes?: PolicyType[];
ingress?: IPolicyIngress[];
egress?: IPolicyEgress[];
}
export class NetworkPolicy extends KubeObject<
NamespaceScopedMetadata,
void,
NetworkPolicySpec
> {
static readonly kind = "NetworkPolicy";
static readonly namespaced = true;
static readonly apiBase = "/apis/networking.k8s.io/v1/networkpolicies";
getMatchLabels(): string[] {
if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return [];
return Object
.entries(this.spec.podSelector.matchLabels)
.map(data => data.join(":"));
}
getTypes(): string[] {
if (!this.spec.policyTypes) return [];
return this.spec.policyTypes;
}
}
export class NetworkPolicyApi extends KubeApi<NetworkPolicy> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {
@@ -3,12 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "@k8slens/utilities";
import { Node } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import { TypedRegEx } from "typed-regex";
export class NodeApi extends KubeApi<Node> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -18,246 +15,3 @@ export class NodeApi extends KubeApi<Node> {
});
}
}
export interface NodeTaint {
key: string;
value?: string;
effect: string;
timeAdded: string;
}
export function formatNodeTaint(taint: NodeTaint): string {
if (taint.value) {
return `${taint.key}=${taint.value}:${taint.effect}`;
}
return `${taint.key}:${taint.effect}`;
}
export interface NodeCondition extends BaseKubeObjectCondition {
/**
* Last time we got an update on a given condition.
*/
lastHeartbeatTime?: string;
}
/**
* These role label prefixs are the ones that are for master nodes
*
* The `master` label has been deprecated in Kubernetes 1.20, and will be removed in 1.25 so we
* have to also use the newer `control-plane` label
*/
const masterNodeLabels = [
"master",
"control-plane",
];
/**
* This regex is used in the `getRoleLabels()` method bellow, but placed here
* as factoring out regexes is best practice.
*/
const nodeRoleLabelKeyMatcher = TypedRegEx("^.*node-role.kubernetes.io/+(?<role>.+)$");
export interface NodeSpec {
podCIDR?: string;
podCIDRs?: string[];
providerID?: string;
/**
* @deprecated see https://issues.k8s.io/61966
*/
externalID?: string;
taints?: NodeTaint[];
unschedulable?: boolean;
}
export interface NodeAddress {
type: "Hostname" | "ExternalIP" | "InternalIP";
address: string;
}
export interface NodeStatusResources extends Partial<Record<string, string>> {
cpu?: string;
"ephemeral-storage"?: string;
"hugepages-1Gi"?: string;
"hugepages-2Mi"?: string;
memory?: string;
pods?: string;
}
export interface ConfigMapNodeConfigSource {
kubeletConfigKey: string;
name: string;
namespace: string;
resourceVersion?: string;
uid?: string;
}
export interface NodeConfigSource {
configMap?: ConfigMapNodeConfigSource;
}
export interface NodeConfigStatus {
active?: NodeConfigSource;
assigned?: NodeConfigSource;
lastKnownGood?: NodeConfigSource;
error?: string;
}
export interface DaemonEndpoint {
Port: number; //it must be uppercase for backwards compatibility
}
export interface NodeDaemonEndpoints {
kubeletEndpoint?: DaemonEndpoint;
}
export interface ContainerImage {
names?: string[];
sizeBytes?: number;
}
export interface NodeSystemInfo {
architecture: string;
bootID: string;
containerRuntimeVersion: string;
kernelVersion: string;
kubeProxyVersion: string;
kubeletVersion: string;
machineID: string;
operatingSystem: string;
osImage: string;
systemUUID: string;
}
export interface AttachedVolume {
name: string;
devicePath: string;
}
export interface NodeStatus {
capacity?: NodeStatusResources;
allocatable?: NodeStatusResources;
conditions?: NodeCondition[];
addresses?: NodeAddress[];
config?: NodeConfigStatus;
daemonEndpoints?: NodeDaemonEndpoints;
images?: ContainerImage[];
nodeInfo?: NodeSystemInfo;
phase?: string;
volumesInUse?: string[];
volumesAttached?: AttachedVolume[];
}
export class Node extends KubeObject<
ClusterScopedMetadata,
NodeStatus,
NodeSpec
> {
static readonly kind = "Node";
static readonly namespaced = false;
static readonly apiBase = "/api/v1/nodes";
/**
* Returns the concatination of all current condition types which have a status
* of `"True"`
*/
getNodeConditionText(): string {
if (!this.status?.conditions) {
return "";
}
return this.status.conditions
.filter(condition => condition.status === "True")
.map(condition => condition.type)
.join(" ");
}
getTaints() {
return this.spec.taints || [];
}
isMasterNode(): boolean {
return this.getRoleLabelItems()
.some(roleLabel => masterNodeLabels.includes(roleLabel));
}
getRoleLabelItems(): string[] {
const { labels } = this.metadata;
const roleLabels: string[] = [];
if (!isObject(labels)) {
return roleLabels;
}
for (const labelKey of Object.keys(labels)) {
const match = nodeRoleLabelKeyMatcher.match(labelKey);
if (match?.groups) {
roleLabels.push(match.groups.role);
}
}
if (typeof labels["kubernetes.io/role"] === "string") {
roleLabels.push(labels["kubernetes.io/role"]);
}
if (typeof labels["node.kubernetes.io/role"] === "string") {
roleLabels.push(labels["node.kubernetes.io/role"]);
}
return roleLabels;
}
getRoleLabels(): string {
return this.getRoleLabelItems().join(", ");
}
getCpuCapacity() {
if (!this.status?.capacity || !this.status.capacity.cpu) return 0;
return cpuUnitsToNumber(this.status.capacity.cpu);
}
getMemoryCapacity() {
if (!this.status?.capacity || !this.status.capacity.memory) return 0;
return unitsToBytes(this.status.capacity.memory);
}
getConditions(): NodeCondition[] {
const conditions = this.status?.conditions || [];
if (this.isUnschedulable()) {
return [{ type: "SchedulingDisabled", status: "True" }, ...conditions];
}
return conditions;
}
getActiveConditions() {
return this.getConditions().filter(c => c.status === "True");
}
getWarningConditions() {
const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"];
return this.getActiveConditions().filter(condition => {
return !goodConditions.includes(condition.type);
});
}
getKubeletVersion() {
return this.status?.nodeInfo?.kubeletVersion ?? "<unknown>";
}
getOperatingSystem(): string {
return this.metadata?.labels?.["kubernetes.io/os"]
|| this.metadata?.labels?.["beta.kubernetes.io/os"]
|| this.status?.nodeInfo?.operatingSystem
|| "linux";
}
isUnschedulable() {
return this.spec.unschedulable;
}
}
@@ -3,13 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { Pod } from "./pod.api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import { object } from "@k8slens/utilities";
import type { ResourceRequirements } from "./types/resource-requirements";
import { PersistentVolumeClaim } from "@k8slens/kube-object";
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -20,54 +16,3 @@ export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
}
}
export interface PersistentVolumeClaimSpec {
accessModes?: string[];
dataSource?: TypedLocalObjectReference;
dataSourceRef?: TypedLocalObjectReference;
resources?: ResourceRequirements;
selector?: LabelSelector;
storageClassName?: string;
volumeMode?: string;
volumeName?: string;
}
export interface PersistentVolumeClaimStatus {
phase: string; // Pending
}
export class PersistentVolumeClaim extends KubeObject<
NamespaceScopedMetadata,
PersistentVolumeClaimStatus,
PersistentVolumeClaimSpec
> {
static readonly kind = "PersistentVolumeClaim";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/persistentvolumeclaims";
getPods(pods: Pod[]): Pod[] {
return pods
.filter(pod => pod.getNs() === this.getNs())
.filter(pod => (
pod.getVolumes()
.filter(volume => volume.persistentVolumeClaim?.claimName === this.getName())
.length > 0
));
}
getStorage(): string {
return this.spec.resources?.requests?.storage ?? "-";
}
getMatchLabels(): string[] {
return object.entries(this.spec.selector?.matchLabels)
.map(([name, val]) => `${name}:${val}`);
}
getMatchExpressions() {
return this.spec.selector?.matchExpressions ?? [];
}
getStatus(): string {
return this.status?.phase ?? "-";
}
}
@@ -3,104 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata, LabelSelector, ObjectReference, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { unitsToBytes } from "@k8slens/utilities";
import { PersistentVolume } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { ResourceRequirements } from "./types/resource-requirements";
export interface PersistentVolumeSpec {
/**
* AccessModes contains the desired access modes the volume should have.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
*/
accessModes?: string[];
dataSource?: TypedLocalObjectReference;
dataSourceRef?: TypedLocalObjectReference;
resources?: ResourceRequirements;
selector?: LabelSelector;
/**
* Name of the StorageClass required by the claim.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
*/
storageClassName?: string;
/**
* Defines what type of volume is required by the claim. Value of Filesystem is implied when not
* included in claim spec.
*/
volumeMode?: string;
/**
* A description of the persistent volume\'s resources and capacity.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity
*/
capacity?: Partial<Record<string, string>>;
flexVolume?: {
driver: string; // ceph.rook.io/rook-ceph-system,
options?: {
clusterNamespace: string; // rook-ceph,
image: string; // pvc-c5d7c485-9f1b-11e8-b0ea-9600000e54fb,
pool: string; // replicapool,
storageClass: string; // rook-ceph-block
};
};
mountOptions?: string[];
claimRef?: ObjectReference;
persistentVolumeReclaimPolicy?: string; // Delete,
nfs?: {
path: string;
server: string;
};
}
export interface PersistentVolumeStatus {
phase: string;
reason?: string;
}
export class PersistentVolume extends KubeObject<
ClusterScopedMetadata,
PersistentVolumeStatus,
PersistentVolumeSpec
> {
static kind = "PersistentVolume";
static namespaced = false;
static apiBase = "/api/v1/persistentvolumes";
getCapacity(inBytes = false) {
const capacity = this.spec.capacity;
if (capacity?.storage) {
if (inBytes) return unitsToBytes(capacity.storage);
return capacity.storage;
}
return 0;
}
getStatus() {
return this.status?.phase || "-";
}
getStorageClass(): string {
return this.spec.storageClassName ?? "";
}
getClaimRefName(): string {
return this.spec.claimRef?.name ?? "";
}
getStorageClassName() {
return this.spec.storageClassName || "";
}
}
export class PersistentVolumeApi extends KubeApi<PersistentVolume> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,78 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { PodDisruptionBudget } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { Condition } from "./types/condition";
export interface V1Beta1PodDisruptionBudgetSpec {
minAvailable: string;
maxUnavailable: string;
selector: LabelSelector;
}
export interface V1PodDisruptionBudgetSpec {
maxUnavailable?: string | number;
minAvailable?: string | number;
selector?: LabelSelector;
}
export type PodDisruptionBudgetSpec =
| V1Beta1PodDisruptionBudgetSpec
| V1PodDisruptionBudgetSpec;
export interface V1Beta1PodDisruptionBudgetStatus {
currentHealthy: number;
desiredHealthy: number;
disruptionsAllowed: number;
expectedPods: number;
}
export interface V1PodDisruptionBudgetStatus {
conditions?: Condition[];
currentHealthy: number;
desiredHealthy: number;
disruptedPods?: Partial<Record<string, string>>;
disruptionsAllowed: number;
expectedPods: number;
observedGeneration?: number;
}
export type PodDisruptionBudgetStatus =
| V1Beta1PodDisruptionBudgetStatus
| V1PodDisruptionBudgetStatus;
export class PodDisruptionBudget extends KubeObject<
NamespaceScopedMetadata,
PodDisruptionBudgetStatus,
PodDisruptionBudgetSpec
> {
static readonly kind = "PodDisruptionBudget";
static readonly namespaced = true;
static readonly apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
getSelectors() {
return KubeObject.stringifyLabels(this.spec.selector?.matchLabels);
}
getMinAvailable() {
return this.spec.minAvailable ?? "N/A";
}
getMaxUnavailable() {
return this.spec.maxUnavailable ?? "N/A";
}
getCurrentHealthy() {
return this.status?.currentHealthy ?? 0;
}
getDesiredHealthy() {
return this.status?.desiredHealthy ?? 0;
}
}
export class PodDisruptionBudgetApi extends KubeApi<PodDisruptionBudget> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,53 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodMetricsData } from "@k8slens/kube-object";
import { PodMetrics } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface PodMetricsData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
timestamp: string;
window: string;
containers: PodMetricsContainer[];
}
export interface PodMetricsContainerUsage {
cpu: string;
memory: string;
}
export interface PodMetricsContainer {
name: string;
usage: PodMetricsContainerUsage;
}
export class PodMetrics extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "PodMetrics";
static readonly namespaced = true;
static readonly apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
timestamp: string;
window: string;
containers: PodMetricsContainer[];
constructor({
timestamp,
window,
containers,
...rest
}: PodMetricsData) {
super(rest);
this.timestamp = timestamp;
this.window = window;
this.containers = containers;
}
}
export class PodMetricsApi extends KubeApi<PodMetrics, PodMetricsData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,113 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { PodSecurityPolicy } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface PodSecurityPolicySpec {
allowPrivilegeEscalation?: boolean;
allowedCSIDrivers?: {
name: string;
}[];
allowedCapabilities: string[];
allowedFlexVolumes?: {
driver: string;
}[];
allowedHostPaths?: {
pathPrefix: string;
readOnly: boolean;
}[];
allowedProcMountTypes?: string[];
allowedUnsafeSysctls?: string[];
defaultAddCapabilities?: string[];
defaultAllowPrivilegeEscalation?: boolean;
forbiddenSysctls?: string[];
fsGroup?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
hostIPC?: boolean;
hostNetwork?: boolean;
hostPID?: boolean;
hostPorts?: {
max: number;
min: number;
}[];
privileged?: boolean;
readOnlyRootFilesystem?: boolean;
requiredDropCapabilities?: string[];
runAsGroup?: {
ranges: {
max: number;
min: number;
}[];
rule: string;
};
runAsUser?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
runtimeClass?: {
allowedRuntimeClassNames: string[];
defaultRuntimeClassName: string;
};
seLinux?: {
rule: string;
seLinuxOptions: {
level: string;
role: string;
type: string;
user: string;
};
};
supplementalGroups?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
volumes?: string[];
}
export class PodSecurityPolicy extends KubeObject<
ClusterScopedMetadata,
void,
PodSecurityPolicySpec
> {
static readonly kind = "PodSecurityPolicy";
static readonly namespaced = false;
static readonly apiBase = "/apis/policy/v1beta1/podsecuritypolicies";
isPrivileged() {
return !!this.spec.privileged;
}
getVolumes() {
return this.spec.volumes || [];
}
getRules() {
const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec;
return {
fsGroup: fsGroup ? fsGroup.rule : "",
runAsGroup: runAsGroup ? runAsGroup.rule : "",
runAsUser: runAsUser ? runAsUser.rule : "",
supplementalGroups: supplementalGroups ? supplementalGroups.rule : "",
seLinux: seLinux ? seLinux.rule : "",
};
}
}
export class PodSecurityPolicyApi extends KubeApi<PodSecurityPolicy> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {
@@ -10,22 +10,9 @@ import type {
ResourceDescriptor,
} from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
import type {
Affinity,
KubeObjectMetadata, KubeStatusData,
LocalObjectReference,
NamespaceScopedMetadata,
Toleration,
} from "../kube-object";
import { isKubeStatusData, KubeObject, KubeStatus } from "../kube-object";
import type { SecretReference } from "./secret.api";
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api";
import { isDefined } from "@k8slens/utilities";
import type { PodSecurityContext } from "./types/pod-security-context";
import type { Probe } from "./types/probe";
import type { Container } from "./types/container";
import type { ObjectFieldSelector, ResourceFieldSelector } from "./types";
import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object";
import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object";
export class PodApi extends KubeApi<Pod> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -73,826 +60,3 @@ export class PodApi extends KubeApi<Pod> {
return this.request.get(path, { query });
}
}
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
export interface PodLogsQuery {
container?: string;
tailLines?: number;
timestamps?: boolean;
sinceTime?: string; // Date.toISOString()-format
follow?: boolean;
previous?: boolean;
}
export enum PodStatusPhase {
TERMINATED = "Terminated",
FAILED = "Failed",
PENDING = "Pending",
RUNNING = "Running",
SUCCEEDED = "Succeeded",
EVICTED = "Evicted",
}
export interface ContainerStateRunning {
startedAt: string;
}
export interface ContainerStateWaiting {
reason: string;
message: string;
}
export interface ContainerStateTerminated {
startedAt: string;
finishedAt: string;
exitCode: number;
reason: string;
containerID?: string;
message?: string;
signal?: number;
}
/**
* ContainerState holds a possible state of container. Only one of its members
* may be specified. If none of them is specified, the default one is
* `ContainerStateWaiting`.
*/
export interface ContainerState {
running?: ContainerStateRunning;
waiting?: ContainerStateWaiting;
terminated?: ContainerStateTerminated;
}
export type ContainerStateValues = Partial<ContainerState[keyof ContainerState]>;
export interface PodContainerStatus {
name: string;
state?: ContainerState;
lastState?: ContainerState;
ready: boolean;
restartCount: number;
image: string;
imageID: string;
containerID?: string;
started?: boolean;
}
export interface AwsElasticBlockStoreSource {
volumeID: string;
fsType: string;
}
export interface AzureDiskSource {
/**
* The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskName: string;
/**
* The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskURI: string;
/**
* Kind of disk
* @default "Shared"
*/
kind?: "Shared" | "Dedicated" | "Managed";
/**
* Disk caching mode.
* @default "None"
*/
cachingMode?: "None" | "ReadOnly" | "ReadWrite";
/**
* The filesystem type to mount.
* @default "ext4"
*/
fsType?: string;
/**
* Whether the filesystem is used as readOnly.
* @default false
*/
readonly?: boolean;
}
export interface AzureFileSource {
/**
* The name of the secret that contains both Azure storage account name and key.
*/
secretName: string;
/**
* The share name to be used.
*/
shareName: string;
/**
* In case the secret is stored in a different namespace.
* @default "default"
*/
secretNamespace?: string;
/**
* Whether the filesystem is used as readOnly.
*/
readOnly: boolean;
}
export interface CephfsSource {
/**
* List of Ceph monitors
*/
monitors: string[];
/**
* Used as the mounted root, rather than the full Ceph tree.
* @default "/"
*/
path?: string;
/**
* The RADOS user name.
* @default "admin"
*/
user?: string;
/**
* The path to the keyring file.
* @default "/etc/ceph/user.secret"
*/
secretFile?: string;
/**
* Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile`
*/
secretRef?: SecretReference;
/**
* Whether the filesystem is used as readOnly.
*
* @default false
*/
readOnly?: boolean;
}
export interface CinderSource {
volumeID: string;
fsType: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: SecretReference;
}
export interface ConfigMapSource {
name: string;
items: {
key: string;
path: string;
}[];
}
export interface DownwardApiSource {
items: {
path: string;
fieldRef: {
fieldPath: string;
};
}[];
}
export interface EphemeralSource {
volumeClaimTemplate: {
/**
* All the rest of the fields are ignored and rejected during validation
*/
metadata?: Pick<KubeObjectMetadata, "labels" | "annotations">;
spec: PersistentVolumeClaimSpec;
};
}
export interface EmptyDirSource {
medium?: string;
sizeLimit?: string;
}
export interface FiberChannelSource {
/**
* A list of World Wide Names
*/
targetWWNs: string[];
/**
* Logical Unit number
*/
lun: number;
/**
* The type of filesystem
* @default "ext4"
*/
fsType?: string;
readOnly: boolean;
}
export interface FlockerSource {
datasetName: string;
}
export interface FlexVolumeSource {
driver: string;
fsType?: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
readOnly?: boolean;
options?: Record<string, string>;
}
export interface GcePersistentDiskSource {
pdName: string;
fsType: string;
}
export interface GitRepoSource {
repository: string;
revision: string;
}
export interface GlusterFsSource {
/**
* The name of the Endpoints object that represents a Gluster cluster configuration.
*/
endpoints: string;
/**
* The Glusterfs volume name.
*/
path: string;
/**
* The boolean that sets the mountpoint readOnly or readWrite.
*/
readOnly: boolean;
}
export interface HostPathSource {
path: string;
/**
* Determines the sorts of checks that will be done
* @default ""
*/
type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice";
}
export interface IScsiSource {
targetPortal: string;
iqn: string;
lun: number;
fsType: string;
readOnly: boolean;
chapAuthDiscovery?: boolean;
chapAuthSession?: boolean;
secretRef?: SecretReference;
}
export interface LocalSource {
path: string;
}
export interface NetworkFsSource {
server: string;
path: string;
readOnly?: boolean;
}
export interface PersistentVolumeClaimSource {
claimName: string;
}
export interface PhotonPersistentDiskSource {
pdID: string;
/**
* @default "ext4"
*/
fsType?: string;
}
export interface PortworxVolumeSource {
volumeID: string;
fsType?: string;
readOnly?: boolean;
}
export interface KeyToPath {
key: string;
path: string;
mode?: number;
}
export interface ConfigMapProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface DownwardAPIVolumeFile {
path: string;
fieldRef?: ObjectFieldSelector;
resourceFieldRef?: ResourceFieldSelector;
mode?: number;
}
export interface DownwardAPIProjection {
items?: DownwardAPIVolumeFile[];
}
export interface SecretProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface ServiceAccountTokenProjection {
audience?: string;
expirationSeconds?: number;
path: string;
}
export interface VolumeProjection {
secret?: SecretProjection;
downwardAPI?: DownwardAPIProjection;
configMap?: ConfigMapProjection;
serviceAccountToken?: ServiceAccountTokenProjection;
}
export interface ProjectedSource {
sources?: VolumeProjection[];
defaultMode?: number;
}
export interface QuobyteSource {
registry: string;
volume: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "serivceaccount"
*/
user?: string;
group?: string;
tenant?: string;
}
export interface RadosBlockDeviceSource {
monitors: string[];
image: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default "rbd"
*/
pool?: string;
/**
* @default "admin"
*/
user?: string;
/**
* @default "/etc/ceph/keyring"
*/
keyring?: string;
secretRef?: SecretReference;
/**
* @default false
*/
readOnly?: boolean;
}
export interface ScaleIoSource {
gateway: string;
system: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
sslEnabled?: boolean;
protectionDomain?: string;
storagePool?: string;
/**
* @default "ThinProvisioned"
*/
storageMode?: "ThickProvisioned" | "ThinProvisioned";
volumeName: string;
/**
* @default "xfs"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
}
export interface SecretSource {
secretName: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
defaultMode?: number;
optional?: boolean;
}
export interface StorageOsSource {
volumeName: string;
/**
* @default Pod.metadata.namespace
*/
volumeNamespace?: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: LocalObjectReference;
}
export interface VsphereVolumeSource {
volumePath: string;
/**
* @default "ext4"
*/
fsType?: string;
storagePolicyName?: string;
storagePolicyID?: string;
}
export interface ContainerStorageInterfaceSource {
driver: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "ext4"
*/
fsType?: string;
volumeAttributes?: Record<string, string>;
controllerPublishSecretRef?: SecretReference;
nodeStageSecretRef?: SecretReference;
nodePublishSecretRef?: SecretReference;
controllerExpandSecretRef?: SecretReference;
}
export interface PodVolumeVariants {
awsElasticBlockStore: AwsElasticBlockStoreSource;
azureDisk: AzureDiskSource;
azureFile: AzureFileSource;
cephfs: CephfsSource;
cinder: CinderSource;
configMap: ConfigMapSource;
csi: ContainerStorageInterfaceSource;
downwardAPI: DownwardApiSource;
emptyDir: EmptyDirSource;
ephemeral: EphemeralSource;
fc: FiberChannelSource;
flexVolume: FlexVolumeSource;
flocker: FlockerSource;
gcePersistentDisk: GcePersistentDiskSource;
gitRepo: GitRepoSource;
glusterfs: GlusterFsSource;
hostPath: HostPathSource;
iscsi: IScsiSource;
local: LocalSource;
nfs: NetworkFsSource;
persistentVolumeClaim: PersistentVolumeClaimSource;
photonPersistentDisk: PhotonPersistentDiskSource;
portworxVolume: PortworxVolumeSource;
projected: ProjectedSource;
quobyte: QuobyteSource;
rbd: RadosBlockDeviceSource;
scaleIO: ScaleIoSource;
secret: SecretSource;
storageos: StorageOsSource;
vsphereVolume: VsphereVolumeSource;
}
/**
* The valid kinds of volume
*/
export type PodVolumeKind = keyof PodVolumeVariants;
export type PodSpecVolume = RequireExactlyOne<PodVolumeVariants> & {
name: string;
};
export interface HostAlias {
ip: string;
hostnames: string[];
}
export interface Sysctl {
name: string;
value: string;
}
export interface TopologySpreadConstraint {
}
export interface PodSpec {
activeDeadlineSeconds?: number;
affinity?: Affinity;
automountServiceAccountToken?: boolean;
containers?: Container[];
dnsPolicy?: string;
enableServiceLinks?: boolean;
ephemeralContainers?: unknown[];
hostAliases?: HostAlias[];
hostIPC?: boolean;
hostname?: string;
hostNetwork?: boolean;
hostPID?: boolean;
imagePullSecrets?: LocalObjectReference[];
initContainers?: Container[];
nodeName?: string;
nodeSelector?: Partial<Record<string, string>>;
overhead?: Partial<Record<string, string>>;
preemptionPolicy?: string;
priority?: number;
priorityClassName?: string;
readinessGates?: unknown[];
restartPolicy?: string;
runtimeClassName?: string;
schedulerName?: string;
securityContext?: PodSecurityContext;
serviceAccount?: string;
serviceAccountName?: string;
setHostnameAsFQDN?: boolean;
shareProcessNamespace?: boolean;
subdomain?: string;
terminationGracePeriodSeconds?: number;
tolerations?: Toleration[];
topologySpreadConstraints?: TopologySpreadConstraint[];
volumes?: PodSpecVolume[];
}
export interface PodCondition {
lastProbeTime?: number;
lastTransitionTime?: string;
message?: string;
reason?: string;
type: string;
status: string;
}
export interface PodStatus {
phase: string;
conditions: PodCondition[];
hostIP: string;
podIP: string;
podIPs?: {
ip: string;
}[];
startTime: string;
initContainerStatuses?: PodContainerStatus[];
containerStatuses?: PodContainerStatus[];
qosClass?: string;
reason?: string;
}
export class Pod extends KubeObject<
NamespaceScopedMetadata,
PodStatus,
PodSpec
> {
static kind = "Pod";
static namespaced = true;
static apiBase = "/api/v1/pods";
getAffinityNumber() {
return Object.keys(this.getAffinity()).length;
}
getInitContainers() {
return this.spec?.initContainers ?? [];
}
getContainers() {
return this.spec?.containers ?? [];
}
getAllContainers() {
return [...this.getContainers(), ...this.getInitContainers()];
}
getRunningContainers() {
const runningContainerNames = new Set(
this.getContainerStatuses()
.filter(({ state }) => state?.running)
.map(({ name }) => name),
);
return this.getAllContainers()
.filter(({ name }) => runningContainerNames.has(name));
}
getContainerStatuses(includeInitContainers = true): PodContainerStatus[] {
const { containerStatuses = [], initContainerStatuses = [] } = this.status ?? {};
if (includeInitContainers) {
return [...containerStatuses, ...initContainerStatuses];
}
return [...containerStatuses];
}
getRestartsCount(): number {
const { containerStatuses = [] } = this.status ?? {};
return containerStatuses.reduce((totalCount, { restartCount }) => totalCount + restartCount, 0);
}
getQosClass() {
return this.status?.qosClass || "";
}
getReason() {
return this.status?.reason || "";
}
getPriorityClassName() {
return this.spec?.priorityClassName || "";
}
getRuntimeClassName() {
return this.spec?.runtimeClassName || "";
}
getServiceAccountName() {
return this.spec?.serviceAccountName || "";
}
getStatus(): PodStatusPhase {
const phase = this.getStatusPhase();
const reason = this.getReason();
const trueConditionTypes = new Set(this.getConditions()
.filter(({ status }) => status === "True")
.map(({ type }) => type));
const isInGoodCondition = ["Initialized", "Ready"].every(condition => trueConditionTypes.has(condition));
if (reason === PodStatusPhase.EVICTED) {
return PodStatusPhase.EVICTED;
}
if (phase === PodStatusPhase.FAILED) {
return PodStatusPhase.FAILED;
}
if (phase === PodStatusPhase.SUCCEEDED) {
return PodStatusPhase.SUCCEEDED;
}
if (phase === PodStatusPhase.RUNNING && isInGoodCondition) {
return PodStatusPhase.RUNNING;
}
return PodStatusPhase.PENDING;
}
// Returns pod phase or container error if occurred
getStatusMessage(): string {
if (this.getReason() === PodStatusPhase.EVICTED) {
return "Evicted";
}
if (this.metadata.deletionTimestamp) {
return "Terminating";
}
return this.getStatusPhase() || "Waiting";
}
getStatusPhase() {
return this.status?.phase;
}
getConditions() {
return this.status?.conditions ?? [];
}
getVolumes() {
return this.spec?.volumes ?? [];
}
getSecrets(): string[] {
return this.getVolumes()
.map(vol => vol.secret?.secretName)
.filter(isDefined);
}
getNodeSelectors(): string[] {
return Object.entries(this.spec?.nodeSelector ?? {})
.map(values => values.join(": "));
}
getTolerations() {
return this.spec?.tolerations ?? [];
}
getAffinity(): Affinity {
return this.spec?.affinity ?? {};
}
hasIssues() {
for (const { type, status } of this.getConditions()) {
if (type === "Ready" && status !== "True") {
return true;
}
}
for (const { state } of this.getContainerStatuses()) {
if (state?.waiting?.reason === "CrashLookBackOff") {
return true;
}
}
return this.getStatusPhase() !== "Running";
}
getLivenessProbe(container: Container) {
return this.getProbe(container, container.livenessProbe);
}
getReadinessProbe(container: Container) {
return this.getProbe(container, container.readinessProbe);
}
getStartupProbe(container: Container) {
return this.getProbe(container, container.startupProbe);
}
private getProbe(container: Container, probe: Probe | undefined): string[] {
const probeItems: string[] = [];
if (!probe) {
return probeItems;
}
const {
httpGet,
exec,
tcpSocket,
initialDelaySeconds = 0,
timeoutSeconds = 0,
periodSeconds = 0,
successThreshold = 0,
failureThreshold = 0,
} = probe;
// HTTP Request
if (httpGet) {
const { path = "", port, host = "", scheme = "HTTP" } = httpGet;
const resolvedPort = typeof port === "number"
? port
// Try and find the port number associated witht the name or fallback to the name itself
: container.ports?.find(containerPort => containerPort.name === port)?.containerPort || port;
probeItems.push(
"http-get",
`${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`,
);
}
// Command
if (exec?.command) {
probeItems.push(`exec [${exec.command.join(" ")}]`);
}
// TCP Probe
if (tcpSocket?.port) {
probeItems.push(`tcp-socket :${tcpSocket.port}`);
}
probeItems.push(
`delay=${initialDelaySeconds}s`,
`timeout=${timeoutSeconds}s`,
`period=${periodSeconds}s`,
`#success=${successThreshold}`,
`#failure=${failureThreshold}`,
);
return probeItems;
}
getNodeName(): string | undefined {
return this.spec?.nodeName;
}
getSelectedNodeOs(): string | undefined {
return this.spec?.nodeSelector?.["kubernetes.io/os"] || this.spec?.nodeSelector?.["beta.kubernetes.io/os"];
}
getIPs(): string[] {
const podIPs = this.status?.podIPs ?? [];
return podIPs.map(value => value.ip);
}
}
@@ -5,56 +5,9 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PreemptionPolicy } from "./types/preemption-policy";
import type { PriorityClassData } from "@k8slens/kube-object";
import { PriorityClass } from "@k8slens/kube-object";
export interface PriorityClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
description?: string;
globalDefault?: boolean;
preemptionPolicy?: PreemptionPolicy;
value: number;
}
export class PriorityClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "PriorityClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/scheduling.k8s.io/v1/priorityclasses";
description?: string;
globalDefault?: boolean;
preemptionPolicy?: PreemptionPolicy;
value?: number;
constructor({ description, globalDefault, preemptionPolicy, value, ...rest }: PriorityClassData) {
super(rest);
this.description = description;
this.globalDefault = globalDefault;
this.preemptionPolicy = preemptionPolicy;
this.value = value;
}
getDescription() {
return this.description || "";
}
getGlobalDefault() {
return (this.globalDefault || false).toString();
}
getPreemptionPolicy() {
return this.preemptionPolicy || "PreemptLowerPriority";
}
getValue() {
return this.value;
}
}
export class PriorityClassApi extends KubeApi<PriorityClass, PriorityClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,11 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
import { ReplicaSet } from "@k8slens/kube-object";
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -17,17 +15,17 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
return (status as { replicas: number })?.replicas;
}
scale(params: { namespace: string; name: string }, replicas: number) {
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
@@ -38,70 +36,3 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
});
}
}
export interface ReplicaSetSpec {
replicas?: number;
selector: LabelSelector;
template?: PodTemplateSpec;
minReadySeconds?: number;
}
export interface ReplicaSetStatus extends KubeObjectStatus {
replicas: number;
fullyLabeledReplicas?: number;
readyReplicas?: number;
availableReplicas?: number;
observedGeneration?: number;
}
export class ReplicaSet extends KubeObject<
NamespaceScopedMetadata,
ReplicaSetStatus,
ReplicaSetSpec
> {
static kind = "ReplicaSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/replicasets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template?.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template?.metadata?.labels);
}
getTolerations() {
return this.spec.template?.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template?.spec?.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getDesired() {
return this.spec.replicas ?? 0;
}
getCurrent() {
return this.status?.availableReplicas ?? 0;
}
getReady() {
return this.status?.readyReplicas ?? 0;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
return containers.map(container => container.image);
}
}
@@ -3,15 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type {
BaseKubeObjectCondition, KubeObjectMetadata,
KubeObjectStatus,
NamespaceScopedMetadata,
} from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types";
import { ReplicationController } from "@k8slens/kube-object";
import type { Scale } from "@k8slens/kube-object";
export class ReplicationControllerApi extends KubeApi<ReplicationController> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -21,15 +16,15 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
getScale(params: { namespace: string; name: string }): Promise<Scale> {
getScale(params: NamespacedResourceDescriptor): Promise<Scale> {
return this.request.get(this.getScaleApiUrl(params));
}
scale(params: { namespace: string; name: string }, replicas: number): Promise<Scale> {
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
metadata: params,
@@ -44,106 +39,3 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
});
}
}
export interface Scale {
apiVersion: "autoscaling/v1";
kind: "Scale";
metadata: KubeObjectMetadata;
spec: {
replicas: number;
};
status: {
replicas: number;
selector: string;
};
}
export interface ReplicationControllerSpec {
/**
* Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available.
* Defaults to 0 (pod will be considered available as soon as it is ready)
*/
minReadySeconds?: number;
/**
* Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified.
* Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller
*/
replicas?: number;
/**
* Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template.
* Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template.
* More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
*/
selector?: Record<string, string>;
/**
* Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef.
* More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template
*/
template: PodTemplateSpec;
}
export interface ReplicationControllerStatus extends KubeObjectStatus {
/**
* The number of available replicas (ready for at least minReadySeconds) for this replication controller.
*/
availableReplicas: number;
/**
* The number of pods that have labels matching the labels of the pod template of the replication controller.
*/
fullyLabeledReplicas: number;
/**
* ObservedGeneration reflects the generation of the most recently observed replication controller.
*/
observedGeneration: number;
/**
* The number of ready replicas for this replication controller.
*/
readyReplicas: number;
/**
* Replicas is the most recently observed number of replicas.
* More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller
*/
replicas: number;
}
export class ReplicationController extends KubeObject<
NamespaceScopedMetadata,
ReplicationControllerStatus,
ReplicationControllerSpec
> {
static kind = "ReplicationController";
static namespaced = true;
static apiBase = "/api/v1/replicationcontrollers";
getMinReadySeconds(): number {
return this.spec?.minReadySeconds ?? 0;
}
getGeneration() {
return this.status?.observedGeneration;
}
getSelectorLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.selector);
}
getReplicas(): number | undefined {
return this.status?.replicas;
}
getDesiredReplicas(): number {
return this.spec?.replicas ?? 0;
}
getAvailableReplicas(): number | undefined {
return this.status?.availableReplicas;
}
getLabeledReplicas(): number | undefined {
return this.status?.fullyLabeledReplicas;
}
getConditions(): BaseKubeObjectCondition[] {
return this.status?.conditions ?? [];
}
}
@@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { Patch } from "rfc6902";
import apiBaseInjectable from "../../api-base.injectable";
import type { AsyncResult, Result } from "@k8slens/utilities";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => AsyncResult<KubeJsonApiData, string>;
@@ -16,21 +16,21 @@ const requestKubeObjectPatchInjectable = getInjectable({
const apiBase = di.inject(apiBaseInjectable);
return async (name, kind, ns, patch) => {
const result = await apiBase.patch("/stack", {
const result = (await apiBase.patch("/stack", {
data: {
name,
kind,
ns,
patch,
},
}) as Result<string, string>;
})) as Result<string, string>;
if (!result.callWasSuccessful) {
return result;
}
try {
const response = JSON.parse(result.response);
const response = JSON.parse(result.response) as KubeJsonApiData;
return {
callWasSuccessful: true,
@@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import apiBaseInjectable from "../../api-base.injectable";
import type { AsyncResult, Result } from "@k8slens/utilities";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
export type RequestKubeObjectCreation = (resourceDescriptor: string) => AsyncResult<KubeJsonApiData, string>;
@@ -3,67 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { ResourceQuota } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export type IResourceQuotaValues = Partial<Record<string, string>> & {
// Compute Resource Quota
"limits.cpu"?: string;
"limits.memory"?: string;
"requests.cpu"?: string;
"requests.memory"?: string;
// Storage Resource Quota
"requests.storage"?: string;
"persistentvolumeclaims"?: string;
// Object Count Quota
"count/pods"?: string;
"count/persistentvolumeclaims"?: string;
"count/services"?: string;
"count/secrets"?: string;
"count/configmaps"?: string;
"count/replicationcontrollers"?: string;
"count/deployments.apps"?: string;
"count/replicasets.apps"?: string;
"count/statefulsets.apps"?: string;
"count/jobs.batch"?: string;
"count/cronjobs.batch"?: string;
"count/deployments.extensions"?: string;
};
export interface ResourceQuotaSpec {
hard: IResourceQuotaValues;
scopeSelector?: {
matchExpressions: {
operator: string;
scopeName: string;
values: string[];
}[];
};
}
export interface ResourceQuotaStatus {
hard: IResourceQuotaValues;
used: IResourceQuotaValues;
}
export class ResourceQuota extends KubeObject<
NamespaceScopedMetadata,
ResourceQuotaStatus,
ResourceQuotaSpec
> {
static readonly kind = "ResourceQuota";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/resourcequotas";
getScopeSelector() {
return this.spec.scopeSelector?.matchExpressions ?? [];
}
}
export class ResourceQuotaApi extends KubeApi<ResourceQuota> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {
@@ -3,45 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleBindingData } from "@k8slens/kube-object";
import { RoleBinding } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { RoleRef } from "./types/role-ref";
import type { Subject } from "./types/subject";
export interface RoleBindingData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
subjects?: Subject[];
roleRef: RoleRef;
}
export class RoleBinding extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "RoleBinding";
static readonly namespaced = true;
static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings";
subjects?: Subject[];
roleRef: RoleRef;
constructor({ subjects, roleRef, ...rest }: RoleBindingData) {
super(rest);
this.subjects = subjects;
this.roleRef = roleRef;
}
getSubjects() {
return this.subjects || [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
export class RoleBindingApi extends KubeApi<RoleBinding, RoleBindingData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,36 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleData } from "@k8slens/kube-object";
import { Role } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { PolicyRule } from "./types/policy-rule";
export interface RoleData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
rules?: PolicyRule[];
}
export class Role extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "Role";
static readonly namespaced = true;
static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/roles";
rules?: PolicyRule[];
constructor({ rules, ...rest }: RoleData) {
super(rest);
this.rules = rules;
}
getRules() {
return this.rules || [];
}
}
export class RoleApi extends KubeApi<Role, RoleData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -5,62 +5,8 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope, Toleration } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface RuntimeClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
}
export interface RuntimeClassOverhead {
podFixed?: string;
}
export interface RuntimeClassScheduling {
nodeSelector?: Partial<Record<string, string>>;
tolerations?: Toleration[];
}
export class RuntimeClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "RuntimeClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/node.k8s.io/v1/runtimeclasses";
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
constructor({ handler, overhead, scheduling, ...rest }: RuntimeClassData) {
super(rest);
this.handler = handler;
this.overhead = overhead;
this.scheduling = scheduling;
}
getHandler() {
return this.handler;
}
getPodFixed() {
return this.overhead?.podFixed ?? "";
}
getNodeSelectors(): string[] {
return Object.entries(this.scheduling?.nodeSelector ?? {})
.map(values => values.join(": "));
}
getTolerations() {
return this.scheduling?.tolerations ?? [];
}
}
import type { RuntimeClassData } from "@k8slens/kube-object";
import { RuntimeClass } from "@k8slens/kube-object";
export class RuntimeClassApi extends KubeApi<RuntimeClass, RuntimeClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,73 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import autoBind from "auto-bind";
export enum SecretType {
Opaque = "Opaque",
ServiceAccountToken = "kubernetes.io/service-account-token",
Dockercfg = "kubernetes.io/dockercfg",
DockerConfigJson = "kubernetes.io/dockerconfigjson",
BasicAuth = "kubernetes.io/basic-auth",
SSHAuth = "kubernetes.io/ssh-auth",
TLS = "kubernetes.io/tls",
BootstrapToken = "bootstrap.kubernetes.io/token",
}
export const reverseSecretTypeMap = {
[SecretType.Opaque]: "Opaque",
[SecretType.ServiceAccountToken]: "ServiceAccountToken",
[SecretType.Dockercfg]: "Dockercfg",
[SecretType.DockerConfigJson]: "DockerConfigJson",
[SecretType.BasicAuth]: "BasicAuth",
[SecretType.SSHAuth]: "SSHAuth",
[SecretType.TLS]: "TLS",
[SecretType.BootstrapToken]: "BootstrapToken",
};
export interface SecretReference {
name: string;
namespace?: string;
}
export interface SecretData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
type: SecretType;
data?: Partial<Record<string, string>>;
}
export class Secret extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "Secret";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/secrets";
type: SecretType;
data: Partial<Record<string, string>>;
constructor({ data = {}, type, ...rest }: SecretData) {
super(rest);
autoBind(this);
this.data = data;
this.type = type;
}
getKeys(): string[] {
return Object.keys(this.data);
}
getToken() {
return this.data.token;
}
}
import { Secret } from "@k8slens/kube-object";
import type { SecretData } from "@k8slens/kube-object";
export class SecretApi extends KubeApi<Secret, SecretData> {
constructor(deps: KubeApiDependencies, options: DerivedKubeApiOptions = {}) {
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { SelfSubjectRulesReview } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
@@ -23,58 +23,3 @@ export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
});
}
}
export interface ISelfSubjectReviewRule {
verbs: string[];
apiGroups?: string[];
resources?: string[];
resourceNames?: string[];
nonResourceURLs?: string[];
}
export interface SelfSubjectRulesReview {
spec: {
namespace?: string;
};
status: {
resourceRules: ISelfSubjectReviewRule[];
nonResourceRules: ISelfSubjectReviewRule[];
incomplete: boolean;
};
}
export class SelfSubjectRulesReview extends KubeObject {
static kind = "SelfSubjectRulesReview";
static namespaced = false;
static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews";
getResourceRules() {
const rules = this.status && this.status.resourceRules || [];
return rules.map(rule => this.normalize(rule));
}
getNonResourceRules() {
const rules = this.status && this.status.nonResourceRules || [];
return rules.map(rule => this.normalize(rule));
}
protected normalize(rule: ISelfSubjectReviewRule): ISelfSubjectReviewRule {
const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule;
return {
apiGroups,
nonResourceURLs,
resourceNames,
verbs,
resources: resources.map((resource, index) => {
const apiGroup = apiGroups.length >= index + 1 ? apiGroups[index] : apiGroups.slice(-1)[0];
const separator = apiGroup == "" ? "" : ".";
return resource + separator + apiGroup;
}),
};
}
}
@@ -3,51 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, LocalObjectReference, NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { ServiceAccountData } from "@k8slens/kube-object";
import { ServiceAccount } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface ServiceAccountData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
automountServiceAccountToken?: boolean;
imagePullSecrets?: LocalObjectReference[];
secrets?: ObjectReference[];
}
export class ServiceAccount extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "ServiceAccount";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/serviceaccounts";
automountServiceAccountToken?: boolean;
imagePullSecrets?: LocalObjectReference[];
secrets?: ObjectReference[];
constructor({
automountServiceAccountToken,
imagePullSecrets,
secrets,
...rest
}: ServiceAccountData) {
super(rest);
this.automountServiceAccountToken = automountServiceAccountToken;
this.imagePullSecrets = imagePullSecrets;
this.secrets = secrets;
}
getSecrets() {
return this.secrets || [];
}
getImagePullSecrets() {
return this.imagePullSecrets || [];
}
}
export class ServiceAccountApi extends KubeApi<ServiceAccount, ServiceAccountData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -3,131 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { Service } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface ServicePort {
name?: string;
protocol: string;
port: number;
targetPort: number;
nodePort?: number;
}
export class ServicePort {
constructor(data: ServicePort) {
Object.assign(this, data);
}
toString() {
if (this.nodePort) {
return `${this.port}:${this.nodePort}/${this.protocol}`;
} else {
return `${this.port}${this.port === this.targetPort ? "" : `:${this.targetPort}`}/${this.protocol}`;
}
}
}
export interface ServiceSpec {
type: string;
clusterIP: string;
clusterIPs?: string[];
externalTrafficPolicy?: string;
externalName?: string;
loadBalancerIP?: string;
loadBalancerSourceRanges?: string[];
sessionAffinity: string;
selector: Partial<Record<string, string>>;
ports: ServicePort[];
healthCheckNodePort?: number;
externalIPs?: string[]; // https://kubernetes.io/docs/concepts/services-networking/service/#external-ips
topologyKeys?: string[];
ipFamilies?: string[];
ipFamilyPolicy?: string;
allocateLoadBalancerNodePorts?: boolean;
loadBalancerClass?: string;
internalTrafficPolicy?: string;
}
export interface ServiceStatus {
loadBalancer?: {
ingress?: {
ip?: string;
hostname?: string;
}[];
};
}
export class Service extends KubeObject<
NamespaceScopedMetadata,
ServiceStatus,
ServiceSpec
> {
static readonly kind = "Service";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/services";
getClusterIp() {
return this.spec.clusterIP;
}
getClusterIps() {
return this.spec.clusterIPs || [];
}
getExternalIps() {
const lb = this.getLoadBalancer();
if (lb?.ingress) {
return lb.ingress.map(val => val.ip || val.hostname);
}
if (Array.isArray(this.spec?.externalIPs)) {
return this.spec.externalIPs;
}
return [];
}
getType() {
return this.spec.type || "-";
}
getSelector(): string[] {
if (!this.spec.selector) return [];
return Object.entries(this.spec.selector).map(val => val.join("="));
}
getPorts(): ServicePort[] {
const ports = this.spec.ports || [];
return ports.map(p => new ServicePort(p));
}
getLoadBalancer() {
return this.status?.loadBalancer;
}
isActive() {
return this.getType() !== "LoadBalancer" || this.getExternalIps().length > 0;
}
getStatus() {
return this.isActive() ? "Active" : "Pending";
}
getIpFamilies() {
return this.spec.ipFamilies || [];
}
getIpFamilyPolicy() {
return this.spec.ipFamilyPolicy || "";
}
}
export class ServiceApi extends KubeApi<Service> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {
@@ -5,12 +5,9 @@
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
import type { PersistentVolumeClaimTemplateSpec } from "./types/persistent-volume-claim-template-spec";
import { StatefulSet } from "@k8slens/kube-object";
export class StatefulSetApi extends KubeApi<StatefulSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@@ -20,109 +17,34 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
getReplicas(params: { namespace: string; name: string }): Promise<number> {
return this.request
.get(this.getScaleApiUrl(params))
.then(({ status }: any) => status?.replicas);
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const apiUrl = this.getScaleApiUrl(params);
const { status = 0 } = await this.request.get(apiUrl) as { status?: number };
return status;
}
scale(params: { namespace: string; name: string }, replicas: number) {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
spec: {
replicas,
},
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.patch(params, {
spec: {
replicas,
},
},
{
headers: {
"content-type": "application/merge-patch+json",
},
});
}, "merge");
}
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() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
export interface StatefulSetSpec {
serviceName: string;
replicas: number;
selector: LabelSelector;
template: PodTemplateSpec;
volumeClaimTemplates: PersistentVolumeClaimTemplateSpec[];
}
export interface StatefulSetStatus {
observedGeneration: number;
replicas: number;
currentReplicas: number;
readyReplicas: number;
currentRevision: string;
updateRevision: string;
collisionCount: number;
}
export class StatefulSet extends KubeObject<
NamespaceScopedMetadata,
StatefulSetStatus,
StatefulSetSpec
> {
static readonly kind = "StatefulSet";
static readonly namespaced = true;
static readonly apiBase = "/apis/apps/v1/statefulsets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata?.labels);
}
getTolerations() {
return this.spec.template.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec?.affinity ?? {};
}
getAffinityNumber() {
return Object.keys(this.getAffinity()).length;
}
getReplicas() {
return this.spec.replicas || 0;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
return containers.map(container => container.image);
}, "strategic");
}
}
@@ -3,87 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { StorageClassData } from "@k8slens/kube-object";
import { StorageClass } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import autoBind from "auto-bind";
export interface TopologySelectorLabelRequirement {
key: string;
values: string[];
}
export interface TopologySelectorTerm {
matchLabelExpressions?: TopologySelectorLabelRequirement[];
}
export interface StorageClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
allowVolumeExpansion?: boolean;
allowedTopologies?: TopologySelectorTerm[];
mountOptions?: string[];
parameters?: Partial<Record<string, string>>;
provisioner: string;
reclaimPolicy?: string;
volumeBindingMode?: string;
}
export class StorageClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "StorageClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/storage.k8s.io/v1/storageclasses";
allowVolumeExpansion?: boolean;
allowedTopologies: TopologySelectorTerm[];
mountOptions: string[];
parameters: Partial<Record<string, string>>;
provisioner: string;
reclaimPolicy: string;
volumeBindingMode?: string;
constructor({
allowVolumeExpansion,
allowedTopologies = [],
mountOptions = [],
parameters = {},
provisioner,
reclaimPolicy = "Delete",
volumeBindingMode,
...rest
}: StorageClassData) {
super(rest);
autoBind(this);
this.allowVolumeExpansion = allowVolumeExpansion;
this.allowedTopologies = allowedTopologies;
this.mountOptions = mountOptions;
this.parameters = parameters;
this.provisioner = provisioner;
this.reclaimPolicy = reclaimPolicy;
this.volumeBindingMode = volumeBindingMode;
}
isDefault() {
const annotations = this.metadata.annotations || {};
return (
annotations["storageclass.kubernetes.io/is-default-class"] === "true" ||
annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true"
);
}
getVolumeBindingMode() {
return this.volumeBindingMode || "-";
}
getReclaimPolicy() {
return this.reclaimPolicy || "-";
}
}
export class StorageClassApi extends KubeApi<StorageClass, StorageClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
@@ -1,10 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector } from "../../kube-object";
export interface AggregationRule {
clusterRoleSelectors?: LabelSelector;
}
@@ -1,19 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* Adds and removes POSIX capabilities from running containers.
*/
export interface Capabilities {
/**
* Added capabilities
*/
add?: string[];
/**
* Removed capabilities
*/
drop?: string[];
}
@@ -1,13 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface Condition {
lastTransitionTime: string;
message: string;
observedGeneration?: number;
reason: string;
status: string;
type: string;
}
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ContainerPort {
containerPort: number;
hostIP?: string;
hostPort?: number;
name?: string;
protocol?: "UDP" | "TCP" | "SCTP";
}
@@ -1,176 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Lifecycle } from "./lifecycle";
import type { ResourceRequirements } from "./resource-requirements";
import type { SecurityContext } from "./security-context";
import type { Probe } from "./probe";
import type { VolumeDevice } from "./volume-device";
import type { VolumeMount } from "./volume-mount";
import type { ContainerPort } from "./container-port";
import type { EnvFromSource } from "./env-from-source";
import type { EnvVar } from "./env-var";
/**
* A single application container that you want to run within a pod.
*/
export interface Container {
/**
* Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable
* references `$(VAR_NAME)` are expanded using the container's environment.
*
* If a variable cannot be resolved, the reference in the input string will be unchanged.
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
*
* Escaped references will never be expanded, regardless of whether the variable exists or not.
* Cannot be updated.
*
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
*/
args?: string[];
/**
* Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this
* is not provided. Variable references `$(VAR_NAME)` are expanded using the container's
* environment.
*
* If a variable cannot be resolved, the reference in the input string will be unchanged.
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
*
* Escaped references will never be expanded, regardless of whether the variable exists or not.
* Cannot be updated.
*
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
*/
command?: string[];
/**
* List of environment variables to set in the container. Cannot be updated.
*/
env?: EnvVar[];
/**
* List of sources to populate environment variables in the container. The keys defined within a
* source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the
* container is starting.
*
* When a key exists in multiple sources, the value associated with the last source will take
* precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be
* updated.
*/
envFrom?: EnvFromSource[];
/**
* Docker image name.
*
* More info: https://kubernetes.io/docs/concepts/containers/images
*/
image?: string;
/**
* Image pull policy. Defaults to `"Always"` if :latest tag is specified, or `"IfNotPresent"`
* otherwise. Cannot be updated.
*
* More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
*/
imagePullPolicy?: "Always" | "Never" | "IfNotPresent";
lifecycle?: Lifecycle;
livenessProbe?: Probe;
/**
* Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique
* name. Cannot be updated.
*/
name: string;
/**
* List of ports to expose from the container. Exposing a port here gives the system additional
* information about the network connections a container uses, but is primarily informational.
* Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is
* listening on the default `"0.0.0.0"` address inside a container will be accessible from the
* network. Cannot be updated.
*/
ports?: ContainerPort[];
readinessProbe?: Probe;
resources?: ResourceRequirements;
securityContext?: SecurityContext;
startupProbe?: Probe;
/**
* Whether this container should allocate a buffer for stdin in the container runtime. If this is
* not set, reads from stdin in the container will always result in EOF.
*
* @default false
*/
stdin?: boolean;
/**
* Whether the container runtime should close the stdin channel after it has been opened by a
* single attach. When stdin is true the stdin stream will remain open across multiple attach
* sessions.
*
* If stdinOnce is set to true, stdin is opened on container start, is empty until the first
* client attaches to stdin, and then remains open and accepts data until the client disconnects,
* at which time stdin is closed and remains closed until the container is restarted.
*
* If this flag is false, a container processes that reads from stdin will never receive an EOF.
*
* @default false
*/
stdinOnce?: boolean;
/**
* Path at which the file to which the container's termination message will be written
* is mounted into the container's filesystem. Message written is intended to be brief final
* status, such as an assertion failure message.
*
* Will be truncated by the node if greater than 4096 bytes.
* The total message length across all containers will be limited to 12kb. Cannot be updated.
*
* @default "/dev/termination-log"
*/
terminationMessagePath?: string;
/**
* Indicate how the termination message should be populated.
*
* - `File`: will use the contents of {@link terminationMessagePath} to populate the container
* status message on both success and failure.
*
* - `FallbackToLogsOnError`: will use the last chunk of container log output if the
* termination message file is empty and the container exited with an error.
*
* The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Cannot be updated.
*
* @default "File"
*/
terminationMessagePolicy?: "File" | "FallbackToLogsOnError";
/**
* Whether this container should allocate a TTY for itself, also requires 'stdin' to be true.
*
* @default false
*/
tty?: boolean;
/**
* volumeDevices is the list of block devices to be used by the container.
*/
volumeDevices?: VolumeDevice[];
/**
* Pod volumes to mount into the container's filesystem. Cannot be updated.
*/
volumeMounts?: VolumeMount[];
/**
* Container's working directory. If not specified, the container runtime's default will be used,
* which might be configured in the container image. Cannot be updated.
*/
workingDir?: string;
}
@@ -1,11 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface CrossVersionObjectReference {
kind: string;
name: string;
apiVersion: string;
}
@@ -1,14 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvSource } from "./env-source";
export interface EnvFromSource {
configMapRef?: EnvSource;
/**
* An identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.
*/
prefix?: string;
secretRef?: EnvSource;
}
@@ -1,13 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LocalObjectReference } from "../../kube-object";
export interface EnvSource extends LocalObjectReference {
/**
* Whether the object must be defined
*/
optional?: boolean;
}
@@ -1,10 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface EnvVarKeySelector {
key: string;
name?: string;
optional?: boolean;
}
@@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvVarKeySelector } from "./env-var-key-selector";
import type { ObjectFieldSelector } from "./object-field-selector";
import type { ResourceFieldSelector } from "./resource-field-selector";
export interface EnvVarSource {
configMapKeyRef?: EnvVarKeySelector;
fieldRef?: ObjectFieldSelector;
resourceFieldRef?: ResourceFieldSelector;
secretKeyRef?: EnvVarKeySelector;
}
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvVarSource } from "./env-var-source";
export interface EnvVar {
name: string;
value?: string;
valueFrom?: EnvVarSource;
}
@@ -1,19 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* ExecAction describes a "run in container" action.
*/
export interface ExecAction {
/**
* Command is the command line to execute inside the container, the working directory for the
* command is root ('\\') in the container's filesystem. The command is simply exec'd, it is not
* run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell,
* you need to explicitly call out to that shell.
*
* Exit status of 0 is treated as live/healthy and non-zero is unhealthy.
*/
command?: string[];
}
@@ -1,9 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ExternalDocumentation {
description?: string;
url?: string;
}
@@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExecAction } from "./exec-action";
import type { HttpGetAction } from "./http-get-action";
import type { TcpSocketAction } from "./tcp-socket-action";
/**
* Handler defines a specific action that should be taken.
*/
export interface Handler {
exec?: ExecAction;
httpGet?: HttpGetAction;
tcpSocket?: TcpSocketAction;
}
@@ -1,38 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { HttpHeader } from "./http-header";
/**
* An action based on HTTP Get requests.
*/
export interface HttpGetAction {
/**
* Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.
*/
host?: string;
/**
* Custom headers to set in the request. HTTP allows repeated headers.
*/
httpHeaders?: HttpHeader[];
/**
* Path to access on the HTTP server.
*/
path?: string;
/**
* The PORT to request from.
*/
port: string | number;
/**
* Scheme to use for connecting to the host.
*
* @default "HTTP"
*/
scheme?: string;
}
@@ -1,19 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* A custom header to be used in HTTP probes and get actions
*/
export interface HttpHeader {
/**
* Field name
*/
name: string;
/**
* The value of the field
*/
value: string;
}
@@ -1,37 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./aggregation-rule";
export * from "./capabilities";
export * from "./container";
export * from "./container-port";
export * from "./cross-version-object-reference";
export * from "./env-from-source";
export * from "./env-source";
export * from "./env-var-key-selector";
export * from "./env-var-source";
export * from "./env-var";
export * from "./exec-action";
export * from "./handler";
export * from "./http-get-action";
export * from "./http-header";
export * from "./job-template-spec";
export * from "./lifecycle";
export * from "./object-field-selector";
export * from "./persistent-volume-claim-template-spec";
export * from "./pod-security-context";
export * from "./pod-template-spec";
export * from "./policy-rule";
export * from "./probe";
export * from "./resource-field-selector";
export * from "./resource-requirements";
export * from "./role-ref";
export * from "./se-linux-options";
export * from "./seccomp-profile";
export * from "./subject";
export * from "./tcp-socket-action";
export * from "./volume-device";
export * from "./volume-mount";
export * from "./windows-security-context-options";
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object";
import type { JobSpec } from "../job.api";
export interface JobTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Namespace>;
spec?: JobSpec;
}
@@ -1,93 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { JsonValue } from "type-fest";
import type { ExternalDocumentation } from "./external-documentation";
export interface JSONSchemaProps {
$ref?: string;
$schema?: string;
additionalItems?: JSONSchemaProps | boolean;
additionalProperties?: JSONSchemaProps | boolean;
allOf?: JSONSchemaProps[];
anyOf?: JSONSchemaProps[];
/**
* default is a default value for undefined object fields.
* Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
* Defaulting requires spec.preserveUnknownFields to be false.
*/
_default?: object;
definitions?: Partial<Record<string, JSONSchemaProps>>;
dependencies?: Partial<Record<string, object>>;
description?: string;
_enum?: object[];
example?: JsonValue;
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean;
externalDocs?: ExternalDocumentation;
/**
* format is an OpenAPI v3 format string.
* Unknown formats are ignored.
*
* The following formats are validated:
* - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
* - uri: an URI as parsed by Golang net/url.ParseRequestURI
* - email: an email address as parsed by Golang net/mail.ParseAddress
* - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
* - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
* - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
* - cidr: a CIDR as parsed by Golang net.ParseCIDR
* - mac: a MAC address as parsed by Golang net.ParseMAC
* - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
* - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
* - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
* - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
* - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
* - isbn10: an ISBN10 number string like "0321751043"
* - isbn13: an ISBN13 number string like "978-0321751041"
* - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
* - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
* - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
* - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
* - byte: base64 encoded binary data
* - password: any kind of string
* - date: a date string like "2006-01-02" as defined by full-date in RFC3339
* - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
* - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
*/
format?: string;
id?: string;
items?: JSONSchemaProps | JSONSchemaProps[];
maxItems?: number;
maxLength?: number;
maxProperties?: number;
maximum?: number;
minItems?: number;
minLength?: number;
minProperties?: number;
minimum?: number;
multipleOf?: number;
not?: JSONSchemaProps;
nullable?: boolean;
oneOf?: JSONSchemaProps[];
pattern?: string;
patternProperties?: Partial<Record<string, JSONSchemaProps>>;
properties?: Partial<Record<string, JSONSchemaProps>>;
required?: Array<string>;
title?: string;
type?: string;
uniqueItems?: boolean;
x_kubernetes_embedded_resource?: boolean;
x_kubernetes_int_or_string?: boolean;
x_kubernetes_list_map_keys?: string[];
x_kubernetes_list_type?: string;
x_kubernetes_map_type?: string;
x_kubernetes_preserve_unknown_fields?: boolean;
}
@@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Handler } from "./handler";
/**
* Lifecycle describes actions that the management system should take in response to container
* lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container
* blocks until the action is complete, unless the container process fails, in which case the
* handler is aborted.
*/
export interface Lifecycle {
postStart?: Handler;
preStop?: Handler;
}
@@ -1,9 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ObjectFieldSelector {
apiVersion?: string;
fieldPath: string;
}
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object";
import type { PersistentVolumeSpec } from "../persistent-volume.api";
export interface PersistentVolumeClaimTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Cluster>;
spec?: PersistentVolumeSpec;
}
@@ -1,22 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { SeLinuxOptions } from "./se-linux-options";
import type { SeccompProfile } from "./seccomp-profile";
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
import type { Sysctl } from "../pod.api";
export interface PodSecurityContext {
fsGroup?: number;
fsGroupChangePolicy?: string;
runAsGroup?: number;
runAsNonRoot?: boolean;
runAsUser?: number;
seLinuxOptions?: SeLinuxOptions;
seccompProfile?: SeccompProfile;
supplementalGroups?: number[];
sysctls?: Sysctl;
windowsOptions?: WindowsSecurityContextOptions;
}
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object";
import type { PodSpec } from "../pod.api";
export interface PodTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Namespace>;
spec?: PodSpec;
}
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface PolicyRule {
verbs: string[];
apiGroups?: string[];
resources?: string[];
resourceNames?: string[];
nonResourceURLs?: string[];
}
@@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type PreemptionPolicy = "Never" | "PreemptLowerPriority";
@@ -1,80 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExecAction } from "./exec-action";
import type { HttpGetAction } from "./http-get-action";
import type { TcpSocketAction } from "./tcp-socket-action";
/**
* Describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.
*/
export interface Probe {
exec?: ExecAction;
/**
* Minimum consecutive failures for the probe to be considered failed after having succeeded.
*
* @default 3
* @minimum 1
*/
failureThreshold?: number;
httpGet?: HttpGetAction;
/**
* Duration after the container has started before liveness probes are initiated.
*
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
*/
initialDelaySeconds?: number;
/**
* How often to perform the probe.
*
* @default 10
* @minimum 1
*/
periodSeconds?: number;
/**
* Minimum consecutive successes for the probe to be considered successful after having failed.
*
* Must be 1 for liveness and startup.
*
* @default 1
* @minimum 1
*/
successThreshold?: number;
tcpSocket?: TcpSocketAction;
/**
* Duration the pod needs to terminate gracefully upon probe failure.
*
* The grace period is the duration in seconds after the processes running in the pod are sent a
* termination signal and the time when the processes are forcibly halted with a kill signal.
*
* Set this value longer than the expected cleanup time for your process.
*
* If this value is not set, the pod's terminationGracePeriodSeconds will be used. Otherwise,
* this value overrides the value provided by the pod spec. Value must be non-negative integer.
* The value zero indicates stop immediately via the kill signal (no opportunity to shut down).
*
* This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
*
* @minimum 1
*/
terminationGracePeriodSeconds?: number;
/**
* Duration after which the probe times out.
*
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
*
* @default 1
* @minimum 1
*/
timeoutSeconds?: number;
}
Loaded 100 of 466 files, more files were not shown because too many files have changed in this diff. Show more