1
0
mirror of https://github.com/lensapp/lens.git synced 2024-09-17 12:17:08 +03:00

chore: Extract @k8slens/kube-object package

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-04-27 10:32:41 -04:00
parent 576373b16b
commit 1bf24db797
466 changed files with 6840 additions and 6377 deletions

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import replicationControllersRouteInjectable from "./replicationcontrollers-route.injectable";
import replicationControllersRouteInjectable from "./route.injectable";
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
const navigateToReplicationControllersInjectable = getInjectable({

View File

@ -7,10 +7,10 @@ import { shouldShowResourceInjectionToken } from "../../../../../../features/clu
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
const replicationControllersRouteInjectable = getInjectable({
id: "replicationcontrollers-route",
id: "replication-controllers-route",
instantiate: (di) => ({
path: "/replicationcontrollers",
path: "/replication-controllers",
clusterFrame: true,
isEnabled: di.inject(shouldShowResourceInjectionToken, {
apiName: "replicationcontrollers",

View File

@ -15,7 +15,7 @@ import loggerInjectable from "../../logger.injectable";
import type { ApiManager } from "../api-manager";
import apiManagerInjectable from "../api-manager/manager.injectable";
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectStore } from "../kube-object.store";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
@ -27,8 +27,8 @@ import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-ap
import assert from "assert";
class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() {
return;
protected checkPreferredVersion() {
return Promise.resolve();
}
}

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { CustomResourceDefinitionSpec } from "../endpoints";
import { CustomResourceDefinition } from "../endpoints";
import type { CustomResourceDefinitionSpec } from "@k8slens/kube-object";
import { CustomResourceDefinition } from "@k8slens/kube-object";
describe("Crds", () => {
describe("getVersion()", () => {

View File

@ -33,7 +33,7 @@ describe("DeploymentApi", () => {
describe("scale", () => {
it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => {
deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5);
void deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5);
expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/deployments/deployment-1/scale", {
data: {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { formatEndpointSubset } from "../endpoints";
import { formatEndpointSubset } from "@k8slens/kube-object";
describe("endpoint tests", () => {
describe("EndpointSubset", () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { computeRuleDeclarations, Ingress } from "../endpoints";
import { computeRuleDeclarations, Ingress } from "@k8slens/kube-object";
describe("Ingress", () => {
it("given no loadbalancer ingresses in status property, loadbalancers should be an empty array", () => {

View File

@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
jest.mock("../kube-object");
jest.mock("../kube-api");
jest.mock("../api-manager", () => ({
apiManager() {

View File

@ -4,7 +4,8 @@
*/
import type { ApiManager } from "../api-manager";
import type { IngressApi } from "../endpoints";
import { Ingress, HorizontalPodAutoscalerApi } from "../endpoints";
import { HorizontalPodAutoscalerApi } from "../endpoints";
import { Ingress } from "@k8slens/kube-object";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable";

View File

@ -4,38 +4,27 @@
*/
import type { KubeApiWatchCallback } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import { PassThrough } from "stream";
import type { DeploymentApi, NamespaceApi } from "../endpoints";
import { Deployment, Pod, PodApi } from "../endpoints";
import { PodApi } from "../endpoints";
import type { DeploymentApi, NamespaceApi } from "../endpoints";
import { Deployment, Pod } from "@k8slens/kube-object";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable";
import type {
CreateKubeApiForRemoteCluster,
} from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable
from "../create-kube-api-for-remote-cluster.injectable";
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "@k8slens/test-utils";
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
import type { IKubeWatchEvent } from "../kube-watch-event";
import type { KubeJsonApiDataFor, KubeStatusData } from "../kube-object";
import setupAutoRegistrationInjectable
from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
import {
createMockResponseFromStream,
createMockResponseFromString,
} from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable
from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable
from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import hostedClusterInjectable
from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable
from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import type { KubeJsonApiDataFor, KubeStatusData, KubeJsonApiData } from "@k8slens/kube-object";
import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import deploymentApiInjectable from "../endpoints/deployment.api.injectable";
@ -51,7 +40,7 @@ describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
let fetchMock: AsyncFnMock<Fetch>;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
@ -70,7 +59,7 @@ describe("createKubeApiForRemoteCluster", () => {
createKubeApiForRemoteCluster = di.inject(createKubeApiForRemoteClusterInjectable);
});
it("builds api client for KubeObject", async () => {
it("builds api client for KubeObject", () => {
const api = createKubeApiForRemoteCluster({
cluster: {
server: "https://127.0.0.1:6443",
@ -150,7 +139,7 @@ describe("KubeApi", () => {
let fetchMock: AsyncFnMock<Fetch>;
let di: DiContainer;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
@ -374,7 +363,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
@ -410,7 +399,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
@ -446,7 +435,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background", "{}"),
);
@ -461,7 +450,7 @@ describe("KubeApi", () => {
describe("eviction-api as better replacement for pod.delete() request", () => {
let evictRequest: Promise<string>;
beforeEach(async () => {
beforeEach(() => {
evictRequest = api.evict({ name: "foo", namespace: "test" });
});
@ -478,7 +467,7 @@ describe("KubeApi", () => {
});
it("should resolve the call with >=200 <300 http code", async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({
apiVersion: "policy/v1",
@ -492,7 +481,7 @@ describe("KubeApi", () => {
});
it("should throw in case of error", async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({
apiVersion: "policy/v1",
@ -502,12 +491,12 @@ describe("KubeApi", () => {
} as KubeStatusData)),
);
expect(async () => evictRequest).rejects.toBe("500: something went wrong");
await expect(async () => evictRequest).rejects.toBe("500: something went wrong");
});
});
});
describe("deleting namespaces (cluser scoped resource)", () => {
describe("deleting namespaces (cluster scoped resource)", () => {
let api: NamespaceApi;
beforeEach(() => {
@ -538,7 +527,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
@ -574,7 +563,7 @@ describe("KubeApi", () => {
describe("when request resolves", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
@ -587,8 +576,8 @@ describe("KubeApi", () => {
});
describe("when deleting by name and namespace", () => {
it("rejects request", () => {
expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined();
it("rejects request", async () => {
await expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined();
});
});
});

View File

@ -5,7 +5,7 @@
import { noop } from "@k8slens/utilities";
import type { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
import type { KubeObjectStoreLoadingParams } from "../kube-object.store";
import { KubeObjectStore } from "../kube-object.store";
@ -30,7 +30,7 @@ class FakeKubeObjectStore extends KubeObjectStore<KubeObject> {
}
async loadItems(params: KubeObjectStoreLoadingParams) {
return this._loadItems(params);
return Promise.resolve(this._loadItems(params));
}
}

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { KubeObject } from "@k8slens/kube-object";
describe("KubeObject", () => {
describe("isJsonApiData", () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Pod } from "../endpoints";
import { Pod } from "@k8slens/kube-object";
describe("Pod tests", () => {
it("getAllContainers() should never throw", () => {

View File

@ -8,7 +8,7 @@ import type { KubeObjectStore } from "../kube-object.store";
import type { IComputedValue } from "mobx";
import { autorun, action, observable } from "mobx";
import type { KubeApi } from "../kube-api";
import type { KubeObject, ObjectReference } from "../kube-object";
import type { KubeObject, ObjectReference } from "@k8slens/kube-object";
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
import { getOrInsertWith, iter } from "@k8slens/utilities";
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
import loggerInjectable from "../../logger.injectable";
import type { KubeApi } from "../kube-api";
import type { KubeObject } from "../kube-object";
import type { KubeObject } from "@k8slens/kube-object";
import type { KubeObjectStoreDependencies } from "../kube-object.store";
import { CustomResourceStore } from "./resource.store";

View File

@ -6,7 +6,7 @@
import type { KubeApi } from "../kube-api";
import type { KubeObjectStoreDependencies } from "../kube-object.store";
import { KubeObjectStore } from "../kube-object.store";
import type { KubeObject } from "../kube-object";
import type { KubeObject } from "@k8slens/kube-object";
export class CustomResourceStore<K extends KubeObject> extends KubeObjectStore<K, KubeApi<K>> {
constructor(deps: KubeObjectStoreDependencies, api: KubeApi<K>) {

View File

@ -10,7 +10,7 @@ import apiBaseInjectable from "./api-base.injectable";
import type { KubeApiConstructor } from "./create-kube-api-for-remote-cluster.injectable";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object";
export interface CreateKubeApiForLocalClusterConfig {
metadata: {

View File

@ -11,7 +11,7 @@ import isDevelopmentInjectable from "../vars/is-development.injectable";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import type { KubeApiOptions } from "./kube-api";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object";
export interface CreateKubeApiForRemoteClusterConfig {
cluster: {

View File

@ -4,47 +4,8 @@
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleRef } from "./types/role-ref";
import type { Subject } from "./types/subject";
export interface ClusterRoleBindingData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
subjects?: Subject[];
roleRef: RoleRef;
}
export class ClusterRoleBinding extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static kind = "ClusterRoleBinding";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings";
subjects?: Subject[];
roleRef: RoleRef;
constructor({
subjects,
roleRef,
...rest
}: ClusterRoleBindingData) {
super(rest);
this.subjects = subjects;
this.roleRef = roleRef;
}
getSubjects() {
return this.subjects ?? [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
import type { ClusterRoleBindingData } from "@k8slens/kube-object";
import { ClusterRoleBinding } from "@k8slens/kube-object";
export class ClusterRoleBindingApi extends KubeApi<ClusterRoleBinding, ClusterRoleBindingData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -5,39 +5,8 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { AggregationRule } from "./types/aggregation-rule";
import type { PolicyRule } from "./types/policy-rule";
export interface ClusterRoleData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
}
export class ClusterRole extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static kind = "ClusterRole";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles";
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
constructor({ rules, aggregationRule, ...rest }: ClusterRoleData) {
super(rest);
this.rules = rules;
this.aggregationRule = aggregationRule;
}
getRules() {
return this.rules || [];
}
}
import type { ClusterRoleData } from "@k8slens/kube-object";
import { ClusterRole } from "@k8slens/kube-object";
export class ClusterRoleApi extends KubeApi<ClusterRole, ClusterRoleData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { Cluster } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
@ -25,58 +25,3 @@ export class ClusterApi extends KubeApi<Cluster> {
});
}
}
export enum ClusterStatus {
ACTIVE = "Active",
CREATING = "Creating",
REMOVING = "Removing",
ERROR = "Error",
}
export interface Cluster {
spec: {
clusterNetwork?: {
serviceDomain?: string;
pods?: {
cidrBlocks?: string[];
};
services?: {
cidrBlocks?: string[];
};
};
providerSpec: {
value: {
profile: string;
};
};
};
status?: {
apiEndpoints: {
host: string;
port: string;
}[];
providerStatus: {
adminUser?: string;
adminPassword?: string;
kubeconfig?: string;
processState?: string;
lensAddress?: string;
};
errorMessage?: string;
errorReason?: string;
};
}
export class Cluster extends KubeObject {
static kind = "Cluster";
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
static namespaced = true;
getStatus() {
if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING;
if (!this.status || !this.status) return ClusterStatus.CREATING;
if (this.status.errorMessage) return ClusterStatus.ERROR;
return ClusterStatus.ACTIVE;
}
}

View File

@ -3,30 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { ComponentStatus } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface ComponentStatusCondition {
type: string;
status: string;
message: string;
}
export interface ComponentStatus {
conditions: ComponentStatusCondition[];
}
export class ComponentStatus extends KubeObject {
static kind = "ComponentStatus";
static namespaced = false;
static apiBase = "/api/v1/componentstatuses";
getTruthyConditions() {
return this.conditions.filter(c => c.status === "True");
}
}
export class ComponentStatusApi extends KubeApi<ComponentStatus> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {

View File

@ -3,45 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ConfigMapData } from "@k8slens/kube-object";
import { ConfigMap } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import autoBind from "auto-bind";
export interface ConfigMapData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
data?: Partial<Record<string, string>>;
binaryData?: Partial<Record<string, string>>;
immutable?: boolean;
}
export class ConfigMap extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "ConfigMap";
static namespaced = true;
static apiBase = "/api/v1/configmaps";
data: Partial<Record<string, string>>;
binaryData: Partial<Record<string, string>>;
immutable?: boolean;
constructor({ data, binaryData, immutable, ...rest }: ConfigMapData) {
super(rest);
autoBind(this);
this.data = data ?? {};
this.binaryData = binaryData ?? {};
this.immutable = immutable;
}
getKeys(): string[] {
return Object.keys(this.data);
}
}
export class ConfigMapApi extends KubeApi<ConfigMap, ConfigMapData> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -3,13 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import type { NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { formatDuration } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { CronJob } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { JobTemplateSpec } from "./types/job-template-spec";
export class CronJobApi extends KubeApi<CronJob> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions) {
@ -19,90 +15,19 @@ export class CronJobApi extends KubeApi<CronJob> {
});
}
suspend(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
suspend: true,
},
private requestSetSuspend(params: NamespacedResourceDescriptor, suspend: boolean) {
return this.patch(params, {
spec: {
suspend,
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}, "strategic");
}
resume(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
suspend: false,
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
export interface CronJobSpec {
concurrencyPolicy?: string;
failedJobsHistoryLimit?: number;
jobTemplate?: JobTemplateSpec;
schedule: string;
startingDeadlineSeconds?: number;
successfulJobsHistoryLimit?: number;
suspend?: boolean;
}
export interface CronJobStatus {
lastScheduleTime?: string;
lastSuccessfulTime?: string;
active?: ObjectReference[];
}
export class CronJob extends KubeObject<
NamespaceScopedMetadata,
CronJobStatus,
CronJobSpec
> {
static readonly kind = "CronJob";
static readonly namespaced = true;
static readonly apiBase = "/apis/batch/v1/cronjobs";
getSuspendFlag() {
return (this.spec.suspend ?? false).toString();
}
getLastScheduleTime() {
if (!this.status?.lastScheduleTime) return "-";
const diff = moment().diff(this.status.lastScheduleTime);
return formatDuration(diff, true);
}
getSchedule() {
return this.spec.schedule;
}
isNeverRun() {
const schedule = this.getSchedule();
const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const stamps = schedule.split(" ");
const day = Number(stamps[stamps.length - 3]); // 1-31
const month = Number(stamps[stamps.length - 2]); // 1-12
if (schedule.startsWith("@")) return false;
return day > daysInMonth[month - 1];
}
isSuspend() {
return this.spec.suspend;
suspend(params: NamespacedResourceDescriptor) {
return this.requestSetSuspend(params, true);
}
resume(params: NamespacedResourceDescriptor) {
return this.requestSetSuspend(params, false);
}
}

View File

@ -3,235 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di";
import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
import { buildURL } from "@k8slens/utilities";
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { CustomResourceDefinition } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { JSONSchemaProps } from "./types/json-schema-props";
interface AdditionalPrinterColumnsCommon {
name: string;
type: "integer" | "number" | "string" | "boolean" | "date";
priority?: number;
format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password";
description?: string;
}
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
jsonPath: string;
};
type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
JSONPath: string;
};
export interface CustomResourceValidation {
openAPIV3Schema?: JSONSchemaProps;
}
export interface CustomResourceDefinitionVersion {
name: string;
served: boolean;
storage: boolean;
schema?: CustomResourceValidation; // required in v1 but not present in v1beta
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
}
export interface CustomResourceDefinitionNames {
categories?: string[];
kind: string;
listKind?: string;
plural: string;
shortNames?: string[];
singular?: string;
}
export interface CustomResourceConversion {
strategy?: string;
webhook?: WebhookConversion;
}
export interface WebhookConversion {
clientConfig?: WebhookClientConfig[];
conversionReviewVersions: string[];
}
export interface WebhookClientConfig {
caBundle?: string;
url?: string;
service?: ServiceReference;
}
export interface ServiceReference {
name: string;
namespace: string;
path?: string;
port?: number;
}
export interface CustomResourceDefinitionSpec {
group: string;
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
version?: string;
names: CustomResourceDefinitionNames;
scope: "Namespaced" | "Cluster";
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
validation?: object;
versions?: CustomResourceDefinitionVersion[];
conversion?: CustomResourceConversion;
/**
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
*/
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[];
preserveUnknownFields?: boolean;
}
export interface CustomResourceDefinitionConditionAcceptedNames {
plural: string;
singular: string;
kind: string;
shortNames: string[];
listKind: string;
}
export interface CustomResourceDefinitionStatus {
conditions?: BaseKubeObjectCondition[];
acceptedNames: CustomResourceDefinitionConditionAcceptedNames;
storedVersions: string[];
}
export class CustomResourceDefinition extends KubeObject<
ClusterScopedMetadata,
CustomResourceDefinitionStatus,
CustomResourceDefinitionSpec
> {
static kind = "CustomResourceDefinition";
static namespaced = false;
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
getResourceUrl() {
const di = getLegacyGlobalDiForExtensionApi();
const customResourcesRoute = di.inject(customResourcesRouteInjectable);
return buildURL(customResourcesRoute.path, {
params: {
group: this.getGroup(),
name: this.getPluralName(),
},
});
}
getResourceApiBase() {
const { group } = this.spec;
return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}`;
}
getPluralName() {
return this.getNames().plural;
}
getResourceKind() {
return this.spec.names.kind;
}
getResourceTitle() {
const name = this.getPluralName();
return name[0].toUpperCase() + name.slice(1);
}
getGroup() {
return this.spec.group;
}
getScope() {
return this.spec.scope;
}
getPreferedVersion(): CustomResourceDefinitionVersion {
const { apiVersion } = this;
switch (apiVersion) {
case "apiextensions.k8s.io/v1":
for (const version of this.spec.versions ?? []) {
if (version.storage) {
return version;
}
}
break;
case "apiextensions.k8s.io/v1beta1": {
const { additionalPrinterColumns: apc } = this.spec;
const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath }));
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
name: this.spec.version!,
served: true,
storage: true,
schema: this.spec.validation,
additionalPrinterColumns,
};
}
}
throw new Error(`Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`);
}
getVersion() {
return this.getPreferedVersion().name;
}
isNamespaced() {
return this.getScope() === "Namespaced";
}
getStoredVersions() {
return this.status?.storedVersions.join(", ") ?? "";
}
getNames() {
return this.spec.names;
}
getConversion() {
return JSON.stringify(this.spec.conversion);
}
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
const columns = this.getPreferedVersion().additionalPrinterColumns ?? [];
return columns
.filter(column => column.name.toLowerCase() != "age" && (ignorePriority || !column.priority));
}
getValidation() {
return JSON.stringify(this.getPreferedVersion().schema, null, 2);
}
getConditions() {
if (!this.status?.conditions) return [];
return this.status.conditions.map(condition => {
const { message, reason, lastTransitionTime, status } = condition;
return {
...condition,
isReady: status === "True",
tooltip: `${message || reason} (${lastTransitionTime})`,
};
});
}
}
export class CustomResourceDefinitionApi extends KubeApi<CustomResourceDefinition> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -13,7 +13,7 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
const daemonSetApiInjectable = getInjectable({
id: "daemon-set-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environements");
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environments");
return new DaemonSetApi({
logger: di.inject(loggerInjectable),

View File

@ -4,83 +4,9 @@
*/
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
export interface RollingUpdateDaemonSet {
maxUnavailable?: number | string;
maxSurge?: number | string;
}
export interface DaemonSetUpdateStrategy {
type: string;
rollingUpdate: RollingUpdateDaemonSet;
}
export interface DaemonSetSpec {
selector: LabelSelector;
template: PodTemplateSpec;
updateStrategy: DaemonSetUpdateStrategy;
minReadySeconds?: number;
revisionHistoryLimit?: number;
}
export interface DaemonSetStatus extends KubeObjectStatus {
collisionCount?: number;
currentNumberScheduled: number;
desiredNumberScheduled: number;
numberAvailable?: number;
numberMisscheduled: number;
numberReady: number;
numberUnavailable?: number;
observedGeneration?: number;
updatedNumberScheduled?: number;
}
export class DaemonSet extends KubeObject<
NamespaceScopedMetadata,
DaemonSetStatus,
DaemonSetSpec
> {
static kind = "DaemonSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/daemonsets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata?.labels);
}
getTolerations() {
return this.spec.template.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec?.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
const initContainers = this.spec.template?.spec?.initContainers ?? [];
return [...containers, ...initContainers].map(container => container.image);
}
}
import { DaemonSet } from "@k8slens/kube-object";
export class DaemonSetApi extends KubeApi<DaemonSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -90,22 +16,15 @@ export class DaemonSetApi extends KubeApi<DaemonSet> {
});
}
restart(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}, "strategic");
}
}

View File

@ -5,11 +5,9 @@
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { PodSpec } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { Deployment } from "@k8slens/kube-object";
import { hasTypedProperty, isNumber, isObject } from "@k8slens/utilities";
export class DeploymentApi extends KubeApi<Deployment> {
@ -20,11 +18,11 @@ export class DeploymentApi extends KubeApi<Deployment> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
if (isObject(status) && hasTypedProperty(status, "replicas", isNumber)) {
@ -34,7 +32,7 @@ export class DeploymentApi extends KubeApi<Deployment> {
return 0;
}
scale(params: { namespace: string; name: string }, replicas: number) {
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
spec: {
@ -49,105 +47,15 @@ export class DeploymentApi extends KubeApi<Deployment> {
});
}
restart(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
export interface DeploymentSpec {
replicas: number;
selector: LabelSelector;
template: {
metadata: {
creationTimestamp?: string;
labels: Partial<Record<string, string>>;
annotations?: Partial<Record<string, string>>;
};
spec: PodSpec;
};
strategy: {
type: string;
rollingUpdate: {
maxUnavailable: number;
maxSurge: number;
};
};
}
export interface DeploymentStatus extends KubeObjectStatus {
observedGeneration: number;
replicas: number;
updatedReplicas: number;
readyReplicas: number;
availableReplicas?: number;
unavailableReplicas?: number;
}
export class Deployment extends KubeObject<
NamespaceScopedMetadata,
DeploymentStatus,
DeploymentSpec
> {
static kind = "Deployment";
static namespaced = true;
static apiBase = "/apis/apps/v1/deployments";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata.labels);
}
getTolerations() {
return this.spec.template.spec.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getConditions(activeOnly = false) {
const { conditions = [] } = this.status ?? {};
if (activeOnly) {
return conditions.filter(c => c.status === "True");
}
return conditions;
}
getConditionsText(activeOnly = true) {
return this.getConditions(activeOnly)
.map(({ type }) => type)
.join(" ");
}
getReplicas() {
return this.spec.replicas || 0;
}, "strategic");
}
}

View File

@ -3,113 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import autoBind from "auto-bind";
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { EndpointsData } from "@k8slens/kube-object";
import { Endpoints } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export function formatEndpointSubset(subset: EndpointSubset): string {
const { addresses, ports } = subset;
if (!addresses || !ports) {
return "";
}
return addresses
.map(address => (
ports
.map(port => `${address.ip}:${port.port}`)
.join(", ")
))
.join(", ");
}
export interface ForZone {
name: string;
}
export interface EndpointHints {
forZones?: ForZone[];
}
export interface EndpointConditions {
ready?: boolean;
serving?: boolean;
terminating?: boolean;
}
export interface EndpointData {
addresses: string[];
conditions?: EndpointConditions;
hints?: EndpointHints;
hostname?: string;
nodeName?: string;
targetRef?: ObjectReference;
zone?: string;
}
export interface EndpointPort {
appProtocol?: string;
name?: string;
protocol?: string;
port: number;
}
export interface EndpointAddress {
hostname?: string;
ip: string;
nodeName?: string;
targetRef?: ObjectReference;
}
export interface EndpointSubset {
addresses?: EndpointAddress[];
notReadyAddresses?: EndpointAddress[];
ports?: EndpointPort[];
}
export interface EndpointsData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
subsets?: EndpointSubset[];
}
export class Endpoints extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "Endpoints";
static namespaced = true;
static apiBase = "/api/v1/endpoints";
subsets?: EndpointSubset[];
constructor({ subsets, ...rest }: EndpointsData) {
super(rest);
autoBind(this);
this.subsets = subsets;
}
getEndpointSubsets(): Required<EndpointSubset>[] {
return this.subsets?.map(({
addresses = [],
notReadyAddresses = [],
ports = [],
}) => ({
addresses,
notReadyAddresses,
ports,
})) ?? [];
}
toString(): string {
return this.getEndpointSubsets()
.map(formatEndpointSubset)
.join(", ") || "<none>";
}
}
export class EndpointsApi extends KubeApi<Endpoints, EndpointsData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,133 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import type { KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { formatDuration } from "@k8slens/utilities";
import type { KubeEventData } from "@k8slens/kube-object";
import { KubeEvent } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface EventSeries {
count?: number;
lastObservedTime?: string;
}
export interface EventSource {
component?: string;
host?: string;
}
export interface KubeEventData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
action?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: Required<ObjectReference>;
lastTimestamp?: string;
message?: string;
reason?: string;
related?: ObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: EventSeries;
source?: EventSource;
type?: string;
}
export class KubeEvent extends KubeObject<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
static kind = "Event";
static namespaced = true;
static apiBase = "/api/v1/events";
action?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: Required<ObjectReference>;
lastTimestamp?: string;
message?: string;
reason?: string;
related?: ObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: EventSeries;
source?: EventSource;
/**
* Current supported values are:
* - "Normal"
* - "Warning"
*/
type?: string;
constructor({
action,
count,
eventTime,
firstTimestamp,
involvedObject,
lastTimestamp,
message,
reason,
related,
reportingComponent,
reportingInstance,
series,
source,
type,
...rest
}: KubeEventData) {
super(rest);
this.action = action;
this.count = count;
this.eventTime = eventTime;
this.firstTimestamp = firstTimestamp;
this.involvedObject = involvedObject;
this.lastTimestamp = lastTimestamp;
this.message = message;
this.reason = reason;
this.related = related;
this.reportingComponent = reportingComponent;
this.reportingInstance = reportingInstance;
this.series = series;
this.source = source;
this.type = type;
}
isWarning() {
return this.type === "Warning";
}
getSource() {
if (!this.source?.component) {
return "<unknown>";
}
const { component, host = "" } = this.source;
return `${component} ${host}`;
}
/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getFirstSeenTime() {
const diff = moment().diff(this.firstTimestamp);
return formatDuration(diff, true);
}
/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getLastSeenTime() {
const diff = moment().diff(this.lastTimestamp);
return formatDuration(diff, true);
}
}
export class KubeEventApi extends KubeApi<KubeEvent, KubeEventData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable";
@ -26,7 +26,7 @@ export interface HelmReleaseDetails {
export type CallForHelmReleaseDetails = (name: string, namespace: string) => Promise<HelmReleaseDetails>;
const requestDetailsEnpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestDetailsEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestHelmReleaseDetailsInjectable = getInjectable({
id: "call-for-helm-release-details",
@ -34,7 +34,7 @@ const requestHelmReleaseDetailsInjectable = getInjectable({
instantiate: (di): CallForHelmReleaseDetails => {
const apiBase = di.inject(apiBaseInjectable);
return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace }));
return (name, namespace) => apiBase.get(requestDetailsEndpoint.compile({ name, namespace }));
},
});

View File

@ -3,365 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OptionVariant } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { CrossVersionObjectReference } from "./types/cross-version-object-reference";
export enum HpaMetricType {
Resource = "Resource",
Pods = "Pods",
Object = "Object",
External = "External",
ContainerResource = "ContainerResource",
}
export interface MetricCurrentTarget {
current?: string;
target?: string;
}
export interface HorizontalPodAutoscalerMetricTarget {
kind: string;
name: string;
apiVersion: string;
}
export interface V2ContainerResourceMetricSource {
container: string;
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ContainerResourceMetricSource {
container: string;
name: string;
targetAverageUtilization?: number;
targetAverageValue?: string;
}
export type ContainerResourceMetricSource =
| V2ContainerResourceMetricSource
| V2Beta1ContainerResourceMetricSource;
export interface V2ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type: string;
value?: string;
averageValue?: string;
};
}
export interface V2Beta1ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector;
targetAverageValue?: string;
targetValue?: string;
metric?: {
selector?: LabelSelector;
};
}
export type ExternalMetricSource =
| V2Beta1ExternalMetricSource
| V2ExternalMetricSource;
export interface V2ObjectMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricSource {
averageValue?: string;
metricName?: string;
selector?: LabelSelector;
targetValue?: string;
describedObject?: CrossVersionObjectReference;
}
export type ObjectMetricSource =
| V2ObjectMetricSource
| V2Beta1ObjectMetricSource;
export interface V2PodsMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
averageValue?: string;
type?: string;
};
}
export interface V2Beta1PodsMetricSource {
metricName?: string;
selector?: LabelSelector;
targetAverageValue?: string;
}
export type PodsMetricSource =
| V2PodsMetricSource
| V2Beta1PodsMetricSource;
export interface V2ResourceMetricSource {
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ResourceMetricSource {
name: string;
targetAverageUtilization?: number;
targetAverageValue?: string;
}
export type ResourceMetricSource =
| V2ResourceMetricSource
| V2Beta1ResourceMetricSource;
export interface BaseHorizontalPodAutoscalerMetricSpec {
containerResource: ContainerResourceMetricSource;
external: ExternalMetricSource;
object: ObjectMetricSource;
pods: PodsMetricSource;
resource: ResourceMetricSource;
}
export type HorizontalPodAutoscalerMetricSpec =
| OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricSpec, "resource">
| OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricSpec, "external">
| OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricSpec, "object">
| OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
| OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">;
interface HorizontalPodAutoscalerBehavior {
scaleUp?: HPAScalingRules;
scaleDown?: HPAScalingRules;
}
interface HPAScalingRules {
stabilizationWindowSecond?: number;
selectPolicy?: ScalingPolicySelect;
policies?: HPAScalingPolicy[];
}
type ScalingPolicySelect = string;
interface HPAScalingPolicy {
type: HPAScalingPolicyType;
value: number;
periodSeconds: number;
}
type HPAScalingPolicyType = string;
export interface V2ContainerResourceMetricStatus {
container?: string;
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ContainerResourceMetricStatus {
container?: string;
currentAverageUtilization?: number;
currentAverageValue?: string;
name: string;
}
export type ContainerResourceMetricStatus =
| V2ContainerResourceMetricStatus
| V2Beta1ContainerResourceMetricStatus;
export interface V2ExternalMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
value?: string;
};
}
export interface V2Beta1ExternalMetricStatus {
currentAverageValue?: string;
currentValue?: string;
metricName?: string;
metricSelector?: LabelSelector;
}
export type ExternalMetricStatus =
| V2Beta1ExternalMetricStatus
| V2ExternalMetricStatus;
export interface V2ObjectMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricStatus {
averageValue?: string;
currentValue?: string;
metricName?: string;
selector?: LabelSelector;
describedObject?: CrossVersionObjectReference;
}
export type ObjectMetricStatus =
| V2Beta1ObjectMetricStatus
| V2ObjectMetricStatus;
export interface V2PodsMetricStatus {
selector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
};
}
export interface V2Beta1PodsMetricStatus {
currentAverageValue?: string;
metricName?: string;
selector?: LabelSelector;
}
export type PodsMetricStatus =
| V2Beta1PodsMetricStatus
| V2PodsMetricStatus;
export interface V2ResourceMetricStatus {
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ResourceMetricStatus {
currentAverageUtilization?: number;
currentAverageValue?: string;
name: string;
}
export type ResourceMetricStatus =
| V2Beta1ResourceMetricStatus
| V2ResourceMetricStatus;
export interface BaseHorizontalPodAutoscalerMetricStatus {
containerResource: ContainerResourceMetricStatus;
external: ExternalMetricStatus;
object: ObjectMetricStatus;
pods: PodsMetricStatus;
resource: ResourceMetricStatus;
}
export type HorizontalPodAutoscalerMetricStatus =
| OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricStatus, "resource">
| OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricStatus, "external">
| OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricStatus, "object">
| OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricStatus, "pods">
| OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricStatus, "containerResource">;
export interface HorizontalPodAutoscalerSpec {
scaleTargetRef: CrossVersionObjectReference;
minReplicas?: number;
maxReplicas: number;
metrics?: HorizontalPodAutoscalerMetricSpec[];
behavior?: HorizontalPodAutoscalerBehavior;
targetCPUUtilizationPercentage?: number; // used only in autoscaling/v1
}
export interface HorizontalPodAutoscalerStatus {
conditions?: BaseKubeObjectCondition[];
currentReplicas: number;
desiredReplicas: number;
currentMetrics?: HorizontalPodAutoscalerMetricStatus[];
currentCPUUtilizationPercentage?: number; // used only in autoscaling/v1
}
export class HorizontalPodAutoscaler extends KubeObject<
NamespaceScopedMetadata,
HorizontalPodAutoscalerStatus,
HorizontalPodAutoscalerSpec
> {
static readonly kind = "HorizontalPodAutoscaler";
static readonly namespaced = true;
static readonly apiBase = "/apis/autoscaling/v2/horizontalpodautoscalers";
getMaxPods() {
return this.spec.maxReplicas ?? 0;
}
getMinPods() {
return this.spec.minReplicas ?? 0;
}
getReplicas() {
return this.status?.currentReplicas ?? 0;
}
getReadyConditions() {
return this.getConditions().filter(({ isReady }) => isReady);
}
getConditions() {
return this.status?.conditions?.map(condition => {
const { message, reason, lastTransitionTime, status } = condition;
return {
...condition,
isReady: status === "True",
tooltip: `${message || reason} (${lastTransitionTime})`,
};
}) ?? [];
}
getMetrics() {
return this.spec.metrics ?? [];
}
getCurrentMetrics() {
return this.status?.currentMetrics ?? [];
}
}
import { HorizontalPodAutoscaler } from "@k8slens/kube-object";
export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -46,5 +46,4 @@ export * from "./service.api";
export * from "./service-account.api";
export * from "./stateful-set.api";
export * from "./storage-class.api";
export * from "./types";
export * from "./vertical-pod-autoscaler.api";

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { IngressClass } from "@k8slens/kube-object";
import type { KubeApiDependencies, ResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
@ -18,84 +17,12 @@ export class IngressClassApi extends KubeApi<IngressClass> {
}
setAsDefault({ name }: ResourceDescriptor, isDefault = true) {
const reqUrl = this.formatUrlForNotListing({ name });
return this.request.patch(reqUrl, {
data: {
metadata: {
annotations: {
[IngressClass.ANNOTATION_IS_DEFAULT]: JSON.stringify(isDefault),
},
return this.patch({ name }, {
metadata: {
annotations: {
[IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault),
},
},
}, {
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
// API docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#ingressclass-v1-networking-k8s-io
export type IngressClassMetadata = KubeObjectMetadata<KubeObjectScope.Cluster> & {
"name": string;
"labels"?: {
[name: string]: string | undefined;
"app.kubernetes.io/component"?: "controller";
};
"annotations"?: {
[name: string]: string | undefined;
"ingressclass.kubernetes.io/is-default-class"?: "true";
};
};
export interface IngressClassParametersReference {
"apiGroup": string; // k8s.example.net
"scope": "Namespace" | "Cluster";
"kind": "ClusterIngressParameter" | "IngressParameter";
"name": string; // external-config-1
"namespace"?: string; // namespaced for IngressClass must be defined in `spec.parameters.namespace` instead of `metadata.namespace` (!)
}
export interface IngressClassSpec {
controller: string; // "example.com/ingress-controller"
parameters?: IngressClassParametersReference;
}
export interface IngressClassStatus {
}
export class IngressClass extends KubeObject<IngressClassMetadata, IngressClassStatus, IngressClassSpec> {
static readonly kind = "IngressClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/networking.k8s.io/v1/ingressclasses";
static readonly ANNOTATION_IS_DEFAULT = "ingressclass.kubernetes.io/is-default-class";
getController(): string {
return this.spec.controller;
}
getCtrlApiGroup() {
return this.spec?.parameters?.apiGroup;
}
getCtrlScope() {
return this.spec?.parameters?.scope;
}
getCtrlNs() {
return this.spec?.parameters?.namespace;
}
getCtrlKind() {
return this.spec?.parameters?.kind;
}
getCtrlName() {
return this.spec?.parameters?.name as string;
}
get isDefault() {
return this.metadata.annotations?.[IngressClass.ANNOTATION_IS_DEFAULT] === "true";
}, "strategic");
}
}

View File

@ -3,12 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isString, iter } from "@k8slens/utilities";
import { Ingress } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
export class IngressApi extends KubeApi<Ingress> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -21,193 +18,3 @@ export class IngressApi extends KubeApi<Ingress> {
});
}
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;
}
// extensions/v1beta1
export interface ExtensionsBackend {
serviceName?: string;
servicePort?: number | string;
}
// networking.k8s.io/v1
export interface NetworkingBackend {
service?: IngressService;
}
export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & {
resource?: TypedLocalObjectReference;
};
export interface IngressService {
name: string;
port: RequireExactlyOne<{
name: string;
number: number;
}>;
}
function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend {
return hasTypedProperty(backend, "serviceName", isString);
}
/**
* Format an ingress backend into the name of the service and port
* @param backend The ingress target
*/
export function getBackendServiceNamePort(backend: IngressBackend | undefined): string {
if (!backend) {
return "<unknown>";
}
if (isExtensionsBackend(backend)) {
return `${backend.serviceName}:${backend.servicePort}`;
}
if (backend.service) {
const { name, port } = backend.service;
return `${name}:${port.number ?? port.name}`;
}
return "<unknown>";
}
export interface HTTPIngressPath {
pathType: "Exact" | "Prefix" | "ImplementationSpecific";
path?: string;
backend?: IngressBackend;
}
export interface HTTPIngressRuleValue {
paths: HTTPIngressPath[];
}
export interface IngressRule {
host?: string;
http?: HTTPIngressRuleValue;
}
export interface IngressSpec {
tls: {
secretName: string;
}[];
rules?: IngressRule[];
// extensions/v1beta1
backend?: ExtensionsBackend;
/**
* The default backend which is exactly on of:
* - service
* - resource
*/
defaultBackend?: RequireExactlyOne<NetworkingBackend & {
resource: {
apiGroup: string;
kind: string;
name: string;
};
}>;
}
export interface IngressStatus {
loadBalancer: {
ingress?: ILoadBalancerIngress[];
};
}
export class Ingress extends KubeObject<
NamespaceScopedMetadata,
IngressStatus,
IngressSpec
> {
static readonly kind = "Ingress";
static readonly namespaced = true;
static readonly apiBase = "/apis/networking.k8s.io/v1/ingresses";
getRules() {
return this.spec.rules ?? [];
}
getRoutes(): string[] {
return computeRouteDeclarations(this).map(({ url, service }) => `${url}${service}`);
}
getServiceNamePort(): ExtensionsBackend | undefined {
const { spec: { backend, defaultBackend } = {}} = this;
const serviceName = defaultBackend?.service?.name ?? backend?.serviceName;
const servicePort = defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort;
if (!serviceName || !servicePort) {
return undefined;
}
return {
serviceName,
servicePort,
};
}
getHosts() {
const { spec: { rules = [] }} = this;
return [...iter.filterMap(rules, rule => rule.host)];
}
getPorts() {
const ports: number[] = [];
const { spec: { tls, rules = [], backend, defaultBackend }} = this;
const httpPort = 80;
const tlsPort = 443;
// Note: not using the port name (string)
const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort;
if (rules.length > 0) {
if (rules.some(rule => rule.http)) {
ports.push(httpPort);
}
} else if (servicePort !== undefined) {
ports.push(Number(servicePort));
}
if (tls && tls.length > 0) {
ports.push(tlsPort);
}
return ports.join(", ");
}
getLoadBalancers() {
return this.status?.loadBalancer?.ingress?.map(address => (
address.hostname || address.ip
)) ?? [];
}
}
export interface ComputedIngressRoute {
displayAsLink: boolean;
pathname: string;
url: string;
service: string;
}
export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] {
const { host = "*", http: { paths } = { paths: [] }} = rule;
const protocol = (ingress.spec?.tls?.length ?? 0) === 0
? "http"
: "https";
return paths.map(({ path = "/", backend }) => ({
displayAsLink: !host.includes("*"),
pathname: path,
url: `${protocol}://${host}${path}`,
service: getBackendServiceNamePort(backend),
}));
}
export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] {
return ingress.getRules().flatMap(rule => computeRuleDeclarations(ingress, rule));
}

View File

@ -5,94 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { PodSpec } from "./pod.api";
import type { Container } from "./types/container";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface JobSpec {
parallelism?: number;
completions?: number;
backoffLimit?: number;
selector?: LabelSelector;
template: {
metadata: {
creationTimestamp?: string;
labels?: Partial<Record<string, string>>;
annotations?: Partial<Record<string, string>>;
};
spec: PodSpec;
};
containers?: Container[];
restartPolicy?: string;
terminationGracePeriodSeconds?: number;
dnsPolicy?: string;
serviceAccountName?: string;
serviceAccount?: string;
schedulerName?: string;
}
export interface JobStatus extends KubeObjectStatus {
startTime: string;
completionTime: string;
succeeded: number;
}
export class Job extends KubeObject<
NamespaceScopedMetadata,
JobStatus,
JobSpec
> {
static readonly kind = "Job";
static readonly namespaced = true;
static readonly apiBase = "/apis/batch/v1/jobs";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector?.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata.labels);
}
getTolerations() {
return this.spec.template.spec.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getDesiredCompletions() {
return this.spec.completions ?? 0;
}
getCompletions() {
return this.status?.succeeded ?? 0;
}
getParallelism() {
return this.spec.parallelism;
}
getCondition() {
// Type of Job condition could be only Complete or Failed
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#jobcondition-v1-batch
return this.status?.conditions?.find(({ status }) => status === "True");
}
getImages() {
return this.spec.template.spec.containers?.map(container => container.image) ?? [];
}
}
import { Job } from "@k8slens/kube-object";
export class JobApi extends KubeApi<Job> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -5,46 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface LeaseSpec {
acquireTime?: string;
holderIdentity: string;
leaseDurationSeconds: number;
leaseTransitions?: number;
renewTime: string;
}
export class Lease extends KubeObject<
NamespaceScopedMetadata,
void,
LeaseSpec
> {
static readonly kind = "Lease";
static readonly namespaced = true;
static readonly apiBase = "/apis/coordination.k8s.io/v1/leases";
getAcquireTime(): string {
return this.spec.acquireTime || "";
}
getHolderIdentity(): string {
return this.spec.holderIdentity;
}
getLeaseDurationSeconds(): number {
return this.spec.leaseDurationSeconds;
}
getLeaseTransitions(): number | undefined {
return this.spec.leaseTransitions;
}
getRenewTime(): string {
return this.spec.renewTime;
}
}
import { Lease } from "@k8slens/kube-object";
export class LeaseApi extends KubeApi<Lease> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -3,63 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { LimitRange } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export enum LimitType {
CONTAINER = "Container",
POD = "Pod",
PVC = "PersistentVolumeClaim",
}
export enum Resource {
MEMORY = "memory",
CPU = "cpu",
STORAGE = "storage",
EPHEMERAL_STORAGE = "ephemeral-storage",
}
export enum LimitPart {
MAX = "max",
MIN = "min",
DEFAULT = "default",
DEFAULT_REQUEST = "defaultRequest",
MAX_LIMIT_REQUEST_RATIO = "maxLimitRequestRatio",
}
type LimitRangeParts = Partial<Record<LimitPart, Record<string, string>>>;
export interface LimitRangeItem extends LimitRangeParts {
type: string;
}
export interface LimitRangeSpec {
limits: LimitRangeItem[];
}
export class LimitRange extends KubeObject<
NamespaceScopedMetadata,
void,
LimitRangeSpec
> {
static readonly kind = "LimitRange";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/limitranges";
getContainerLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.CONTAINER);
}
getPodLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.POD);
}
getPVCLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.PVC);
}
}
export class LimitRangeApi extends KubeApi<LimitRange> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -7,7 +7,6 @@ import { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-ti
import apiBaseInjectable from "../../api-base.injectable";
import type { MetricData } from "../metrics.api";
export interface RequestMetricsParams {
/**
* timestamp in seconds or valid date-string

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { PersistentVolumeClaim } from "../persistent-volume-claim.api";
import type { PersistentVolumeClaim } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PersistentVolumeClaimMetricData {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { DaemonSet } from "../daemon-set.api";
import type { DaemonSet } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Deployment } from "../deployment.api";
import type { Deployment } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Job } from "../job.api";
import type { Job } from "@k8slens/kube-object";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { ReplicaSet } from "../replica-set.api";
import type { ReplicaSet } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface ReplicaSetPodMetricData {

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { StatefulSet } from "../stateful-set.api";
import type { StatefulSet } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface StatefulSetPodMetricData {

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MetricData } from "../metrics.api";
import type { Pod } from "../pod.api";
import type { Pod } from "@k8slens/kube-object";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PodMetricData {

View File

@ -2,151 +2,9 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { MutatingWebhookConfiguration } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
interface WebhookClientConfig {
// `url` gives the location of the webhook
url?: string;
// `service` is a reference to the service for this webhook. Either `service` or `url` must be specified.
service?: ServiceReference;
// `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
// If unspecified, system trust roots on the apiserver are used.
caBundle?: string;
}
interface RuleWithOperations {
// APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one.
apiGroups: string[];
// APIVersions is the API versions the resources belong to. '*' is all versions.
apiVersions?: string[];
// Resources is a list of resources this rule applies to.
// For example: 'pods' means pods.
// '*' means all resources, but not subresources.
// 'pods/' means all subresources of pods.
// '*/scale' means all scale subresources. Allowed values are "Resource" / "Resource/Scale" / "Resource/Status".
resources: string[];
// Operations is a list of operations this rule applies to.
// The valid values are: "CREATE" / "UPDATE" / "DELETE" / "CONNECT".
operations: string[];
// Scope specifies the scope of this rule. Valid values are "Cluster" / "Namespace".
// Default is "Cluster".
scope?: string;
}
export interface Webhook {
// The name of the webhook configuration.
name: string;
// ClientConfig defines how to communicate with the hook.
clientConfig: WebhookClientConfig;
// Rules describes what operations on what resources/subresources the webhook cares about.
// The webhook cares about an operation if it matches _any_ Rule.
rules?: RuleWithOperations[];
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions
// the webhook expects. API server will try to use first version in the list which it
// supports. If none of the versions specified in this list supported by API server,
// validation will fail for this object.
admissionReviewVersions?: string[];
// TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored
// or the API call will fail depending on the failure policy.
timeoutSeconds?: number;
// FailurePolicy specifies how unrecognized errors from the webhook are handled - allowed values are Ignore or Fail.
// Defaults to Fail.
failurePolicy?: string;
// matchPolicy defines how the "rules" list is used to match incoming requests. Allowed values are "Exact" or "Equivalent".
// - Exact: match a request only if it exactly matches a specified rule.
// - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version.
// Defaults to "Equivalent".
matchPolicy?: string;
// NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object
// matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels.
// If both the object and the webhook configuration specify namespaceSelector, they must match.
namespaceSelector?: LabelSelector;
// ObjectSelector decides whether to run the webhook based on if the object has matching labels.
// objectSelector and namespaceSelector are ANDed. An empty objectSelector matches all objects.
// A null objectSelector matches no objects.
objectSelector?: LabelSelector;
// SideEffects states whether this webhookk should run when no mutating or validating webhook
// needs to run. This should be false when the webhook only applies to resources that have
// the sideEffects field set to None. Defaults to true.
sideEffects?: string;
// reinvocationPolicy indicates whether this webhook should be called multiple times as part of a
// single admission evaluation. Allowed values are "Never" and "IfNeeded"
reinvocationPolicy?: "Never" | "IfNeeded";
}
export interface MutatingWebhook extends Webhook {
}
interface ServiceReference {
// `namespace` is the namespace of the service.
namespace: string;
// `name` is the name of the service.
name: string;
// `path` is an optional URL path which will be sent in any request to this service.
path?: string;
// `port` is an optional service port which will be used when accessing the service.
port?: number | string;
}
interface MutatingWebhookConfigurationData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
webhooks?: MutatingWebhook[];
}
export class MutatingWebhookConfiguration extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "MutatingWebhookConfiguration";
static namespaced = false;
static apiBase = "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations";
webhooks?: MutatingWebhook[];
constructor({ webhooks, ...rest }: MutatingWebhookConfigurationData) {
super(rest);
this.webhooks = webhooks;
}
getWebhooks(): MutatingWebhook[] {
return this.webhooks ?? [];
}
getClientConfig(serviceName: string, serviceNamespace: string): WebhookClientConfig | undefined {
const webhooks = this.getWebhooks();
for (const webhook of webhooks) {
if (webhook.clientConfig.service?.name === serviceName && webhook.clientConfig.service?.namespace === serviceNamespace) {
return webhook.clientConfig;
}
}
return undefined;
}
}
export class MutatingWebhookConfigurationApi extends KubeApi<MutatingWebhookConfiguration> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -5,47 +5,7 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object";
import { KubeObject } from "../kube-object";
export enum NamespaceStatusKind {
ACTIVE = "Active",
TERMINATING = "Terminating",
}
export interface NamespaceSpec {
finalizers?: string[];
}
export interface NamespaceStatus extends KubeObjectStatus {
phase?: string;
}
export class Namespace extends KubeObject<
ClusterScopedMetadata,
NamespaceStatus,
NamespaceSpec
> {
static readonly kind = "Namespace";
static readonly namespaced = false;
static readonly apiBase = "/api/v1/namespaces";
getStatus() {
return this.status?.phase ?? "-";
}
isSubnamespace() {
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of"));
}
isChildOf(parentName: string) {
return this.getLabels().find(label => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`);
}
isControlledByHNC() {
return this.getLabels().includes("hnc.x-k8s.io/included-namespace=true");
}
}
import { Namespace } from "@k8slens/kube-object";
export class NamespaceApi extends KubeApi<Namespace> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -3,126 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { NetworkPolicy } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface IPolicyIpBlock {
cidr: string;
except?: string[];
}
/**
* @deprecated Use `LabelSelector` instead
*/
export type IPolicySelector = LabelSelector;
export interface NetworkPolicyPort {
/**
* The protocol which network traffic must match.
*
* One of:
* - `"TCP"`
* - `"UDP"`
* - `"SCTP"`
*
* @default "TCP"
*/
protocol?: string;
/**
* The port on the given protocol. This can either be a numerical or named
* port on a pod. If this field is not provided, this matches all port names and
* numbers.
*
* If present, only traffic on the specified protocol AND port will be matched.
*/
port?: number | string;
/**
* If set, indicates that the range of ports from port to endPort, inclusive,
* should be allowed by the policy. This field cannot be defined if the port field
* is not defined or if the port field is defined as a named (string) port.
*
* The endPort must be equal or greater than port.
*/
endPort?: number;
}
export interface NetworkPolicyPeer {
/**
* IPBlock defines policy on a particular IPBlock. If this field is set then
* neither of the other fields can be.
*/
ipBlock?: IPolicyIpBlock;
/**
* Selects Namespaces using cluster-scoped labels. This field follows standard label
* selector semantics; if present but empty, it selects all namespaces.
*
* If PodSelector is also set, then the NetworkPolicyPeer as a whole selects
* the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
*
* Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
*/
namespaceSelector?: LabelSelector;
/**
* This is a label selector which selects Pods. This field follows standard label
* selector semantics; if present but empty, it selects all pods.
*
* If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects
* the Pods matching PodSelector in the Namespaces selected by NamespaceSelector.
*
* Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
*/
podSelector?: LabelSelector;
}
export interface IPolicyIngress {
from?: NetworkPolicyPeer[];
ports?: NetworkPolicyPort[];
}
export interface IPolicyEgress {
to?: NetworkPolicyPeer[];
ports?: NetworkPolicyPort[];
}
export type PolicyType = "Ingress" | "Egress";
export interface NetworkPolicySpec {
podSelector: LabelSelector;
policyTypes?: PolicyType[];
ingress?: IPolicyIngress[];
egress?: IPolicyEgress[];
}
export class NetworkPolicy extends KubeObject<
NamespaceScopedMetadata,
void,
NetworkPolicySpec
> {
static readonly kind = "NetworkPolicy";
static readonly namespaced = true;
static readonly apiBase = "/apis/networking.k8s.io/v1/networkpolicies";
getMatchLabels(): string[] {
if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return [];
return Object
.entries(this.spec.podSelector.matchLabels)
.map(data => data.join(":"));
}
getTypes(): string[] {
if (!this.spec.policyTypes) return [];
return this.spec.policyTypes;
}
}
export class NetworkPolicyApi extends KubeApi<NetworkPolicy> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {

View File

@ -3,12 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "@k8slens/utilities";
import { Node } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import { TypedRegEx } from "typed-regex";
export class NodeApi extends KubeApi<Node> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -18,246 +15,3 @@ export class NodeApi extends KubeApi<Node> {
});
}
}
export interface NodeTaint {
key: string;
value?: string;
effect: string;
timeAdded: string;
}
export function formatNodeTaint(taint: NodeTaint): string {
if (taint.value) {
return `${taint.key}=${taint.value}:${taint.effect}`;
}
return `${taint.key}:${taint.effect}`;
}
export interface NodeCondition extends BaseKubeObjectCondition {
/**
* Last time we got an update on a given condition.
*/
lastHeartbeatTime?: string;
}
/**
* These role label prefixs are the ones that are for master nodes
*
* The `master` label has been deprecated in Kubernetes 1.20, and will be removed in 1.25 so we
* have to also use the newer `control-plane` label
*/
const masterNodeLabels = [
"master",
"control-plane",
];
/**
* This regex is used in the `getRoleLabels()` method bellow, but placed here
* as factoring out regexes is best practice.
*/
const nodeRoleLabelKeyMatcher = TypedRegEx("^.*node-role.kubernetes.io/+(?<role>.+)$");
export interface NodeSpec {
podCIDR?: string;
podCIDRs?: string[];
providerID?: string;
/**
* @deprecated see https://issues.k8s.io/61966
*/
externalID?: string;
taints?: NodeTaint[];
unschedulable?: boolean;
}
export interface NodeAddress {
type: "Hostname" | "ExternalIP" | "InternalIP";
address: string;
}
export interface NodeStatusResources extends Partial<Record<string, string>> {
cpu?: string;
"ephemeral-storage"?: string;
"hugepages-1Gi"?: string;
"hugepages-2Mi"?: string;
memory?: string;
pods?: string;
}
export interface ConfigMapNodeConfigSource {
kubeletConfigKey: string;
name: string;
namespace: string;
resourceVersion?: string;
uid?: string;
}
export interface NodeConfigSource {
configMap?: ConfigMapNodeConfigSource;
}
export interface NodeConfigStatus {
active?: NodeConfigSource;
assigned?: NodeConfigSource;
lastKnownGood?: NodeConfigSource;
error?: string;
}
export interface DaemonEndpoint {
Port: number; //it must be uppercase for backwards compatibility
}
export interface NodeDaemonEndpoints {
kubeletEndpoint?: DaemonEndpoint;
}
export interface ContainerImage {
names?: string[];
sizeBytes?: number;
}
export interface NodeSystemInfo {
architecture: string;
bootID: string;
containerRuntimeVersion: string;
kernelVersion: string;
kubeProxyVersion: string;
kubeletVersion: string;
machineID: string;
operatingSystem: string;
osImage: string;
systemUUID: string;
}
export interface AttachedVolume {
name: string;
devicePath: string;
}
export interface NodeStatus {
capacity?: NodeStatusResources;
allocatable?: NodeStatusResources;
conditions?: NodeCondition[];
addresses?: NodeAddress[];
config?: NodeConfigStatus;
daemonEndpoints?: NodeDaemonEndpoints;
images?: ContainerImage[];
nodeInfo?: NodeSystemInfo;
phase?: string;
volumesInUse?: string[];
volumesAttached?: AttachedVolume[];
}
export class Node extends KubeObject<
ClusterScopedMetadata,
NodeStatus,
NodeSpec
> {
static readonly kind = "Node";
static readonly namespaced = false;
static readonly apiBase = "/api/v1/nodes";
/**
* Returns the concatination of all current condition types which have a status
* of `"True"`
*/
getNodeConditionText(): string {
if (!this.status?.conditions) {
return "";
}
return this.status.conditions
.filter(condition => condition.status === "True")
.map(condition => condition.type)
.join(" ");
}
getTaints() {
return this.spec.taints || [];
}
isMasterNode(): boolean {
return this.getRoleLabelItems()
.some(roleLabel => masterNodeLabels.includes(roleLabel));
}
getRoleLabelItems(): string[] {
const { labels } = this.metadata;
const roleLabels: string[] = [];
if (!isObject(labels)) {
return roleLabels;
}
for (const labelKey of Object.keys(labels)) {
const match = nodeRoleLabelKeyMatcher.match(labelKey);
if (match?.groups) {
roleLabels.push(match.groups.role);
}
}
if (typeof labels["kubernetes.io/role"] === "string") {
roleLabels.push(labels["kubernetes.io/role"]);
}
if (typeof labels["node.kubernetes.io/role"] === "string") {
roleLabels.push(labels["node.kubernetes.io/role"]);
}
return roleLabels;
}
getRoleLabels(): string {
return this.getRoleLabelItems().join(", ");
}
getCpuCapacity() {
if (!this.status?.capacity || !this.status.capacity.cpu) return 0;
return cpuUnitsToNumber(this.status.capacity.cpu);
}
getMemoryCapacity() {
if (!this.status?.capacity || !this.status.capacity.memory) return 0;
return unitsToBytes(this.status.capacity.memory);
}
getConditions(): NodeCondition[] {
const conditions = this.status?.conditions || [];
if (this.isUnschedulable()) {
return [{ type: "SchedulingDisabled", status: "True" }, ...conditions];
}
return conditions;
}
getActiveConditions() {
return this.getConditions().filter(c => c.status === "True");
}
getWarningConditions() {
const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"];
return this.getActiveConditions().filter(condition => {
return !goodConditions.includes(condition.type);
});
}
getKubeletVersion() {
return this.status?.nodeInfo?.kubeletVersion ?? "<unknown>";
}
getOperatingSystem(): string {
return this.metadata?.labels?.["kubernetes.io/os"]
|| this.metadata?.labels?.["beta.kubernetes.io/os"]
|| this.status?.nodeInfo?.operatingSystem
|| "linux";
}
isUnschedulable() {
return this.spec.unschedulable;
}
}

View File

@ -3,13 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { Pod } from "./pod.api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import { object } from "@k8slens/utilities";
import type { ResourceRequirements } from "./types/resource-requirements";
import { PersistentVolumeClaim } from "@k8slens/kube-object";
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -20,54 +16,3 @@ export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
}
}
export interface PersistentVolumeClaimSpec {
accessModes?: string[];
dataSource?: TypedLocalObjectReference;
dataSourceRef?: TypedLocalObjectReference;
resources?: ResourceRequirements;
selector?: LabelSelector;
storageClassName?: string;
volumeMode?: string;
volumeName?: string;
}
export interface PersistentVolumeClaimStatus {
phase: string; // Pending
}
export class PersistentVolumeClaim extends KubeObject<
NamespaceScopedMetadata,
PersistentVolumeClaimStatus,
PersistentVolumeClaimSpec
> {
static readonly kind = "PersistentVolumeClaim";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/persistentvolumeclaims";
getPods(pods: Pod[]): Pod[] {
return pods
.filter(pod => pod.getNs() === this.getNs())
.filter(pod => (
pod.getVolumes()
.filter(volume => volume.persistentVolumeClaim?.claimName === this.getName())
.length > 0
));
}
getStorage(): string {
return this.spec.resources?.requests?.storage ?? "-";
}
getMatchLabels(): string[] {
return object.entries(this.spec.selector?.matchLabels)
.map(([name, val]) => `${name}:${val}`);
}
getMatchExpressions() {
return this.spec.selector?.matchExpressions ?? [];
}
getStatus(): string {
return this.status?.phase ?? "-";
}
}

View File

@ -3,104 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata, LabelSelector, ObjectReference, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { unitsToBytes } from "@k8slens/utilities";
import { PersistentVolume } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { ResourceRequirements } from "./types/resource-requirements";
export interface PersistentVolumeSpec {
/**
* AccessModes contains the desired access modes the volume should have.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
*/
accessModes?: string[];
dataSource?: TypedLocalObjectReference;
dataSourceRef?: TypedLocalObjectReference;
resources?: ResourceRequirements;
selector?: LabelSelector;
/**
* Name of the StorageClass required by the claim.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
*/
storageClassName?: string;
/**
* Defines what type of volume is required by the claim. Value of Filesystem is implied when not
* included in claim spec.
*/
volumeMode?: string;
/**
* A description of the persistent volume\'s resources and capacity.
*
* More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity
*/
capacity?: Partial<Record<string, string>>;
flexVolume?: {
driver: string; // ceph.rook.io/rook-ceph-system,
options?: {
clusterNamespace: string; // rook-ceph,
image: string; // pvc-c5d7c485-9f1b-11e8-b0ea-9600000e54fb,
pool: string; // replicapool,
storageClass: string; // rook-ceph-block
};
};
mountOptions?: string[];
claimRef?: ObjectReference;
persistentVolumeReclaimPolicy?: string; // Delete,
nfs?: {
path: string;
server: string;
};
}
export interface PersistentVolumeStatus {
phase: string;
reason?: string;
}
export class PersistentVolume extends KubeObject<
ClusterScopedMetadata,
PersistentVolumeStatus,
PersistentVolumeSpec
> {
static kind = "PersistentVolume";
static namespaced = false;
static apiBase = "/api/v1/persistentvolumes";
getCapacity(inBytes = false) {
const capacity = this.spec.capacity;
if (capacity?.storage) {
if (inBytes) return unitsToBytes(capacity.storage);
return capacity.storage;
}
return 0;
}
getStatus() {
return this.status?.phase || "-";
}
getStorageClass(): string {
return this.spec.storageClassName ?? "";
}
getClaimRefName(): string {
return this.spec.claimRef?.name ?? "";
}
getStorageClassName() {
return this.spec.storageClassName || "";
}
}
export class PersistentVolumeApi extends KubeApi<PersistentVolume> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,78 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { PodDisruptionBudget } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { Condition } from "./types/condition";
export interface V1Beta1PodDisruptionBudgetSpec {
minAvailable: string;
maxUnavailable: string;
selector: LabelSelector;
}
export interface V1PodDisruptionBudgetSpec {
maxUnavailable?: string | number;
minAvailable?: string | number;
selector?: LabelSelector;
}
export type PodDisruptionBudgetSpec =
| V1Beta1PodDisruptionBudgetSpec
| V1PodDisruptionBudgetSpec;
export interface V1Beta1PodDisruptionBudgetStatus {
currentHealthy: number;
desiredHealthy: number;
disruptionsAllowed: number;
expectedPods: number;
}
export interface V1PodDisruptionBudgetStatus {
conditions?: Condition[];
currentHealthy: number;
desiredHealthy: number;
disruptedPods?: Partial<Record<string, string>>;
disruptionsAllowed: number;
expectedPods: number;
observedGeneration?: number;
}
export type PodDisruptionBudgetStatus =
| V1Beta1PodDisruptionBudgetStatus
| V1PodDisruptionBudgetStatus;
export class PodDisruptionBudget extends KubeObject<
NamespaceScopedMetadata,
PodDisruptionBudgetStatus,
PodDisruptionBudgetSpec
> {
static readonly kind = "PodDisruptionBudget";
static readonly namespaced = true;
static readonly apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
getSelectors() {
return KubeObject.stringifyLabels(this.spec.selector?.matchLabels);
}
getMinAvailable() {
return this.spec.minAvailable ?? "N/A";
}
getMaxUnavailable() {
return this.spec.maxUnavailable ?? "N/A";
}
getCurrentHealthy() {
return this.status?.currentHealthy ?? 0;
}
getDesiredHealthy() {
return this.status?.desiredHealthy ?? 0;
}
}
export class PodDisruptionBudgetApi extends KubeApi<PodDisruptionBudget> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,53 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodMetricsData } from "@k8slens/kube-object";
import { PodMetrics } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface PodMetricsData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
timestamp: string;
window: string;
containers: PodMetricsContainer[];
}
export interface PodMetricsContainerUsage {
cpu: string;
memory: string;
}
export interface PodMetricsContainer {
name: string;
usage: PodMetricsContainerUsage;
}
export class PodMetrics extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "PodMetrics";
static readonly namespaced = true;
static readonly apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
timestamp: string;
window: string;
containers: PodMetricsContainer[];
constructor({
timestamp,
window,
containers,
...rest
}: PodMetricsData) {
super(rest);
this.timestamp = timestamp;
this.window = window;
this.containers = containers;
}
}
export class PodMetricsApi extends KubeApi<PodMetrics, PodMetricsData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,113 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { PodSecurityPolicy } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface PodSecurityPolicySpec {
allowPrivilegeEscalation?: boolean;
allowedCSIDrivers?: {
name: string;
}[];
allowedCapabilities: string[];
allowedFlexVolumes?: {
driver: string;
}[];
allowedHostPaths?: {
pathPrefix: string;
readOnly: boolean;
}[];
allowedProcMountTypes?: string[];
allowedUnsafeSysctls?: string[];
defaultAddCapabilities?: string[];
defaultAllowPrivilegeEscalation?: boolean;
forbiddenSysctls?: string[];
fsGroup?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
hostIPC?: boolean;
hostNetwork?: boolean;
hostPID?: boolean;
hostPorts?: {
max: number;
min: number;
}[];
privileged?: boolean;
readOnlyRootFilesystem?: boolean;
requiredDropCapabilities?: string[];
runAsGroup?: {
ranges: {
max: number;
min: number;
}[];
rule: string;
};
runAsUser?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
runtimeClass?: {
allowedRuntimeClassNames: string[];
defaultRuntimeClassName: string;
};
seLinux?: {
rule: string;
seLinuxOptions: {
level: string;
role: string;
type: string;
user: string;
};
};
supplementalGroups?: {
rule: string;
ranges: {
max: number;
min: number;
}[];
};
volumes?: string[];
}
export class PodSecurityPolicy extends KubeObject<
ClusterScopedMetadata,
void,
PodSecurityPolicySpec
> {
static readonly kind = "PodSecurityPolicy";
static readonly namespaced = false;
static readonly apiBase = "/apis/policy/v1beta1/podsecuritypolicies";
isPrivileged() {
return !!this.spec.privileged;
}
getVolumes() {
return this.spec.volumes || [];
}
getRules() {
const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec;
return {
fsGroup: fsGroup ? fsGroup.rule : "",
runAsGroup: runAsGroup ? runAsGroup.rule : "",
runAsUser: runAsUser ? runAsUser.rule : "",
supplementalGroups: supplementalGroups ? supplementalGroups.rule : "",
seLinux: seLinux ? seLinux.rule : "",
};
}
}
export class PodSecurityPolicyApi extends KubeApi<PodSecurityPolicy> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {

View File

@ -10,22 +10,9 @@ import type {
ResourceDescriptor,
} from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
import type {
Affinity,
KubeObjectMetadata, KubeStatusData,
LocalObjectReference,
NamespaceScopedMetadata,
Toleration,
} from "../kube-object";
import { isKubeStatusData, KubeObject, KubeStatus } from "../kube-object";
import type { SecretReference } from "./secret.api";
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api";
import { isDefined } from "@k8slens/utilities";
import type { PodSecurityContext } from "./types/pod-security-context";
import type { Probe } from "./types/probe";
import type { Container } from "./types/container";
import type { ObjectFieldSelector, ResourceFieldSelector } from "./types";
import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object";
import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object";
export class PodApi extends KubeApi<Pod> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -73,826 +60,3 @@ export class PodApi extends KubeApi<Pod> {
return this.request.get(path, { query });
}
}
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
export interface PodLogsQuery {
container?: string;
tailLines?: number;
timestamps?: boolean;
sinceTime?: string; // Date.toISOString()-format
follow?: boolean;
previous?: boolean;
}
export enum PodStatusPhase {
TERMINATED = "Terminated",
FAILED = "Failed",
PENDING = "Pending",
RUNNING = "Running",
SUCCEEDED = "Succeeded",
EVICTED = "Evicted",
}
export interface ContainerStateRunning {
startedAt: string;
}
export interface ContainerStateWaiting {
reason: string;
message: string;
}
export interface ContainerStateTerminated {
startedAt: string;
finishedAt: string;
exitCode: number;
reason: string;
containerID?: string;
message?: string;
signal?: number;
}
/**
* ContainerState holds a possible state of container. Only one of its members
* may be specified. If none of them is specified, the default one is
* `ContainerStateWaiting`.
*/
export interface ContainerState {
running?: ContainerStateRunning;
waiting?: ContainerStateWaiting;
terminated?: ContainerStateTerminated;
}
export type ContainerStateValues = Partial<ContainerState[keyof ContainerState]>;
export interface PodContainerStatus {
name: string;
state?: ContainerState;
lastState?: ContainerState;
ready: boolean;
restartCount: number;
image: string;
imageID: string;
containerID?: string;
started?: boolean;
}
export interface AwsElasticBlockStoreSource {
volumeID: string;
fsType: string;
}
export interface AzureDiskSource {
/**
* The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskName: string;
/**
* The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`.
*/
diskURI: string;
/**
* Kind of disk
* @default "Shared"
*/
kind?: "Shared" | "Dedicated" | "Managed";
/**
* Disk caching mode.
* @default "None"
*/
cachingMode?: "None" | "ReadOnly" | "ReadWrite";
/**
* The filesystem type to mount.
* @default "ext4"
*/
fsType?: string;
/**
* Whether the filesystem is used as readOnly.
* @default false
*/
readonly?: boolean;
}
export interface AzureFileSource {
/**
* The name of the secret that contains both Azure storage account name and key.
*/
secretName: string;
/**
* The share name to be used.
*/
shareName: string;
/**
* In case the secret is stored in a different namespace.
* @default "default"
*/
secretNamespace?: string;
/**
* Whether the filesystem is used as readOnly.
*/
readOnly: boolean;
}
export interface CephfsSource {
/**
* List of Ceph monitors
*/
monitors: string[];
/**
* Used as the mounted root, rather than the full Ceph tree.
* @default "/"
*/
path?: string;
/**
* The RADOS user name.
* @default "admin"
*/
user?: string;
/**
* The path to the keyring file.
* @default "/etc/ceph/user.secret"
*/
secretFile?: string;
/**
* Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile`
*/
secretRef?: SecretReference;
/**
* Whether the filesystem is used as readOnly.
*
* @default false
*/
readOnly?: boolean;
}
export interface CinderSource {
volumeID: string;
fsType: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: SecretReference;
}
export interface ConfigMapSource {
name: string;
items: {
key: string;
path: string;
}[];
}
export interface DownwardApiSource {
items: {
path: string;
fieldRef: {
fieldPath: string;
};
}[];
}
export interface EphemeralSource {
volumeClaimTemplate: {
/**
* All the rest of the fields are ignored and rejected during validation
*/
metadata?: Pick<KubeObjectMetadata, "labels" | "annotations">;
spec: PersistentVolumeClaimSpec;
};
}
export interface EmptyDirSource {
medium?: string;
sizeLimit?: string;
}
export interface FiberChannelSource {
/**
* A list of World Wide Names
*/
targetWWNs: string[];
/**
* Logical Unit number
*/
lun: number;
/**
* The type of filesystem
* @default "ext4"
*/
fsType?: string;
readOnly: boolean;
}
export interface FlockerSource {
datasetName: string;
}
export interface FlexVolumeSource {
driver: string;
fsType?: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
readOnly?: boolean;
options?: Record<string, string>;
}
export interface GcePersistentDiskSource {
pdName: string;
fsType: string;
}
export interface GitRepoSource {
repository: string;
revision: string;
}
export interface GlusterFsSource {
/**
* The name of the Endpoints object that represents a Gluster cluster configuration.
*/
endpoints: string;
/**
* The Glusterfs volume name.
*/
path: string;
/**
* The boolean that sets the mountpoint readOnly or readWrite.
*/
readOnly: boolean;
}
export interface HostPathSource {
path: string;
/**
* Determines the sorts of checks that will be done
* @default ""
*/
type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice";
}
export interface IScsiSource {
targetPortal: string;
iqn: string;
lun: number;
fsType: string;
readOnly: boolean;
chapAuthDiscovery?: boolean;
chapAuthSession?: boolean;
secretRef?: SecretReference;
}
export interface LocalSource {
path: string;
}
export interface NetworkFsSource {
server: string;
path: string;
readOnly?: boolean;
}
export interface PersistentVolumeClaimSource {
claimName: string;
}
export interface PhotonPersistentDiskSource {
pdID: string;
/**
* @default "ext4"
*/
fsType?: string;
}
export interface PortworxVolumeSource {
volumeID: string;
fsType?: string;
readOnly?: boolean;
}
export interface KeyToPath {
key: string;
path: string;
mode?: number;
}
export interface ConfigMapProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface DownwardAPIVolumeFile {
path: string;
fieldRef?: ObjectFieldSelector;
resourceFieldRef?: ResourceFieldSelector;
mode?: number;
}
export interface DownwardAPIProjection {
items?: DownwardAPIVolumeFile[];
}
export interface SecretProjection {
name: string;
items?: KeyToPath[];
optional?: boolean;
}
export interface ServiceAccountTokenProjection {
audience?: string;
expirationSeconds?: number;
path: string;
}
export interface VolumeProjection {
secret?: SecretProjection;
downwardAPI?: DownwardAPIProjection;
configMap?: ConfigMapProjection;
serviceAccountToken?: ServiceAccountTokenProjection;
}
export interface ProjectedSource {
sources?: VolumeProjection[];
defaultMode?: number;
}
export interface QuobyteSource {
registry: string;
volume: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "serivceaccount"
*/
user?: string;
group?: string;
tenant?: string;
}
export interface RadosBlockDeviceSource {
monitors: string[];
image: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default "rbd"
*/
pool?: string;
/**
* @default "admin"
*/
user?: string;
/**
* @default "/etc/ceph/keyring"
*/
keyring?: string;
secretRef?: SecretReference;
/**
* @default false
*/
readOnly?: boolean;
}
export interface ScaleIoSource {
gateway: string;
system: string;
secretRef?: LocalObjectReference;
/**
* @default false
*/
sslEnabled?: boolean;
protectionDomain?: string;
storagePool?: string;
/**
* @default "ThinProvisioned"
*/
storageMode?: "ThickProvisioned" | "ThinProvisioned";
volumeName: string;
/**
* @default "xfs"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
}
export interface SecretSource {
secretName: string;
items?: {
key: string;
path: string;
mode?: number;
}[];
defaultMode?: number;
optional?: boolean;
}
export interface StorageOsSource {
volumeName: string;
/**
* @default Pod.metadata.namespace
*/
volumeNamespace?: string;
/**
* @default "ext4"
*/
fsType?: string;
/**
* @default false
*/
readOnly?: boolean;
secretRef?: LocalObjectReference;
}
export interface VsphereVolumeSource {
volumePath: string;
/**
* @default "ext4"
*/
fsType?: string;
storagePolicyName?: string;
storagePolicyID?: string;
}
export interface ContainerStorageInterfaceSource {
driver: string;
/**
* @default false
*/
readOnly?: boolean;
/**
* @default "ext4"
*/
fsType?: string;
volumeAttributes?: Record<string, string>;
controllerPublishSecretRef?: SecretReference;
nodeStageSecretRef?: SecretReference;
nodePublishSecretRef?: SecretReference;
controllerExpandSecretRef?: SecretReference;
}
export interface PodVolumeVariants {
awsElasticBlockStore: AwsElasticBlockStoreSource;
azureDisk: AzureDiskSource;
azureFile: AzureFileSource;
cephfs: CephfsSource;
cinder: CinderSource;
configMap: ConfigMapSource;
csi: ContainerStorageInterfaceSource;
downwardAPI: DownwardApiSource;
emptyDir: EmptyDirSource;
ephemeral: EphemeralSource;
fc: FiberChannelSource;
flexVolume: FlexVolumeSource;
flocker: FlockerSource;
gcePersistentDisk: GcePersistentDiskSource;
gitRepo: GitRepoSource;
glusterfs: GlusterFsSource;
hostPath: HostPathSource;
iscsi: IScsiSource;
local: LocalSource;
nfs: NetworkFsSource;
persistentVolumeClaim: PersistentVolumeClaimSource;
photonPersistentDisk: PhotonPersistentDiskSource;
portworxVolume: PortworxVolumeSource;
projected: ProjectedSource;
quobyte: QuobyteSource;
rbd: RadosBlockDeviceSource;
scaleIO: ScaleIoSource;
secret: SecretSource;
storageos: StorageOsSource;
vsphereVolume: VsphereVolumeSource;
}
/**
* The valid kinds of volume
*/
export type PodVolumeKind = keyof PodVolumeVariants;
export type PodSpecVolume = RequireExactlyOne<PodVolumeVariants> & {
name: string;
};
export interface HostAlias {
ip: string;
hostnames: string[];
}
export interface Sysctl {
name: string;
value: string;
}
export interface TopologySpreadConstraint {
}
export interface PodSpec {
activeDeadlineSeconds?: number;
affinity?: Affinity;
automountServiceAccountToken?: boolean;
containers?: Container[];
dnsPolicy?: string;
enableServiceLinks?: boolean;
ephemeralContainers?: unknown[];
hostAliases?: HostAlias[];
hostIPC?: boolean;
hostname?: string;
hostNetwork?: boolean;
hostPID?: boolean;
imagePullSecrets?: LocalObjectReference[];
initContainers?: Container[];
nodeName?: string;
nodeSelector?: Partial<Record<string, string>>;
overhead?: Partial<Record<string, string>>;
preemptionPolicy?: string;
priority?: number;
priorityClassName?: string;
readinessGates?: unknown[];
restartPolicy?: string;
runtimeClassName?: string;
schedulerName?: string;
securityContext?: PodSecurityContext;
serviceAccount?: string;
serviceAccountName?: string;
setHostnameAsFQDN?: boolean;
shareProcessNamespace?: boolean;
subdomain?: string;
terminationGracePeriodSeconds?: number;
tolerations?: Toleration[];
topologySpreadConstraints?: TopologySpreadConstraint[];
volumes?: PodSpecVolume[];
}
export interface PodCondition {
lastProbeTime?: number;
lastTransitionTime?: string;
message?: string;
reason?: string;
type: string;
status: string;
}
export interface PodStatus {
phase: string;
conditions: PodCondition[];
hostIP: string;
podIP: string;
podIPs?: {
ip: string;
}[];
startTime: string;
initContainerStatuses?: PodContainerStatus[];
containerStatuses?: PodContainerStatus[];
qosClass?: string;
reason?: string;
}
export class Pod extends KubeObject<
NamespaceScopedMetadata,
PodStatus,
PodSpec
> {
static kind = "Pod";
static namespaced = true;
static apiBase = "/api/v1/pods";
getAffinityNumber() {
return Object.keys(this.getAffinity()).length;
}
getInitContainers() {
return this.spec?.initContainers ?? [];
}
getContainers() {
return this.spec?.containers ?? [];
}
getAllContainers() {
return [...this.getContainers(), ...this.getInitContainers()];
}
getRunningContainers() {
const runningContainerNames = new Set(
this.getContainerStatuses()
.filter(({ state }) => state?.running)
.map(({ name }) => name),
);
return this.getAllContainers()
.filter(({ name }) => runningContainerNames.has(name));
}
getContainerStatuses(includeInitContainers = true): PodContainerStatus[] {
const { containerStatuses = [], initContainerStatuses = [] } = this.status ?? {};
if (includeInitContainers) {
return [...containerStatuses, ...initContainerStatuses];
}
return [...containerStatuses];
}
getRestartsCount(): number {
const { containerStatuses = [] } = this.status ?? {};
return containerStatuses.reduce((totalCount, { restartCount }) => totalCount + restartCount, 0);
}
getQosClass() {
return this.status?.qosClass || "";
}
getReason() {
return this.status?.reason || "";
}
getPriorityClassName() {
return this.spec?.priorityClassName || "";
}
getRuntimeClassName() {
return this.spec?.runtimeClassName || "";
}
getServiceAccountName() {
return this.spec?.serviceAccountName || "";
}
getStatus(): PodStatusPhase {
const phase = this.getStatusPhase();
const reason = this.getReason();
const trueConditionTypes = new Set(this.getConditions()
.filter(({ status }) => status === "True")
.map(({ type }) => type));
const isInGoodCondition = ["Initialized", "Ready"].every(condition => trueConditionTypes.has(condition));
if (reason === PodStatusPhase.EVICTED) {
return PodStatusPhase.EVICTED;
}
if (phase === PodStatusPhase.FAILED) {
return PodStatusPhase.FAILED;
}
if (phase === PodStatusPhase.SUCCEEDED) {
return PodStatusPhase.SUCCEEDED;
}
if (phase === PodStatusPhase.RUNNING && isInGoodCondition) {
return PodStatusPhase.RUNNING;
}
return PodStatusPhase.PENDING;
}
// Returns pod phase or container error if occurred
getStatusMessage(): string {
if (this.getReason() === PodStatusPhase.EVICTED) {
return "Evicted";
}
if (this.metadata.deletionTimestamp) {
return "Terminating";
}
return this.getStatusPhase() || "Waiting";
}
getStatusPhase() {
return this.status?.phase;
}
getConditions() {
return this.status?.conditions ?? [];
}
getVolumes() {
return this.spec?.volumes ?? [];
}
getSecrets(): string[] {
return this.getVolumes()
.map(vol => vol.secret?.secretName)
.filter(isDefined);
}
getNodeSelectors(): string[] {
return Object.entries(this.spec?.nodeSelector ?? {})
.map(values => values.join(": "));
}
getTolerations() {
return this.spec?.tolerations ?? [];
}
getAffinity(): Affinity {
return this.spec?.affinity ?? {};
}
hasIssues() {
for (const { type, status } of this.getConditions()) {
if (type === "Ready" && status !== "True") {
return true;
}
}
for (const { state } of this.getContainerStatuses()) {
if (state?.waiting?.reason === "CrashLookBackOff") {
return true;
}
}
return this.getStatusPhase() !== "Running";
}
getLivenessProbe(container: Container) {
return this.getProbe(container, container.livenessProbe);
}
getReadinessProbe(container: Container) {
return this.getProbe(container, container.readinessProbe);
}
getStartupProbe(container: Container) {
return this.getProbe(container, container.startupProbe);
}
private getProbe(container: Container, probe: Probe | undefined): string[] {
const probeItems: string[] = [];
if (!probe) {
return probeItems;
}
const {
httpGet,
exec,
tcpSocket,
initialDelaySeconds = 0,
timeoutSeconds = 0,
periodSeconds = 0,
successThreshold = 0,
failureThreshold = 0,
} = probe;
// HTTP Request
if (httpGet) {
const { path = "", port, host = "", scheme = "HTTP" } = httpGet;
const resolvedPort = typeof port === "number"
? port
// Try and find the port number associated witht the name or fallback to the name itself
: container.ports?.find(containerPort => containerPort.name === port)?.containerPort || port;
probeItems.push(
"http-get",
`${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`,
);
}
// Command
if (exec?.command) {
probeItems.push(`exec [${exec.command.join(" ")}]`);
}
// TCP Probe
if (tcpSocket?.port) {
probeItems.push(`tcp-socket :${tcpSocket.port}`);
}
probeItems.push(
`delay=${initialDelaySeconds}s`,
`timeout=${timeoutSeconds}s`,
`period=${periodSeconds}s`,
`#success=${successThreshold}`,
`#failure=${failureThreshold}`,
);
return probeItems;
}
getNodeName(): string | undefined {
return this.spec?.nodeName;
}
getSelectedNodeOs(): string | undefined {
return this.spec?.nodeSelector?.["kubernetes.io/os"] || this.spec?.nodeSelector?.["beta.kubernetes.io/os"];
}
getIPs(): string[] {
const podIPs = this.status?.podIPs ?? [];
return podIPs.map(value => value.ip);
}
}

View File

@ -5,56 +5,9 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PreemptionPolicy } from "./types/preemption-policy";
import type { PriorityClassData } from "@k8slens/kube-object";
import { PriorityClass } from "@k8slens/kube-object";
export interface PriorityClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
description?: string;
globalDefault?: boolean;
preemptionPolicy?: PreemptionPolicy;
value: number;
}
export class PriorityClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "PriorityClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/scheduling.k8s.io/v1/priorityclasses";
description?: string;
globalDefault?: boolean;
preemptionPolicy?: PreemptionPolicy;
value?: number;
constructor({ description, globalDefault, preemptionPolicy, value, ...rest }: PriorityClassData) {
super(rest);
this.description = description;
this.globalDefault = globalDefault;
this.preemptionPolicy = preemptionPolicy;
this.value = value;
}
getDescription() {
return this.description || "";
}
getGlobalDefault() {
return (this.globalDefault || false).toString();
}
getPreemptionPolicy() {
return this.preemptionPolicy || "PreemptLowerPriority";
}
getValue() {
return this.value;
}
}
export class PriorityClassApi extends KubeApi<PriorityClass, PriorityClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,11 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
import { ReplicaSet } from "@k8slens/kube-object";
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -17,17 +15,17 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
return (status as { replicas: number })?.replicas;
}
scale(params: { namespace: string; name: string }, replicas: number) {
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
@ -38,70 +36,3 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
});
}
}
export interface ReplicaSetSpec {
replicas?: number;
selector: LabelSelector;
template?: PodTemplateSpec;
minReadySeconds?: number;
}
export interface ReplicaSetStatus extends KubeObjectStatus {
replicas: number;
fullyLabeledReplicas?: number;
readyReplicas?: number;
availableReplicas?: number;
observedGeneration?: number;
}
export class ReplicaSet extends KubeObject<
NamespaceScopedMetadata,
ReplicaSetStatus,
ReplicaSetSpec
> {
static kind = "ReplicaSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/replicasets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template?.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template?.metadata?.labels);
}
getTolerations() {
return this.spec.template?.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template?.spec?.affinity;
}
getAffinityNumber() {
return Object.keys(this.getAffinity() ?? {}).length;
}
getDesired() {
return this.spec.replicas ?? 0;
}
getCurrent() {
return this.status?.availableReplicas ?? 0;
}
getReady() {
return this.status?.readyReplicas ?? 0;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
return containers.map(container => container.image);
}
}

View File

@ -3,15 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type {
BaseKubeObjectCondition, KubeObjectMetadata,
KubeObjectStatus,
NamespaceScopedMetadata,
} from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types";
import { ReplicationController } from "@k8slens/kube-object";
import type { Scale } from "@k8slens/kube-object";
export class ReplicationControllerApi extends KubeApi<ReplicationController> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -21,15 +16,15 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
getScale(params: { namespace: string; name: string }): Promise<Scale> {
getScale(params: NamespacedResourceDescriptor): Promise<Scale> {
return this.request.get(this.getScaleApiUrl(params));
}
scale(params: { namespace: string; name: string }, replicas: number): Promise<Scale> {
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
metadata: params,
@ -44,106 +39,3 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
});
}
}
export interface Scale {
apiVersion: "autoscaling/v1";
kind: "Scale";
metadata: KubeObjectMetadata;
spec: {
replicas: number;
};
status: {
replicas: number;
selector: string;
};
}
export interface ReplicationControllerSpec {
/**
* Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available.
* Defaults to 0 (pod will be considered available as soon as it is ready)
*/
minReadySeconds?: number;
/**
* Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified.
* Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller
*/
replicas?: number;
/**
* Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template.
* Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template.
* More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
*/
selector?: Record<string, string>;
/**
* Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef.
* More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template
*/
template: PodTemplateSpec;
}
export interface ReplicationControllerStatus extends KubeObjectStatus {
/**
* The number of available replicas (ready for at least minReadySeconds) for this replication controller.
*/
availableReplicas: number;
/**
* The number of pods that have labels matching the labels of the pod template of the replication controller.
*/
fullyLabeledReplicas: number;
/**
* ObservedGeneration reflects the generation of the most recently observed replication controller.
*/
observedGeneration: number;
/**
* The number of ready replicas for this replication controller.
*/
readyReplicas: number;
/**
* Replicas is the most recently observed number of replicas.
* More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller
*/
replicas: number;
}
export class ReplicationController extends KubeObject<
NamespaceScopedMetadata,
ReplicationControllerStatus,
ReplicationControllerSpec
> {
static kind = "ReplicationController";
static namespaced = true;
static apiBase = "/api/v1/replicationcontrollers";
getMinReadySeconds(): number {
return this.spec?.minReadySeconds ?? 0;
}
getGeneration() {
return this.status?.observedGeneration;
}
getSelectorLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.selector);
}
getReplicas(): number | undefined {
return this.status?.replicas;
}
getDesiredReplicas(): number {
return this.spec?.replicas ?? 0;
}
getAvailableReplicas(): number | undefined {
return this.status?.availableReplicas;
}
getLabeledReplicas(): number | undefined {
return this.status?.fullyLabeledReplicas;
}
getConditions(): BaseKubeObjectCondition[] {
return this.status?.conditions ?? [];
}
}

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { Patch } from "rfc6902";
import apiBaseInjectable from "../../api-base.injectable";
import type { AsyncResult, Result } from "@k8slens/utilities";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => AsyncResult<KubeJsonApiData, string>;
@ -16,21 +16,21 @@ const requestKubeObjectPatchInjectable = getInjectable({
const apiBase = di.inject(apiBaseInjectable);
return async (name, kind, ns, patch) => {
const result = await apiBase.patch("/stack", {
const result = (await apiBase.patch("/stack", {
data: {
name,
kind,
ns,
patch,
},
}) as Result<string, string>;
})) as Result<string, string>;
if (!result.callWasSuccessful) {
return result;
}
try {
const response = JSON.parse(result.response);
const response = JSON.parse(result.response) as KubeJsonApiData;
return {
callWasSuccessful: true,

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import apiBaseInjectable from "../../api-base.injectable";
import type { AsyncResult, Result } from "@k8slens/utilities";
import type { KubeJsonApiData } from "../../kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
export type RequestKubeObjectCreation = (resourceDescriptor: string) => AsyncResult<KubeJsonApiData, string>;

View File

@ -3,67 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { ResourceQuota } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export type IResourceQuotaValues = Partial<Record<string, string>> & {
// Compute Resource Quota
"limits.cpu"?: string;
"limits.memory"?: string;
"requests.cpu"?: string;
"requests.memory"?: string;
// Storage Resource Quota
"requests.storage"?: string;
"persistentvolumeclaims"?: string;
// Object Count Quota
"count/pods"?: string;
"count/persistentvolumeclaims"?: string;
"count/services"?: string;
"count/secrets"?: string;
"count/configmaps"?: string;
"count/replicationcontrollers"?: string;
"count/deployments.apps"?: string;
"count/replicasets.apps"?: string;
"count/statefulsets.apps"?: string;
"count/jobs.batch"?: string;
"count/cronjobs.batch"?: string;
"count/deployments.extensions"?: string;
};
export interface ResourceQuotaSpec {
hard: IResourceQuotaValues;
scopeSelector?: {
matchExpressions: {
operator: string;
scopeName: string;
values: string[];
}[];
};
}
export interface ResourceQuotaStatus {
hard: IResourceQuotaValues;
used: IResourceQuotaValues;
}
export class ResourceQuota extends KubeObject<
NamespaceScopedMetadata,
ResourceQuotaStatus,
ResourceQuotaSpec
> {
static readonly kind = "ResourceQuota";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/resourcequotas";
getScopeSelector() {
return this.spec.scopeSelector?.matchExpressions ?? [];
}
}
export class ResourceQuotaApi extends KubeApi<ResourceQuota> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {

View File

@ -3,45 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleBindingData } from "@k8slens/kube-object";
import { RoleBinding } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { RoleRef } from "./types/role-ref";
import type { Subject } from "./types/subject";
export interface RoleBindingData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
subjects?: Subject[];
roleRef: RoleRef;
}
export class RoleBinding extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "RoleBinding";
static readonly namespaced = true;
static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings";
subjects?: Subject[];
roleRef: RoleRef;
constructor({ subjects, roleRef, ...rest }: RoleBindingData) {
super(rest);
this.subjects = subjects;
this.roleRef = roleRef;
}
getSubjects() {
return this.subjects || [];
}
getSubjectNames(): string {
return this.getSubjects().map(subject => subject.name).join(", ");
}
}
export class RoleBindingApi extends KubeApi<RoleBinding, RoleBindingData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,36 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleData } from "@k8slens/kube-object";
import { Role } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { PolicyRule } from "./types/policy-rule";
export interface RoleData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
rules?: PolicyRule[];
}
export class Role extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "Role";
static readonly namespaced = true;
static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/roles";
rules?: PolicyRule[];
constructor({ rules, ...rest }: RoleData) {
super(rest);
this.rules = rules;
}
getRules() {
return this.rules || [];
}
}
export class RoleApi extends KubeApi<Role, RoleData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -5,62 +5,8 @@
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope, Toleration } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface RuntimeClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
}
export interface RuntimeClassOverhead {
podFixed?: string;
}
export interface RuntimeClassScheduling {
nodeSelector?: Partial<Record<string, string>>;
tolerations?: Toleration[];
}
export class RuntimeClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "RuntimeClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/node.k8s.io/v1/runtimeclasses";
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
constructor({ handler, overhead, scheduling, ...rest }: RuntimeClassData) {
super(rest);
this.handler = handler;
this.overhead = overhead;
this.scheduling = scheduling;
}
getHandler() {
return this.handler;
}
getPodFixed() {
return this.overhead?.podFixed ?? "";
}
getNodeSelectors(): string[] {
return Object.entries(this.scheduling?.nodeSelector ?? {})
.map(values => values.join(": "));
}
getTolerations() {
return this.scheduling?.tolerations ?? [];
}
}
import type { RuntimeClassData } from "@k8slens/kube-object";
import { RuntimeClass } from "@k8slens/kube-object";
export class RuntimeClassApi extends KubeApi<RuntimeClass, RuntimeClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,73 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import autoBind from "auto-bind";
export enum SecretType {
Opaque = "Opaque",
ServiceAccountToken = "kubernetes.io/service-account-token",
Dockercfg = "kubernetes.io/dockercfg",
DockerConfigJson = "kubernetes.io/dockerconfigjson",
BasicAuth = "kubernetes.io/basic-auth",
SSHAuth = "kubernetes.io/ssh-auth",
TLS = "kubernetes.io/tls",
BootstrapToken = "bootstrap.kubernetes.io/token",
}
export const reverseSecretTypeMap = {
[SecretType.Opaque]: "Opaque",
[SecretType.ServiceAccountToken]: "ServiceAccountToken",
[SecretType.Dockercfg]: "Dockercfg",
[SecretType.DockerConfigJson]: "DockerConfigJson",
[SecretType.BasicAuth]: "BasicAuth",
[SecretType.SSHAuth]: "SSHAuth",
[SecretType.TLS]: "TLS",
[SecretType.BootstrapToken]: "BootstrapToken",
};
export interface SecretReference {
name: string;
namespace?: string;
}
export interface SecretData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
type: SecretType;
data?: Partial<Record<string, string>>;
}
export class Secret extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "Secret";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/secrets";
type: SecretType;
data: Partial<Record<string, string>>;
constructor({ data = {}, type, ...rest }: SecretData) {
super(rest);
autoBind(this);
this.data = data;
this.type = type;
}
getKeys(): string[] {
return Object.keys(this.data);
}
getToken() {
return this.data.token;
}
}
import { Secret } from "@k8slens/kube-object";
import type { SecretData } from "@k8slens/kube-object";
export class SecretApi extends KubeApi<Secret, SecretData> {
constructor(deps: KubeApiDependencies, options: DerivedKubeApiOptions = {}) {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import { SelfSubjectRulesReview } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
@ -23,58 +23,3 @@ export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
});
}
}
export interface ISelfSubjectReviewRule {
verbs: string[];
apiGroups?: string[];
resources?: string[];
resourceNames?: string[];
nonResourceURLs?: string[];
}
export interface SelfSubjectRulesReview {
spec: {
namespace?: string;
};
status: {
resourceRules: ISelfSubjectReviewRule[];
nonResourceRules: ISelfSubjectReviewRule[];
incomplete: boolean;
};
}
export class SelfSubjectRulesReview extends KubeObject {
static kind = "SelfSubjectRulesReview";
static namespaced = false;
static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews";
getResourceRules() {
const rules = this.status && this.status.resourceRules || [];
return rules.map(rule => this.normalize(rule));
}
getNonResourceRules() {
const rules = this.status && this.status.nonResourceRules || [];
return rules.map(rule => this.normalize(rule));
}
protected normalize(rule: ISelfSubjectReviewRule): ISelfSubjectReviewRule {
const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule;
return {
apiGroups,
nonResourceURLs,
resourceNames,
verbs,
resources: resources.map((resource, index) => {
const apiGroup = apiGroups.length >= index + 1 ? apiGroups[index] : apiGroups.slice(-1)[0];
const separator = apiGroup == "" ? "" : ".";
return resource + separator + apiGroup;
}),
};
}
}

View File

@ -3,51 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope, LocalObjectReference, NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { ServiceAccountData } from "@k8slens/kube-object";
import { ServiceAccount } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface ServiceAccountData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
automountServiceAccountToken?: boolean;
imagePullSecrets?: LocalObjectReference[];
secrets?: ObjectReference[];
}
export class ServiceAccount extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static readonly kind = "ServiceAccount";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/serviceaccounts";
automountServiceAccountToken?: boolean;
imagePullSecrets?: LocalObjectReference[];
secrets?: ObjectReference[];
constructor({
automountServiceAccountToken,
imagePullSecrets,
secrets,
...rest
}: ServiceAccountData) {
super(rest);
this.automountServiceAccountToken = automountServiceAccountToken;
this.imagePullSecrets = imagePullSecrets;
this.secrets = secrets;
}
getSecrets() {
return this.secrets || [];
}
getImagePullSecrets() {
return this.imagePullSecrets || [];
}
}
export class ServiceAccountApi extends KubeApi<ServiceAccount, ServiceAccountData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -3,131 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { Service } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface ServicePort {
name?: string;
protocol: string;
port: number;
targetPort: number;
nodePort?: number;
}
export class ServicePort {
constructor(data: ServicePort) {
Object.assign(this, data);
}
toString() {
if (this.nodePort) {
return `${this.port}:${this.nodePort}/${this.protocol}`;
} else {
return `${this.port}${this.port === this.targetPort ? "" : `:${this.targetPort}`}/${this.protocol}`;
}
}
}
export interface ServiceSpec {
type: string;
clusterIP: string;
clusterIPs?: string[];
externalTrafficPolicy?: string;
externalName?: string;
loadBalancerIP?: string;
loadBalancerSourceRanges?: string[];
sessionAffinity: string;
selector: Partial<Record<string, string>>;
ports: ServicePort[];
healthCheckNodePort?: number;
externalIPs?: string[]; // https://kubernetes.io/docs/concepts/services-networking/service/#external-ips
topologyKeys?: string[];
ipFamilies?: string[];
ipFamilyPolicy?: string;
allocateLoadBalancerNodePorts?: boolean;
loadBalancerClass?: string;
internalTrafficPolicy?: string;
}
export interface ServiceStatus {
loadBalancer?: {
ingress?: {
ip?: string;
hostname?: string;
}[];
};
}
export class Service extends KubeObject<
NamespaceScopedMetadata,
ServiceStatus,
ServiceSpec
> {
static readonly kind = "Service";
static readonly namespaced = true;
static readonly apiBase = "/api/v1/services";
getClusterIp() {
return this.spec.clusterIP;
}
getClusterIps() {
return this.spec.clusterIPs || [];
}
getExternalIps() {
const lb = this.getLoadBalancer();
if (lb?.ingress) {
return lb.ingress.map(val => val.ip || val.hostname);
}
if (Array.isArray(this.spec?.externalIPs)) {
return this.spec.externalIPs;
}
return [];
}
getType() {
return this.spec.type || "-";
}
getSelector(): string[] {
if (!this.spec.selector) return [];
return Object.entries(this.spec.selector).map(val => val.join("="));
}
getPorts(): ServicePort[] {
const ports = this.spec.ports || [];
return ports.map(p => new ServicePort(p));
}
getLoadBalancer() {
return this.status?.loadBalancer;
}
isActive() {
return this.getType() !== "LoadBalancer" || this.getExternalIps().length > 0;
}
getStatus() {
return this.isActive() ? "Active" : "Pending";
}
getIpFamilies() {
return this.spec.ipFamilies || [];
}
getIpFamilyPolicy() {
return this.spec.ipFamilyPolicy || "";
}
}
export class ServiceApi extends KubeApi<Service> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
super(deps, {

View File

@ -5,12 +5,9 @@
import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
import type { PersistentVolumeClaimTemplateSpec } from "./types/persistent-volume-claim-template-spec";
import { StatefulSet } from "@k8slens/kube-object";
export class StatefulSetApi extends KubeApi<StatefulSet> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -20,109 +17,34 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
});
}
protected getScaleApiUrl(params: { namespace: string; name: string }) {
return `${this.getUrl(params)}/scale`;
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
getReplicas(params: { namespace: string; name: string }): Promise<number> {
return this.request
.get(this.getScaleApiUrl(params))
.then(({ status }: any) => status?.replicas);
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const apiUrl = this.getScaleApiUrl(params);
const { status = 0 } = await this.request.get(apiUrl) as { status?: number };
return status;
}
scale(params: { namespace: string; name: string }, replicas: number) {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
spec: {
replicas,
},
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.patch(params, {
spec: {
replicas,
},
},
{
headers: {
"content-type": "application/merge-patch+json",
},
});
}, "merge");
}
restart(params: { namespace: string; name: string }) {
return this.request.patch(this.getUrl(params), {
data: {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
restart(params: NamespacedResourceDescriptor) {
return this.patch(params, {
spec: {
template: {
metadata: {
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
},
},
},
},
{
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
}
}
export interface StatefulSetSpec {
serviceName: string;
replicas: number;
selector: LabelSelector;
template: PodTemplateSpec;
volumeClaimTemplates: PersistentVolumeClaimTemplateSpec[];
}
export interface StatefulSetStatus {
observedGeneration: number;
replicas: number;
currentReplicas: number;
readyReplicas: number;
currentRevision: string;
updateRevision: string;
collisionCount: number;
}
export class StatefulSet extends KubeObject<
NamespaceScopedMetadata,
StatefulSetStatus,
StatefulSetSpec
> {
static readonly kind = "StatefulSet";
static readonly namespaced = true;
static readonly apiBase = "/apis/apps/v1/statefulsets";
getSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.selector.matchLabels);
}
getNodeSelectors(): string[] {
return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector);
}
getTemplateLabels(): string[] {
return KubeObject.stringifyLabels(this.spec.template.metadata?.labels);
}
getTolerations() {
return this.spec.template.spec?.tolerations ?? [];
}
getAffinity() {
return this.spec.template.spec?.affinity ?? {};
}
getAffinityNumber() {
return Object.keys(this.getAffinity()).length;
}
getReplicas() {
return this.spec.replicas || 0;
}
getImages() {
const containers = this.spec.template?.spec?.containers ?? [];
return containers.map(container => container.image);
}, "strategic");
}
}

View File

@ -3,87 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { StorageClassData } from "@k8slens/kube-object";
import { StorageClass } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import autoBind from "auto-bind";
export interface TopologySelectorLabelRequirement {
key: string;
values: string[];
}
export interface TopologySelectorTerm {
matchLabelExpressions?: TopologySelectorLabelRequirement[];
}
export interface StorageClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
allowVolumeExpansion?: boolean;
allowedTopologies?: TopologySelectorTerm[];
mountOptions?: string[];
parameters?: Partial<Record<string, string>>;
provisioner: string;
reclaimPolicy?: string;
volumeBindingMode?: string;
}
export class StorageClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "StorageClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/storage.k8s.io/v1/storageclasses";
allowVolumeExpansion?: boolean;
allowedTopologies: TopologySelectorTerm[];
mountOptions: string[];
parameters: Partial<Record<string, string>>;
provisioner: string;
reclaimPolicy: string;
volumeBindingMode?: string;
constructor({
allowVolumeExpansion,
allowedTopologies = [],
mountOptions = [],
parameters = {},
provisioner,
reclaimPolicy = "Delete",
volumeBindingMode,
...rest
}: StorageClassData) {
super(rest);
autoBind(this);
this.allowVolumeExpansion = allowVolumeExpansion;
this.allowedTopologies = allowedTopologies;
this.mountOptions = mountOptions;
this.parameters = parameters;
this.provisioner = provisioner;
this.reclaimPolicy = reclaimPolicy;
this.volumeBindingMode = volumeBindingMode;
}
isDefault() {
const annotations = this.metadata.annotations || {};
return (
annotations["storageclass.kubernetes.io/is-default-class"] === "true" ||
annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true"
);
}
getVolumeBindingMode() {
return this.volumeBindingMode || "-";
}
getReclaimPolicy() {
return this.reclaimPolicy || "-";
}
}
export class StorageClassApi extends KubeApi<StorageClass, StorageClassData> {
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {

View File

@ -1,55 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Capabilities } from "./capabilities";
import type { SeLinuxOptions } from "./se-linux-options";
import type { SeccompProfile } from "./seccomp-profile";
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
/**
* SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.
*/
export interface SecurityContext {
/**
* AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN
*/
allowPrivilegeEscalation?: boolean;
capabilities?: Capabilities;
/**
* Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.
*/
privileged?: boolean;
/**
* procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled.
*/
procMount?: string;
/**
* Whether this container has a read-only root filesystem. Default is false.
*/
readOnlyRootFilesystem?: boolean;
/**
* The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsGroup?: number;
/**
* Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsNonRoot?: boolean;
/**
* The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsUser?: number;
seLinuxOptions?: SeLinuxOptions;
seccompProfile?: SeccompProfile;
windowsOptions?: WindowsSecurityContextOptions;
}

View File

@ -2,40 +2,9 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import { ValidatingWebhookConfiguration } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { Webhook } from "./mutating-webhook-configuration.api";
export interface ValidatingWebhook extends Webhook {
}
interface ValidatingWebhookConfigurationData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
webhooks?: ValidatingWebhook[];
}
export class ValidatingWebhookConfiguration extends KubeObject<
NamespaceScopedMetadata,
void,
void
> {
static kind = "ValidatingWebhookConfiguration";
static namespaced = false;
static apiBase = "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations";
webhooks?: ValidatingWebhook[];
constructor({ webhooks, ...rest }: ValidatingWebhookConfigurationData) {
super(rest);
this.webhooks = webhooks;
}
getWebhooks(): ValidatingWebhook[] {
return this.webhooks ?? [];
}
}
export class ValidatingWebhookConfigurationApi extends KubeApi<ValidatingWebhookConfiguration> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -3,141 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { BaseKubeObjectCondition, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { VerticalPodAutoscaler } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { CrossVersionObjectReference } from "./types/cross-version-object-reference";
export enum ResourceName {
ResourceCPU = "cpu",
ResourceMemory = "memory",
ResourceStorage = "storage",
}
export type ResourceList = Partial<Record<string, string>>;
export interface RecommendedContainerResources {
containerName?: string;
target: ResourceList;
lowerBound?: ResourceList;
upperBound?: ResourceList;
uncappedTarget?: ResourceList;
}
export interface RecommendedPodResources {
containerRecommendations?: RecommendedContainerResources[];
}
export interface VerticalPodAutoscalerStatus {
conditions?: BaseKubeObjectCondition[];
recommendation?: RecommendedPodResources;
}
export interface VerticalPodAutoscalerRecommenderSelector {
name: string;
}
export enum ContainerScalingMode {
ContainerScalingModeAuto = "Auto",
ContainerScalingModeOff = "Off",
}
export enum ControlledValues {
ControlledValueRequestsAndLimits = "RequestsAndLimits",
ControlledValueRequestsOnly = "RequestsOnly",
}
/**
* ContainerResourcePolicy controls how autoscaler computes the recommended resources for
* a specific container.
*/
export interface ContainerResourcePolicy {
containerName?: string;
mode?: ContainerScalingMode;
minAllowed?: ResourceList;
maxAllowed?: ResourceList;
controlledResources?: ResourceName[];
controlledValues?: ControlledValues;
}
/**
* Controls how the autoscaler computes recommended resources.
* The resource policy may be used to set constraints on the recommendations for individual
* containers.
* If not specified, the autoscaler computes recommended resources for all containers in the
* pod, without additional constraints.
*/
export interface PodResourcePolicy {
containerPolicies?: ContainerResourcePolicy[]; // Per-container resource policies.
}
export enum UpdateMode {
/**
* UpdateModeOff means that autoscaler never changes Pod resources.
* The recommender still sets the recommended resources in the
* VerticalPodAutoscaler object. This can be used for a "dry run".
*/
UpdateModeOff = "Off",
/**
* UpdateModeInitial means that autoscaler only assigns resources on pod
* creation and does not change them during the lifetime of the pod.
*/
UpdateModeInitial = "Initial",
/**
* UpdateModeRecreate means that autoscaler assigns resources on pod
* creation and additionally can update them during the lifetime of the
* pod by deleting and recreating the pod.
*/
UpdateModeRecreate = "Recreate",
/**
* UpdateModeAuto means that autoscaler assigns resources on pod creation
* and additionally can update them during the lifetime of the pod,
* using any available update method. Currently this is equivalent to
* Recreate, which is the only available update method.
*/
UpdateModeAuto = "Auto",
}
export interface PodUpdatePolicy {
minReplicas?: number;
updateMode?: UpdateMode;
}
export interface VerticalPodAutoscalerSpec {
targetRef: CrossVersionObjectReference;
updatePolicy?: PodUpdatePolicy;
resourcePolicy?: PodResourcePolicy;
recommenders?: VerticalPodAutoscalerRecommenderSelector[];
}
export class VerticalPodAutoscaler extends KubeObject<
NamespaceScopedMetadata,
VerticalPodAutoscalerStatus,
VerticalPodAutoscalerSpec
> {
static readonly kind = "VerticalPodAutoscaler";
static readonly namespaced = true;
static readonly apiBase = "/apis/autoscaling.k8s.io/v1/verticalpodautoscalers";
getReadyConditions() {
return this.getConditions().filter(({ isReady }) => isReady);
}
getConditions() {
return this.status?.conditions?.map(condition => {
const { message, reason, lastTransitionTime, status } = condition;
return {
...condition,
isReady: status === "True",
tooltip: `${message || reason || ""} (${lastTransitionTime})`,
};
}) ?? [];
}
getMode() {
return this.spec.updatePolicy?.updateMode ?? UpdateMode.UpdateModeAuto;
}
}
export class VerticalPodAutoscalerApi extends KubeApi<VerticalPodAutoscaler> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -249,12 +249,14 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
return [];
}
if (Array.isArray(error.errors)) {
return error.errors.map(error => error.title);
const { errors, message } = error as { errors?: { title: string }[]; message?: string };
if (Array.isArray(errors)) {
return errors.map(error => error.title);
}
if (isString(error.message)) {
return [error.message];
if (isString(message)) {
return [message];
}
return [res.statusText || "Error!"];

View File

@ -8,11 +8,11 @@
import { merge } from "lodash";
import { stringify } from "querystring";
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata } from "./kube-object";
import { KubeObject, KubeStatus, isKubeStatusData } from "./kube-object";
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata, KubeJsonApiData, KubeObject, KubeObjectScope } from "@k8slens/kube-object";
import { isJsonApiData, isJsonApiDataList, isPartialJsonApiData, KubeStatus, isKubeStatusData } from "@k8slens/kube-object";
import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-event";
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
import type { KubeJsonApi } from "./kube-json-api";
import type { Disposer } from "@k8slens/utilities";
import { isDefined, noop, WrappedAbortController } from "@k8slens/utilities";
import type { RequestInit, Response } from "@k8slens/node-fetch";
@ -157,7 +157,7 @@ const getOrderedVersions = (list: KubeApiResourceVersionList, allowedUsableVersi
export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background";
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (data: IKubeWatchEvent<T> | null, error: KubeStatus | Response | null | any) => void;
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (data: IKubeWatchEvent<T> | null, error: KubeStatus | Response | null | Record<string, unknown>) => void;
export interface KubeApiWatchOptions<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>> {
/**
@ -213,6 +213,29 @@ export interface ResourceDescriptor {
namespace?: string;
}
export type SpecificResourceDescriptor<Scope extends KubeObjectScope> = {
/**
* The name of the kubernetes resource
*/
name: string;
} & (
Scope extends KubeObjectScope.Cluster
? {}
: Scope extends KubeObjectScope.Namespace
? {
/**
* The namespace that the resource lives in
*/
namespace: string;
}
: {
namespace?: string;
}
);
export type NamespacedResourceDescriptor = SpecificResourceDescriptor<KubeObjectScope.Namespace>;
export type ClusterScopedResourceDescriptor = SpecificResourceDescriptor<KubeObjectScope.Cluster>;
export interface DeleteResourceDescriptor extends ResourceDescriptor {
/**
* This determinines how child resources should be handled by kubernetes
@ -441,7 +464,7 @@ export class KubeApi<
const KubeObjectConstructor = this.objectConstructor;
// process items list response, check before single item since there is overlap
if (KubeObject.isJsonApiDataList(data, KubeObject.isPartialJsonApiData)) {
if (isJsonApiDataList(data, isPartialJsonApiData)) {
const { apiVersion, items, metadata } = data;
this.setResourceVersion(namespace, metadata.resourceVersion);
@ -467,22 +490,21 @@ export class KubeApi<
}
// process a single item
if (KubeObject.isJsonApiData(data)) {
if (isJsonApiData(data)) {
this.ensureMetadataSelfLink(data.metadata);
return new KubeObjectConstructor(data as never);
return new KubeObjectConstructor(data as Data);
}
if (!Array.isArray(data)) {
return null;
}
// custom apis might return array for list response, e.g. users, groups, etc.
if (Array.isArray(data)) {
return data.map(data => {
this.ensureMetadataSelfLink(data.metadata);
return new KubeObjectConstructor(data);
});
}
return null;
return data
.filter(isJsonApiData)
.map((data) => (this.ensureMetadataSelfLink(data.metadata), data))
.map((data) => new KubeObjectConstructor(data as Data));
}
private ensureMetadataSelfLink<T extends { selfLink?: string; namespace?: string; name: string }>(metadata: T): asserts metadata is T & { selfLink: string } {
@ -595,7 +617,7 @@ export class KubeApi<
/**
* Some k8s resources might implement special "delete" (e.g. pod.api)
* See also: https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/
* By default should work same as KubeObject.remove()
* By default should work same as delete()
*/
async evict(desc: DeleteResourceDescriptor): Promise<KubeStatus | KubeObject | unknown> {
return this.delete(desc);
@ -714,7 +736,7 @@ export class KubeApi<
});
}
byline(response.body).on("data", (line) => {
byline(response.body).on("data", (line: string) => {
try {
const event = JSON.parse(line) as IKubeWatchEvent<Data>;
@ -731,11 +753,11 @@ export class KubeApi<
}
});
})
.catch(error => {
.catch((error: unknown) => {
if (!abortController.signal.aborted) {
this.dependencies.logger.error(`[KUBE-API] watch (${watchId}) threw ${watchUrl}`, error);
}
callback(null, error);
callback(null, error as Record<string, unknown>);
});
return () => {

View File

@ -3,35 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { JsonApiData, JsonApiError } from "./json-api";
import type { JsonApiError } from "./json-api";
import { JsonApi } from "./json-api";
import type { Response } from "@k8slens/node-fetch";
import type { KubeJsonApiObjectMetadata } from "./kube-object";
export interface KubeJsonApiListMetadata {
resourceVersion: string;
selfLink?: string;
}
export interface KubeJsonApiDataList<T = KubeJsonApiData> {
kind: string;
apiVersion: string;
items: T[];
metadata: KubeJsonApiListMetadata;
}
export interface KubeJsonApiData<
Metadata extends KubeJsonApiObjectMetadata = KubeJsonApiObjectMetadata,
Status = unknown,
Spec = unknown,
> extends JsonApiData {
kind: string;
apiVersion: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
[otherKeys: string]: unknown;
}
import type { KubeJsonApiData } from "@k8slens/kube-object";
export interface KubeJsonApiError extends JsonApiError {
code: number;

View File

@ -6,8 +6,8 @@
import { action, computed, makeObservable, observable, reaction } from "mobx";
import type { Disposer } from "@k8slens/utilities";
import { waitUntilDefined, includes, rejectPromiseBy, object } from "@k8slens/utilities";
import type { KubeJsonApiDataFor, KubeObject } from "./kube-object";
import { KubeStatus } from "./kube-object";
import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object";
import { KubeStatus } from "@k8slens/kube-object";
import type { IKubeWatchEvent } from "./kube-watch-event";
import { ItemStore } from "../item.store";
import type { KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "./kube-api";
@ -398,18 +398,18 @@ export class KubeObjectStore<
}
async removeSelectedItems() {
await Promise.all(this.selectedItems.map(this.remove));
await Promise.all(this.selectedItems.map(item => this.remove(item)));
}
async removeItems(items: K[]) {
await Promise.all(items.map(this.remove));
await Promise.all(items.map(item => this.remove(item)));
}
// collect items from watch-api events to avoid UI blowing up with huge streams of data
protected readonly eventsBuffer = observable.array<IKubeWatchEvent<D>>([], { deep: false });
protected bindWatchEventsUpdater(delay = 1000) {
reaction(() => [...this.eventsBuffer], this.updateFromEventsBuffer, {
reaction(() => [...this.eventsBuffer], () => this.updateFromEventsBuffer(), {
delay,
});
}
@ -456,7 +456,7 @@ export class KubeObjectStore<
const signal = abortController.signal;
const callback: KubeApiWatchCallback<D> = (data, error) => {
if (!this.isLoaded || error?.type === "aborted") return;
if (!this.isLoaded || (error as Record<string, unknown> | null)?.type === "aborted") return;
if (error instanceof Response) {
if (error.status === 404 || error.status === 401) {
@ -471,7 +471,7 @@ export class KubeObjectStore<
clearTimeout(timedRetry);
// resourceVersion has gone, let's try to reload
timedRetry = setTimeout(() => {
(
void (
namespace
? this.loadAll({ namespaces: [namespace], reqInit: { signal }, ...opts })
: this.loadAll({ merge: false, reqInit: { signal }, ...opts })

View File

@ -1,743 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Base class for all kubernetes objects
import moment from "moment";
import type {
KubeJsonApiData,
KubeJsonApiDataList,
KubeJsonApiListMetadata,
} from "./kube-json-api";
import {
formatDuration,
hasOptionalTypedProperty,
hasTypedProperty,
isObject,
isString,
isNumber,
bindPredicate,
isTypedArray,
isRecord,
} from "@k8slens/utilities";
import type { ItemObject } from "@k8slens/list-layout";
import type { Patch } from "rfc6902";
import assert from "assert";
import type { JsonObject } from "type-fest";
import requestKubeObjectPatchInjectable from "./endpoints/resource-applier.api/request-patch.injectable";
import { apiKubeInjectionToken } from "./api-kube";
import requestKubeObjectCreationInjectable from "./endpoints/resource-applier.api/request-update.injectable";
import { dump } from "js-yaml";
import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di";
import autoBind from "auto-bind";
export type KubeJsonApiDataFor<K> = K extends KubeObject<infer Metadata, infer Status, infer Spec>
? KubeJsonApiData<Metadata, Status, Spec>
: never;
export interface KubeObjectConstructorData {
readonly kind?: string;
readonly namespaced?: boolean;
readonly apiBase?: string;
}
export type KubeObjectConstructor<K extends KubeObject, Data> = (new (data: Data) => K) & KubeObjectConstructorData;
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export type KubeTemplateObjectMetadata<Namespaced extends KubeObjectScope> = Pick<KubeJsonApiObjectMetadata<KubeObjectScope>, "annotations" | "finalizers" | "generateName" | "labels" | "ownerReferences"> & {
name?: string;
namespace?: ScopedNamespace<Namespaced>;
};
export interface BaseKubeJsonApiObjectMetadata<Namespaced extends KubeObjectScope> {
/**
* Annotations is an unstructured key value map stored with a resource that may be set by
* external tools to store and retrieve arbitrary metadata. They are not queryable and should be
* preserved when modifying objects.
*
* More info: http://kubernetes.io/docs/user-guide/annotations
*/
annotations?: Partial<Record<string, string>>;
/**
* The name of the cluster which the object belongs to. This is used to distinguish resources
* with same name and namespace in different clusters. This field is not set anywhere right now
* and apiserver is going to ignore it if set in create or update request.
*/
clusterName?: string;
/**
* CreationTimestamp is a timestamp representing the server time when this object was created. It
* is not guaranteed to be set in happens-before order across separate operations. Clients may
* not set this value. It is represented in RFC3339 form and is in UTC. Populated by the system.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
*/
readonly creationTimestamp?: string;
/**
* Number of seconds allowed for this object to gracefully terminate before it will be removed
* from the system. Only set when deletionTimestamp is also set. May only be shortened.
*/
readonly deletionGracePeriodSeconds?: number;
/**
* DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field
* is set by the server when a graceful deletion is requested by the user, and is not directly
* settable by a client. The resource is expected to be deleted (no longer visible from resource
* lists, and not reachable by name) after the time in this field, once the finalizers list is
* empty. As long as the finalizers list contains items, deletion is blocked. Once the
* `deletionTimestamp` is set, this value may not be unset or be set further into the future,
* although it may be shortened or the resource may be deleted prior to this time. For example,
* a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a
* graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet
* will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the
* pod from the API. In the presence of network partitions, this object may still exist after
* this timestamp, until an administrator or automated process can determine the resource is
* fully terminated. If not set, graceful deletion of the object has not been requested.
* Populated by the system when a graceful deletion is requested.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
*/
readonly deletionTimestamp?: string;
/**
* Must be empty before the object is deleted from the registry. Each entry is an identifier for
* the responsible component that will remove the entry from the list. If the deletionTimestamp
* of the object is non-nil, entries in this list can only be removed. Finalizers may be
* processed and removed in any order. Order is NOT enforced because it introduces significant
* risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder
* it. If the finalizer list is processed in order, then this can lead to a situation in which
* the component responsible for the first finalizer in the list is waiting for a signal (field
* value, external system, or other) produced by a component responsible for a finalizer later in
* the list, resulting in a deadlock. Without enforced ordering finalizers are free to order
* amongst themselves and are not vulnerable to ordering changes in the list.
*/
finalizers?: string[];
/**
* GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the
* Name field has not been provided. If this field is used, the name returned to the client will
* be different than the name passed. This value will also be combined with a unique suffix. The
* provided value has the same validation rules as the Name field, and may be truncated by the
* length of the suffix required to make the value unique on the server. If this field is
* specified and the generated name exists, the server will NOT return a 409 - instead, it will
* either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not
* be found in the time allotted, and the client should retry (optionally after the time indicated
* in the Retry-After header). Applied only if Name is not specified.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency
*/
generateName?: string;
/**
* A sequence number representing a specific generation of the desired state. Populated by the
* system.
*/
readonly generation?: number;
/**
* Map of string keys and values that can be used to organize and categorize (scope and select)
* objects. May match selectors of replication controllers and services.
*
* More info: http://kubernetes.io/docs/user-guide/labels
*/
labels?: Partial<Record<string, string>>;
/**
* ManagedFields maps workflow-id and version to the set of fields that are managed by that
* workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set
* or understand this field. A workflow can be the user's name, a controller's name, or the name
* of a specific apply path like "ci-cd". The set of fields is always in the version that the
* workflow used when modifying the object.
*/
managedFields?: unknown[];
/**
* Name must be unique within a namespace. Is required when creating resources, although some
* resources may allow a client to request the generation of an appropriate name automatically.
* Name is primarily intended for creation idempotence and configuration definition.
*
* More info: http://kubernetes.io/docs/user-guide/identifiers#names
*/
readonly name: string;
/**
* Namespace defines the space within which each name must be unique. An empty namespace is
* equivalent to the "default" namespace, but "default" is the canonical representation. Not all
* objects are required to be scoped to a namespace - the value of this field for those objects
* will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces
*/
readonly namespace?: ScopedNamespace<Namespaced>;
/**
* List of objects depended by this object. If ALL objects in the list have been deleted, this
* object will be garbage collected. If this object is managed by a controller, then an entry in
* this list will point to this controller, with the controller field set to true. There cannot
* be more than one managing controller.
*/
ownerReferences?: OwnerReference[];
/**
* An opaque value that represents the internal version of this object that can be used by
* clients to determine when objects have changed. May be used for optimistic concurrency, change
* detection, and the watch operation on a resource or set of resources. Clients must treat these
* values as opaque and passed unmodified back to the server. They may only be valid for a
* particular resource or set of resources. Populated by the system. Value must be treated as
* opaque by clients.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
*/
readonly resourceVersion?: string;
/**
* SelfLink is a URL representing this object. Populated by the system.
*/
readonly selfLink?: string;
/**
* UID is the unique in time and space value for this object. It is typically generated by the
* server on successful creation of a resource and is not allowed to change on PUT operations.
* Populated by the system.
*
* More info: http://kubernetes.io/docs/user-guide/identifiers#uids
*/
readonly uid?: string;
[key: string]: unknown;
}
export type KubeJsonApiObjectMetadata<Namespaced extends KubeObjectScope = KubeObjectScope> =
BaseKubeJsonApiObjectMetadata<Namespaced>
& (
Namespaced extends KubeObjectScope.Namespace
? { readonly namespace: string }
: {}
);
export type KubeObjectMetadata<Namespaced extends KubeObjectScope = KubeObjectScope> =
KubeJsonApiObjectMetadata<Namespaced>
& {
readonly selfLink: string;
readonly uid: string;
readonly name: string;
readonly resourceVersion: string;
};
export type NamespaceScopedMetadata = KubeObjectMetadata<KubeObjectScope.Namespace>;
export type ClusterScopedMetadata = KubeObjectMetadata<KubeObjectScope.Cluster>;
export interface KubeStatusData {
kind: string;
apiVersion: string;
code: number;
message?: string;
reason?: string;
status?: string;
}
export interface EvictionObject {
kind: "Eviction";
apiVersion: string | "policy/v1";
metadata: Partial<KubeObjectMetadata>;
deleteOptions?: {
kind?: string;
apiVersion?: string;
dryRun?: string[];
gracePeriodSeconds?: number;
orphanDependents?: boolean;
propagationPolicy?: string;
preconditions?: {
resourceVersion: string;
uid: string;
}[];
};
}
export function isKubeStatusData(object: unknown): object is KubeStatusData {
return isObject(object)
&& hasTypedProperty(object, "kind", isString)
&& hasTypedProperty(object, "apiVersion", isString)
&& hasTypedProperty(object, "code", isNumber)
&& (
hasOptionalTypedProperty(object, "message", isString)
|| hasOptionalTypedProperty(object, "reason", isString)
|| hasOptionalTypedProperty(object, "status", isString)
)
&& object.kind === "Status";
}
export class KubeStatus {
public readonly kind = "Status";
public readonly apiVersion: string;
public readonly code: number;
public readonly message?: string;
public readonly reason?: string;
public readonly status?: string;
constructor(data: KubeStatusData) {
this.apiVersion = data.apiVersion;
this.code = data.code;
this.message = data.message || "";
this.reason = data.reason || "";
this.status = data.status || "";
}
getExplanation(): string {
const { code, message, reason, status } = this;
return `${code}: ${message || reason || status}`;
}
}
export interface BaseKubeObjectCondition {
/**
* Last time the condition transit from one status to another.
*
* @type Date
*/
lastTransitionTime?: string;
/**
* A human readable message indicating details about last transition.
*/
message?: string;
/**
* brief (usually one word) readon for the condition's last transition.
*/
reason?: string;
/**
* Status of the condition
*/
status: "True" | "False" | "Unknown";
/**
* Type of the condition
*/
type: string;
}
export interface KubeObjectStatus {
conditions?: BaseKubeObjectCondition[];
}
export type KubeMetaField = keyof KubeJsonApiObjectMetadata;
export class KubeCreationError extends Error {
constructor(message: string, public data: any) {
super(message);
}
}
export type LabelMatchExpression = {
/**
* The label key that the selector applies to.
*/
key: string;
} & (
{
/**
* This represents the key's relationship to a set of values.
*/
operator: "Exists" | "DoesNotExist";
values?: undefined;
}
|
{
operator: "In" | "NotIn";
/**
* The set of values for to match according to the operator for the label.
*/
values: string[];
}
);
export interface Toleration {
key?: string;
operator?: string;
effect?: string;
value?: string;
tolerationSeconds?: number;
}
export interface ObjectReference {
apiVersion?: string;
fieldPath?: string;
kind?: string;
name: string;
namespace?: string;
resourceVersion?: string;
uid?: string;
}
export interface LocalObjectReference {
name: string;
}
export interface TypedLocalObjectReference {
apiGroup?: string;
kind: string;
name: string;
}
export interface NodeAffinity {
nodeSelectorTerms?: LabelSelector[];
weight: number;
preference: LabelSelector;
}
export interface PodAffinity {
labelSelector: LabelSelector;
topologyKey: string;
}
export interface SpecificAffinity<T> {
requiredDuringSchedulingIgnoredDuringExecution?: T[];
preferredDuringSchedulingIgnoredDuringExecution?: T[];
}
export interface Affinity {
nodeAffinity?: SpecificAffinity<NodeAffinity>;
podAffinity?: SpecificAffinity<PodAffinity>;
podAntiAffinity?: SpecificAffinity<PodAffinity>;
}
export interface LabelSelector {
matchLabels?: Partial<Record<string, string>>;
matchExpressions?: LabelMatchExpression[];
}
export enum KubeObjectScope {
Namespace,
Cluster,
}
export type ScopedNamespace<Namespaced extends KubeObjectScope> = (
Namespaced extends KubeObjectScope.Namespace
? string
: Namespaced extends KubeObjectScope.Cluster
? undefined
: string | undefined
);
const resourceApplierAnnotationsForFiltering = [
"kubectl.kubernetes.io/last-applied-configuration",
];
const filterOutResourceApplierAnnotations = (label: string) => !resourceApplierAnnotationsForFiltering.some(key => label.startsWith(key));
export interface RawKubeObject<
Metadata extends KubeObjectMetadata = KubeObjectMetadata,
Status = Record<string, unknown>,
Spec = Record<string, unknown>,
> {
apiVersion: string;
kind: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
[otherFields: string]: unknown;
}
export class KubeObject<
Metadata extends KubeObjectMetadata<KubeObjectScope> = KubeObjectMetadata<KubeObjectScope>,
Status = unknown,
Spec = unknown,
> implements ItemObject {
static readonly kind?: string;
static readonly namespaced?: boolean;
static readonly apiBase?: string;
apiVersion!: string;
kind!: string;
metadata!: Metadata;
status?: Status;
spec!: Spec;
static create<
Metadata extends KubeObjectMetadata = KubeObjectMetadata,
Status = unknown,
Spec = unknown,
>(data: KubeJsonApiData<Metadata, Status, Spec>) {
return new KubeObject(data);
}
static isNonSystem(item: KubeJsonApiData | KubeObject<KubeObjectMetadata<KubeObjectScope>, unknown, unknown>) {
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)
&& hasOptionalTypedProperty(object, "resourceVersion", isString)
&& hasOptionalTypedProperty(object, "selfLink", isString)
);
}
static isKubeJsonApiMetadata(object: unknown): object is KubeJsonApiObjectMetadata {
return (
isObject(object)
&& hasTypedProperty(object, "uid", isString)
&& hasTypedProperty(object, "name", isString)
&& hasTypedProperty(object, "resourceVersion", isString)
&& hasOptionalTypedProperty(object, "selfLink", isString)
&& hasOptionalTypedProperty(object, "namespace", isString)
&& hasOptionalTypedProperty(object, "creationTimestamp", isString)
&& hasOptionalTypedProperty(object, "continue", isString)
&& hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString))
&& hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString))
&& hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
static isPartialJsonApiMetadata(object: unknown): object is Partial<KubeJsonApiObjectMetadata> {
return (
isObject(object)
&& hasOptionalTypedProperty(object, "uid", isString)
&& hasOptionalTypedProperty(object, "name", isString)
&& hasOptionalTypedProperty(object, "resourceVersion", isString)
&& hasOptionalTypedProperty(object, "selfLink", isString)
&& hasOptionalTypedProperty(object, "namespace", isString)
&& hasOptionalTypedProperty(object, "creationTimestamp", isString)
&& hasOptionalTypedProperty(object, "continue", isString)
&& hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString))
&& hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString))
&& hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
static isPartialJsonApiData(object: unknown): object is Partial<KubeJsonApiData> {
return (
isObject(object)
&& hasOptionalTypedProperty(object, "kind", isString)
&& hasOptionalTypedProperty(object, "apiVersion", isString)
&& hasOptionalTypedProperty(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?: Partial<Record<string, string>>): string[] {
if (!labels) return [];
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
}
constructor(data: KubeJsonApiData<Metadata, Status, Spec>) {
if (!isObject(data)) {
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
}
if (!isObject(data.metadata)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data);
}
if (!isString(data.metadata.name)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.name being a string`, data);
}
if (!isString(data.metadata.uid)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.uid being a string`, data);
}
if (!isString(data.metadata.resourceVersion)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.resourceVersion being a string`, data);
}
if (!isString(data.metadata.selfLink)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.selfLink being a string`, data);
}
Object.assign(this, data);
autoBind(this);
}
get selfLink() {
return this.metadata.selfLink;
}
getId() {
return this.metadata.uid;
}
getResourceVersion() {
return this.metadata.resourceVersion;
}
getScopedName() {
const ns = this.getNs();
const res = ns ? `${ns}/` : "";
return res + this.getName();
}
getName() {
return this.metadata.name;
}
getNs(): Metadata["namespace"] {
// avoid "null" serialization via JSON.stringify when post data
return (this.metadata.namespace || undefined) as never;
}
/**
* This function computes the number of milliseconds from the UNIX EPOCH to the
* creation timestamp of this object.
*/
getCreationTimestamp() {
if (!this.metadata.creationTimestamp) {
return Date.now();
}
return new Date(this.metadata.creationTimestamp).getTime();
}
/**
* @deprecated This function computes a new "now" on every call which might cause subtle issues if called multiple times
*
* NOTE: Generally you can use `getCreationTimestamp` instead.
*/
getTimeDiffFromNow(): number {
if (!this.metadata.creationTimestamp) {
return 0;
}
return Date.now() - new Date(this.metadata.creationTimestamp).getTime();
}
/**
* @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times
*
* NOTE: this function also is not reactive to updates in the current time so it should not be used for renderering
*/
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);
if (!filter) {
return labels;
}
return labels.filter(filterOutResourceApplierAnnotations);
}
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() {
return JSON.parse(JSON.stringify(this)) as JsonObject;
}
/**
* @deprecated use KubeApi.patch instead
*/
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
const di = getLegacyGlobalDiForExtensionApi();
const requestKubeObjectPatch = di.inject(requestKubeObjectPatchInjectable);
const result = await requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch);
if (!result.callWasSuccessful) {
throw new Error(result.error);
}
return result.response;
}
/**
* Perform a full update (or more specifically a replace)
*
* Note: this is brittle if `data` is not actually partial (but instead whole).
* As fields such as `resourceVersion` will probably out of date. This is a
* common race condition.
*
* @deprecated use KubeApi.update instead
*/
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
const di = getLegacyGlobalDiForExtensionApi();
const requestKubeObjectCreation = di.inject(requestKubeObjectCreationInjectable);
const descriptor = dump({
...this.toPlainObject(),
...data,
});
const result = await requestKubeObjectCreation(descriptor);
if (!result.callWasSuccessful) {
throw new Error(result.error);
}
return result.response;
}
/**
* @deprecated use KubeApi.delete instead
*/
delete(params?: object) {
assert(this.selfLink, "selfLink must be present to delete self");
const di = getLegacyGlobalDiForExtensionApi();
const apiKube = di.inject(apiKubeInjectionToken);
return apiKube.del(this.selfLink, params);
}
}

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeStatusData } from "./kube-object";
import type { KubeStatusData } from "@k8slens/kube-object";
export type IKubeWatchEvent<T> = {
readonly type: "ADDED" | "MODIFIED" | "DELETED";

View File

@ -16,7 +16,7 @@ import { asLegacyGlobalFunctionForExtensionApi, asLegacyGlobalForExtensionApi, g
import type { KubernetesCluster } from "./catalog";
import type { KubeApiDataFrom, KubeObjectStoreOptions } from "../../common/k8s-api/kube-object.store";
import { KubeObjectStore as InternalKubeObjectStore } from "../../common/k8s-api/kube-object.store";
import type { KubeJsonApiDataFor, KubeObject } from "../../common/k8s-api/kube-object";
import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies, KubeApiOptions } from "../../common/k8s-api/kube-api";
import { KubeApi as InternalKubeApi } from "../../common/k8s-api/kube-api";
import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
@ -120,7 +120,17 @@ export type { CreateKubeApiForLocalClusterConfig as ILocalKubeApiConfig } from "
export {
KubeObject,
KubeStatus,
} from "../../common/k8s-api/kube-object";
isJsonApiData,
isJsonApiDataList,
isKubeJsonApiListMetadata,
isKubeJsonApiMetadata,
isKubeObjectNonSystem,
isKubeStatusData,
isPartialJsonApiData,
isPartialJsonApiMetadata,
createKubeObject,
stringifyLabels,
} from "@k8slens/kube-object";
export type {
OwnerReference,
KubeObjectMetadata,
@ -130,11 +140,8 @@ export type {
KubeJsonApiObjectMetadata,
KubeStatusData,
KubeJsonApiDataFor,
} from "../../common/k8s-api/kube-object";
export type {
KubeJsonApiData,
} from "../../common/k8s-api/kube-json-api";
} from "@k8slens/kube-object";
function KubeJsonApiCstr(config: JsonApiConfig, reqInit?: RequestInit) {
const di = getLegacyGlobalDiForExtensionApi();
@ -291,4 +298,4 @@ export {
ClusterRole,
ClusterRoleBinding,
CustomResourceDefinition,
} from "../../common/k8s-api/endpoints";
} from "@k8slens/kube-object";

View File

@ -3,22 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RenderResult } from "@testing-library/react";
import type {
ApplicationBuilder,
} from "../../../../renderer/components/test-utils/get-application-builder";
import {
getApplicationBuilder,
} from "../../../../renderer/components/test-utils/get-application-builder";
import type { ApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
import React from "react";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable";
import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
import type { KubeApi } from "../../../../common/k8s-api/kube-api";
import showDetailsInjectable
from "../../../../renderer/components/kube-detail-params/show-details.injectable";
import type {
FakeExtensionOptions,
} from "../../../../renderer/components/test-utils/get-extension-fake";
import showDetailsInjectable from "../../../../renderer/components/kube-detail-params/show-details.injectable";
import type { FakeExtensionOptions } from "../../../../renderer/components/test-utils/get-extension-fake";
import { observable } from "mobx";
describe("disable kube object detail items when cluster is not relevant", () => {
@ -37,7 +30,7 @@ describe("disable kube object detail items when cluster is not relevant", () =>
} as Partial<KubeApi<KubeObject>> as KubeApi<KubeObject>;
const store = {
api,
loadFromPath: async () => getKubeObjectStub("some-kind", "some-api-version"),
loadFromPath: async () => Promise.resolve(getKubeObjectStub("some-kind", "some-api-version")),
getByPath() {
},
} as Partial<KubeObjectStore<KubeObject>> as KubeObjectStore<KubeObject>;

View File

@ -8,7 +8,7 @@ import { getApplicationBuilder } from "../../../../renderer/components/test-util
import type { IObservableValue } from "mobx";
import { runInAction, computed, observable } from "mobx";
import React from "react";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable";
import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
import type { KubeApi } from "../../../../common/k8s-api/kube-api";
@ -33,7 +33,7 @@ describe("reactively hide kube object detail item", () => {
} as Partial<KubeApi<KubeObject>> as KubeApi<KubeObject>;
const store = {
api,
loadFromPath: async () => getKubeObjectStub("some-kind", "some-api-version"),
loadFromPath: async () => Promise.resolve(getKubeObjectStub("some-kind", "some-api-version")),
getByPath() {
},
} as Partial<KubeObjectStore<KubeObject>> as KubeObjectStore<KubeObject>;

View File

@ -14,7 +14,7 @@ import { computed, runInAction } from "mobx";
import React from "react";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectMenu } from "../../../../renderer/components/kube-object-menu";
describe("disable kube object menu items when cluster is not relevant", () => {

View File

@ -12,7 +12,7 @@ import { observable, runInAction, computed } from "mobx";
import React from "react";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectMenu } from "../../../../renderer/components/kube-object-menu";
describe("reactively hide kube object menu item", () => {

View File

@ -15,7 +15,7 @@ import React from "react";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
import { KubeObjectStatusIcon } from "../../../../renderer/components/kube-object-status-icon/kube-object-status-icon";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectStatusLevel } from "../../../../common/k8s-api/kube-object-status";
describe("disable kube object statuses when cluster is not relevant", () => {

View File

@ -12,7 +12,7 @@ import { observable, runInAction, computed } from "mobx";
import React from "react";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import { KubeObjectStatusLevel } from "../../../../common/k8s-api/kube-object-status";
import { KubeObjectStatusIcon } from "../../../../renderer/components/kube-object-status-icon";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObjectStatusLevel } from "../../../common/k8s-api/kube-object-status";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import React from "react";
import type { DiContainer } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";

View File

@ -13,7 +13,7 @@ import getRandomIdForEditResourceTabInjectable from "../../../renderer/component
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import dockStoreInjectable from "../../../renderer/components/dock/dock/store.injectable";
import { Namespace } from "../../../common/k8s-api/endpoints";
import { Namespace } from "@k8slens/kube-object";
import showSuccessNotificationInjectable from "../../../renderer/components/notifications/show-success-notification.injectable";
import showErrorNotificationInjectable from "../../../renderer/components/notifications/show-error-notification.injectable";
import readJsonFileInjectable from "../../../common/fs/read-json-file.injectable";
@ -23,8 +23,7 @@ import type { ApiKubePatch } from "../../../renderer/k8s/api-kube-patch.injectab
import type { ApiKubeGet } from "../../../renderer/k8s/api-kube-get.injectable";
import apiKubePatchInjectable from "../../../renderer/k8s/api-kube-patch.injectable";
import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable";
import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api";
import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope } from "../../../common/k8s-api/kube-object";
import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope, KubeJsonApiData } from "@k8slens/kube-object";
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
import type { ShowNotification } from "../../../renderer/components/notifications";
import React from "react";
@ -542,9 +541,9 @@ metadata:
readJsonFileInjectable,
);
const actual = (await readJsonFile(
const actual = await readJsonFile(
"/some-directory-for-lens-local-storage/some-cluster-id.json",
)) as any;
) as Record<string, Record<string, unknown>>;
expect(
actual.edit_resource_store["some-first-tab-id"],

View File

@ -13,7 +13,7 @@ import requestKubeResourceInjectable from "../../../renderer/components/dock/edi
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import writeJsonFileInjectable from "../../../common/fs/write-json-file.injectable";
import { TabKind } from "../../../renderer/components/dock/dock/store";
import { Namespace } from "../../../common/k8s-api/endpoints";
import { Namespace } from "@k8slens/kube-object";
describe("cluster/namespaces - edit namespaces from previously opened tab", () => {
let builder: ApplicationBuilder;

View File

@ -7,9 +7,11 @@ import type { RenderResult } from "@testing-library/react";
import navigateToPodsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable";
import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import podStoreInjectable from "../../../renderer/components/workloads-pods/store.injectable";
import type { PodMetrics } from "../../../common/k8s-api/endpoints";
import { Pod } from "../../../common/k8s-api/endpoints";
import type { PodMetrics, PodStatus } from "@k8slens/kube-object";
import { Pod } from "@k8slens/kube-object";
import type { PodMetricsApi } from "../../../common/k8s-api/endpoints/pod-metrics.api";
import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable";
import type { RequestMetrics } from "../../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
import requestMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
@ -18,7 +20,7 @@ describe("workloads / pods", () => {
let applicationBuilder: ApplicationBuilder;
const podMetrics: PodMetrics[] = [];
beforeEach(async () => {
beforeEach(() => {
applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame();
applicationBuilder.namespaces.add("default");
applicationBuilder.beforeWindowStart(({ windowDi }) => {
@ -28,8 +30,8 @@ describe("workloads / pods", () => {
});
windowDi.override(podMetricsApiInjectable, () => ({
list: async () => podMetrics,
} as any));
list: async () => Promise.resolve(podMetrics),
} as PodMetricsApi));
const apiManager = windowDi.inject(apiManagerInjectable);
const podStore = windowDi.inject(podStoreInjectable);
@ -79,7 +81,7 @@ describe("workloads / pods", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("shows item list is empty", async () => {
it("shows item list is empty", () => {
expect(rendered.getByText("Item list is empty")).toBeInTheDocument();
});
});
@ -87,7 +89,7 @@ describe("workloads / pods", () => {
describe("given a namespace has pods", () => {
beforeEach(async () => {
applicationBuilder.afterWindowStart(({ windowDi }) => {
windowDi.override(requestMetricsInjectable, () => () => ({} as any));
windowDi.override(requestMetricsInjectable, () => (() => Promise.resolve({})) as unknown as RequestMetrics);
const podStore = windowDi.inject(podStoreInjectable);
@ -111,7 +113,7 @@ describe("workloads / pods", () => {
},
],
},
status: {} as any,
status: {} as PodStatus,
}));
podStore.isLoaded = true;
});

View File

@ -27,7 +27,7 @@ import stopLoadingLogsInjectable from "../../renderer/components/dock/logs/stop-
import { dockerPod } from "../../renderer/components/dock/logs/__test__/pod.mock";
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import type { Container } from "../../common/k8s-api/endpoints";
import type { Container } from "@k8slens/kube-object";
describe("download logs options in logs dock tab", () => {
let windowDi: DiContainer;
@ -36,7 +36,7 @@ describe("download logs options in logs dock tab", () => {
let openSaveFileDialogMock: jest.MockedFunction<() => void>;
let callForLogsMock: jest.MockedFunction<CallForLogs>;
let getLogsMock: jest.Mock;
let getSplittedLogsMock: jest.Mock;
let getSplitLogsMock: jest.Mock;
let showErrorNotificationMock: jest.Mock;
const logs = new Map([["timestamp", "some-logs"]]);
const pod = dockerPod;
@ -55,7 +55,7 @@ describe("download logs options in logs dock tab", () => {
callForLogsMock = jest.fn();
getLogsMock = jest.fn();
getSplittedLogsMock = jest.fn();
getSplitLogsMock = jest.fn();
builder.beforeWindowStart(({ windowDi }) => {
windowDi.override(callForLogsInjectable, () => callForLogsMock);
@ -63,7 +63,7 @@ describe("download logs options in logs dock tab", () => {
// Overriding internals of logsViewModelInjectable
windowDi.override(getLogsInjectable, () => getLogsMock);
windowDi.override(getLogsWithoutTimestampsInjectable, () => getLogsMock);
windowDi.override(getTimestampSplitLogsInjectable, () => getSplittedLogsMock);
windowDi.override(getTimestampSplitLogsInjectable, () => getSplitLogsMock);
windowDi.override(reloadLogsInjectable, () => jest.fn());
windowDi.override(getLogTabDataInjectable, () => () => ({
selectedPodId: selectedPod.getId(),
@ -106,11 +106,11 @@ describe("download logs options in logs dock tab", () => {
});
describe("when logs not available", () => {
beforeEach(async () => {
beforeEach(() => {
const createLogsTab = windowDi.inject(createPodLogsTabInjectable);
getLogsMock.mockReturnValue([]);
getSplittedLogsMock.mockReturnValue([]);
getSplitLogsMock.mockReturnValue([]);
createLogsTab({
selectedPod: pod,
@ -130,11 +130,11 @@ describe("download logs options in logs dock tab", () => {
});
describe("when logs available", () => {
beforeEach(async () => {
beforeEach(() => {
const createLogsTab = windowDi.inject(createPodLogsTabInjectable);
getLogsMock.mockReturnValue(["some-logs"]);
getSplittedLogsMock.mockReturnValue([...logs]);
getSplitLogsMock.mockReturnValue([...logs]);
createLogsTab({
selectedPod: pod,
@ -174,13 +174,13 @@ describe("download logs options in logs dock tab", () => {
});
describe("when call for all logs resolves with logs", () => {
beforeEach(async () => {
beforeEach(() => {
callForLogsMock.mockResolvedValue("all-logs");
});
describe("when selected 'download all logs'", () => {
beforeEach(async () => {
await act(async () => {
beforeEach(() => {
act(() => {
const button = rendered.getByTestId("download-all-logs");
button.click();
@ -194,11 +194,11 @@ describe("download logs options in logs dock tab", () => {
);
});
it("shows save dialog with proper attributes", async () => {
it("shows save dialog with proper attributes", () => {
expect(openSaveFileDialogMock).toHaveBeenCalledWith("docker-exporter.log", "all-logs", "text/plain");
});
it("doesn't block download dropdown for interaction after click", async () => {
it("doesn't block download dropdown for interaction after click", () => {
expect(rendered.getByTestId("download-logs-dropdown")).not.toHaveAttribute("disabled");
});
});
@ -225,13 +225,13 @@ describe("download logs options in logs dock tab", () => {
});
describe("when call for logs resolves with no logs", () => {
beforeEach(async () => {
beforeEach(() => {
callForLogsMock.mockResolvedValue("");
});
describe("when selected 'download all logs'", () => {
beforeEach(async () => {
await act(async () => {
beforeEach(() => {
act(() => {
const button = rendered.getByTestId("download-all-logs");
button.click();
@ -254,8 +254,8 @@ describe("download logs options in logs dock tab", () => {
});
describe("when selected 'download all logs'", () => {
beforeEach(async () => {
await act(async () => {
beforeEach(() => {
act(() => {
const button = rendered.getByTestId("download-all-logs");
button.click();
@ -269,7 +269,7 @@ describe("download logs options in logs dock tab", () => {
);
});
it("doesn't show save dialog", async () => {
it("doesn't show save dialog", () => {
expect(openSaveFileDialogMock).not.toHaveBeenCalled();
});
});

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncResult } from "@k8slens/utilities";
import execHelmInjectable from "../../../exec-helm/exec-helm.injectable";
import yaml from "js-yaml";
import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../../common/k8s-api/kube-json-api";
import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object";
const callForHelmManifestInjectable = getInjectable({
id: "call-for-helm-manifest",

View File

@ -9,7 +9,7 @@ import execFileWithInputInjectable from "./exec-file-with-input/exec-file-with-i
import { getErrorMessage } from "../../../../../common/utils/get-error-message";
import { map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
export type CallForKubeResourcesByManifest = (
namespace: string,

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable";
import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../common/k8s-api/kube-json-api";
import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object";
import type { AsyncResult } from "@k8slens/utilities";
export type GetHelmReleaseResources = (

View File

@ -13,7 +13,7 @@ import asyncFn from "@async-fn/jest";
import type { ExecFileWithInput } from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
import execFileWithInputInjectable from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
import type { AsyncResult } from "@k8slens/utilities";
import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api";
import type { KubeJsonApiData } from "@k8slens/kube-object";
describe("get helm release resources", () => {
let getHelmReleaseResources: GetHelmReleaseResources;

View File

@ -15,6 +15,7 @@ import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
import { initialNodeShellImage } from "../../../common/cluster-types";
import type { LoadProxyKubeconfig } from "../../cluster/load-proxy-kubeconfig.injectable";
import type { Pod } from "@k8slens/kube-object";
export interface NodeShellSessionArgs extends ShellSessionArgs {
nodeName: string;
@ -148,13 +149,13 @@ export class NodeShellSession extends ShellSession {
.watch(`/api/v1/namespaces/kube-system/pods`,
{},
// callback is called for each received object.
(type, { metadata: { name }, status }) => {
(type, { metadata: { name }, status }: Pod) => {
if (name === this.podName) {
switch (status.phase) {
switch (status?.phase) {
case "Running":
return resolve();
case "Failed":
return reject(`Failed to be created: ${status.message || "unknown error"}`);
return reject(`Failed to be created: ${(status as unknown as Record<string, string>).message || "unknown error"}`);
}
}
},
@ -167,12 +168,13 @@ export class NodeShellSession extends ShellSession {
.then(req => {
setTimeout(() => {
this.dependencies.logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
req.abort();
reject("Pod creation timed out");
}, 2 * 60 * 1000); // 2 * 60 * 1000
})
.catch(error => {
this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${String(error)}`);
reject(error);
});
});

View File

@ -5,9 +5,9 @@
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import { customResourceDefinitionApiInjectionToken } from "../../../common/k8s-api/api-manager/crd-api-token";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
import type { CustomResourceDefinition } from "@k8slens/kube-object";
import { KubeApi } from "../../../common/k8s-api/kube-api";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { KubeObject } from "@k8slens/kube-object";
import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper";

View File

@ -4,7 +4,7 @@
*/
import type { CronJobStore } from "../workloads-cronjobs/store";
import cronJobStoreInjectable from "../workloads-cronjobs/store.injectable";
import { CronJob } from "../../../common/k8s-api/endpoints";
import { CronJob } from "@k8slens/kube-object";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";

View File

@ -7,7 +7,7 @@ import { observable } from "mobx";
import type { DaemonSetStore } from "../workloads-daemonsets/store";
import daemonSetStoreInjectable from "../workloads-daemonsets/store.injectable";
import podStoreInjectable from "../workloads-pods/store.injectable";
import { DaemonSet, Pod } from "../../../common/k8s-api/endpoints";
import { DaemonSet, Pod } from "@k8slens/kube-object";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";

Some files were not shown because too many files have changed in this diff Show More