mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Extract @k8slens/kube-object package
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
576373b16b
commit
1bf24db797
466 files changed
+6838
-6375
No files matched your search
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types";
|
||||
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(", ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types";
|
||||
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 || [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import autoBind from "auto-bind";
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { formatDuration } from "@k8slens/utilities";
|
||||
import moment from "moment";
|
||||
import type { ObjectReference, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { JobTemplateSpec } from "../types/job-template-spec";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { buildURL } from "@k8slens/utilities";
|
||||
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { JSONSchemaProps } from "../types/json-schema-props";
|
||||
import type { WebhookClientConfig } from "./mutating-webhook-configuration";
|
||||
|
||||
export 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 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() {
|
||||
// TODO: replace this magic string with a use of `customResourcesRouteInjectable` when that is extracted
|
||||
return buildURL("/crd/:group?/:name?", {
|
||||
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})`,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types";
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodSpec } from "./pod";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import autoBind from "auto-bind";
|
||||
import type {
|
||||
ObjectReference,
|
||||
KubeJsonApiData,
|
||||
KubeObjectMetadata,
|
||||
KubeObjectScope,
|
||||
NamespaceScopedMetadata,
|
||||
} from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { formatDuration } from "@k8slens/utilities";
|
||||
import moment from "moment";
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { OptionVariant } from "@k8slens/utilities";
|
||||
import type { LabelSelector, BaseKubeObjectCondition, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { CrossVersionObjectReference } from "../types/cross-version-object-reference";
|
||||
|
||||
export type HpaMetricType = "Resource" | "Pods" | "Object" | "External" | "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<"Resource", BaseHorizontalPodAutoscalerMetricSpec, "resource">
|
||||
| OptionVariant<"External", BaseHorizontalPodAutoscalerMetricSpec, "external">
|
||||
| OptionVariant<"Object", BaseHorizontalPodAutoscalerMetricSpec, "object">
|
||||
| OptionVariant<"Pods", BaseHorizontalPodAutoscalerMetricSpec, "pods">
|
||||
| OptionVariant<"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<"Resource", BaseHorizontalPodAutoscalerMetricStatus, "resource">
|
||||
| OptionVariant<"External", BaseHorizontalPodAutoscalerMetricStatus, "external">
|
||||
| OptionVariant<"Object", BaseHorizontalPodAutoscalerMetricStatus, "object">
|
||||
| OptionVariant<"Pods", BaseHorizontalPodAutoscalerMetricStatus, "pods">
|
||||
| OptionVariant<"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 ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./cluster-role-binding";
|
||||
export * from "./cluster-role";
|
||||
export * from "./cluster";
|
||||
export * from "./component-status";
|
||||
export * from "./config-map";
|
||||
export * from "./cron-job";
|
||||
export * from "./custom-resource-definition";
|
||||
export * from "./daemon-set";
|
||||
export * from "./deployment";
|
||||
export * from "./endpoint";
|
||||
export * from "./events";
|
||||
export * from "./horizontal-pod-autoscaler";
|
||||
export * from "./ingress-class";
|
||||
export * from "./ingress";
|
||||
export * from "./job";
|
||||
export * from "./lease";
|
||||
export * from "./limit-range";
|
||||
export * from "./mutating-webhook-configuration";
|
||||
export * from "./namespace";
|
||||
export * from "./network-policy";
|
||||
export * from "./node";
|
||||
export * from "./persistent-volume-claim";
|
||||
export * from "./persistent-volume";
|
||||
export * from "./pod-disruption-budget";
|
||||
export * from "./pod-metrics";
|
||||
export * from "./pod-security-policy";
|
||||
export * from "./pod";
|
||||
export * from "./priority-class";
|
||||
export * from "./replica-set";
|
||||
export * from "./replication-controller";
|
||||
export * from "./resource-quota";
|
||||
export * from "./role-binding";
|
||||
export * from "./role";
|
||||
export * from "./runtime-class";
|
||||
export * from "./secret";
|
||||
export * from "./self-subject-rules-reviews";
|
||||
export * from "./service-account";
|
||||
export * from "./service";
|
||||
export * from "./stateful-set";
|
||||
export * from "./storage-class";
|
||||
export * from "./validating-webhook-configuration";
|
||||
export * from "./vertical-pod-autoscaler";
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export interface IngressClassParametersReference {
|
||||
/**
|
||||
* For example: `"k8s.example.net"`
|
||||
*/
|
||||
apiGroup: string;
|
||||
scope: "Namespace" | "Cluster";
|
||||
kind: "ClusterIngressParameter" | "IngressParameter";
|
||||
name: string;
|
||||
/**
|
||||
* The namespace for IngressClass must be defined in `spec.parameters.namespace` instead of `metadata.namespace` (!)
|
||||
*/
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface IngressClassSpec {
|
||||
controller: string; // "example.com/ingress-controller"
|
||||
parameters?: IngressClassParametersReference;
|
||||
}
|
||||
|
||||
export interface IngressClassStatus {}
|
||||
|
||||
export class IngressClass extends KubeObject<ClusterScopedMetadata, 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;
|
||||
}
|
||||
|
||||
get isDefault() {
|
||||
return this.metadata.annotations?.[IngressClass.ANNOTATION_IS_DEFAULT] === "true";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { hasTypedProperty, isString, iter } from "@k8slens/utilities";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
import type { TypedLocalObjectReference, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line xss/no-mixed-html
|
||||
const unknownPortString = "<unknown>";
|
||||
|
||||
/**
|
||||
* 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 unknownPortString;
|
||||
}
|
||||
|
||||
if (isExtensionsBackend(backend)) {
|
||||
return `${backend.serviceName}:${backend.servicePort}`;
|
||||
}
|
||||
|
||||
if (backend.service) {
|
||||
const { name, port } = backend.service;
|
||||
|
||||
return `${name}:${port.number ?? port.name}`;
|
||||
}
|
||||
|
||||
return unknownPortString;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { Container } from "../types/container";
|
||||
import type { PodSpec } from "./pod";
|
||||
|
||||
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) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { NamespaceScopedMetadata } from "../api-types";
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
LabelSelector,
|
||||
KubeJsonApiData,
|
||||
KubeObjectMetadata,
|
||||
KubeObjectScope,
|
||||
NamespaceScopedMetadata,
|
||||
} from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export interface WebhookClientConfig {
|
||||
/**
|
||||
* The location of the webhook
|
||||
*/
|
||||
url?: string;
|
||||
|
||||
/**
|
||||
* A reference to the service for this webhook. Either `service` or `url` must be specified.
|
||||
*/
|
||||
service?: ServiceReference;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
export interface RuleWithOperations {
|
||||
/**
|
||||
* The API groups the resources belong to. '*' is all groups.
|
||||
* If '*' is present, the length of the slice must be one.
|
||||
*/
|
||||
apiGroups: string[];
|
||||
|
||||
/**
|
||||
* The API versions the resources belong to. '*' is all versions.
|
||||
*/
|
||||
apiVersions?: string[];
|
||||
|
||||
/**
|
||||
* 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[];
|
||||
|
||||
/**
|
||||
* A list of operations this rule applies to.
|
||||
*/
|
||||
operations: ("CREATE" | "UPDATE" | "DELETE" | "CONNECT")[];
|
||||
|
||||
/**
|
||||
* The scope of this rule.
|
||||
*
|
||||
* @default "Cluster"
|
||||
*/
|
||||
scope?: "Cluster" | "Namespace";
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
/**
|
||||
* The name of the webhook configuration.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 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[];
|
||||
|
||||
/**
|
||||
* 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[];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Specifies how unrecognized errors from the webhook are handled
|
||||
* @default "Fail"
|
||||
*/
|
||||
failurePolicy?: "Ignore" | "Fail";
|
||||
|
||||
/**
|
||||
* Defines how the "rules" list is used to match incoming requests.
|
||||
* - 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.
|
||||
* @default "Equivalent"
|
||||
*/
|
||||
matchPolicy?: "Exact" | "Equivalent";
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Indicates whether this webhook should be called multiple times as part of a single admission evaluation.
|
||||
*/
|
||||
reinvocationPolicy?: "Never" | "IfNeeded";
|
||||
}
|
||||
|
||||
export interface ServiceReference {
|
||||
/**
|
||||
* The namespace of the service.
|
||||
*/
|
||||
namespace: string;
|
||||
|
||||
/**
|
||||
* The name of the service.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The URL path which will be sent in any request to this service.
|
||||
*/
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* The service port which will be used when accessing the service.
|
||||
*/
|
||||
port?: number | string;
|
||||
}
|
||||
|
||||
export interface MutatingWebhookConfigurationData
|
||||
extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
|
||||
webhooks?: Webhook[];
|
||||
}
|
||||
|
||||
export class MutatingWebhookConfiguration extends KubeObject<NamespaceScopedMetadata, void, void> {
|
||||
static kind = "MutatingWebhookConfiguration";
|
||||
|
||||
static namespaced = false;
|
||||
|
||||
static apiBase = "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations";
|
||||
|
||||
webhooks?: Webhook[];
|
||||
|
||||
constructor({ webhooks, ...rest }: MutatingWebhookConfigurationData) {
|
||||
super(rest);
|
||||
this.webhooks = webhooks;
|
||||
}
|
||||
|
||||
getWebhooks(): Webhook[] {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeObjectStatus, ClusterScopedMetadata } from "../api-types";
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export interface PolicyIpBlock {
|
||||
cidr: string;
|
||||
except?: string[];
|
||||
}
|
||||
|
||||
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?: PolicyIpBlock;
|
||||
|
||||
/**
|
||||
* 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 PolicyIngress {
|
||||
from?: NetworkPolicyPeer[];
|
||||
ports?: NetworkPolicyPort[];
|
||||
}
|
||||
|
||||
export interface PolicyEgress {
|
||||
to?: NetworkPolicyPeer[];
|
||||
ports?: NetworkPolicyPort[];
|
||||
}
|
||||
|
||||
export type PolicyType = "Ingress" | "Egress";
|
||||
|
||||
export interface NetworkPolicySpec {
|
||||
podSelector: LabelSelector;
|
||||
policyTypes?: PolicyType[];
|
||||
ingress?: PolicyIngress[];
|
||||
egress?: PolicyEgress[];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Node } from "./node";
|
||||
|
||||
describe("Node tests", () => {
|
||||
describe("isMasterNode()", () => {
|
||||
it("given a master node labelled before kubernetes 1.20, should return true", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/master": "NoSchedule",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.isMasterNode()).toBe(true);
|
||||
});
|
||||
|
||||
it("given a master node labelled after kubernetes 1.20, should return true", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/control-plane": "NoSchedule",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.isMasterNode()).toBe(true);
|
||||
});
|
||||
|
||||
it("given a non master node, should return false", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.isMasterNode()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRoleLabels()", () => {
|
||||
it("should return empty string if labels is not present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("");
|
||||
});
|
||||
|
||||
it("should return empty string if labels is empty object", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("");
|
||||
});
|
||||
|
||||
it("should return rest of keys with substring node-role.kubernetes.io/", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/foobar": "bat",
|
||||
"hellonode-role.kubernetes.io/foobar1": "bat",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, foobar1");
|
||||
});
|
||||
|
||||
it("should return rest of keys with substring node-role.kubernetes.io/ after last /", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/foobar": "bat",
|
||||
"hellonode-role.kubernetes.io//////foobar1": "bat",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, foobar1");
|
||||
});
|
||||
|
||||
it("should return value of label kubernetes.io/role if present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"kubernetes.io/role": "master",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("master");
|
||||
});
|
||||
|
||||
it("should return value of label node.kubernetes.io/role if present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node.kubernetes.io/role": "master",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("master");
|
||||
});
|
||||
|
||||
it("all sources should be joined together", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"aksjhdkjahsdnode-role.kubernetes.io/foobar": "bat",
|
||||
"kubernetes.io/role": "master",
|
||||
"node.kubernetes.io/role": "master-v2-max",
|
||||
},
|
||||
selfLink: "/api/v1/nodes/bar",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, master, master-v2-max");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isObject, cpuUnitsToNumber, unitsToBytes } from "@k8slens/utilities";
|
||||
import { TypedRegEx } from "typed-regex";
|
||||
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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.
|
||||
*/
|
||||
// eslint-disable-next-line xss/no-mixed-html
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Pod } from "./pod";
|
||||
import { object } from "@k8slens/utilities";
|
||||
import type { TypedLocalObjectReference, LabelSelector, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { ResourceRequirements } from "../types/resource-requirements";
|
||||
|
||||
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 ?? "-";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { unitsToBytes } from "@k8slens/utilities";
|
||||
import type { TypedLocalObjectReference, LabelSelector, ObjectReference, ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
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 || "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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 : "",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import type { Container } from "../types/container";
|
||||
import { Pod, type PodContainerStatus } from "./pod";
|
||||
|
||||
interface GetDummyPodOptions {
|
||||
running?: number;
|
||||
dead?: number;
|
||||
initRunning?: number;
|
||||
initDead?: number;
|
||||
}
|
||||
|
||||
function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
const { running = 0, dead = 0, initDead = 0, initRunning = 0 } = rawOpts;
|
||||
|
||||
const containers: Container[] = [];
|
||||
const initContainers: Container[] = [];
|
||||
const containerStatuses: PodContainerStatus[] = [];
|
||||
const initContainerStatuses: PodContainerStatus[] = [];
|
||||
const pod = new Pod({
|
||||
apiVersion: "v1",
|
||||
kind: "Pod",
|
||||
metadata: {
|
||||
uid: "1",
|
||||
name: "test",
|
||||
resourceVersion: "v1",
|
||||
namespace: "default",
|
||||
selfLink: "/api/v1/pods/default/test",
|
||||
},
|
||||
spec: {
|
||||
containers,
|
||||
initContainers,
|
||||
serviceAccount: "dummy",
|
||||
serviceAccountName: "dummy",
|
||||
},
|
||||
status: {
|
||||
phase: "Running",
|
||||
conditions: [],
|
||||
hostIP: "10.0.0.1",
|
||||
podIP: "10.0.0.1",
|
||||
startTime: "now",
|
||||
containerStatuses,
|
||||
initContainerStatuses,
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 0; i < running; i += 1) {
|
||||
const name = `container_running_${i}`;
|
||||
|
||||
containers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
containerStatuses.push({
|
||||
image: "dummy",
|
||||
imageID: "dummy",
|
||||
name,
|
||||
ready: true,
|
||||
restartCount: i,
|
||||
state: {
|
||||
running: {
|
||||
startedAt: "before",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < dead; i += 1) {
|
||||
const name = `container_dead_${i}`;
|
||||
|
||||
containers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
containerStatuses.push({
|
||||
image: "dummy",
|
||||
imageID: "dummy",
|
||||
name,
|
||||
ready: false,
|
||||
restartCount: i,
|
||||
state: {
|
||||
terminated: {
|
||||
startedAt: "before",
|
||||
exitCode: i + 1,
|
||||
finishedAt: "later",
|
||||
reason: `reason_${i}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < initRunning; i += 1) {
|
||||
const name = `container_init-running_${i}`;
|
||||
|
||||
initContainers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
initContainerStatuses.push({
|
||||
image: "dummy",
|
||||
imageID: "dummy",
|
||||
name,
|
||||
ready: true,
|
||||
restartCount: i,
|
||||
state: {
|
||||
running: {
|
||||
startedAt: "before",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < initDead; i += 1) {
|
||||
const name = `container_init-dead_${i}`;
|
||||
|
||||
initContainers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
initContainerStatuses.push({
|
||||
image: "dummy",
|
||||
imageID: "dummy",
|
||||
name,
|
||||
ready: false,
|
||||
restartCount: i,
|
||||
state: {
|
||||
terminated: {
|
||||
startedAt: "before",
|
||||
exitCode: i + 1,
|
||||
finishedAt: "later",
|
||||
reason: `reason_${i}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return pod;
|
||||
}
|
||||
|
||||
describe("Pods", () => {
|
||||
const podTests = [];
|
||||
|
||||
for (let r = 0; r < 3; r += 1) {
|
||||
for (let d = 0; d < 3; d += 1) {
|
||||
for (let ir = 0; ir < 3; ir += 1) {
|
||||
for (let id = 0; id < 3; id += 1) {
|
||||
podTests.push([r, d, ir, id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe.each(podTests)(
|
||||
"for [%d running, %d dead] & initial [%d running, %d dead]",
|
||||
(running, dead, initRunning, initDead) => {
|
||||
const pod = getDummyPod({ running, dead, initRunning, initDead });
|
||||
|
||||
function getNamedContainer(name: string) {
|
||||
return {
|
||||
image: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
it("getRunningContainers should return only running and init running", () => {
|
||||
const res = [
|
||||
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
|
||||
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
|
||||
];
|
||||
|
||||
expect(pod.getRunningContainers()).toStrictEqual(res);
|
||||
});
|
||||
|
||||
it("getAllContainers should return all containers", () => {
|
||||
const res = [
|
||||
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
|
||||
...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_dead_${index}`)),
|
||||
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
|
||||
...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_init-dead_${index}`)),
|
||||
];
|
||||
|
||||
expect(pod.getAllContainers()).toStrictEqual(res);
|
||||
});
|
||||
|
||||
it("getRestartsCount should return total restart counts", () => {
|
||||
function sum(len: number): number {
|
||||
let res = 0;
|
||||
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
res += i;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
expect(pod.getRestartsCount()).toStrictEqual(sum(running) + sum(dead));
|
||||
});
|
||||
|
||||
it("hasIssues should return false", () => {
|
||||
expect(pod.hasIssues()).toStrictEqual(false);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("getSelectedNodeOs", () => {
|
||||
it("should return stable", () => {
|
||||
const pod = getDummyPod();
|
||||
|
||||
pod.spec.nodeSelector = {
|
||||
"kubernetes.io/os": "foobar",
|
||||
};
|
||||
|
||||
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar");
|
||||
});
|
||||
|
||||
it("should return beta", () => {
|
||||
const pod = getDummyPod();
|
||||
|
||||
pod.spec.nodeSelector = {
|
||||
"beta.kubernetes.io/os": "foobar1",
|
||||
};
|
||||
|
||||
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar1");
|
||||
});
|
||||
|
||||
it("should return stable over beta", () => {
|
||||
const pod = getDummyPod();
|
||||
|
||||
pod.spec.nodeSelector = {
|
||||
"kubernetes.io/os": "foobar2",
|
||||
"beta.kubernetes.io/os": "foobar3",
|
||||
};
|
||||
|
||||
expect(pod.getSelectedNodeOs()).toStrictEqual("foobar2");
|
||||
});
|
||||
|
||||
it("should return undefined if none set", () => {
|
||||
const pod = getDummyPod();
|
||||
|
||||
expect(pod.getSelectedNodeOs()).toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasIssues", () => {
|
||||
it("should return true if a condition isn't ready", () => {
|
||||
const pod = getDummyPod({ running: 1 });
|
||||
|
||||
pod.status?.conditions.push({
|
||||
type: "Ready",
|
||||
status: "foobar",
|
||||
lastProbeTime: 1,
|
||||
lastTransitionTime: "longer ago",
|
||||
});
|
||||
|
||||
expect(pod.hasIssues()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it("should return false if a condition is non-ready", () => {
|
||||
const pod = getDummyPod({ running: 1 });
|
||||
|
||||
pod.status?.conditions.push({
|
||||
type: "dummy",
|
||||
status: "foobar",
|
||||
lastProbeTime: 1,
|
||||
lastTransitionTime: "longer ago",
|
||||
});
|
||||
|
||||
expect(pod.hasIssues()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it("should return true if a current container is in a crash loop back off", () => {
|
||||
const pod = getDummyPod({ running: 1 });
|
||||
const firstStatus = pod.status?.containerStatuses?.[0];
|
||||
|
||||
assert(firstStatus);
|
||||
|
||||
firstStatus.state = {
|
||||
waiting: {
|
||||
reason: "CrashLookBackOff",
|
||||
message: "too much foobar",
|
||||
},
|
||||
};
|
||||
|
||||
expect(pod.hasIssues()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it("should return true if a current phase isn't running", () => {
|
||||
const pod = getDummyPod({ running: 1 });
|
||||
|
||||
assert(pod.status);
|
||||
|
||||
pod.status.phase = "not running";
|
||||
|
||||
expect(pod.hasIssues()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it("should return false if a current phase is running", () => {
|
||||
const pod = getDummyPod({ running: 1 });
|
||||
|
||||
assert(pod.status);
|
||||
|
||||
pod.status.phase = "Running";
|
||||
|
||||
expect(pod.hasIssues()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,834 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isDefined } from "@k8slens/utilities";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim";
|
||||
import type {
|
||||
Affinity,
|
||||
KubeObjectMetadata,
|
||||
LocalObjectReference,
|
||||
NamespaceScopedMetadata,
|
||||
Toleration,
|
||||
} from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { Container, ObjectFieldSelector, PodSecurityContext, Probe, ResourceFieldSelector } from "../types";
|
||||
import type { SecretReference } from "./secret";
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PreemptionPolicy } from "../types/preemption-policy";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodTemplateSpec } from "../types/pod-template-spec";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
KubeObjectMetadata,
|
||||
KubeObjectStatus,
|
||||
NamespaceScopedMetadata,
|
||||
BaseKubeObjectCondition,
|
||||
} from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodTemplateSpec } from "../types/pod-template-spec";
|
||||
|
||||
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 ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export type ResourceQuotaValues = 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: ResourceQuotaValues;
|
||||
scopeSelector?: {
|
||||
matchExpressions: {
|
||||
operator: string;
|
||||
scopeName: string;
|
||||
values: string[];
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResourceQuotaStatus {
|
||||
hard: ResourceQuotaValues;
|
||||
used: ResourceQuotaValues;
|
||||
}
|
||||
|
||||
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 ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
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(", ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
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 || [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
KubeJsonApiData,
|
||||
KubeObjectMetadata,
|
||||
KubeObjectScope,
|
||||
Toleration,
|
||||
ClusterScopedMetadata,
|
||||
} from "../api-types";
|
||||
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 ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import autoBind from "auto-bind";
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export interface SelfSubjectReviewRule {
|
||||
verbs: string[];
|
||||
apiGroups?: string[];
|
||||
resources?: string[];
|
||||
resourceNames?: string[];
|
||||
nonResourceURLs?: string[];
|
||||
}
|
||||
|
||||
export interface SelfSubjectRulesReviewSpec {
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface SelfSubjectRulesReviewStatus {
|
||||
resourceRules: SelfSubjectReviewRule[];
|
||||
nonResourceRules: SelfSubjectReviewRule[];
|
||||
incomplete: boolean;
|
||||
}
|
||||
|
||||
export class SelfSubjectRulesReview extends KubeObject<
|
||||
ClusterScopedMetadata,
|
||||
SelfSubjectRulesReviewStatus,
|
||||
SelfSubjectRulesReviewSpec
|
||||
> {
|
||||
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: SelfSubjectReviewRule): SelfSubjectReviewRule {
|
||||
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;
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
KubeJsonApiData,
|
||||
KubeObjectMetadata,
|
||||
KubeObjectScope,
|
||||
LocalObjectReference,
|
||||
ObjectReference,
|
||||
NamespaceScopedMetadata,
|
||||
} from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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 || [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
export interface ServicePortSpec {
|
||||
name?: string;
|
||||
protocol: string;
|
||||
port: number;
|
||||
targetPort: number;
|
||||
nodePort?: number;
|
||||
}
|
||||
|
||||
export class ServicePort {
|
||||
name?: string;
|
||||
|
||||
protocol: string;
|
||||
|
||||
port: number;
|
||||
|
||||
targetPort: number;
|
||||
|
||||
nodePort?: number;
|
||||
|
||||
constructor(data: ServicePortSpec) {
|
||||
this.name = data.name;
|
||||
this.protocol = data.protocol;
|
||||
this.port = data.port;
|
||||
this.targetPort = data.targetPort;
|
||||
this.nodePort = data.nodePort;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const targetPort = this.nodePort ? `:${this.nodePort}` : this.port !== this.targetPort ? `:${this.targetPort}` : "";
|
||||
|
||||
return `${this.port}${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: ServicePortSpec[];
|
||||
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 || "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LabelSelector, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PersistentVolumeClaimTemplateSpec } from "../types/persistent-volume-claim-template-spec";
|
||||
import type { PodTemplateSpec } from "../types/pod-template-spec";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import autoBind from "auto-bind";
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
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 || "-";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { Webhook } from "./mutating-webhook-configuration";
|
||||
|
||||
export interface ValidatingWebhookConfigurationData
|
||||
extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
|
||||
webhooks?: Webhook[];
|
||||
}
|
||||
|
||||
export class ValidatingWebhookConfiguration extends KubeObject<NamespaceScopedMetadata, void, void> {
|
||||
static kind = "ValidatingWebhookConfiguration";
|
||||
|
||||
static namespaced = false;
|
||||
|
||||
static apiBase = "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations";
|
||||
|
||||
webhooks?: Webhook[];
|
||||
|
||||
constructor({ webhooks, ...rest }: ValidatingWebhookConfigurationData) {
|
||||
super(rest);
|
||||
this.webhooks = webhooks;
|
||||
}
|
||||
|
||||
getWebhooks(): Webhook[] {
|
||||
return this.webhooks ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { BaseKubeObjectCondition, NamespaceScopedMetadata } from "../api-types";
|
||||
import { KubeObject } from "../kube-object";
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user