Refactor k8s api to common (#3553)

* refactor kube api to common

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor more utils to common

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor more utils to common

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* switch to use node-fetch on both sides

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* cleanup

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* improve logger

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix lint errors

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix/improve tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix node-fetch require error on prod build

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* preload fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* read serverAddress from apiBase

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* cleanup

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2021-08-10 13:26:14 +03:00
1 parent 476aa74101
commit 58a446bd45
250 files changed
+1244 -644

No files matched your search

@@ -1,63 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { ingressStore } from "../../components/+network-ingresses/ingress.store";
import { apiManager } from "../api-manager";
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() {
return;
}
}
describe("ApiManager", () => {
describe("registerApi", () => {
it("re-register store if apiBase changed", async () => {
const apiBase = "apis/v1/foo";
const fallbackApiBase = "/apis/extensions/v1beta1/foo";
const kubeApi = new TestApi({
objectConstructor: KubeObject,
apiBase,
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
});
apiManager.registerApi(apiBase, kubeApi);
// Define to use test api for ingress store
Object.defineProperty(ingressStore, "api", { value: kubeApi });
apiManager.registerStore(ingressStore, [kubeApi]);
// Test that store is returned with original apiBase
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
// Change apiBase similar as checkPreferredVersion does
Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase });
apiManager.registerApi(fallbackApiBase, kubeApi);
// Test that store is returned with new apiBase
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
});
});
});
-112
View File
@@ -1,112 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { CustomResourceDefinition } from "../endpoints";
import type { KubeObjectMetadata } from "../kube-object";
describe("Crds", () => {
describe("getVersion", () => {
it("should get the first version name from the list of versions", () => {
const crd = new CustomResourceDefinition({
apiVersion: "foo",
kind: "CustomResourceDefinition",
metadata: {} as KubeObjectMetadata,
});
crd.spec = {
versions: [
{
name: "123",
served: false,
storage: false,
}
]
} as any;
expect(crd.getVersion()).toBe("123");
});
it("should get the first version name from the list of versions (length 2)", () => {
const crd = new CustomResourceDefinition({
apiVersion: "foo",
kind: "CustomResourceDefinition",
metadata: {} as KubeObjectMetadata,
});
crd.spec = {
versions: [
{
name: "123",
served: false,
storage: false,
},
{
name: "1234",
served: false,
storage: false,
}
]
} as any;
expect(crd.getVersion()).toBe("123");
});
it("should get the first version name from the list of versions (length 2) even with version field", () => {
const crd = new CustomResourceDefinition({
apiVersion: "foo",
kind: "CustomResourceDefinition",
metadata: {} as KubeObjectMetadata,
});
crd.spec = {
version: "abc",
versions: [
{
name: "123",
served: false,
storage: false,
},
{
name: "1234",
served: false,
storage: false,
}
]
} as any;
expect(crd.getVersion()).toBe("123");
});
it("should get the first version name from the version field", () => {
const crd = new CustomResourceDefinition({
apiVersion: "foo",
kind: "CustomResourceDefinition",
metadata: {} as KubeObjectMetadata,
});
crd.spec = {
version: "abc"
} as any;
expect(crd.getVersion()).toBe("abc");
});
});
});
@@ -1,60 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { EndpointSubset } from "../endpoints";
describe("endpoint tests", () => {
describe("EndpointSubset", () => {
it.each([
4,
false,
null,
{},
[],
"ahe",
/a/,
])("should always initialize fields when given %j", (data: any) => {
const sub = new EndpointSubset(data);
expect(sub.addresses).toStrictEqual([]);
expect(sub.notReadyAddresses).toStrictEqual([]);
expect(sub.ports).toStrictEqual([]);
});
it("toString should be addresses X ports", () => {
const sub = new EndpointSubset({
addresses: [{
ip: "1.1.1.1",
}, {
ip: "1.1.1.2",
}] as any,
notReadyAddresses: [],
ports: [{
port: "81",
}, {
port: "82",
}] as any,
});
expect(sub.toString()).toBe("1.1.1.1:81, 1.1.1.1:82, 1.1.1.2:81, 1.1.1.2:82");
});
});
});
@@ -1,146 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
jest.mock("../kube-object");
jest.mock("../kube-api");
jest.mock("../api-manager", () => ({
apiManager() {
return {
registerStore: jest.fn(),
};
}
}));
import { IKubeApiParsed, parseKubeApi } from "../kube-api-parse";
/**
* [<input-url>, <expected-result>]
*/
type KubeApiParseTestData = [string, Required<IKubeApiParsed>];
const tests: KubeApiParseTestData[] = [
["/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com", {
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
apiPrefix: "/apis",
apiGroup: "apiextensions.k8s.io",
apiVersion: "v1beta1",
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
namespace: undefined,
resource: "customresourcedefinitions",
name: "prometheuses.monitoring.coreos.com"
}],
["/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27", {
apiBase: "/api/v1/pods",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
namespace: "kube-system",
resource: "pods",
name: "coredns-6955765f44-v8p27"
}],
["/apis/stable.example.com/foo1/crontabs", {
apiBase: "/apis/stable.example.com/foo1/crontabs",
apiPrefix: "/apis",
apiGroup: "stable.example.com",
apiVersion: "foo1",
apiVersionWithGroup: "stable.example.com/foo1",
resource: "crontabs",
name: undefined,
namespace: undefined,
}],
["/apis/cluster.k8s.io/v1alpha1/clusters", {
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
apiPrefix: "/apis",
apiGroup: "cluster.k8s.io",
apiVersion: "v1alpha1",
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
resource: "clusters",
name: undefined,
namespace: undefined,
}],
["/api/v1/namespaces", {
apiBase: "/api/v1/namespaces",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
resource: "namespaces",
name: undefined,
namespace: undefined,
}],
["/api/v1/secrets", {
apiBase: "/api/v1/secrets",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
resource: "secrets",
name: undefined,
namespace: undefined,
}],
["/api/v1/nodes/minikube", {
apiBase: "/api/v1/nodes",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
resource: "nodes",
name: "minikube",
namespace: undefined,
}],
["/api/foo-bar/nodes/minikube", {
apiBase: "/api/foo-bar/nodes",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "foo-bar",
apiVersionWithGroup: "foo-bar",
resource: "nodes",
name: "minikube",
namespace: undefined,
}],
["/api/v1/namespaces/kube-public", {
apiBase: "/api/v1/namespaces",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
resource: "namespaces",
name: "kube-public",
namespace: undefined,
}],
];
const throwtests = [
undefined,
"",
"ajklsmh"
];
describe("parseApi unit tests", () => {
it.each(tests)("testing %j", (url, expected) => {
expect(parseKubeApi(url)).toStrictEqual(expected);
});
it.each(throwtests)("testing %j should throw", (url) => {
expect(() => parseKubeApi(url)).toThrowError("invalid apiPath");
});
});
-106
View File
@@ -1,106 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
describe("KubeApi", () => {
it("uses url from apiBase if apiBase contains the resource", async () => {
(fetch as any).mockResponse(async (request: any) => {
if (request.url === "/api-kube/apis/networking.k8s.io/v1") {
return {
body: JSON.stringify({
resources: [{
name: "ingresses"
}] as any[]
})
};
} else if (request.url === "/api-kube/apis/extensions/v1beta1") {
// Even if the old API contains ingresses, KubeApi should prefer the apiBase url
return {
body: JSON.stringify({
resources: [{
name: "ingresses"
}] as any[]
})
};
} else {
return {
body: JSON.stringify({
resources: [] as any[]
})
};
}
});
const apiBase = "/apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({
objectConstructor: KubeObject,
apiBase,
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
});
await kubeApi.get();
expect(kubeApi.apiPrefix).toEqual("/apis");
expect(kubeApi.apiGroup).toEqual("networking.k8s.io");
});
it("uses url from fallbackApiBases if apiBase lacks the resource", async () => {
(fetch as any).mockResponse(async (request: any) => {
if (request.url === "/api-kube/apis/networking.k8s.io/v1") {
return {
body: JSON.stringify({
resources: [] as any[]
})
};
} else if (request.url === "/api-kube/apis/extensions/v1beta1") {
return {
body: JSON.stringify({
resources: [{
name: "ingresses"
}] as any[]
})
};
} else {
return {
body: JSON.stringify({
resources: [] as any[]
})
};
}
});
const apiBase = "apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({
objectConstructor: KubeObject,
apiBase,
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
});
await kubeApi.get();
expect(kubeApi.apiPrefix).toEqual("/apis");
expect(kubeApi.apiGroup).toEqual("extensions");
});
});
@@ -1,235 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
describe("KubeObject", () => {
describe("isJsonApiData", () => {
{
type TestCase = [any];
const tests: TestCase[] = [
[false],
[true],
[null],
[undefined],
[""],
[1],
[(): unknown => void 0],
[Symbol("hello")],
[{}],
];
it.each(tests)("should reject invalid value: %p", (input) => {
expect(KubeObject.isJsonApiData(input)).toBe(false);
});
}
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { apiVersion: "", metadata: {uid: "", name: "", resourceVersion: "", selfLink: ""} }],
["apiVersion", { kind: "", metadata: {uid: "", name: "", resourceVersion: "", selfLink: ""} }],
["metadata", { kind: "", apiVersion: "" }],
["metadata.uid", { kind: "", apiVersion: "", metadata: { name: "", resourceVersion: "", selfLink: ""} }],
["metadata.name", { kind: "", apiVersion: "", metadata: { uid: "", resourceVersion: "", selfLink: "" } }],
["metadata.resourceVersion", { kind: "", apiVersion: "", metadata: { uid: "", name: "", selfLink: "" } }],
];
it.each(tests)("should reject with missing: %s", (missingField, input) => {
expect(KubeObject.isJsonApiData(input)).toBe(false);
});
}
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { kind: 1, apiVersion: "", metadata: {} }],
["apiVersion", { apiVersion: 1, kind: "", metadata: {} }],
["metadata", { kind: "", apiVersion: "", metadata: "" }],
["metadata.uid", { kind: "", apiVersion: "", metadata: { uid: 1 } }],
["metadata.name", { kind: "", apiVersion: "", metadata: { uid: "", name: 1 } }],
["metadata.resourceVersion", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: 1 } }],
["metadata.selfLink", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: 1 } }],
["metadata.namespace", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", namespace: 1 } }],
["metadata.creationTimestamp", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", creationTimestamp: 1 } }],
["metadata.continue", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", continue: 1 } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: 1 } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: [1] } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: {} } }],
["metadata.labels", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", labels: 1 } }],
["metadata.labels", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", labels: { food: 1 } } }],
["metadata.annotations", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: 1 } }],
["metadata.annotations", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: { food: 1 } } }],
];
it.each(tests)("should reject with wrong type for field: %s", (missingField, input) => {
expect(KubeObject.isJsonApiData(input)).toBe(false);
});
}
it("should accept valid KubeJsonApiData (ignoring other fields)", () => {
const valid = { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: { food: "" } } };
expect(KubeObject.isJsonApiData(valid)).toBe(true);
});
});
describe("isPartialJsonApiData", () => {
{
type TestCase = [any];
const tests: TestCase[] = [
[false],
[true],
[null],
[undefined],
[""],
[1],
[(): unknown => void 0],
[Symbol("hello")],
];
it.each(tests)("should reject invalid value: %p", (input) => {
expect(KubeObject.isPartialJsonApiData(input)).toBe(false);
});
}
it("should accept {}", () => {
expect(KubeObject.isPartialJsonApiData({})).toBe(true);
});
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "" } }],
["apiVersion", { kind: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "" } }],
["metadata", { kind: "", apiVersion: "" }],
];
it.each(tests)("should not reject with missing top level field: %s", (missingField, input) => {
expect(KubeObject.isPartialJsonApiData(input)).toBe(true);
});
}
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { kind: 1, apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "" } }],
["apiVersion", { apiVersion: 1, kind: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "" } }],
["metadata", { kind: "", apiVersion: "", metadata: "" }],
["metadata.uid", { kind: "", apiVersion: "", metadata: { uid: 1, name: "", resourceVersion: "", selfLink: "" } }],
["metadata.name", { kind: "", apiVersion: "", metadata: { uid: "", name: 1, resourceVersion: "", selfLink: "" } }],
["metadata.resourceVersion", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: 1, selfLink: "" } }],
["metadata.selfLink", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: 1 } }],
["metadata.namespace", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", namespace: 1 } }],
["metadata.creationTimestamp", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", creationTimestamp: 1 } }],
["metadata.continue", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", continue: 1 } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: 1 } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: [1] } }],
["metadata.finalizers", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", finalizers: {} } }],
["metadata.labels", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", labels: 1 } }],
["metadata.labels", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", labels: { food: 1 } } }],
["metadata.annotations", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: 1 } }],
["metadata.annotations", { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: { food: 1 } } }],
];
it.each(tests)("should reject with wrong type for field: %s", (missingField, input) => {
expect(KubeObject.isPartialJsonApiData(input)).toBe(false);
});
}
it("should accept valid Partial<KubeJsonApiData> (ignoring other fields)", () => {
const valid = { kind: "", apiVersion: "", metadata: { uid: "", name: "", resourceVersion: "", selfLink: "", annotations: { food: "" } } };
expect(KubeObject.isPartialJsonApiData(valid)).toBe(true);
});
});
describe("isJsonApiDataList", () => {
function isAny(val: unknown): val is any {
return !Boolean(void val);
}
function isNotAny(val: unknown): val is any {
return Boolean(void val);
}
function isBoolean(val: unknown): val is Boolean {
return typeof val === "boolean";
}
{
type TestCase = [any];
const tests: TestCase[] = [
[false],
[true],
[null],
[undefined],
[""],
[1],
[(): unknown => void 0],
[Symbol("hello")],
[{}],
];
it.each(tests)("should reject invalid value: %p", (input) => {
expect(KubeObject.isJsonApiDataList(input, isAny)).toBe(false);
});
}
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { apiVersion: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
["apiVersion", { kind: "", items: [], metadata: { resourceVersion: "", selfLink: "" } }],
["metadata", { kind: "", items: [], apiVersion: "" }],
];
it.each(tests)("should reject with missing: %s", (missingField, input) => {
expect(KubeObject.isJsonApiDataList(input, isAny)).toBe(false);
});
}
{
type TestCase = [string, any];
const tests: TestCase[] = [
["kind", { kind: 1, items: [], apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } }],
["apiVersion", { kind: "", items: [], apiVersion: 1, metadata: { resourceVersion: "", selfLink: "" } }],
["metadata", { kind: "", items: [], apiVersion: "", metadata: 1 }],
["metadata.resourceVersion", { kind: "", items: [], apiVersion: "", metadata: { resourceVersion: 1, selfLink: "" } }],
["metadata.selfLink", { kind: "", items: [], apiVersion: "", metadata: { resourceVersion: "", selfLink: 1 } }],
["items", { kind: "", items: 1, apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } }],
["items", { kind: "", items: "", apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } }],
["items", { kind: "", items: {}, apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } }],
["items[0]", { kind: "", items: [""], apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } }],
];
it.each(tests)("should reject with wrong type for field: %s", (missingField, input) => {
expect(KubeObject.isJsonApiDataList(input, isNotAny)).toBe(false);
});
}
it("should accept valid KubeJsonApiDataList (ignoring other fields)", () => {
const valid = { kind: "", items: [false], apiVersion: "", metadata: { resourceVersion: "", selfLink: "" } };
expect(KubeObject.isJsonApiDataList(valid, isBoolean)).toBe(true);
});
});
});
@@ -1,38 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { Pod } from "../endpoints";
describe("Pod tests", () => {
it("getAllContainers() should never throw", () => {
const pod = new Pod({
apiVersion: "foo",
kind: "Pod",
metadata: {
name: "foobar",
resourceVersion: "foobar",
uid: "foobar",
},
});
expect(pod.getAllContainers()).toStrictEqual([]);
});
});
-324
View File
@@ -1,324 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { Pod } from "../endpoints";
interface GetDummyPodOptions {
running?: number;
dead?: number;
initRunning?: number;
initDead?: number;
}
function getDummyPodDefaultOptions(): Required<GetDummyPodOptions> {
return {
running: 0,
dead: 0,
initDead: 0,
initRunning: 0,
};
}
function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Pod {
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
metadata: {
uid: "1",
name: "test",
resourceVersion: "v1",
selfLink: "http"
}
});
pod.spec = {
containers: [],
initContainers: [],
serviceAccount: "dummy",
serviceAccountName: "dummy",
};
pod.status = {
phase: "Running",
conditions: [],
hostIP: "10.0.0.1",
podIP: "10.0.0.1",
startTime: "now",
containerStatuses: [],
initContainerStatuses: [],
};
for (let i = 0; i < opts.running; i += 1) {
const name = `container_r_${i}`;
pod.spec.containers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.containerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: true,
restartCount: i,
state: {
running: {
startedAt: "before"
},
}
});
}
for (let i = 0; i < opts.dead; i += 1) {
const name = `container_d_${i}`;
pod.spec.containers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.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 < opts.initRunning; i += 1) {
const name = `container_ir_${i}`;
pod.spec.initContainers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.initContainerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
ready: true,
restartCount: i,
state: {
running: {
startedAt: "before"
}
}
});
}
for (let i = 0; i < opts.initDead; i += 1) {
const name = `container_id_${i}`;
pod.spec.initContainers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.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: "dummy",
name
};
}
it("getRunningContainers should return only running and init running", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_r_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_ir_${index}`)),
];
expect(pod.getRunningContainers()).toStrictEqual(res);
});
it("getAllContainers should return all containers", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_r_${index}`)),
...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_d_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_ir_${index}`)),
...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_id_${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 });
pod.status.containerStatuses[0].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 });
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 });
pod.status.phase = "Running";
expect(pod.hasIssues()).toStrictEqual(false);
});
});
});
-136
View File
@@ -1,136 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { KubeObjectStore } from "../kube-object.store";
import { action, observable, makeObservable } from "mobx";
import { autoBind, iter } from "../utils";
import type { KubeApi } from "./kube-api";
import type { KubeObject } from "./kube-object";
import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse";
export class ApiManager {
private apis = observable.map<string, KubeApi<KubeObject>>();
private stores = observable.map<string, KubeObjectStore<KubeObject>>();
constructor() {
makeObservable(this);
autoBind(this);
}
getApi(pathOrCallback: string | ((api: KubeApi<KubeObject>) => boolean)) {
if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
}
return iter.find(this.apis.values(), pathOrCallback ?? (() => true));
}
getApiByKind(kind: string, apiVersion: string) {
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
}
registerApi(apiBase: string, api: KubeApi<KubeObject>) {
if (!this.apis.has(apiBase)) {
this.stores.forEach((store) => {
if (store.api === api) {
this.stores.set(apiBase, store);
}
});
this.apis.set(apiBase, api);
}
}
protected resolveApi<K extends KubeObject>(api?: string | KubeApi<K>): KubeApi<K> | undefined {
if (!api) {
return undefined;
}
if (typeof api === "string") {
return this.getApi(api) as KubeApi<K>;
}
return api;
}
unregisterApi(api: string | KubeApi<KubeObject>) {
if (typeof api === "string") this.apis.delete(api);
else {
const apis = Array.from(this.apis.entries());
const entry = apis.find(entry => entry[1] === api);
if (entry) this.unregisterApi(entry[0]);
}
}
@action
registerStore(store: KubeObjectStore<KubeObject>, apis: KubeApi<KubeObject>[] = [store.api]) {
apis.forEach(api => {
this.stores.set(api.apiBase, store);
});
}
getStore<S extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): S | undefined {
return this.stores.get(this.resolveApi(api)?.apiBase) as S;
}
lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject.getNs()
} = ref;
if (!kind) return "";
// search in registered apis by 'kind' & 'apiVersion'
const api = this.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
if (api) {
return api.getUrl({ namespace, name });
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (this.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = this.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace });
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource });
}
}
export const apiManager = new ApiManager();
@@ -1,58 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
export type ClusterRoleBindingSubjectKind = "Group" | "ServiceAccount" | "User";
export interface ClusterRoleBindingSubject {
kind: ClusterRoleBindingSubjectKind;
name: string;
apiGroup?: string;
namespace?: string;
}
export interface ClusterRoleBinding {
subjects?: ClusterRoleBindingSubject[];
roleRef: {
kind: string;
name: string;
apiGroup?: string;
};
}
export class ClusterRoleBinding extends KubeObject {
static kind = "ClusterRoleBinding";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings";
getSubjects() {
return this.subjects || [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
export const clusterRoleBindingApi = new KubeApi({
objectConstructor: ClusterRoleBinding,
});
@@ -1,46 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
export interface ClusterRole {
rules: {
verbs: string[];
apiGroups: string[];
resources: string[];
resourceNames?: string[];
}[];
}
export class ClusterRole extends KubeObject {
static kind = "ClusterRole";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles";
getRules() {
return this.rules || [];
}
}
export const clusterRoleApi = new KubeApi({
objectConstructor: ClusterRole,
});
-127
View File
@@ -1,127 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { IMetrics, IMetricsReqParams, metricsApi } from "./metrics.api";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export class ClusterApi extends KubeApi<Cluster> {
static kind = "Cluster";
static namespaced = true;
}
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
const nodes = nodeNames.join("|");
const opts = { category: "cluster", nodes };
return metricsApi.getMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
cpuCapacity: opts,
cpuAllocatableCapacity: opts,
podUsage: opts,
podCapacity: opts,
podAllocatableCapacity: opts,
fsSize: opts,
fsUsage: opts
}, params);
}
export enum ClusterStatus {
ACTIVE = "Active",
CREATING = "Creating",
REMOVING = "Removing",
ERROR = "Error"
}
export interface IClusterMetrics<T = IMetrics> {
[metric: string]: T;
memoryUsage: T;
memoryRequests: T;
memoryLimits: T;
memoryCapacity: T;
cpuUsage: T;
cpuRequests: T;
cpuLimits: T;
cpuCapacity: T;
podUsage: T;
podCapacity: T;
fsSize: T;
fsUsage: T;
}
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";
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;
}
}
export const clusterApi = new ClusterApi({
objectConstructor: Cluster,
});
@@ -1,47 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export interface IComponentStatusCondition {
type: string;
status: string;
message: string;
}
export interface ComponentStatus {
conditions: IComponentStatusCondition[];
}
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 const componentStatusApi = new KubeApi({
objectConstructor: ComponentStatus,
});
@@ -1,52 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { KubeApi } from "../kube-api";
import { autoBind } from "../../../common/utils";
export interface ConfigMap {
data: {
[param: string]: string;
};
}
export class ConfigMap extends KubeObject {
static kind = "ConfigMap";
static namespaced = true;
static apiBase = "/api/v1/configmaps";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
this.data ??= {};
}
getKeys(): string[] {
return Object.keys(this.data);
}
}
export const configMapApi = new KubeApi({
objectConstructor: ConfigMap,
});
-180
View File
@@ -1,180 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import { crdResourcesURL } from "../../../common/routes";
type AdditionalPrinterColumnsCommon = {
name: string;
type: "integer" | "number" | "string" | "boolean" | "date";
priority: number;
description: string;
};
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
jsonPath: string;
};
type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
JSONPath: string;
};
export interface CustomResourceDefinition {
spec: {
group: string;
version?: string; // deprecated in v1 api
names: {
plural: string;
singular: string;
kind: string;
listKind: string;
};
scope: "Namespaced" | "Cluster" | string;
validation?: any;
versions?: {
name: string;
served: boolean;
storage: boolean;
schema?: unknown; // required in v1 but not present in v1beta
additionalPrinterColumns?: AdditionalPrinterColumnsV1[]
}[];
conversion: {
strategy?: string;
webhook?: any;
};
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; // removed in v1
};
status: {
conditions: {
lastTransitionTime: string;
message: string;
reason: string;
status: string;
type?: string;
}[];
acceptedNames: {
plural: string;
singular: string;
kind: string;
shortNames: string[];
listKind: string;
};
storedVersions: string[];
};
}
export class CustomResourceDefinition extends KubeObject {
static kind = "CustomResourceDefinition";
static namespaced = false;
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
getResourceUrl() {
return crdResourcesURL({
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.substr(1);
}
getGroup() {
return this.spec.group;
}
getScope() {
return this.spec.scope;
}
getVersion() {
// v1 has removed the spec.version property, if it is present it must match the first version
return this.spec.versions?.[0]?.name ?? this.spec.version;
}
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.spec.versions?.find(a => this.getVersion() == a.name)?.additionalPrinterColumns
?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
?? [];
return columns
.filter(column => column.name != "Age")
.filter(column => ignorePriority ? true : !column.priority);
}
getValidation() {
return JSON.stringify(this.spec.validation ?? this.spec.versions?.[0]?.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 const crdApi = new KubeApi<CustomResourceDefinition>({
objectConstructor: CustomResourceDefinition,
checkPreferredVersion: true,
});
-145
View File
@@ -1,145 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import moment from "moment";
import { KubeObject } from "../kube-object";
import type { IPodContainer } from "./pods.api";
import { formatDuration } from "../../utils/formatDuration";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class CronJobApi extends KubeApi<CronJob> {
suspend(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
suspend: true
}
}
},
{
headers: {
"content-type": "application/strategic-merge-patch+json"
}
});
}
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"
}
});
}
}
export interface CronJob {
spec: {
schedule: string;
concurrencyPolicy: string;
suspend: boolean;
jobTemplate: {
metadata: {
creationTimestamp?: string;
labels?: {
[key: string]: string;
};
annotations?: {
[key: string]: string;
};
};
spec: {
template: {
metadata: {
creationTimestamp?: string;
};
spec: {
containers: IPodContainer[];
restartPolicy: string;
terminationGracePeriodSeconds: number;
dnsPolicy: string;
hostPID: boolean;
schedulerName: string;
};
};
};
};
successfulJobsHistoryLimit: number;
failedJobsHistoryLimit: number;
};
status: {
lastScheduleTime?: string;
};
}
export class CronJob extends KubeObject {
static kind = "CronJob";
static namespaced = true;
static apiBase = "/apis/batch/v1beta1/cronjobs";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSuspendFlag() {
return this.spec.suspend.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;
}
}
export const cronJobApi = new CronJobApi({
objectConstructor: CronJob,
});
@@ -1,121 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import get from "lodash/get";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { IPodContainer, IPodMetrics } from "./pods.api";
export class DaemonSet extends WorkloadKubeObject {
static kind = "DaemonSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/daemonsets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
selector: {
matchLabels: {
[name: string]: string;
};
};
template: {
metadata: {
creationTimestamp?: string;
labels: {
name: string;
};
};
spec: {
containers: IPodContainer[];
initContainers?: IPodContainer[];
restartPolicy: string;
terminationGracePeriodSeconds: number;
dnsPolicy: string;
hostPID: boolean;
affinity?: IAffinity;
nodeSelector?: {
[selector: string]: string;
};
securityContext: {};
schedulerName: string;
tolerations: {
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[];
};
};
updateStrategy: {
type: string;
rollingUpdate: {
maxUnavailable: number;
};
};
revisionHistoryLimit: number;
};
declare status: {
currentNumberScheduled: number;
numberMisscheduled: number;
desiredNumberScheduled: number;
numberReady: number;
observedGeneration: number;
updatedNumberScheduled: number;
numberAvailable: number;
numberUnavailable: number;
};
getImages() {
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
const initContainers: IPodContainer[] = get(this, "spec.template.spec.initContainers", []);
return [...containers, ...initContainers].map(container => container.image);
}
}
export class DaemonSetApi extends KubeApi<DaemonSet> {
}
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<IPodMetrics> {
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export const daemonSetApi = new DaemonSetApi({
objectConstructor: DaemonSet,
});
@@ -1,237 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import moment from "moment";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { IPodMetrics } from "./pods.api";
import type { KubeJsonApiData } from "../kube-json-api";
export class DeploymentApi extends KubeApi<Deployment> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
}
getReplicas(params: { namespace: string; name: string }): Promise<number> {
return this.request
.get(this.getScaleApiUrl(params))
.then(({ status }: any) => status?.replicas);
}
scale(params: { namespace: string; name: string }, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
spec: {
replicas
}
}
});
}
restart(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
template: {
metadata: {
annotations: {"kubectl.kubernetes.io/restartedAt" : moment.utc().format()}
}
}
}
}
},
{
headers: {
"content-type": "application/strategic-merge-patch+json"
}
});
}
}
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<IPodMetrics> {
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
interface IContainerProbe {
httpGet?: {
path?: string;
port: number;
scheme: string;
host?: string;
};
exec?: {
command: string[];
};
tcpSocket?: {
port: number;
};
initialDelaySeconds?: number;
timeoutSeconds?: number;
periodSeconds?: number;
successThreshold?: number;
failureThreshold?: number;
}
export class Deployment extends WorkloadKubeObject {
static kind = "Deployment";
static namespaced = true;
static apiBase = "/apis/apps/v1/deployments";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
replicas: number;
selector: { matchLabels: { [app: string]: string } };
template: {
metadata: {
creationTimestamp?: string;
labels: { [app: string]: string };
annotations?: { [app: string]: string };
};
spec: {
containers: {
name: string;
image: string;
args?: string[];
ports?: {
name: string;
containerPort: number;
protocol: string;
}[];
env?: {
name: string;
value: string;
}[];
resources: {
limits?: {
cpu: string;
memory: string;
};
requests: {
cpu: string;
memory: string;
};
};
volumeMounts?: {
name: string;
mountPath: string;
}[];
livenessProbe?: IContainerProbe;
readinessProbe?: IContainerProbe;
startupProbe?: IContainerProbe;
terminationMessagePath: string;
terminationMessagePolicy: string;
imagePullPolicy: string;
}[];
restartPolicy: string;
terminationGracePeriodSeconds: number;
dnsPolicy: string;
affinity?: IAffinity;
nodeSelector?: {
[selector: string]: string;
};
serviceAccountName: string;
serviceAccount: string;
securityContext: {};
schedulerName: string;
tolerations?: {
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[];
volumes?: {
name: string;
configMap: {
name: string;
defaultMode: number;
optional: boolean;
};
}[];
};
};
strategy: {
type: string;
rollingUpdate: {
maxUnavailable: number;
maxSurge: number;
};
};
};
declare status: {
observedGeneration: number;
replicas: number;
updatedReplicas: number;
readyReplicas: number;
availableReplicas?: number;
unavailableReplicas?: number;
conditions: {
type: string;
status: string;
lastUpdateTime: string;
lastTransitionTime: string;
reason: string;
message: string;
}[];
};
getConditions(activeOnly = false) {
const { conditions } = this.status;
if (!conditions) return [];
if (activeOnly) {
return conditions.filter(c => c.status === "True");
}
return conditions;
}
getConditionsText(activeOnly = true) {
return this.getConditions(activeOnly).map(({ type }) => type).join(" ");
}
getReplicas() {
return this.spec.replicas || 0;
}
}
export const deploymentApi = new DeploymentApi({
objectConstructor: Deployment,
});
-154
View File
@@ -1,154 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import { get } from "lodash";
export interface IEndpointPort {
name?: string;
protocol: string;
port: number;
}
export interface IEndpointAddress {
hostname: string;
ip: string;
nodeName: string;
}
export interface IEndpointSubset {
addresses: IEndpointAddress[];
notReadyAddresses: IEndpointAddress[];
ports: IEndpointPort[];
}
interface ITargetRef {
kind: string;
namespace: string;
name: string;
uid: string;
resourceVersion: string;
apiVersion: string;
}
export class EndpointAddress implements IEndpointAddress {
hostname: string;
ip: string;
nodeName: string;
targetRef?: {
kind: string;
namespace: string;
name: string;
uid: string;
resourceVersion: string;
};
static create(data: IEndpointAddress): EndpointAddress {
return new EndpointAddress(data);
}
constructor(data: IEndpointAddress) {
Object.assign(this, data);
}
getId() {
return this.ip;
}
getName() {
return this.hostname;
}
getTargetRef(): ITargetRef {
if (this.targetRef) {
return Object.assign(this.targetRef, {apiVersion: "v1"});
} else {
return null;
}
}
}
export class EndpointSubset implements IEndpointSubset {
addresses: IEndpointAddress[];
notReadyAddresses: IEndpointAddress[];
ports: IEndpointPort[];
constructor(data: IEndpointSubset) {
this.addresses = get(data, "addresses", []);
this.notReadyAddresses = get(data, "notReadyAddresses", []);
this.ports = get(data, "ports", []);
}
getAddresses(): EndpointAddress[] {
return this.addresses.map(EndpointAddress.create);
}
getNotReadyAddresses(): EndpointAddress[] {
return this.notReadyAddresses.map(EndpointAddress.create);
}
toString(): string {
return this.addresses
.map(address => (
this.ports
.map(port => `${address.ip}:${port.port}`)
.join(", ")
))
.join(", ");
}
}
export interface Endpoint {
subsets: IEndpointSubset[];
}
export class Endpoint extends KubeObject {
static kind = "Endpoints";
static namespaced = true;
static apiBase = "/api/v1/endpoints";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getEndpointSubsets(): EndpointSubset[] {
const subsets = this.subsets || [];
return subsets.map(s => new EndpointSubset(s));
}
toString(): string {
if(this.subsets) {
return this.getEndpointSubsets().map(es => es.toString()).join(", ");
} else {
return "<none>";
}
}
}
export const endpointApi = new KubeApi({
objectConstructor: Endpoint,
});
-82
View File
@@ -1,82 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import moment from "moment";
import { KubeObject } from "../kube-object";
import { formatDuration } from "../../utils/formatDuration";
import { KubeApi } from "../kube-api";
export interface KubeEvent {
involvedObject: {
kind: string;
namespace: string;
name: string;
uid: string;
apiVersion: string;
resourceVersion: string;
fieldPath: string;
};
reason: string;
message: string;
source: {
component: string;
host: string;
};
firstTimestamp: string;
lastTimestamp: string;
count: number;
type: "Normal" | "Warning" | string;
eventTime: null;
reportingComponent: string;
reportingInstance: string;
}
export class KubeEvent extends KubeObject {
static kind = "Event";
static namespaced = true;
static apiBase = "/api/v1/events";
isWarning() {
return this.type === "Warning";
}
getSource() {
const { component, host } = this.source;
return `${component} ${host || ""}`;
}
getFirstSeenTime() {
const diff = moment().diff(this.firstTimestamp);
return formatDuration(diff, true);
}
getLastSeenTime() {
const diff = moment().diff(this.lastTimestamp);
return formatDuration(diff, true);
}
}
export const eventApi = new KubeApi({
objectConstructor: KubeEvent,
});
@@ -1,163 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { compile } from "path-to-regexp";
import { apiBase } from "../index";
import { stringify } from "querystring";
import { autoBind } from "../../utils";
export type RepoHelmChartList = Record<string, HelmChart[]>;
export type HelmChartList = Record<string, RepoHelmChartList>;
export interface IHelmChartDetails {
readme: string;
versions: HelmChart[];
}
const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
repo?: string;
name?: string;
}) => string;
/**
* Get a list of all helm charts from all saved helm repos
*/
export async function listCharts(): Promise<HelmChart[]> {
const data = await apiBase.get<HelmChartList>(endpoint());
return Object
.values(data)
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), [])
.map(([chart]) => HelmChart.create(chart));
}
export interface GetChartDetailsOptions {
version?: string;
reqInit?: RequestInit;
}
/**
* Get the readme and all versions of a chart
* @param repo The repo to get from
* @param name The name of the chart to request the data of
* @param options.version The version of the chart's readme to get, default latest
* @param options.reqInit A way for passing in an abort controller or other browser request options
*/
export async function getChartDetails(repo: string, name: string, { version, reqInit }: GetChartDetailsOptions = {}): Promise<IHelmChartDetails> {
const path = endpoint({ repo, name });
const { readme, ...data } = await apiBase.get<IHelmChartDetails>(`${path}?${stringify({ version })}`, undefined, reqInit);
const versions = data.versions.map(HelmChart.create);
return {
readme,
versions,
};
}
/**
* Get chart values related to a specific repos' version of a chart
* @param repo The repo to get from
* @param name The name of the chart to request the data of
* @param version The version to get the values from
*/
export async function getChartValues(repo: string, name: string, version: string): Promise<string> {
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
}
export interface HelmChart {
apiVersion: string;
name: string;
version: string;
repo: string;
kubeVersion?: string;
created: string;
description?: string;
digest: string;
keywords?: string[];
home?: string;
sources?: string[];
maintainers?: {
name: string;
email: string;
url: string;
}[];
engine?: string;
icon?: string;
appVersion?: string;
deprecated?: boolean;
tillerVersion?: string;
}
export class HelmChart {
constructor(data: HelmChart) {
Object.assign(this, data);
autoBind(this);
}
static create(data: any) {
return new HelmChart(data);
}
getId() {
return `${this.repo}:${this.apiVersion}/${this.name}@${this.getAppVersion()}+${this.digest}`;
}
getName() {
return this.name;
}
getFullName(splitter = "/") {
return [this.getRepository(), this.getName()].join(splitter);
}
getDescription() {
return this.description;
}
getIcon() {
return this.icon;
}
getHome() {
return this.home;
}
getMaintainers() {
return this.maintainers || [];
}
getVersion() {
return this.version;
}
getRepository() {
return this.repo;
}
getAppVersion() {
return this.appVersion || "";
}
getKeywords() {
return this.keywords || [];
}
}
@@ -1,241 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import jsYaml from "js-yaml";
import { autoBind, formatDuration } from "../../utils";
import capitalize from "lodash/capitalize";
import { apiBase } from "../index";
import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.store";
import type { ItemObject } from "../../item.store";
import { KubeObject } from "../kube-object";
import type { JsonApiData } from "../json-api";
import { buildURLPositional } from "../../../common/utils/buildUrl";
import type { KubeJsonApiData } from "../kube-json-api";
interface IReleasePayload {
name: string;
namespace: string;
version: string;
config: string; // release values
manifest: string;
info: {
deleted: string;
description: string;
first_deployed: string;
last_deployed: string;
notes: string;
status: string;
};
}
interface IReleaseRawDetails extends IReleasePayload {
resources: KubeJsonApiData[];
}
export interface IReleaseDetails extends IReleasePayload {
resources: KubeObject[];
}
export interface IReleaseCreatePayload {
name?: string;
repo: string;
chart: string;
namespace: string;
version: string;
values: string;
}
export interface IReleaseUpdatePayload {
repo: string;
chart: string;
version: string;
values: string;
}
export interface IReleaseUpdateDetails {
log: string;
release: IReleaseDetails;
}
export interface IReleaseRevision {
revision: number;
updated: string;
status: string;
chart: string;
app_version: string;
description: string;
}
type EndpointParams = {}
| { namespace: string }
| { namespace: string, name: string }
| { namespace: string, name: string, route: string };
interface EndpointQuery {
all?: boolean;
}
const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
const releases = await apiBase.get<HelmRelease[]>(endpoint({ namespace }));
return releases.map(HelmRelease.create);
}
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> {
const path = endpoint({ name, namespace });
const { resources: rawResources, ...details } = await apiBase.get<IReleaseRawDetails>(path);
const resources = rawResources.map(KubeObject.create);
return {
...details,
resources
};
}
export async function createRelease(payload: IReleaseCreatePayload): Promise<IReleaseUpdateDetails> {
const { repo, ...data } = payload;
data.chart = `${repo}/${data.chart}`;
data.values = jsYaml.safeLoad(data.values);
return apiBase.post(endpoint(), { data });
}
export async function updateRelease(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
const { repo, ...data } = payload;
data.chart = `${repo}/${data.chart}`;
data.values = jsYaml.safeLoad(data.values);
return apiBase.put(endpoint({ name, namespace }), { data });
}
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
const path = endpoint({ name, namespace });
return apiBase.del(path);
}
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
const route = "values";
const path = endpoint({ name, namespace, route }, { all });
return apiBase.get<string>(path);
}
export async function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
const route = "history";
const path = endpoint({ name, namespace, route });
return apiBase.get(path);
}
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
const route = "rollback";
const path = endpoint({ name, namespace, route });
const data = { revision };
return apiBase.put(path, { data });
}
export interface HelmRelease {
appVersion: string;
name: string;
namespace: string;
chart: string;
status: string;
updated: string;
revision: string;
}
export class HelmRelease implements ItemObject {
constructor(data: any) {
Object.assign(this, data);
autoBind(this);
}
static create(data: any) {
return new HelmRelease(data);
}
getId() {
return this.namespace + this.name;
}
getName() {
return this.name;
}
getNs() {
return this.namespace;
}
getChart(withVersion = false) {
let chart = this.chart;
if (!withVersion && this.getVersion() != "") {
const search = new RegExp(`-${this.getVersion()}`);
chart = chart.replace(search, "");
}
return chart;
}
getRevision() {
return parseInt(this.revision, 10);
}
getStatus() {
return capitalize(this.status);
}
getVersion() {
const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/);
return versions?.[0] ?? "";
}
getUpdated(humanize = true, compact = true) {
const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date()
const updatedDate = new Date(updated).getTime();
const diff = Date.now() - updatedDate;
if (humanize) {
return formatDuration(diff, compact);
}
return diff;
}
// Helm does not store from what repository the release is installed,
// so we have to try to guess it by searching charts
async getRepo() {
const chartName = this.getChart();
const version = this.getVersion();
const versions = await helmChartStore.getVersions(chartName);
const chartVersion = versions.find(chartVersion => chartVersion.version === version);
return chartVersion ? chartVersion.repo : "";
}
}
-168
View File
@@ -1,168 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export enum HpaMetricType {
Resource = "Resource",
Pods = "Pods",
Object = "Object",
External = "External",
}
export type IHpaMetricData<T = any> = T & {
target?: {
kind: string;
name: string;
apiVersion: string;
};
name?: string;
metricName?: string;
currentAverageUtilization?: number;
currentAverageValue?: string;
targetAverageUtilization?: number;
targetAverageValue?: string;
};
export interface IHpaMetric {
[kind: string]: IHpaMetricData;
type: HpaMetricType;
resource?: IHpaMetricData<{ name: string }>;
pods?: IHpaMetricData;
external?: IHpaMetricData;
object?: IHpaMetricData<{
describedObject: {
apiVersion: string;
kind: string;
name: string;
};
}>;
}
export interface HorizontalPodAutoscaler {
spec: {
scaleTargetRef: {
kind: string;
name: string;
apiVersion: string;
};
minReplicas: number;
maxReplicas: number;
metrics: IHpaMetric[];
};
status: {
currentReplicas: number;
desiredReplicas: number;
currentMetrics: IHpaMetric[];
conditions: {
lastTransitionTime: string;
message: string;
reason: string;
status: string;
type: string;
}[];
};
}
export class HorizontalPodAutoscaler extends KubeObject {
static kind = "HorizontalPodAutoscaler";
static namespaced = true;
static apiBase = "/apis/autoscaling/v2beta1/horizontalpodautoscalers";
getMaxPods() {
return this.spec.maxReplicas || 0;
}
getMinPods() {
return this.spec.minReplicas || 0;
}
getReplicas() {
return this.status.currentReplicas;
}
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})`
};
});
}
getMetrics() {
return this.spec.metrics || [];
}
getCurrentMetrics() {
return this.status.currentMetrics || [];
}
protected getMetricName(metric: IHpaMetric): string {
const { type, resource, pods, object, external } = metric;
switch (type) {
case HpaMetricType.Resource:
return resource.name;
case HpaMetricType.Pods:
return pods.metricName;
case HpaMetricType.Object:
return object.metricName;
case HpaMetricType.External:
return external.metricName;
}
}
// todo: refactor
getMetricValues(metric: IHpaMetric): string {
const metricType = metric.type.toLowerCase();
const currentMetric = this.getCurrentMetrics().find(current =>
metric.type == current.type && this.getMetricName(metric) == this.getMetricName(current)
);
const current = currentMetric ? currentMetric[metricType] : null;
const target = metric[metricType];
let currentValue = "unknown";
let targetValue = "unknown";
if (current) {
currentValue = current.currentAverageUtilization || current.currentAverageValue || current.currentValue;
if (current.currentAverageUtilization) currentValue += "%";
}
if (target) {
targetValue = target.targetAverageUtilization || target.targetAverageValue || target.targetValue;
if (target.targetAverageUtilization) targetValue += "%";
}
return `${currentValue} / ${targetValue}`;
}
}
export const hpaApi = new KubeApi({
objectConstructor: HorizontalPodAutoscaler,
});
-57
View File
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Kubernetes apis
// Docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/
export * from "./cluster.api";
export * from "./cluster-role.api";
export * from "./cluster-role-binding.api";
export * from "./configmap.api";
export * from "./crd.api";
export * from "./cron-job.api";
export * from "./daemon-set.api";
export * from "./deployment.api";
export * from "./endpoint.api";
export * from "./events.api";
export * from "./hpa.api";
export * from "./ingress.api";
export * from "./job.api";
export * from "./limit-range.api";
export * from "./namespaces.api";
export * from "./network-policy.api";
export * from "./nodes.api";
export * from "./persistent-volume.api";
export * from "./persistent-volume-claims.api";
export * from "./pods.api";
export * from "./poddisruptionbudget.api";
export * from "./pod-metrics.api";
export * from "./podsecuritypolicy.api";
export * from "./replica-set.api";
export * from "./resource-quota.api";
export * from "./role.api";
export * from "./role-binding.api";
export * from "./secret.api";
export * from "./selfsubjectrulesreviews.api";
export * from "./service.api";
export * from "./service-accounts.api";
export * from "./stateful-set.api";
export * from "./storage-class.api";
-212
View File
@@ -1,212 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class IngressApi extends KubeApi<Ingress> {
}
export function getMetricsForIngress(ingress: string, namespace: string): Promise<IIngressMetrics> {
const opts = { category: "ingress", ingress };
return metricsApi.getMetrics({
bytesSentSuccess: opts,
bytesSentFailure: opts,
requestDurationSeconds: opts,
responseDurationSeconds: opts
}, {
namespace,
});
}
export interface IIngressMetrics<T = IMetrics> {
[metric: string]: T;
bytesSentSuccess: T;
bytesSentFailure: T;
requestDurationSeconds: T;
responseDurationSeconds: T;
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;
}
// extensions/v1beta1
interface IExtensionsBackend {
serviceName: string;
servicePort: number;
}
// networking.k8s.io/v1
interface INetworkingBackend {
service: IIngressService;
}
export type IIngressBackend = IExtensionsBackend | INetworkingBackend;
export interface IIngressService {
name: string;
port: {
name?: string;
number?: number;
}
}
export const getBackendServiceNamePort = (backend: IIngressBackend) => {
// .service is available with networking.k8s.io/v1, otherwise using extensions/v1beta1 interface
const serviceName = "service" in backend ? backend.service.name : backend.serviceName;
// Port is specified either with a number or name
const servicePort = "service" in backend ? backend.service.port.number ?? backend.service.port.name : backend.servicePort;
return { serviceName, servicePort };
};
export interface Ingress {
spec: {
tls: {
secretName: string;
}[];
rules?: {
host?: string;
http: {
paths: {
path?: string;
backend: IIngressBackend;
}[];
};
}[];
// extensions/v1beta1
backend?: IExtensionsBackend;
// networking.k8s.io/v1
defaultBackend?: INetworkingBackend & {
resource: {
apiGroup: string;
kind: string;
name: string;
}
}
};
status: {
loadBalancer: {
ingress: ILoadBalancerIngress[];
};
};
}
export class Ingress extends KubeObject {
static kind = "Ingress";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/ingresses";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getRoutes() {
const { spec: { tls, rules } } = this;
if (!rules) return [];
let protocol = "http";
const routes: string[] = [];
if (tls && tls.length > 0) {
protocol += "s";
}
rules.map(rule => {
const host = rule.host ? rule.host : "*";
if (rule.http && rule.http.paths) {
rule.http.paths.forEach(path => {
const { serviceName, servicePort } = getBackendServiceNamePort(path.backend);
routes.push(`${protocol}://${host}${path.path || "/"} ⇢ ${serviceName}:${servicePort}`);
});
}
});
return routes;
}
getServiceNamePort() {
const { spec } = this;
const serviceName = spec?.defaultBackend?.service.name ?? spec?.backend?.serviceName;
const servicePort = spec?.defaultBackend?.service.port.number ?? spec?.defaultBackend?.service.port.name ?? spec?.backend?.servicePort;
return {
serviceName,
servicePort
};
}
getHosts() {
const { spec: { rules } } = this;
if (!rules) return [];
return rules.filter(rule => rule.host).map(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 && rules.length > 0) {
if (rules.some(rule => rule.hasOwnProperty("http"))) {
ports.push(httpPort);
}
} else if (servicePort !== undefined) {
ports.push(Number(servicePort));
}
if (tls && tls.length > 0) {
ports.push(tlsPort);
}
return ports.join(", ");
}
getLoadBalancers() {
const { status: { loadBalancer = { ingress: [] } } } = this;
return (loadBalancer.ingress ?? []).map(address => (
address.hostname || address.ip
));
}
}
export const ingressApi = new IngressApi({
objectConstructor: Ingress,
// Add fallback for Kubernetes <1.19
checkPreferredVersion: true,
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
logStuff: true
} as any);
-153
View File
@@ -1,153 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import get from "lodash/get";
import { autoBind } from "../../utils";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { JsonApiParams } from "../json-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { IPodContainer, IPodMetrics } from "./pods.api";
export class Job extends WorkloadKubeObject {
static kind = "Job";
static namespaced = true;
static apiBase = "/apis/batch/v1/jobs";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
parallelism?: number;
completions?: number;
backoffLimit?: number;
selector?: {
matchLabels: {
[name: string]: string;
};
};
template: {
metadata: {
creationTimestamp?: string;
labels?: {
[name: string]: string;
};
annotations?: {
[name: string]: string;
};
};
spec: {
containers: IPodContainer[];
restartPolicy: string;
terminationGracePeriodSeconds: number;
dnsPolicy: string;
hostPID: boolean;
affinity?: IAffinity;
nodeSelector?: {
[selector: string]: string;
};
tolerations?: {
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[];
schedulerName: string;
};
};
containers?: IPodContainer[];
restartPolicy?: string;
terminationGracePeriodSeconds?: number;
dnsPolicy?: string;
serviceAccountName?: string;
serviceAccount?: string;
schedulerName?: string;
};
declare status: {
conditions: {
type: string;
status: string;
lastProbeTime: string;
lastTransitionTime: string;
message?: string;
}[];
startTime: string;
completionTime: string;
succeeded: number;
};
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.14/#jobcondition-v1-batch
return this.status.conditions?.find(({ status }) => status === "True");
}
getImages() {
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
return [...containers].map(container => container.image);
}
delete() {
const params: JsonApiParams = {
query: { propagationPolicy: "Background" }
};
return super.delete(params);
}
}
export class JobApi extends KubeApi<Job> {
}
export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise<IPodMetrics> {
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export const jobApi = new JobApi({
objectConstructor: Job,
});
@@ -1,85 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import { autoBind } from "../../utils";
import type { KubeJsonApiData } from "../kube-json-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 LimitRange {
spec: {
limits: LimitRangeItem[];
};
}
export class LimitRange extends KubeObject {
static kind = "LimitRange";
static namespaced = true;
static apiBase = "/api/v1/limitranges";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
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 const limitRangeApi = new KubeApi({
objectConstructor: LimitRange,
});
-192
View File
@@ -1,192 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Metrics api
import moment from "moment";
import { apiBase } from "../index";
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
export interface IMetrics {
status: string;
data: {
resultType: string;
result: IMetricsResult[];
};
}
export interface IMetricsResult {
metric: {
[name: string]: string;
instance: string;
node?: string;
pod?: string;
kubernetes?: string;
kubernetes_node?: string;
kubernetes_namespace?: string;
};
values: [number, string][];
}
export interface MetricProviderInfo {
name: string;
id: string;
isConfigurable: boolean;
}
export interface IMetricsReqParams {
start?: number | string; // timestamp in seconds or valid date-string
end?: number | string;
step?: number; // step in seconds (default: 60s = each point 1m)
range?: number; // time-range in seconds for data aggregation (default: 3600s = last 1h)
namespace?: string; // rbac-proxy validation param
}
export interface IResourceMetrics<T extends IMetrics> {
[metric: string]: T;
cpuUsage: T;
memoryUsage: T;
fsUsage: T;
networkReceive: T;
networkTransmit: T;
}
export const metricsApi = {
async getMetrics<T = IMetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
const { range = 3600, step = 60, namespace } = reqParams;
let { start, end } = reqParams;
if (!start && !end) {
const timeNow = Date.now() / 1000;
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
start = now - range;
end = now;
}
return apiBase.post("/metrics", {
data: query,
query: {
start, end, step,
"kubernetes_namespace": namespace,
}
});
},
async getMetricProviders(): Promise<MetricProviderInfo[]> {
return apiBase.get("/metrics/providers");
}
};
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
if (!metrics?.data?.result) {
return {
data: {
resultType: "",
result: [{
metric: {},
values: []
} as IMetricsResult],
},
status: "",
};
}
const { result } = metrics.data;
if (result.length) {
if (frames > 0) {
// fill the gaps
result.forEach(res => {
if (!res.values || !res.values.length) return;
let now = moment().startOf("minute").subtract(1, "minute").unix();
let timestamp = res.values[0][0];
while (timestamp <= now) {
timestamp = moment.unix(timestamp).add(1, "minute").unix();
if (!res.values.find((value) => value[0] === timestamp)) {
res.values.push([timestamp, "0"]);
}
}
while (res.values.length < frames) {
const timestamp = moment.unix(res.values[0][0]).subtract(1, "minute").unix();
if (!res.values.find((value) => value[0] === timestamp)) {
res.values.unshift([timestamp, "0"]);
}
now = timestamp;
}
});
}
}
else {
// always return at least empty values array
result.push({
metric: {},
values: []
} as IMetricsResult);
}
return metrics;
}
export function isMetricsEmpty(metrics: Record<string, IMetrics>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
}
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | void {
if (!metrics) return;
const itemMetrics = { ...metrics };
for (const metric in metrics) {
if (!metrics[metric]?.data?.result) {
continue;
}
const results = metrics[metric].data.result;
const result = results.find(res => Object.values(res.metric)[0] == itemName);
itemMetrics[metric].data.result = result ? [result] : [];
}
return itemMetrics;
}
export function getMetricLastPoints(metrics: Record<string, IMetrics>) {
const result: Partial<{ [metric: string]: number }> = {};
Object.keys(metrics).forEach(metricName => {
try {
const metric = metrics[metricName];
if (metric.data.result.length) {
result[metricName] = +metric.data.result[0].values.slice(-1)[0][1];
}
} catch (e) {
}
return result;
}, {});
return result;
}
@@ -1,74 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { autoBind } from "../../utils";
import { metricsApi } from "./metrics.api";
import type { IPodMetrics } from "./pods.api";
import type { KubeJsonApiData } from "../kube-json-api";
export enum NamespaceStatus {
ACTIVE = "Active",
TERMINATING = "Terminating",
}
export interface Namespace {
status?: {
phase: string;
};
}
export class Namespace extends KubeObject {
static kind = "Namespace";
static namespaced = false;
static apiBase = "/api/v1/namespaces";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getStatus() {
return this.status?.phase ?? "-";
}
}
export class NamespaceApi extends KubeApi<Namespace> {
}
export function getMetricsForNamespace(namespace: string, selector = ""): Promise<IPodMetrics> {
const opts = { category: "pods", pods: ".*", namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export const namespacesApi = new NamespaceApi({
objectConstructor: Namespace,
});
@@ -1,101 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IPolicyIpBlock {
cidr: string;
except?: string[];
}
export interface IPolicySelector {
matchLabels: {
[label: string]: string;
};
}
export interface IPolicyIngress {
from: {
ipBlock?: IPolicyIpBlock;
namespaceSelector?: IPolicySelector;
podSelector?: IPolicySelector;
}[];
ports: {
protocol: string;
port: number;
}[];
}
export interface IPolicyEgress {
to: {
ipBlock: IPolicyIpBlock;
}[];
ports: {
protocol: string;
port: number;
}[];
}
export interface NetworkPolicy {
spec: {
podSelector: {
matchLabels: {
[label: string]: string;
role: string;
};
};
policyTypes: string[];
ingress: IPolicyIngress[];
egress: IPolicyEgress[];
};
}
export class NetworkPolicy extends KubeObject {
static kind = "NetworkPolicy";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/networkpolicies";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
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 const networkPolicyApi = new KubeApi({
objectConstructor: NetworkPolicy,
});
-230
View File
@@ -1,230 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { autoBind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class NodesApi extends KubeApi<Node> {
}
export function getMetricsForAllNodes(): Promise<INodeMetrics> {
const opts = { category: "nodes"};
return metricsApi.getMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuCapacity: opts,
fsSize: opts,
fsUsage: opts
});
}
export interface INodeMetrics<T = IMetrics> {
[metric: string]: T;
memoryUsage: T;
workloadMemoryUsage: T;
memoryCapacity: T;
memoryAllocatableCapacity: T;
cpuUsage: T;
cpuCapacity: T;
fsUsage: T;
fsSize: T;
}
export interface Node {
spec: {
podCIDR?: string;
podCIDRs?: string[];
providerID?: string;
/**
* @deprecated see https://issues.k8s.io/61966
*/
externalID?: string;
taints?: {
key: string;
value: string;
effect: string;
timeAdded: string;
}[];
unschedulable?: boolean;
};
status: {
capacity?: {
cpu: string;
["ephemeral-storage"]: string;
["hugepages-1Gi"]: string;
["hugepages-2Mi"]: string;
memory: string;
pods: string;
};
allocatable?: {
cpu: string;
["ephemeral-storage"]: string;
["hugepages-1Gi"]: string;
["hugepages-2Mi"]: string;
memory: string;
pods: string;
};
conditions?: {
type: string;
status: string;
lastHeartbeatTime?: string;
lastTransitionTime?: string;
reason?: string;
message?: string;
}[];
addresses?: {
type: string;
address: string;
}[];
daemonEndpoints?: {
kubeletEndpoint: {
Port: number; //it must be uppercase for backwards compatibility
}
}
nodeInfo?: {
machineID: string;
systemUUID: string;
bootID: string;
kernelVersion: string;
osImage: string;
containerRuntimeVersion: string;
kubeletVersion: string;
kubeProxyVersion: string;
operatingSystem: string;
architecture: string;
};
images?: {
names: string[];
sizeBytes?: number;
}[];
volumesInUse?: string[];
volumesAttached?: {
name: string;
devicePath: string;
}[];
};
}
export class Node extends KubeObject {
static kind = "Node";
static namespaced = false;
static apiBase = "/api/v1/nodes";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getNodeConditionText() {
const { conditions } = this.status;
if (!conditions) return "";
return conditions.reduce((types, current) => {
if (current.status !== "True") return "";
return types += ` ${current.type}`;
}, "");
}
getTaints() {
return this.spec.taints || [];
}
getRoleLabels() {
if (!this.metadata?.labels || typeof this.metadata.labels !== "object") {
return "";
}
const roleLabels = Object.keys(this.metadata.labels)
.filter(key => key.includes("node-role.kubernetes.io"))
.map(key => key.match(/([^/]+$)/)[0]); // all after last slash
if (this.metadata.labels["kubernetes.io/role"] != undefined) {
roleLabels.push(this.metadata.labels["kubernetes.io/role"]);
}
return roleLabels.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() {
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;
}
getOperatingSystem(): string {
const label = this.getLabels().find(label => label.startsWith("kubernetes.io/os="));
if (label) {
return label.split("=", 2)[1];
}
return "linux";
}
isUnschedulable() {
return this.spec.unschedulable;
}
}
export const nodesApi = new NodesApi({
objectConstructor: Node,
});
@@ -1,121 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import type { Pod } from "./pods.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class PersistentVolumeClaimsApi extends KubeApi<PersistentVolumeClaim> {
}
export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise<IPvcMetrics> {
return metricsApi.getMetrics({
diskUsage: { category: "pvc", pvc: pvc.getName() },
diskCapacity: { category: "pvc", pvc: pvc.getName() }
}, {
namespace: pvc.getNs()
});
}
export interface IPvcMetrics<T = IMetrics> {
[key: string]: T;
diskUsage: T;
diskCapacity: T;
}
export interface PersistentVolumeClaim {
spec: {
accessModes: string[];
storageClassName: string;
selector: {
matchLabels: {
release: string;
};
matchExpressions: {
key: string; // environment,
operator: string; // In,
values: string[]; // [dev]
}[];
};
resources: {
requests: {
storage: string; // 8Gi
};
};
};
status: {
phase: string; // Pending
};
}
export class PersistentVolumeClaim extends KubeObject {
static kind = "PersistentVolumeClaim";
static namespaced = true;
static apiBase = "/api/v1/persistentvolumeclaims";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getPods(allPods: Pod[]): Pod[] {
const pods = allPods.filter(pod => pod.getNs() === this.getNs());
return pods.filter(pod => {
return pod.getVolumes().filter(volume =>
volume.persistentVolumeClaim &&
volume.persistentVolumeClaim.claimName === this.getName()
).length > 0;
});
}
getStorage(): string {
if (!this.spec.resources || !this.spec.resources.requests) return "-";
return this.spec.resources.requests.storage;
}
getMatchLabels(): string[] {
if (!this.spec.selector || !this.spec.selector.matchLabels) return [];
return Object.entries(this.spec.selector.matchLabels)
.map(([name, val]) => `${name}:${val}`);
}
getMatchExpressions() {
if (!this.spec.selector || !this.spec.selector.matchExpressions) return [];
return this.spec.selector.matchExpressions;
}
getStatus(): string {
if (this.status) return this.status.phase;
return "-";
}
}
export const pvcApi = new PersistentVolumeClaimsApi({
objectConstructor: PersistentVolumeClaim,
});
@@ -1,107 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { unitsToBytes } from "../../utils/convertMemory";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface PersistentVolume {
spec: {
capacity: {
storage: string; // 8Gi
};
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[];
accessModes: string[]; // [ReadWriteOnce]
claimRef: {
kind: string; // PersistentVolumeClaim,
namespace: string; // storage,
name: string; // nfs-provisioner,
uid: string; // c5d7c485-9f1b-11e8-b0ea-9600000e54fb,
apiVersion: string; // v1,
resourceVersion: string; // 292180
};
persistentVolumeReclaimPolicy: string; // Delete,
storageClassName: string; // rook-ceph-block
nfs?: {
path: string;
server: string;
};
};
status?: {
phase: string;
reason?: string;
};
}
export class PersistentVolume extends KubeObject {
static kind = "PersistentVolume";
static namespaced = false;
static apiBase = "/api/v1/persistentvolumes";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getCapacity(inBytes = false) {
const capacity = this.spec.capacity;
if (capacity) {
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 const persistentVolumeApi = new KubeApi({
objectConstructor: PersistentVolume,
});
@@ -1,45 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export interface PodMetrics {
timestamp: string;
window: string;
containers: {
name: string;
usage: {
cpu: string;
memory: string;
};
}[];
}
export class PodMetrics extends KubeObject {
static kind = "PodMetrics";
static namespaced = true;
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
}
export const podMetricsApi = new KubeApi({
objectConstructor: PodMetrics,
});
@@ -1,77 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface PodDisruptionBudget {
spec: {
minAvailable: string;
maxUnavailable: string;
selector: { matchLabels: { [app: string]: string } };
};
status: {
currentHealthy: number
desiredHealthy: number
disruptionsAllowed: number
expectedPods: number
};
}
export class PodDisruptionBudget extends KubeObject {
static kind = "PodDisruptionBudget";
static namespaced = true;
static apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSelectors() {
const selector = this.spec.selector;
return KubeObject.stringifyLabels(selector ? selector.matchLabels : null);
}
getMinAvailable() {
return this.spec.minAvailable || "N/A";
}
getMaxUnavailable() {
return this.spec.maxUnavailable || "N/A";
}
getCurrentHealthy() {
return this.status.currentHealthy;
}
getDesiredHealthy() {
return this.status.desiredHealthy;
}
}
export const pdbApi = new KubeApi({
objectConstructor: PodDisruptionBudget,
});
-507
View File
@@ -1,507 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class PodsApi extends KubeApi<Pod> {
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
const path = `${this.getUrl(params)}/log`;
return this.request.get(path, { query });
}
}
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
const podSelector = pods.map(pod => pod.getName()).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
memoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface IPodMetrics<T = IMetrics> {
[metric: string]: T;
cpuUsage: T;
memoryUsage: T;
fsUsage: T;
networkReceive: T;
networkTransmit: T;
cpuRequests?: T;
cpuLimits?: T;
memoryRequests?: T;
memoryLimits?: T;
}
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
export interface IPodLogsQuery {
container?: string;
tailLines?: number;
timestamps?: boolean;
sinceTime?: string; // Date.toISOString()-format
follow?: boolean;
previous?: boolean;
}
export enum PodStatus {
TERMINATED = "Terminated",
FAILED = "Failed",
PENDING = "Pending",
RUNNING = "Running",
SUCCEEDED = "Succeeded",
EVICTED = "Evicted"
}
export interface IPodContainer {
name: string;
image: string;
command?: string[];
args?: string[];
ports?: {
name?: string;
containerPort: number;
protocol: string;
}[];
resources?: {
limits: {
cpu: string;
memory: string;
};
requests: {
cpu: string;
memory: string;
};
};
env?: {
name: string;
value?: string;
valueFrom?: {
fieldRef?: {
apiVersion: string;
fieldPath: string;
};
secretKeyRef?: {
key: string;
name: string;
};
configMapKeyRef?: {
key: string;
name: string;
};
};
}[];
envFrom?: {
configMapRef?: {
name: string;
};
secretRef?: {
name: string;
}
}[];
volumeMounts?: {
name: string;
readOnly: boolean;
mountPath: string;
}[];
livenessProbe?: IContainerProbe;
readinessProbe?: IContainerProbe;
startupProbe?: IContainerProbe;
imagePullPolicy: string;
}
interface IContainerProbe {
httpGet?: {
path?: string;
port: number;
scheme: string;
host?: string;
};
exec?: {
command: string[];
};
tcpSocket?: {
port: number;
};
initialDelaySeconds?: number;
timeoutSeconds?: number;
periodSeconds?: number;
successThreshold?: number;
failureThreshold?: number;
}
export interface IPodContainerStatus {
name: string;
state?: {
[index: string]: object;
running?: {
startedAt: string;
};
waiting?: {
reason: string;
message: string;
};
terminated?: {
startedAt: string;
finishedAt: string;
exitCode: number;
reason: string;
};
};
lastState?: {
[index: string]: object;
running?: {
startedAt: string;
};
waiting?: {
reason: string;
message: string;
};
terminated?: {
startedAt: string;
finishedAt: string;
exitCode: number;
reason: string;
};
};
ready: boolean;
restartCount: number;
image: string;
imageID: string;
containerID?: string;
started?: boolean;
}
export class Pod extends WorkloadKubeObject {
static kind = "Pod";
static namespaced = true;
static apiBase = "/api/v1/pods";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec?: {
volumes?: {
name: string;
persistentVolumeClaim: {
claimName: string;
};
emptyDir: {
medium?: string;
sizeLimit?: string;
};
configMap: {
name: string;
};
secret: {
secretName: string;
defaultMode: number;
};
}[];
initContainers: IPodContainer[];
containers: IPodContainer[];
restartPolicy?: string;
terminationGracePeriodSeconds?: number;
activeDeadlineSeconds?: number;
dnsPolicy?: string;
serviceAccountName: string;
serviceAccount: string;
automountServiceAccountToken?: boolean;
priority?: number;
priorityClassName?: string;
nodeName?: string;
nodeSelector?: {
[selector: string]: string;
};
securityContext?: {};
imagePullSecrets?: {
name: string;
}[];
hostNetwork?: boolean;
hostPID?: boolean;
hostIPC?: boolean;
shareProcessNamespace?: boolean;
hostname?: string;
subdomain?: string;
schedulerName?: string;
tolerations?: {
key?: string;
operator?: string;
effect?: string;
tolerationSeconds?: number;
value?: string;
}[];
hostAliases?: {
ip: string;
hostnames: string[];
};
affinity?: IAffinity;
};
declare status?: {
phase: string;
conditions: {
type: string;
status: string;
lastProbeTime: number;
lastTransitionTime: string;
}[];
hostIP: string;
podIP: string;
podIPs?: {
ip: string
}[];
startTime: string;
initContainerStatuses?: IPodContainerStatus[];
containerStatuses?: IPodContainerStatus[];
qosClass?: string;
reason?: string;
};
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) {
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 || "";
}
getStatus(): PodStatus {
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 === PodStatus.EVICTED) {
return PodStatus.EVICTED;
}
if (phase === PodStatus.FAILED) {
return PodStatus.FAILED;
}
if (phase === PodStatus.SUCCEEDED) {
return PodStatus.SUCCEEDED;
}
if (phase === PodStatus.RUNNING && isInGoodCondition) {
return PodStatus.RUNNING;
}
return PodStatus.PENDING;
}
// Returns pod phase or container error if occurred
getStatusMessage() {
if (this.getReason() === PodStatus.EVICTED) return "Evicted";
if (this.metadata.deletionTimestamp) return "Terminating";
const statuses = this.getContainerStatuses(false); // not including initContainers
for (const { state } of statuses.reverse()) {
if (state.waiting) {
return state.waiting.reason || "Waiting";
}
if (state.terminated) {
return state.terminated.reason || "Terminated";
}
}
return this.getStatusPhase();
}
getStatusPhase() {
return this.status?.phase;
}
getConditions() {
return this.status?.conditions || [];
}
getVolumes() {
return this.spec.volumes || [];
}
getSecrets(): string[] {
return this.getVolumes()
.filter(vol => vol.secret)
.map(vol => vol.secret.secretName);
}
getNodeSelectors(): string[] {
const { nodeSelector = {} } = this.spec;
return Object.entries(nodeSelector).map(values => values.join(": "));
}
getTolerations() {
return this.spec.tolerations || [];
}
getAffinity(): IAffinity {
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: IPodContainer) {
return this.getProbe(container.livenessProbe);
}
getReadinessProbe(container: IPodContainer) {
return this.getProbe(container.readinessProbe);
}
getStartupProbe(container: IPodContainer) {
return this.getProbe(container.startupProbe);
}
getProbe(probeData: IContainerProbe) {
if (!probeData) return [];
const {
httpGet, exec, tcpSocket, initialDelaySeconds, timeoutSeconds,
periodSeconds, successThreshold, failureThreshold
} = probeData;
const probe = [];
// HTTP Request
if (httpGet) {
const { path, port, host, scheme } = httpGet;
probe.push(
"http-get",
`${scheme.toLowerCase()}://${host || ""}:${port || ""}${path || ""}`,
);
}
// Command
if (exec && exec.command) {
probe.push(`exec [${exec.command.join(" ")}]`);
}
// TCP Probe
if (tcpSocket && tcpSocket.port) {
probe.push(`tcp-socket :${tcpSocket.port}`);
}
probe.push(
`delay=${initialDelaySeconds || "0"}s`,
`timeout=${timeoutSeconds || "0"}s`,
`period=${periodSeconds || "0"}s`,
`#success=${successThreshold || "0"}`,
`#failure=${failureThreshold || "0"}`,
);
return probe;
}
getNodeName() {
return this.spec.nodeName;
}
getSelectedNodeOs(): string | undefined {
return this.spec.nodeSelector?.["kubernetes.io/os"] || this.spec.nodeSelector?.["beta.kubernetes.io/os"];
}
getIPs(): string[] {
if(!this.status.podIPs) return [];
const podIPs = this.status.podIPs;
return podIPs.map(value => value.ip);
}
}
export const podsApi = new PodsApi({
objectConstructor: Pod,
});
@@ -1,122 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface PodSecurityPolicy {
spec: {
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 {
static kind = "PodSecurityPolicy";
static namespaced = false;
static apiBase = "/apis/policy/v1beta1/podsecuritypolicies";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
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 const pspApi = new KubeApi({
objectConstructor: PodSecurityPolicy,
});
@@ -1,128 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import get from "lodash/get";
import { autoBind } from "../../utils";
import { WorkloadKubeObject } from "../workload-kube-object";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { IPodContainer, IPodMetrics, Pod } from "./pods.api";
import type { KubeJsonApiData } from "../kube-json-api";
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
}
getReplicas(params: { namespace: string; name: string }): Promise<number> {
return this.request
.get(this.getScaleApiUrl(params))
.then(({ status }: any) => status?.replicas);
}
scale(params: { namespace: string; name: string }, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
spec: {
replicas
}
}
});
}
}
export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise<IPodMetrics> {
const podSelector = replicasets.map(replicaset => `${replicaset.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export class ReplicaSet extends WorkloadKubeObject {
static kind = "ReplicaSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/replicasets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
replicas?: number;
selector: { matchLabels: { [app: string]: string } };
template?: {
metadata: {
labels: {
app: string;
};
};
spec?: Pod["spec"];
};
minReadySeconds?: number;
};
declare status: {
replicas: number;
fullyLabeledReplicas?: number;
readyReplicas?: number;
availableReplicas?: number;
observedGeneration?: number;
conditions?: {
type: string;
status: string;
lastUpdateTime: string;
lastTransitionTime: string;
reason: string;
message: string;
}[];
};
getDesired() {
return this.spec.replicas || 0;
}
getCurrent() {
return this.status.availableReplicas || 0;
}
getReady() {
return this.status.readyReplicas || 0;
}
getImages() {
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
return [...containers].map(container => container.image);
}
}
export const replicaSetApi = new ReplicaSetApi({
objectConstructor: ReplicaSet,
});
@@ -1,40 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import jsYaml from "js-yaml";
import type { KubeJsonApiData } from "../kube-json-api";
import { apiBase } from "../index";
export const resourceApplierApi = {
annotations: [
"kubectl.kubernetes.io/last-applied-configuration"
],
async update(resource: object | string): Promise<KubeJsonApiData | null> {
if (typeof resource === "string") {
resource = jsYaml.safeLoad(resource);
}
const [data = null] = await apiBase.post<KubeJsonApiData[]>("/stack", { data: resource });
return data;
}
};
@@ -1,85 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export interface IResourceQuotaValues {
[quota: 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 ResourceQuota {
spec: {
hard: IResourceQuotaValues;
scopeSelector?: {
matchExpressions: {
operator: string;
scopeName: string;
values: string[];
}[];
};
};
status: {
hard: IResourceQuotaValues;
used: IResourceQuotaValues;
};
}
export class ResourceQuota extends KubeObject {
static kind = "ResourceQuota";
static namespaced = true;
static apiBase = "/api/v1/resourcequotas";
getScopeSelector() {
const { matchExpressions = [] } = this.spec.scopeSelector || {};
return matchExpressions;
}
}
export const resourceQuotaApi = new KubeApi({
objectConstructor: ResourceQuota,
});
@@ -1,66 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export type RoleBindingSubjectKind = "Group" | "ServiceAccount" | "User";
export interface RoleBindingSubject {
kind: RoleBindingSubjectKind;
name: string;
namespace?: string;
apiGroup?: string;
}
export interface RoleBinding {
subjects?: RoleBindingSubject[];
roleRef: {
kind: string;
name: string;
apiGroup?: string;
};
}
export class RoleBinding extends KubeObject {
static kind = "RoleBinding";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSubjects() {
return this.subjects || [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
export const roleBindingApi = new KubeApi({
objectConstructor: RoleBinding,
});
-46
View File
@@ -1,46 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export interface Role {
rules: {
verbs: string[];
apiGroups: string[];
resources: string[];
resourceNames?: string[];
}[];
}
export class Role extends KubeObject {
static kind = "Role";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/roles";
getRules() {
return this.rules || [];
}
}
export const roleApi = new KubeApi({
objectConstructor: Role,
});
-74
View File
@@ -1,74 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
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 interface ISecretRef {
key?: string;
name: string;
}
export interface Secret {
type: SecretType;
data: {
[prop: string]: string;
token?: string;
};
}
export class Secret extends KubeObject {
static kind = "Secret";
static namespaced = true;
static apiBase = "/api/v1/secrets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
this.data ??= {};
}
getKeys(): string[] {
return Object.keys(this.data);
}
getToken() {
return this.data.token;
}
}
export const secretsApi = new KubeApi({
objectConstructor: Secret,
});
@@ -1,91 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
create({ namespace = "default" }): Promise<SelfSubjectRulesReview> {
return super.create({}, {
spec: {
namespace
},
});
}
}
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;
})
};
}
}
export const selfSubjectRulesReviewApi = new SelfSubjectRulesReviewApi({
objectConstructor: SelfSubjectRulesReview,
});
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface ServiceAccount {
secrets?: {
name: string;
}[];
imagePullSecrets?: {
name: string;
}[];
}
export class ServiceAccount extends KubeObject {
static kind = "ServiceAccount";
static namespaced = true;
static apiBase = "/api/v1/serviceaccounts";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSecrets() {
return this.secrets || [];
}
getImagePullSecrets() {
return this.imagePullSecrets || [];
}
}
export const serviceAccountsApi = new KubeApi<ServiceAccount>({
objectConstructor: ServiceAccount,
});
-148
View File
@@ -1,148 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-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 Service {
spec: {
type: string;
clusterIP: string;
clusterIPs?: string[];
externalTrafficPolicy?: string;
externalName: string;
loadBalancerIP?: string;
loadBalancerSourceRanges?: string[];
sessionAffinity: string;
selector: { [key: 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;
};
status: {
loadBalancer?: {
ingress?: {
ip?: string;
hostname?: string;
}[];
};
};
}
export class Service extends KubeObject {
static kind = "Service";
static namespaced = true;
static apiBase = "/api/v1/services";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getClusterIp() {
return this.spec.clusterIP;
}
getClusterIps() {
return this.spec.clusterIPs || [];
}
getExternalIps() {
const lb = this.getLoadBalancer();
if (lb && lb.ingress) {
return lb.ingress.map(val => val.ip || val.hostname);
}
return this.spec.externalIPs || [];
}
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 const serviceApi = new KubeApi({
objectConstructor: Service,
});
@@ -1,154 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import get from "lodash/get";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { IPodContainer, IPodMetrics } from "./pods.api";
import type { KubeJsonApiData } from "../kube-json-api";
export class StatefulSetApi extends KubeApi<StatefulSet> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
}
getReplicas(params: { namespace: string; name: string }): Promise<number> {
return this.request
.get(this.getScaleApiUrl(params))
.then(({ status }: any) => status?.replicas);
}
scale(params: { namespace: string; name: string }, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
spec: {
replicas
}
}
});
}
}
export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise<IPodMetrics> {
const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export class StatefulSet extends WorkloadKubeObject {
static kind = "StatefulSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/statefulsets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
serviceName: string;
replicas: number;
selector: {
matchLabels: {
[key: string]: string;
};
};
template: {
metadata: {
labels: {
app: string;
};
};
spec: {
containers: {
name: string;
image: string;
ports: {
containerPort: number;
name: string;
}[];
volumeMounts: {
name: string;
mountPath: string;
}[];
}[];
affinity?: IAffinity;
nodeSelector?: {
[selector: string]: string;
};
tolerations: {
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[];
};
};
volumeClaimTemplates: {
metadata: {
name: string;
};
spec: {
accessModes: string[];
resources: {
requests: {
storage: string;
};
};
};
}[];
};
declare status: {
observedGeneration: number;
replicas: number;
currentReplicas: number;
readyReplicas: number;
currentRevision: string;
updateRevision: string;
collisionCount: number;
};
getReplicas() {
return this.spec.replicas || 0;
}
getImages() {
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
return [...containers].map(container => container.image);
}
}
export const statefulSetApi = new StatefulSetApi({
objectConstructor: StatefulSet,
});
@@ -1,67 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface StorageClass {
provisioner: string; // e.g. "storage.k8s.io/v1"
mountOptions?: string[];
volumeBindingMode: string;
reclaimPolicy: string;
parameters: {
[param: string]: string; // every provisioner has own set of these parameters
};
}
export class StorageClass extends KubeObject {
static kind = "StorageClass";
static namespaced = false;
static apiBase = "/apis/storage.k8s.io/v1/storageclasses";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
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 const storageClassApi = new KubeApi({
objectConstructor: StorageClass,
});
+6 -13
View File
@@ -19,19 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { JsonApi, JsonApiErrorParsed } from "./json-api";
import { KubeJsonApi } from "./kube-json-api";
import type { JsonApiErrorParsed } from "../../common/k8s-api/json-api";
import type { Response } from "node-fetch";
import { Notifications } from "../components/notifications";
import { apiKubePrefix, apiPrefix, isDebugging, isDevelopment } from "../../common/vars";
import { apiBase, apiKube } from "../../common/k8s-api";
export { apiBase, apiKube } from "../../common/k8s-api";
export const apiBase = new JsonApi({
apiBase: apiPrefix,
debug: isDevelopment || isDebugging,
});
export const apiKube = new KubeJsonApi({
apiBase: apiKubePrefix,
debug: isDevelopment,
});
// Common handler for HTTP api errors
export function onApiError(error: JsonApiErrorParsed, res: Response) {
@@ -43,5 +36,5 @@ export function onApiError(error: JsonApiErrorParsed, res: Response) {
}
}
apiBase.onError.addListener(onApiError);
apiKube.onError.addListener(onApiError);
if (apiBase) apiBase.onError.addListener(onApiError);
if (apiKube) apiKube.onError.addListener(onApiError);
-218
View File
@@ -1,218 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Base http-service / json-api class
import { stringify } from "querystring";
import { EventEmitter } from "../../common/event-emitter";
import { randomBytes } from "crypto";
export interface JsonApiData {
}
export interface JsonApiError {
code?: number;
message?: string;
errors?: { id: string; title: string; status?: number }[];
}
export interface JsonApiParams<D = any> {
query?: { [param: string]: string | number | any };
data?: D; // request body
}
export interface JsonApiLog {
method: string;
reqUrl: string;
reqInit: RequestInit;
data?: any;
error?: any;
}
export interface JsonApiConfig {
apiBase: string;
debug?: boolean;
}
export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
static reqInitDefault: RequestInit = {
headers: {
"content-type": "application/json"
}
};
static configDefault: Partial<JsonApiConfig> = {
debug: false
};
constructor(protected config: JsonApiConfig, protected reqInit?: RequestInit) {
this.config = Object.assign({}, JsonApi.configDefault, config);
this.reqInit = Object.assign({}, JsonApi.reqInitDefault, reqInit);
this.parseResponse = this.parseResponse.bind(this);
}
public onData = new EventEmitter<[D, Response]>();
public onError = new EventEmitter<[JsonApiErrorParsed, Response]>();
get<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "get" });
}
getResponse(path: string, params?: P, init: RequestInit = {}): Promise<Response> {
const reqPath = `${this.config.apiBase}${path}`;
const subdomain = randomBytes(2).toString("hex");
let reqUrl = `http://${subdomain}.${window.location.host}${reqPath}`; // hack around browser connection limits (chromium allows 6 per domain)
const reqInit: RequestInit = { ...init };
const { query } = params || {} as P;
if (!reqInit.method) {
reqInit.method = "get";
}
if (query) {
const queryString = stringify(query);
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
}
this.writeLog({
method: reqInit.method.toUpperCase(),
reqUrl: reqPath,
reqInit,
});
return fetch(reqUrl, reqInit);
}
post<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "post" });
}
put<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "put" });
}
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "PATCH" });
}
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "delete" });
}
protected async request<D>(path: string, params?: P, init: RequestInit = {}) {
let reqUrl = this.config.apiBase + path;
const reqInit: RequestInit = { ...this.reqInit, ...init };
const { data, query } = params || {} as P;
if (data && !reqInit.body) {
reqInit.body = JSON.stringify(data);
}
if (query) {
const queryString = stringify(query);
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
}
const infoLog: JsonApiLog = {
method: reqInit.method.toUpperCase(),
reqUrl,
reqInit,
};
const res = await fetch(reqUrl, reqInit);
return this.parseResponse<D>(res, infoLog);
}
protected async parseResponse<D>(res: Response, log: JsonApiLog): Promise<D> {
const { status } = res;
const text = await res.text();
let data;
try {
data = text ? JSON.parse(text) : ""; // DELETE-requests might not have response-body
} catch (e) {
data = text;
}
if (status >= 200 && status < 300) {
this.onData.emit(data, res);
this.writeLog({ ...log, data });
return data;
}
if (log.method === "GET" && res.status === 403) {
this.writeLog({ ...log, error: data });
throw data;
}
const error = new JsonApiErrorParsed(data, this.parseError(data, res));
this.onError.emit(error, res);
this.writeLog({ ...log, error });
throw error;
}
protected parseError(error: JsonApiError | string, res: Response): string[] {
if (typeof error === "string") {
return [error];
}
if (Array.isArray(error.errors)) {
return error.errors.map(error => error.title);
}
if (error.message) {
return [error.message];
}
return [res.statusText || "Error!"];
}
protected writeLog(log: JsonApiLog) {
if (!this.config.debug) return;
const { method, reqUrl, ...params } = log;
let textStyle = "font-weight: bold;";
if (params.data) textStyle += "background: green; color: white;";
if (params.error) textStyle += "background: red; color: white;";
console.log(`%c${method} ${reqUrl}`, textStyle, params);
}
}
export class JsonApiErrorParsed {
isUsedForNotification = false;
constructor(private error: JsonApiError | DOMException, private messages: string[]) {
}
get isAborted() {
return this.error.code === DOMException.ABORT_ERR;
}
toString() {
return this.messages.join("\n");
}
}
-159
View File
@@ -1,159 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Parse kube-api path and get api-version, group, etc.
import { splitArray } from "../../common/utils";
import { isDebugging } from "../../common/vars";
import logger from "../../main/logger";
import { inspect } from "util";
export interface IKubeObjectRef {
kind: string;
apiVersion: string;
name: string;
namespace?: string;
}
export interface IKubeApiLinkRef {
apiPrefix?: string;
apiVersion: string;
resource: string;
name: string;
namespace?: string;
}
export interface IKubeApiParsed extends IKubeApiLinkRef {
apiBase: string;
apiGroup: string;
apiVersionWithGroup: string;
}
export function parseKubeApi(path: string): IKubeApiParsed {
if (!isDebugging) {
return _parseKubeApi(path);
}
try {
const res = _parseKubeApi(path);
logger.debug(`parseKubeApi(${inspect(path, false, null, false)}) -> ${inspect(res, false, null, false)}`);
return res;
} catch (error) {
logger.debug(`parseKubeApi(${inspect(path, false, null, false)}) threw: ${error}`);
throw error;
}
}
function _parseKubeApi(path: string): IKubeApiParsed {
const apiPath = new URL(path, location.origin).pathname;
const [, prefix, ...parts] = apiPath.split("/");
const apiPrefix = `/${prefix}`;
const [left, right, namespaced] = splitArray(parts, "namespaces");
let apiGroup, apiVersion, namespace, resource, name;
if (namespaced) {
switch (right.length) {
case 1:
name = right[0];
// fallthrough
case 0:
resource = "namespaces"; // special case this due to `split` removing namespaces
break;
default:
[namespace, resource, name] = right;
break;
}
apiVersion = left.pop();
apiGroup = left.join("/");
} else {
switch (left.length) {
case 0:
throw new Error(`invalid apiPath: ${apiPath}`);
case 4:
[apiGroup, apiVersion, resource, name] = left;
break;
case 2:
resource = left.pop();
// fallthrough
case 1:
apiVersion = left.pop();
apiGroup = "";
break;
default:
/**
* Given that
* - `apiVersion` is `GROUP/VERSION` and
* - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i
* where length <= 63
* - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253
*
* There is no well defined selection from an array of items that were
* separated by '/'
*
* Solution is to create a heuristic. Namely:
* 1. if '.' in left[0] then apiGroup <- left[0]
* 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1]
* 3. otherwise assume apiVersion <- left[0]
* 4. always resource, name <- left[(0 or 1)+1..]
*/
if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
[apiGroup, apiVersion] = left;
resource = left.slice(2).join("/");
} else {
apiGroup = "";
apiVersion = left[0];
[resource, name] = left.slice(1);
}
break;
}
}
const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/");
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
if (!apiBase) {
throw new Error(`invalid apiPath: ${apiPath}`);
}
return {
apiBase,
apiPrefix, apiGroup,
apiVersion, apiVersionWithGroup,
namespace, resource, name,
};
}
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
let { namespace } = ref;
if (namespace) {
namespace = `namespaces/${namespace}`;
}
return [apiPrefix, apiVersion, namespace, resource, name]
.filter(v => v)
.join("/");
}
-503
View File
@@ -1,503 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Base class for building all kubernetes apis
import merge from "lodash/merge";
import { stringify } from "querystring";
import { apiKubePrefix, isDevelopment, isTestEnv } from "../../common/vars";
import logger from "../../main/logger";
import { apiManager } from "./api-manager";
import { apiKube } from "./index";
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-api";
import { ReadableWebToNodeStream } from "../utils/readableStream";
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
import { noop } from "../utils";
export interface IKubeApiOptions<T extends KubeObject> {
/**
* base api-path for listing all resources, e.g. "/api/v1/pods"
*/
apiBase?: string;
/**
* If the API uses a different API endpoint (e.g. apiBase) depending on the cluster version,
* fallback API bases can be listed individually.
* The first (existing) API base is used in the requests, if apiBase is not found.
* This option only has effect if checkPreferredVersion is true.
*/
fallbackApiBases?: string[];
objectConstructor: KubeObjectConstructor<T>;
request?: KubeJsonApi;
isNamespaced?: boolean;
kind?: string;
checkPreferredVersion?: boolean;
}
export interface IKubeApiQueryParams {
watch?: boolean | number;
resourceVersion?: string;
timeoutSeconds?: number;
limit?: number; // doesn't work with ?watch
continue?: string; // might be used with ?limit from second request
labelSelector?: string | string[]; // restrict list of objects by their labels, e.g. labelSelector: ["label=value"]
fieldSelector?: string | string[]; // restrict list of objects by their fields, e.g. fieldSelector: "field=name"
}
export interface KubeApiListOptions {
namespace?: string;
reqInit?: RequestInit;
}
export interface IKubePreferredVersion {
preferredVersion?: {
version: string;
}
}
export interface IKubeResourceList {
resources: {
kind: string;
name: string;
namespaced: boolean;
singularName: string;
storageVersionHash: string;
verbs: string[];
}[];
}
export interface IKubeApiCluster {
metadata: {
uid: string;
}
}
export function forCluster<T extends KubeObject>(cluster: IKubeApiCluster, kubeClass: KubeObjectConstructor<T>): KubeApi<T> {
const request = new KubeJsonApi({
apiBase: apiKubePrefix,
debug: isDevelopment,
}, {
headers: {
"X-Cluster-ID": cluster.metadata.uid
}
});
return new KubeApi({
objectConstructor: kubeClass,
request
});
}
export function ensureObjectSelfLink(api: KubeApi<KubeObject>, object: KubeJsonApiData) {
if (!object.metadata.selfLink) {
object.metadata.selfLink = createKubeApiURL({
apiPrefix: api.apiPrefix,
apiVersion: api.apiVersionWithGroup,
resource: api.apiResource,
namespace: api.isNamespaced ? object.metadata.namespace : undefined,
name: object.metadata.name,
});
}
}
export type KubeApiWatchCallback = (data: IKubeWatchEvent<KubeJsonApiData>, error: any) => void;
export type KubeApiWatchOptions = {
namespace: string;
callback?: KubeApiWatchCallback;
abortController?: AbortController
};
export class KubeApi<T extends KubeObject> {
readonly kind: string;
readonly apiBase: string;
readonly apiPrefix: string;
readonly apiGroup: string;
readonly apiVersion: string;
readonly apiVersionPreferred?: string;
readonly apiResource: string;
readonly isNamespaced: boolean;
public objectConstructor: KubeObjectConstructor<T>;
protected request: KubeJsonApi;
protected resourceVersions = new Map<string, string>();
protected watchDisposer: () => void;
constructor(protected options: IKubeApiOptions<T>) {
const {
objectConstructor,
request = apiKube,
kind = options.objectConstructor?.kind,
isNamespaced = options.objectConstructor?.namespaced
} = options || {};
if (!options.apiBase) {
options.apiBase = objectConstructor.apiBase;
}
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase);
this.kind = kind;
this.isNamespaced = isNamespaced;
this.apiBase = apiBase;
this.apiPrefix = apiPrefix;
this.apiGroup = apiGroup;
this.apiVersion = apiVersion;
this.apiResource = resource;
this.request = request;
this.objectConstructor = objectConstructor;
this.checkPreferredVersion();
this.parseResponse = this.parseResponse.bind(this);
apiManager.registerApi(apiBase, this);
}
get apiVersionWithGroup() {
return [this.apiGroup, this.apiVersionPreferred ?? this.apiVersion]
.filter(Boolean)
.join("/");
}
/**
* Returns the latest API prefix/group that contains the required resource.
* First tries options.apiBase, then urls in order from options.fallbackApiBases.
*/
private async getLatestApiPrefixGroup() {
// Note that this.options.apiBase is the "full" url, whereas this.apiBase is parsed
const apiBases = [this.options.apiBase, ...this.options.fallbackApiBases];
for (const apiUrl of apiBases) {
// Split e.g. "/apis/extensions/v1beta1/ingresses" to parts
const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl);
// Request available resources
try {
const response = await this.request.get<IKubeResourceList>(`${apiPrefix}/${apiVersionWithGroup}`);
// If the resource is found in the group, use this apiUrl
if (response.resources?.find(kubeResource => kubeResource.name === resource)) {
return { apiPrefix, apiGroup };
}
} catch (error) {
// Exception is ignored as we can try the next url
console.error(error);
}
}
// Avoid throwing in tests
if (isTestEnv) {
return {
apiPrefix: this.apiPrefix,
apiGroup: this.apiGroup
};
}
throw new Error(`Can't find working API for the Kubernetes resource ${this.apiResource}`);
}
/**
* Get the apiPrefix and apiGroup to be used for fetching the preferred version.
*/
private async getPreferredVersionPrefixGroup() {
if (this.options.fallbackApiBases) {
try {
return await this.getLatestApiPrefixGroup();
} catch (error) {
// If valid API wasn't found, log the error and return defaults below
logger.error(error);
}
}
return {
apiPrefix: this.apiPrefix,
apiGroup: this.apiGroup
};
}
protected async checkPreferredVersion() {
if (this.options.fallbackApiBases && !this.options.checkPreferredVersion) {
throw new Error("checkPreferredVersion must be enabled if fallbackApiBases is set in KubeApi");
}
if (this.options.checkPreferredVersion && this.apiVersionPreferred === undefined) {
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
Object.defineProperty(this, "apiPrefix", {
value: apiPrefix
});
Object.defineProperty(this, "apiGroup", {
value: apiGroup
});
const res = await this.request.get<IKubePreferredVersion>(`${this.apiPrefix}/${this.apiGroup}`);
Object.defineProperty(this, "apiVersionPreferred", {
value: res?.preferredVersion?.version ?? null,
});
if (this.apiVersionPreferred) {
Object.defineProperty(this, "apiBase", { value: this.getUrl() });
apiManager.registerApi(this.apiBase, this);
}
}
}
setResourceVersion(namespace = "", newVersion: string) {
this.resourceVersions.set(namespace, newVersion);
}
getResourceVersion(namespace = "") {
return this.resourceVersions.get(namespace);
}
async refreshResourceVersion(params?: KubeApiListOptions) {
return this.list(params, { limit: 1 });
}
getUrl({ name = "", namespace = "" } = {}, query?: Partial<IKubeApiQueryParams>) {
const resourcePath = createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,
resource: this.apiResource,
namespace: this.isNamespaced ? namespace : undefined,
name,
});
return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : "");
}
protected normalizeQuery(query: Partial<IKubeApiQueryParams> = {}) {
if (query.labelSelector) {
query.labelSelector = [query.labelSelector].flat().join(",");
}
if (query.fieldSelector) {
query.fieldSelector = [query.fieldSelector].flat().join(",");
}
return query;
}
protected parseResponse(data: unknown, namespace?: string): T | T[] | null {
if (!data) return null;
const KubeObjectConstructor = this.objectConstructor;
// process items list response, check before single item since there is overlap
if (KubeObject.isJsonApiDataList(data, KubeObject.isPartialJsonApiData)) {
const { apiVersion, items, metadata } = data;
this.setResourceVersion(namespace, metadata.resourceVersion);
this.setResourceVersion("", metadata.resourceVersion);
return items.map((item) => {
const object = new KubeObjectConstructor({
kind: this.kind,
apiVersion,
...item,
});
ensureObjectSelfLink(this, object);
return object;
});
}
// process a single item
if (KubeObject.isJsonApiData(data)) {
const object = new KubeObjectConstructor(data);
ensureObjectSelfLink(this, object);
return object;
}
// custom apis might return array for list response, e.g. users, groups, etc.
if (Array.isArray(data)) {
return data.map(data => new KubeObjectConstructor(data));
}
return null;
}
async list({ namespace = "", reqInit }: KubeApiListOptions = {}, query?: IKubeApiQueryParams): Promise<T[] | null> {
await this.checkPreferredVersion();
const url = this.getUrl({ namespace });
const res = await this.request.get(url, { query }, reqInit);
const parsed = this.parseResponse(res, namespace);
if (Array.isArray(parsed)) {
return parsed;
}
if (!parsed) {
return null;
}
throw new Error(`GET multiple request to ${url} returned not an array: ${JSON.stringify(parsed)}`);
}
async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise<T | null> {
await this.checkPreferredVersion();
const url = this.getUrl({ namespace, name });
const res = await this.request.get(url, { query });
const parsed = this.parseResponse(res);
if (Array.isArray(parsed)) {
throw new Error(`GET single request to ${url} returned an array: ${JSON.stringify(parsed)}`);
}
return parsed;
}
async create({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T | null> {
await this.checkPreferredVersion();
const apiUrl = this.getUrl({ namespace });
const res = await this.request.post(apiUrl, {
data: merge({
kind: this.kind,
apiVersion: this.apiVersionWithGroup,
metadata: {
name,
namespace
}
}, data)
});
const parsed = this.parseResponse(res);
if (Array.isArray(parsed)) {
throw new Error(`POST request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
}
return parsed;
}
async update({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T | null> {
await this.checkPreferredVersion();
const apiUrl = this.getUrl({ namespace, name });
const res = await this.request.put(apiUrl, { data });
const parsed = this.parseResponse(res);
if (Array.isArray(parsed)) {
throw new Error(`PUT request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
}
return parsed;
}
async delete({ name = "", namespace = "default" }) {
await this.checkPreferredVersion();
const apiUrl = this.getUrl({ namespace, name });
return this.request.del(apiUrl);
}
getWatchUrl(namespace = "", query: IKubeApiQueryParams = {}) {
return this.getUrl({ namespace }, {
watch: 1,
resourceVersion: this.getResourceVersion(namespace),
...query,
});
}
watch(opts: KubeApiWatchOptions = { namespace: "" }): () => void {
let errorReceived = false;
let timedRetry: NodeJS.Timeout;
const { abortController: { abort, signal } = new AbortController(), namespace, callback = noop } = opts;
signal.addEventListener("abort", () => {
clearTimeout(timedRetry);
});
const watchUrl = this.getWatchUrl(namespace);
const responsePromise = this.request.getResponse(watchUrl, null, { signal });
responsePromise
.then(response => {
if (!response.ok) {
return callback(null, response);
}
const nodeStream = new ReadableWebToNodeStream(response.body);
["end", "close", "error"].forEach((eventName) => {
nodeStream.on(eventName, () => {
if (errorReceived) return; // kubernetes errors should be handled in a callback
clearTimeout(timedRetry);
timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry
this.watch({ ...opts, namespace, callback });
}, 1000);
});
});
byline(nodeStream).on("data", (line) => {
try {
const event: IKubeWatchEvent<KubeJsonApiData> = JSON.parse(line);
if (event.type === "ERROR" && event.object.kind === "Status") {
errorReceived = true;
return callback(null, new KubeStatus(event.object as any));
}
this.modifyWatchEvent(event);
callback(event, null);
} catch (ignore) {
// ignore parse errors
}
});
})
.catch(error => {
if (error instanceof DOMException) return; // AbortController rejects, we can ignore it
callback(null, error);
});
return abort;
}
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
switch (event.type) {
case "ADDED":
case "DELETED":
case "MODIFIED": {
ensureObjectSelfLink(this, event.object);
const { namespace, resourceVersion } = event.object.metadata;
this.setResourceVersion(namespace, resourceVersion);
this.setResourceVersion("", resourceVersion);
break;
}
}
}
}
-81
View File
@@ -1,81 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { JsonApi, JsonApiData, JsonApiError } from "./json-api";
export interface KubeJsonApiListMetadata {
resourceVersion: string;
selfLink?: string;
}
export interface KubeJsonApiDataList<T = KubeJsonApiData> {
kind: string;
apiVersion: string;
items: T[];
metadata: KubeJsonApiListMetadata;
}
export interface KubeJsonApiMetadata {
uid: string;
name: string;
namespace?: string;
creationTimestamp?: string;
resourceVersion: string;
continue?: string;
finalizers?: string[];
selfLink?: string;
labels?: {
[label: string]: string;
};
annotations?: {
[annotation: string]: string;
};
[key: string]: any;
}
export interface KubeJsonApiData extends JsonApiData {
kind: string;
apiVersion: string;
metadata: KubeJsonApiMetadata;
}
export interface KubeJsonApiError extends JsonApiError {
code: number;
status: string;
message?: string;
reason: string;
details: {
name: string;
kind: string;
};
}
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
protected parseError(error: KubeJsonApiError | any, res: Response): string[] {
const { status, reason, message } = error;
if (status && reason) {
return [message || `${status}: ${reason}`];
}
return super.parseError(error, res);
}
}
-306
View File
@@ -1,306 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Base class for all kubernetes objects
import moment from "moment";
import type { KubeJsonApiData, KubeJsonApiDataList, KubeJsonApiListMetadata, KubeJsonApiMetadata } from "./kube-json-api";
import { autoBind, formatDuration } from "../utils";
import type { ItemObject } from "../item.store";
import { apiKube } from "./index";
import type { JsonApiParams } from "./json-api";
import { resourceApplierApi } from "./endpoints/resource-applier.api";
import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing";
import _ from "lodash";
export type KubeObjectConstructor<K extends KubeObject> = (new (data: KubeJsonApiData | any) => K) & {
kind?: string;
namespaced?: boolean;
apiBase?: string;
};
export interface KubeObjectMetadata {
uid: string;
name: string;
namespace?: string;
creationTimestamp: string;
resourceVersion: string;
selfLink: string;
deletionTimestamp?: string;
finalizers?: string[];
continue?: string; // provided when used "?limit=" query param to fetch objects list
labels?: {
[label: string]: string;
};
annotations?: {
[annotation: string]: string;
};
ownerReferences?: {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller: boolean;
blockOwnerDeletion: boolean;
}[];
}
export interface KubeStatusData {
kind: string;
apiVersion: string;
code: number;
message?: string;
reason?: string;
}
export class KubeStatus {
public readonly kind = "Status";
public readonly apiVersion: string;
public readonly code: number;
public readonly message: string;
public readonly reason: string;
constructor(data: KubeStatusData) {
this.apiVersion = data.apiVersion;
this.code = data.code;
this.message = data.message || "";
this.reason = data.reason || "";
}
}
export interface KubeObjectStatus {
conditions?: {
lastTransitionTime: string;
message: string;
reason: string;
status: string;
type?: string;
}[];
}
export type KubeMetaField = keyof KubeObjectMetadata;
export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata, Status = any, Spec = any> implements ItemObject {
static readonly kind: string;
static readonly namespaced: boolean;
apiVersion: string;
kind: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
managedFields?: any;
static create(data: KubeJsonApiData) {
return new KubeObject(data);
}
static isNonSystem(item: KubeJsonApiData | KubeObject) {
return !item.metadata.name.startsWith("system:");
}
static isJsonApiData(object: unknown): object is KubeJsonApiData {
return (
isObject(object)
&& hasTypedProperty(object, "kind", isString)
&& hasTypedProperty(object, "apiVersion", isString)
&& hasTypedProperty(object, "metadata", KubeObject.isKubeJsonApiMetadata)
);
}
static isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata {
return (
isObject(object)
&& hasOptionalProperty(object, "resourceVersion", isString)
&& hasOptionalProperty(object, "selfLink", isString)
);
}
static isKubeJsonApiMetadata(object: unknown): object is KubeJsonApiMetadata {
return (
isObject(object)
&& hasTypedProperty(object, "uid", isString)
&& hasTypedProperty(object, "name", isString)
&& hasTypedProperty(object, "resourceVersion", isString)
&& hasOptionalProperty(object, "selfLink", isString)
&& hasOptionalProperty(object, "namespace", isString)
&& hasOptionalProperty(object, "creationTimestamp", isString)
&& hasOptionalProperty(object, "continue", isString)
&& hasOptionalProperty(object, "finalizers", bindPredicate(isTypedArray, isString))
&& hasOptionalProperty(object, "labels", bindPredicate(isRecord, isString, isString))
&& hasOptionalProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
static isPartialJsonApiMetadata(object: unknown): object is Partial<KubeJsonApiMetadata> {
return (
isObject(object)
&& hasOptionalProperty(object, "uid", isString)
&& hasOptionalProperty(object, "name", isString)
&& hasOptionalProperty(object, "resourceVersion", isString)
&& hasOptionalProperty(object, "selfLink", isString)
&& hasOptionalProperty(object, "namespace", isString)
&& hasOptionalProperty(object, "creationTimestamp", isString)
&& hasOptionalProperty(object, "continue", isString)
&& hasOptionalProperty(object, "finalizers", bindPredicate(isTypedArray, isString))
&& hasOptionalProperty(object, "labels", bindPredicate(isRecord, isString, isString))
&& hasOptionalProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
static isPartialJsonApiData(object: unknown): object is Partial<KubeJsonApiData> {
return (
isObject(object)
&& hasOptionalProperty(object, "kind", isString)
&& hasOptionalProperty(object, "apiVersion", isString)
&& hasOptionalProperty(object, "metadata", KubeObject.isPartialJsonApiMetadata)
);
}
static isJsonApiDataList<T>(object: unknown, verifyItem: (val: unknown) => val is T): object is KubeJsonApiDataList<T> {
return (
isObject(object)
&& hasTypedProperty(object, "kind", isString)
&& hasTypedProperty(object, "apiVersion", isString)
&& hasTypedProperty(object, "metadata", KubeObject.isKubeJsonApiListMetadata)
&& hasTypedProperty(object, "items", bindPredicate(isTypedArray, verifyItem))
);
}
static stringifyLabels(labels?: { [name: string]: string }): string[] {
if (!labels) return [];
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
}
protected static readonly nonEditableFields = [
"apiVersion",
"kind",
"metadata.name",
"metadata.selfLink",
"metadata.resourceVersion",
"metadata.uid",
"managedFields",
"status",
];
constructor(data: KubeJsonApiData) {
Object.assign(this, data);
autoBind(this);
}
get selfLink() {
return this.metadata.selfLink;
}
getId() {
return this.metadata.uid;
}
getResourceVersion() {
return this.metadata.resourceVersion;
}
getName() {
return this.metadata.name;
}
getNs() {
// avoid "null" serialization via JSON.stringify when post data
return this.metadata.namespace || undefined;
}
getTimeDiffFromNow(): number {
return Date.now() - new Date(this.metadata.creationTimestamp).getTime();
}
getAge(humanize = true, compact = true, fromNow = false): string | number {
if (fromNow) {
return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used
}
const diff = this.getTimeDiffFromNow();
if (humanize) {
return formatDuration(diff, compact);
}
return diff;
}
getFinalizers(): string[] {
return this.metadata.finalizers || [];
}
getLabels(): string[] {
return KubeObject.stringifyLabels(this.metadata.labels);
}
getAnnotations(filter = false): string[] {
const labels = KubeObject.stringifyLabels(this.metadata.annotations);
return filter ? labels.filter(label => {
const skip = resourceApplierApi.annotations.some(key => label.startsWith(key));
return !skip;
}) : labels;
}
getOwnerRefs() {
const refs = this.metadata?.ownerReferences || [];
const namespace = this.getNs();
return refs.map(ownerRef => ({ ...ownerRef, namespace }));
}
getSearchFields() {
const { getName, getId, getNs, getAnnotations, getLabels } = this;
return [
getName(),
getNs(),
getId(),
...getLabels(),
...getAnnotations(true),
];
}
toPlainObject(): object {
return JSON.parse(JSON.stringify(this));
}
// use unified resource-applier api for updating all k8s objects
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
for (const field of KubeObject.nonEditableFields) {
if (!_.isEqual(_.get(this, field), _.get(data, field))) {
throw new Error(`Failed to update Kube Object: ${field} has been modified`);
}
}
return resourceApplierApi.update({
...this.toPlainObject(),
...data,
});
}
delete(params?: JsonApiParams) {
return apiKube.del(this.selfLink, params);
}
}
-156
View File
@@ -1,156 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Kubernetes watch-api client
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
import type { KubeObjectStore } from "../kube-object.store";
import type { ClusterContext } from "../components/context";
import plimit from "p-limit";
import { comparer, observable, reaction, makeObservable } from "mobx";
import { autoBind, Disposer, noop } from "../utils";
import type { KubeApi } from "./kube-api";
import type { KubeJsonApiData } from "./kube-json-api";
import { isDebugging, isProduction } from "../../common/vars";
import type { KubeObject } from "./kube-object";
export interface IKubeWatchEvent<T extends KubeJsonApiData> {
type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR";
object?: T;
}
export interface IKubeWatchSubscribeStoreOptions {
namespaces?: string[]; // default: all accessible namespaces
preload?: boolean; // preload store items, default: true
waitUntilLoaded?: boolean; // subscribe only after loading all stores, default: true
loadOnce?: boolean; // check store.isLoaded to skip loading if done already, default: false
}
export interface IKubeWatchLog {
message: string | string[] | Error;
meta?: object;
cssStyle?: string;
}
export class KubeWatchApi {
@observable context: ClusterContext = null;
constructor() {
makeObservable(this);
autoBind(this);
}
isAllowedApi(api: KubeApi<KubeObject>): boolean {
return Boolean(this.context?.cluster.isAllowedResource(api.kind));
}
preloadStores(stores: KubeObjectStore<KubeObject>[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) {
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
const preloading: Promise<any>[] = [];
for (const store of stores) {
preloading.push(limitRequests(async () => {
if (store.isLoaded && opts.loadOnce) return; // skip
return store.loadAll({ namespaces: opts.namespaces });
}));
}
return {
loading: Promise.allSettled(preloading),
cancelLoading: () => limitRequests.clearQueue(),
};
}
subscribeStores(stores: KubeObjectStore<KubeObject>[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer {
const { preload = true, waitUntilLoaded = true, loadOnce = false, } = opts;
const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? [];
const unsubscribeList: Function[] = [];
let isUnsubscribed = false;
const load = (namespaces = subscribingNamespaces) => this.preloadStores(stores, { namespaces, loadOnce });
let preloading = preload && load();
let cancelReloading: Disposer = noop;
const subscribe = () => {
if (isUnsubscribed) return;
stores.forEach((store) => {
unsubscribeList.push(store.subscribe());
});
};
if (preloading) {
if (waitUntilLoaded) {
preloading.loading.then(subscribe, error => {
this.log({
message: new Error("Loading stores has failed"),
meta: { stores, error, options: opts },
});
});
} else {
subscribe();
}
// reload stores only for context namespaces change
cancelReloading = reaction(() => this.context?.contextNamespaces, namespaces => {
preloading?.cancelLoading();
unsubscribeList.forEach(unsubscribe => unsubscribe());
unsubscribeList.length = 0;
preloading = load(namespaces);
preloading.loading.then(subscribe);
}, {
equals: comparer.shallow,
});
}
// unsubscribe
return () => {
if (isUnsubscribed) return;
isUnsubscribed = true;
cancelReloading();
preloading?.cancelLoading();
unsubscribeList.forEach(unsubscribe => unsubscribe());
unsubscribeList.length = 0;
};
}
protected log({ message, cssStyle = "", meta = {} }: IKubeWatchLog) {
if (isProduction && !isDebugging) {
return;
}
const logInfo = [`%c[KUBE-WATCH-API]:`, `font-weight: bold; ${cssStyle}`, message].flat().map(String);
const logMeta = {
time: new Date().toLocaleString(),
...meta,
};
if (message instanceof Error) {
console.error(...logInfo, logMeta);
} else {
console.info(...logInfo, logMeta);
}
}
}
export const kubeWatchApi = new KubeWatchApi();
-105
View File
@@ -1,105 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import get from "lodash/get";
import { KubeObject } from "./kube-object";
export interface IToleration {
key?: string;
operator?: string;
effect?: string;
value?: string;
tolerationSeconds?: number;
}
interface IMatchExpression {
key: string;
operator: string;
values: string[];
}
interface INodeAffinity {
nodeSelectorTerms?: {
matchExpressions: IMatchExpression[];
}[];
weight: number;
preference: {
matchExpressions: IMatchExpression[];
};
}
interface IPodAffinity {
labelSelector: {
matchExpressions: IMatchExpression[];
};
topologyKey: string;
}
export interface IAffinity {
nodeAffinity?: {
requiredDuringSchedulingIgnoredDuringExecution?: INodeAffinity[];
preferredDuringSchedulingIgnoredDuringExecution?: INodeAffinity[];
};
podAffinity?: {
requiredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[];
preferredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[];
};
podAntiAffinity?: {
requiredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[];
preferredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[];
};
}
export class WorkloadKubeObject extends KubeObject {
getSelectors(): string[] {
const selector = this.spec.selector;
return KubeObject.stringifyLabels(selector ? selector.matchLabels : null);
}
getNodeSelectors(): string[] {
const nodeSelector = get(this, "spec.template.spec.nodeSelector");
return KubeObject.stringifyLabels(nodeSelector);
}
getTemplateLabels(): string[] {
const labels = get(this, "spec.template.metadata.labels");
return KubeObject.stringifyLabels(labels);
}
getTolerations(): IToleration[] {
return get(this, "spec.template.spec.tolerations", []);
}
getAffinity(): IAffinity {
return get(this, "spec.template.spec.affinity");
}
getAffinityNumber() {
const affinity = this.getAffinity();
if (!affinity) return 0;
return Object.keys(affinity).length;
}
}
@@ -22,7 +22,7 @@
import "./helm-chart-details.scss";
import React, { Component } from "react";
import { getChartDetails, HelmChart } from "../../api/endpoints/helm-charts.api";
import { getChartDetails, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
import { observable, autorun, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Drawer, DrawerItem } from "../drawer";
@@ -22,8 +22,8 @@
import semver from "semver";
import { observable, makeObservable } from "mobx";
import { autoBind, sortCompareChartVersions } from "../../utils";
import { getChartDetails, HelmChart, listCharts } from "../../api/endpoints/helm-charts.api";
import { ItemStore } from "../../item.store";
import { getChartDetails, HelmChart, listCharts } from "../../../common/k8s-api/endpoints/helm-charts.api";
import { ItemStore } from "../../../common/item.store";
import flatten from "lodash/flatten";
export interface IChartVersion {
@@ -25,7 +25,7 @@ import React, { Component } from "react";
import type { RouteComponentProps } from "react-router";
import { observer } from "mobx-react";
import { helmChartStore } from "./helm-chart.store";
import type { HelmChart } from "../../api/endpoints/helm-charts.api";
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
import { HelmChartDetails } from "./helm-chart-details";
import { navigation } from "../../navigation";
import { ItemListLayout } from "../item-object-list/item-list-layout";
@@ -27,7 +27,7 @@ import isEqual from "lodash/isEqual";
import { observable, reaction, makeObservable } from "mobx";
import { Link } from "react-router-dom";
import kebabCase from "lodash/kebabCase";
import { getRelease, getReleaseValues, HelmRelease, IReleaseDetails } from "../../api/endpoints/helm-releases.api";
import { getRelease, getReleaseValues, HelmRelease, IReleaseDetails } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { HelmReleaseMenu } from "./release-menu";
import { Drawer, DrawerItem, DrawerTitle } from "../drawer";
import { Badge } from "../badge";
@@ -41,10 +41,10 @@ import { releaseStore } from "./release.store";
import { Notifications } from "../notifications";
import { createUpgradeChartTab } from "../dock/upgrade-chart.store";
import { ThemeStore } from "../../theme.store";
import { apiManager } from "../../api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { SubTitle } from "../layout/sub-title";
import { secretsStore } from "../+config-secrets/secrets.store";
import { Secret } from "../../api/endpoints";
import { Secret } from "../../../common/k8s-api/endpoints";
import { getDetailsUrl } from "../kube-detail-params";
import { Checkbox } from "../checkbox";
@@ -20,7 +20,7 @@
*/
import React from "react";
import type { HelmRelease } from "../../api/endpoints/helm-releases.api";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { boundMethod, cssNames } from "../../utils";
import { releaseStore } from "./release.store";
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
@@ -26,7 +26,7 @@ import { observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { getReleaseHistory, HelmRelease, IReleaseRevision } from "../../api/endpoints/helm-releases.api";
import { getReleaseHistory, HelmRelease, IReleaseRevision } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { releaseStore } from "./release.store";
import { Select, SelectOption } from "../select";
import { Notifications } from "../notifications";
@@ -22,9 +22,9 @@
import isEqual from "lodash/isEqual";
import { action, observable, reaction, when, makeObservable } from "mobx";
import { autoBind } from "../../utils";
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../api/endpoints/helm-releases.api";
import { ItemStore } from "../../item.store";
import type { Secret } from "../../api/endpoints";
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { ItemStore } from "../../../common/item.store";
import type { Secret } from "../../../common/k8s-api/endpoints";
import { secretsStore } from "../+config-secrets/secrets.store";
import { namespaceStore } from "../+namespaces/namespace.store";
import { Notifications } from "../notifications";
@@ -26,7 +26,7 @@ import kebabCase from "lodash/kebabCase";
import { disposeOnUnmount, observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { releaseStore } from "./release.store";
import type { HelmRelease } from "../../api/endpoints/helm-releases.api";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { ReleaseDetails } from "./release-details";
import { ReleaseRollbackDialog } from "./release-rollback-dialog";
import { navigation } from "../../navigation";
@@ -25,14 +25,14 @@ import React from "react";
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity";
import { ItemObject, ItemStore } from "../../item.store";
import { ItemObject, ItemStore } from "../../../common/item.store";
import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog";
import { autoBind } from "../../../common/utils";
import { Badge } from "../badge";
import { navigation } from "../../navigation";
import { searchUrlParam } from "../input";
import { makeCss } from "../../../common/utils/makeCss";
import { KubeObject } from "../../api/kube-object";
import { KubeObject } from "../../../common/k8s-api/kube-object";
const css = makeCss(styles);
@@ -30,12 +30,12 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
import { nodesStore } from "../+nodes/nodes.store";
import { eventStore } from "../+events/event.store";
import { boundMethod, cssNames, prevDefault } from "../../utils";
import type { ItemObject } from "../../item.store";
import type { ItemObject } from "../../../common/item.store";
import { Spinner } from "../spinner";
import { ThemeStore } from "../../theme.store";
import { apiManager } from "../../api/api-manager";
import { kubeSelectedUrlParam, showDetails } from "../kube-detail-params";
import { kubeWatchApi } from "../../api/kube-watch-api";
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
import { apiManager } from "../../../common/k8s-api/api-manager";
interface Props {
className?: string;
@@ -31,7 +31,7 @@ import { Spinner } from "../spinner";
import { ZebraStripes } from "../chart/zebra-stripes.plugin";
import { ClusterNoMetrics } from "./cluster-no-metrics";
import { ClusterMetricSwitchers } from "./cluster-metric-switchers";
import { getMetricLastPoints } from "../../api/endpoints/metrics.api";
import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api";
export const ClusterMetrics = observer(() => {
const { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics } = clusterOverviewStore;
@@ -20,12 +20,12 @@
*/
import { action, observable, reaction, when, makeObservable } from "mobx";
import { KubeObjectStore } from "../../kube-object.store";
import { Cluster, clusterApi, getMetricsByNodeNames, IClusterMetrics } from "../../api/endpoints";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { Cluster, clusterApi, getMetricsByNodeNames, IClusterMetrics } from "../../../common/k8s-api/endpoints";
import { autoBind, createStorage } from "../../utils";
import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api";
import { IMetricsReqParams, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { nodesStore } from "../+nodes/nodes.store";
import { apiManager } from "../../api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
export enum MetricType {
MEMORY = "memory",
@@ -31,7 +31,7 @@ import { ChartData, PieChart } from "../chart";
import { ClusterNoMetrics } from "./cluster-no-metrics";
import { bytesToUnits } from "../../utils";
import { ThemeStore } from "../../theme.store";
import { getMetricLastPoints } from "../../api/endpoints/metrics.api";
import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api";
function createLabels(rawLabelData: [string, number | undefined][]): string[] {
return rawLabelData.map(([key, value]) => `${key}: ${value?.toFixed(2) || "N/A"}`);
@@ -28,9 +28,9 @@ import { DrawerItem, DrawerTitle } from "../drawer";
import { Badge } from "../badge";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { cssNames } from "../../utils";
import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api";
import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../../common/k8s-api/endpoints/hpa.api";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { apiManager } from "../../api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { KubeObjectMeta } from "../kube-object-meta";
import { getDetailsUrl } from "../kube-detail-params";
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { HorizontalPodAutoscaler, hpaApi } from "../../api/endpoints/hpa.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { HorizontalPodAutoscaler, hpaApi } from "../../../common/k8s-api/endpoints/hpa.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class HPAStore extends KubeObjectStore<HorizontalPodAutoscaler> {
api = hpaApi;
@@ -25,7 +25,7 @@ import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import type { HorizontalPodAutoscaler } from "../../api/endpoints/hpa.api";
import type { HorizontalPodAutoscaler } from "../../../common/k8s-api/endpoints/hpa.api";
import { hpaStore } from "./hpa.store";
import { Badge } from "../badge";
import { cssNames } from "../../utils";
@@ -24,7 +24,7 @@ import "./limit-range-details.scss";
import React from "react";
import { observer } from "mobx-react";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../api/endpoints/limit-range.api";
import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../../common/k8s-api/endpoints/limit-range.api";
import { KubeObjectMeta } from "../kube-object-meta";
import { DrawerItem } from "../drawer/drawer-item";
import { Badge } from "../badge";
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { apiManager } from "../../api/api-manager";
import { LimitRange, limitRangeApi } from "../../api/endpoints/limit-range.api";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { LimitRange, limitRangeApi } from "../../../common/k8s-api/endpoints/limit-range.api";
export class LimitRangesStore extends KubeObjectStore<LimitRange> {
api = limitRangeApi;
@@ -30,7 +30,7 @@ import { Input } from "../input";
import { Button } from "../button";
import { configMapsStore } from "./config-maps.store";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { ConfigMap } from "../../api/endpoints";
import type { ConfigMap } from "../../../common/k8s-api/endpoints";
import { KubeObjectMeta } from "../kube-object-meta";
interface Props extends KubeObjectDetailsProps<ConfigMap> {
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { ConfigMap, configMapApi } from "../../api/endpoints/configmap.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { ConfigMap, configMapApi } from "../../../common/k8s-api/endpoints/configmap.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class ConfigMapsStore extends KubeObjectStore<ConfigMap> {
api = configMapApi;
@@ -26,7 +26,7 @@ import { observer } from "mobx-react";
import { DrawerItem } from "../drawer";
import { Badge } from "../badge";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { PodDisruptionBudget } from "../../api/endpoints";
import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints";
import { KubeObjectMeta } from "../kube-object-meta";
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { pdbApi, PodDisruptionBudget } from "../../api/endpoints/poddisruptionbudget.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { pdbApi, PodDisruptionBudget } from "../../../common/k8s-api/endpoints/poddisruptionbudget.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class PodDisruptionBudgetsStore extends KubeObjectStore<PodDisruptionBudget> {
api = pdbApi;
@@ -24,7 +24,7 @@ import "./pod-disruption-budgets.scss";
import * as React from "react";
import { observer } from "mobx-react";
import { podDisruptionBudgetsStore } from "./pod-disruption-budgets.store";
import type { PodDisruptionBudget } from "../../api/endpoints/poddisruptionbudget.api";
import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/poddisruptionbudget.api";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { KubeObjectListLayout } from "../kube-object-list-layout";
@@ -28,7 +28,7 @@ import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { Input } from "../input";
import { systemName } from "../input/input_validators";
import { IResourceQuotaValues, resourceQuotaApi } from "../../api/endpoints/resource-quota.api";
import { IResourceQuotaValues, resourceQuotaApi } from "../../../common/k8s-api/endpoints/resource-quota.api";
import { Select } from "../select";
import { Icon } from "../icon";
import { Button } from "../button";
@@ -26,7 +26,7 @@ import { observer } from "mobx-react";
import { DrawerItem, DrawerTitle } from "../drawer";
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { ResourceQuota } from "../../api/endpoints/resource-quota.api";
import type { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api";
import { LineProgress } from "../line-progress";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { KubeObjectMeta } from "../kube-object-meta";
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { ResourceQuota, resourceQuotaApi } from "../../../common/k8s-api/endpoints/resource-quota.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class ResourceQuotasStore extends KubeObjectStore<ResourceQuota> {
api = resourceQuotaApi;
@@ -28,12 +28,12 @@ import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { Input } from "../input";
import { systemName } from "../input/input_validators";
import { Secret, secretsApi, SecretType } from "../../api/endpoints";
import { Secret, secretsApi, SecretType } from "../../../common/k8s-api/endpoints";
import { SubTitle } from "../layout/sub-title";
import { NamespaceSelect } from "../+namespaces/namespace-select";
import { Select, SelectOption } from "../select";
import { Icon } from "../icon";
import type { KubeObjectMetadata } from "../../api/kube-object";
import type { KubeObjectMetadata } from "../../../common/k8s-api/kube-object";
import { base64 } from "../../utils";
import { Notifications } from "../notifications";
import upperFirst from "lodash/upperFirst";
@@ -33,7 +33,7 @@ import { base64 } from "../../utils";
import { Icon } from "../icon";
import { secretsStore } from "./secrets.store";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { Secret } from "../../api/endpoints";
import type { Secret } from "../../../common/k8s-api/endpoints";
import { KubeObjectMeta } from "../kube-object-meta";
interface Props extends KubeObjectDetailsProps<Secret> {
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectStore } from "../../kube-object.store";
import { Secret, secretsApi } from "../../api/endpoints";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { Secret, secretsApi } from "../../../common/k8s-api/endpoints";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class SecretsStore extends KubeObjectStore<Secret> {
api = secretsApi;
@@ -24,7 +24,7 @@ import "./crd-details.scss";
import React from "react";
import { Link } from "react-router-dom";
import { observer } from "mobx-react";
import type { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
import { AceEditor } from "../ace-editor";
import { Badge } from "../badge";
import { DrawerItem, DrawerTitle } from "../drawer";
@@ -28,7 +28,7 @@ import { Link } from "react-router-dom";
import { stopPropagation } from "../../utils";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { crdStore } from "./crd.store";
import type { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
import { Select, SelectOption } from "../select";
import { createPageParam } from "../../navigation";
import { Icon } from "../icon";
@@ -30,9 +30,9 @@ import { DrawerItem } from "../drawer";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { KubeObjectMeta } from "../kube-object-meta";
import { Input } from "../input";
import type { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../api/endpoints/crd.api";
import type { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
import { parseJsonPath } from "../../utils/jsonPath";
import type { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../api/kube-object";
import type { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
interface Props extends KubeObjectDetailsProps<KubeObject> {
crd: CustomResourceDefinition;
@@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { KubeApi } from "../../api/kube-api";
import { KubeObjectStore } from "../../kube-object.store";
import type { KubeObject } from "../../api/kube-object";
import type { KubeApi } from "../../../common/k8s-api/kube-api";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
export class CRDResourceStore<K extends KubeObject> extends KubeObjectStore<K> {
api: KubeApi<K>;
@@ -26,11 +26,11 @@ import jsonPath from "jsonpath";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import type { KubeObject } from "../../api/kube-object";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { computed, makeObservable } from "mobx";
import { crdStore } from "./crd.store";
import type { TableSortCallbacks } from "../table";
import { apiManager } from "../../api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { parseJsonPath } from "../../utils/jsonPath";
import type { CRDRouteParams } from "../../../common/routes";
@@ -20,13 +20,13 @@
*/
import { computed, reaction, makeObservable } from "mobx";
import { KubeObjectStore } from "../../kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { autoBind } from "../../utils";
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { apiManager } from "../../api/api-manager";
import { KubeApi } from "../../api/kube-api";
import { crdApi, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { KubeApi } from "../../../common/k8s-api/kube-api";
import { CRDResourceStore } from "./crd-resource.store";
import { KubeObject } from "../../api/kube-object";
import { KubeObject } from "../../../common/k8s-api/kube-object";
function initStore(crd: CustomResourceDefinition) {
const apiBase = crd.getResourceApiBase();
@@ -27,12 +27,12 @@ import { DrawerItem, DrawerTitle } from "../drawer";
import { Link } from "react-router-dom";
import { observer } from "mobx-react";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { KubeEvent } from "../../api/endpoints/events.api";
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
import { KubeObjectMeta } from "../kube-object-meta";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { apiManager } from "../../api/api-manager";
import { LocaleDate } from "../locale-date";
import { getDetailsUrl } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
interface Props extends KubeObjectDetailsProps<KubeEvent> {
}
@@ -21,13 +21,13 @@
import groupBy from "lodash/groupBy";
import compact from "lodash/compact";
import { KubeObjectStore } from "../../kube-object.store";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { autoBind } from "../../utils";
import { eventApi, KubeEvent } from "../../api/endpoints/events.api";
import type { KubeObject } from "../../api/kube-object";
import { Pod } from "../../api/endpoints/pods.api";
import { eventApi, KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { Pod } from "../../../common/k8s-api/endpoints/pods.api";
import { podsStore } from "../+workloads-pods/pods.store";
import { apiManager } from "../../api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class EventStore extends KubeObjectStore<KubeEvent> {
api = eventApi;
+2 -2
View File
@@ -28,16 +28,16 @@ import { orderBy } from "lodash";
import { TabLayout } from "../layout/tab-layout";
import { EventStore, eventStore } from "./event.store";
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object-list-layout";
import type { KubeEvent } from "../../api/endpoints/events.api";
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
import type { TableSortCallbacks, TableSortParams } from "../table";
import type { HeaderCustomizer } from "../item-object-list";
import { Tooltip } from "../tooltip";
import { Link } from "react-router-dom";
import { cssNames, IClassName, stopPropagation } from "../../utils";
import { Icon } from "../icon";
import { apiManager } from "../../api/api-manager";
import { eventsURL } from "../../../common/routes";
import { getDetailsUrl } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
enum columnId {
message = "message",
@@ -23,7 +23,7 @@ import "./kube-event-details.scss";
import React from "react";
import { observer } from "mobx-react";
import type { KubeObject } from "../../api/kube-object";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { DrawerItem, DrawerTitle } from "../drawer";
import { cssNames } from "../../utils";
import { eventStore } from "./event.store";
@@ -23,10 +23,10 @@ import "./kube-event-icon.scss";
import React from "react";
import { Icon } from "../icon";
import type { KubeObject } from "../../api/kube-object";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { eventStore } from "./event.store";
import { cssNames } from "../../utils";
import type { KubeEvent } from "../../api/endpoints/events.api";
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
interface Props {
object: KubeObject;
@@ -27,7 +27,7 @@ import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { namespaceStore } from "./namespace.store";
import type { Namespace } from "../../api/endpoints";
import type { Namespace } from "../../../common/k8s-api/endpoints";
import { Input } from "../input";
import { systemName } from "../input/input_validators";
import { Notifications } from "../notifications";
@@ -26,7 +26,7 @@ import { computed, makeObservable, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { DrawerItem } from "../drawer";
import { boundMethod, cssNames } from "../../utils";
import { getMetricsForNamespace, IPodMetrics, Namespace } from "../../api/endpoints";
import { getMetricsForNamespace, IPodMetrics, Namespace } from "../../../common/k8s-api/endpoints";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { Link } from "react-router-dom";
import { Spinner } from "../spinner";
@@ -28,7 +28,7 @@ import { Select, SelectOption, SelectProps } from "../select";
import { cssNames } from "../../utils";
import { Icon } from "../icon";
import { namespaceStore } from "./namespace.store";
import { kubeWatchApi } from "../../api/kube-watch-api";
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
interface Props extends SelectProps {
showIcons?: boolean;
@@ -21,9 +21,9 @@
import { action, comparer, computed, IReactionDisposer, IReactionOptions, makeObservable, reaction, } from "mobx";
import { autoBind, createStorage, noop } from "../../utils";
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store";
import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store";
import { Namespace, namespacesApi } from "../../../common/k8s-api/endpoints/namespaces.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
export class NamespaceStore extends KubeObjectStore<Namespace> {
api = namespacesApi;
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff. Show more