chore: Extract @k8slens/kube-object package

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

No files matched your search

@@ -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));
}
+91
View File
@@ -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");
});
});
});
+253
View File
@@ -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);
});
});
});
+834
View File
@@ -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;
}
}