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

+6
View File
@@ -0,0 +1,6 @@
module.exports = {
extends: "@k8slens/eslint-config/eslint",
parserOptions: {
project: "./tsconfig.json",
},
};
+1
View File
@@ -0,0 +1 @@
"@k8slens/eslint-config/prettier"
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./src/api-types";
export * from "./src/kube-object";
export * from "./src/kube-status";
export * from "./src/specifics";
export * from "./src/types";
export * from "./src/utils";
+1
View File
@@ -0,0 +1 @@
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@k8slens/kube-object",
"private": false,
"version": "1.0.0-alpha.1",
"description": "Injection tokens for list layout",
"type": "commonjs",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {
"name": "OpenLens Authors",
"email": "info@k8slens.dev"
},
"license": "MIT",
"homepage": "https://github.com/lensapp/lens",
"scripts": {
"build": "webpack",
"clean": "rimraf dist/",
"dev": "webpack --mode=development --watch",
"test": "jest --coverage --runInBand",
"lint": "lens-lint",
"lint:fix": "lens-lint --fix"
},
"peerDependencies": {
"@k8slens/list-layout": "^1.0.0-alpha.1",
"@k8slens/utilities": "^1.0.0-alpha.2",
"@ogre-tools/injectable": "^15.1.2",
"auto-bind": "^4.0.0",
"moment": "^2.29.4",
"rfc6902": "^5.0.1",
"type-fest": "^2.19.0",
"typed-regex": "^0.0.8"
},
"devDependencies": {
"@k8slens/eslint-config": "^6.5.0-alpha.2",
"@k8slens/jest": "^6.5.0-alpha.4",
"@k8slens/typescript": "^6.5.0-alpha.2"
}
}
+401
View File
@@ -0,0 +1,401 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObject } from "./kube-object";
/**
* The metadata for LIST requests to the KubeApi
*/
export interface KubeJsonApiListMetadata {
resourceVersion: string;
selfLink?: string;
}
export interface KubeJsonApiDataList<T = KubeJsonApiData> {
kind: string;
apiVersion: string;
items: T[];
metadata: KubeJsonApiListMetadata;
}
export interface KubeJsonApiData<
Metadata extends KubeJsonApiObjectMetadata = KubeJsonApiObjectMetadata,
Status = unknown,
Spec = unknown,
> {
kind: string;
apiVersion: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
[otherKeys: string]: unknown;
}
export type KubeJsonApiDataFor<K> = K extends KubeObject<infer Metadata, infer Status, infer Spec>
? KubeJsonApiData<Metadata, Status, Spec>
: never;
export interface KubeObjectConstructorData {
readonly kind?: string;
readonly namespaced?: boolean;
readonly apiBase?: string;
}
export type KubeObjectConstructor<K extends KubeObject, Data> = (new (data: Data) => K) & KubeObjectConstructorData;
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export type KubeTemplateObjectMetadata<Namespaced extends KubeObjectScope> = Pick<
KubeJsonApiObjectMetadata<KubeObjectScope>,
"annotations" | "finalizers" | "generateName" | "labels" | "ownerReferences"
> & {
name?: string;
namespace?: ScopedNamespace<Namespaced>;
};
export interface BaseKubeJsonApiObjectMetadata<Namespaced extends KubeObjectScope> {
/**
* Annotations is an unstructured key value map stored with a resource that may be set by
* external tools to store and retrieve arbitrary metadata. They are not queryable and should be
* preserved when modifying objects.
*
* More info: http://kubernetes.io/docs/user-guide/annotations
*/
annotations?: Partial<Record<string, string>>;
/**
* The name of the cluster which the object belongs to. This is used to distinguish resources
* with same name and namespace in different clusters. This field is not set anywhere right now
* and api server is going to ignore it if set in create or update request.
*/
clusterName?: string;
/**
* CreationTimestamp is a timestamp representing the server time when this object was created. It
* is not guaranteed to be set in happens-before order across separate operations. Clients may
* not set this value. It is represented in RFC3339 form and is in UTC. Populated by the system.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
*/
readonly creationTimestamp?: string;
/**
* Number of seconds allowed for this object to gracefully terminate before it will be removed
* from the system. Only set when deletionTimestamp is also set. May only be shortened.
*/
readonly deletionGracePeriodSeconds?: number;
/**
* DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field
* is set by the server when a graceful deletion is requested by the user, and is not directly
* settable by a client. The resource is expected to be deleted (no longer visible from resource
* lists, and not reachable by name) after the time in this field, once the finalizers list is
* empty. As long as the finalizers list contains items, deletion is blocked. Once the
* `deletionTimestamp` is set, this value may not be unset or be set further into the future,
* although it may be shortened or the resource may be deleted prior to this time. For example,
* a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a
* graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet
* will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the
* pod from the API. In the presence of network partitions, this object may still exist after
* this timestamp, until an administrator or automated process can determine the resource is
* fully terminated. If not set, graceful deletion of the object has not been requested.
* Populated by the system when a graceful deletion is requested.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
*/
readonly deletionTimestamp?: string;
/**
* Must be empty before the object is deleted from the registry. Each entry is an identifier for
* the responsible component that will remove the entry from the list. If the deletionTimestamp
* of the object is non-nil, entries in this list can only be removed. Finalizers may be
* processed and removed in any order. Order is NOT enforced because it introduces significant
* risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder
* it. If the finalizer list is processed in order, then this can lead to a situation in which
* the component responsible for the first finalizer in the list is waiting for a signal (field
* value, external system, or other) produced by a component responsible for a finalizer later in
* the list, resulting in a deadlock. Without enforced ordering finalizers are free to order
* amongst themselves and are not vulnerable to ordering changes in the list.
*/
finalizers?: string[];
/**
* GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the
* Name field has not been provided. If this field is used, the name returned to the client will
* be different than the name passed. This value will also be combined with a unique suffix. The
* provided value has the same validation rules as the Name field, and may be truncated by the
* length of the suffix required to make the value unique on the server. If this field is
* specified and the generated name exists, the server will NOT return a 409 - instead, it will
* either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not
* be found in the time allotted, and the client should retry (optionally after the time indicated
* in the Retry-After header). Applied only if Name is not specified.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency
*/
generateName?: string;
/**
* A sequence number representing a specific generation of the desired state. Populated by the
* system.
*/
readonly generation?: number;
/**
* Map of string keys and values that can be used to organize and categorize (scope and select)
* objects. May match selectors of replication controllers and services.
*
* More info: http://kubernetes.io/docs/user-guide/labels
*/
labels?: Partial<Record<string, string>>;
/**
* ManagedFields maps workflow-id and version to the set of fields that are managed by that
* workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set
* or understand this field. A workflow can be the user's name, a controller's name, or the name
* of a specific apply path like "ci-cd". The set of fields is always in the version that the
* workflow used when modifying the object.
*/
managedFields?: unknown[];
/**
* Name must be unique within a namespace. Is required when creating resources, although some
* resources may allow a client to request the generation of an appropriate name automatically.
* Name is primarily intended for creation idempotence and configuration definition.
*
* More info: http://kubernetes.io/docs/user-guide/identifiers#names
*/
readonly name: string;
/**
* Namespace defines the space within which each name must be unique. An empty namespace is
* equivalent to the "default" namespace, but "default" is the canonical representation. Not all
* objects are required to be scoped to a namespace - the value of this field for those objects
* will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces
*/
readonly namespace?: ScopedNamespace<Namespaced>;
/**
* List of objects depended by this object. If ALL objects in the list have been deleted, this
* object will be garbage collected. If this object is managed by a controller, then an entry in
* this list will point to this controller, with the controller field set to true. There cannot
* be more than one managing controller.
*/
ownerReferences?: OwnerReference[];
/**
* An opaque value that represents the internal version of this object that can be used by
* clients to determine when objects have changed. May be used for optimistic concurrency, change
* detection, and the watch operation on a resource or set of resources. Clients must treat these
* values as opaque and passed unmodified back to the server. They may only be valid for a
* particular resource or set of resources. Populated by the system. Value must be treated as
* opaque by clients.
*
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
*/
readonly resourceVersion?: string;
/**
* SelfLink is a URL representing this object. Populated by the system.
*/
readonly selfLink?: string;
/**
* UID is the unique in time and space value for this object. It is typically generated by the
* server on successful creation of a resource and is not allowed to change on PUT operations.
* Populated by the system.
*
* More info: http://kubernetes.io/docs/user-guide/identifiers#uids
*/
readonly uid?: string;
[key: string]: unknown;
}
export type KubeJsonApiObjectMetadata<Namespaced extends KubeObjectScope = KubeObjectScope> =
BaseKubeJsonApiObjectMetadata<Namespaced> &
(Namespaced extends KubeObjectScope.Namespace ? { readonly namespace: string } : {});
export type KubeObjectMetadata<Namespaced extends KubeObjectScope = KubeObjectScope> =
KubeJsonApiObjectMetadata<Namespaced> & {
readonly selfLink: string;
readonly uid: string;
readonly name: string;
readonly resourceVersion: string;
};
export type NamespaceScopedMetadata = KubeObjectMetadata<KubeObjectScope.Namespace>;
export type ClusterScopedMetadata = KubeObjectMetadata<KubeObjectScope.Cluster>;
export interface KubeStatusData {
kind: string;
apiVersion: string;
code: number;
message?: string;
reason?: string;
status?: string;
}
export interface EvictionObject {
kind: "Eviction";
apiVersion: string | "policy/v1";
metadata: Partial<KubeObjectMetadata>;
deleteOptions?: {
kind?: string;
apiVersion?: string;
dryRun?: string[];
gracePeriodSeconds?: number;
orphanDependents?: boolean;
propagationPolicy?: string;
preconditions?: {
resourceVersion: string;
uid: string;
}[];
};
}
export interface BaseKubeObjectCondition {
/**
* Last time the condition transit from one status to another.
*
* @type Date
*/
lastTransitionTime?: string;
/**
* A human readable message indicating details about last transition.
*/
message?: string;
/**
* brief (usually one word) reason for the condition's last transition.
*/
reason?: string;
/**
* Status of the condition
*/
status: "True" | "False" | "Unknown";
/**
* Type of the condition
*/
type: string;
}
export interface KubeObjectStatus {
conditions?: BaseKubeObjectCondition[];
}
export type KubeMetaField = keyof KubeJsonApiObjectMetadata;
export class KubeCreationError extends Error {
constructor(message: string, public data: unknown) {
super(message);
}
}
export type LabelMatchExpression = {
/**
* The label key that the selector applies to.
*/
key: string;
} & (
| {
/**
* This represents the key's relationship to a set of values.
*/
operator: "Exists" | "DoesNotExist";
values?: undefined;
}
| {
operator: "In" | "NotIn";
/**
* The set of values for to match according to the operator for the label.
*/
values: string[];
}
);
export interface Toleration {
key?: string;
operator?: string;
effect?: string;
value?: string;
tolerationSeconds?: number;
}
export interface ObjectReference {
apiVersion?: string;
fieldPath?: string;
kind?: string;
name: string;
namespace?: string;
resourceVersion?: string;
uid?: string;
}
export interface LocalObjectReference {
name: string;
}
export interface TypedLocalObjectReference {
apiGroup?: string;
kind: string;
name: string;
}
export interface NodeAffinity {
nodeSelectorTerms?: LabelSelector[];
weight: number;
preference: LabelSelector;
}
export interface PodAffinity {
labelSelector: LabelSelector;
topologyKey: string;
}
export interface SpecificAffinity<T> {
requiredDuringSchedulingIgnoredDuringExecution?: T[];
preferredDuringSchedulingIgnoredDuringExecution?: T[];
}
export interface Affinity {
nodeAffinity?: SpecificAffinity<NodeAffinity>;
podAffinity?: SpecificAffinity<PodAffinity>;
podAntiAffinity?: SpecificAffinity<PodAffinity>;
}
export interface LabelSelector {
matchLabels?: Partial<Record<string, string>>;
matchExpressions?: LabelMatchExpression[];
}
export enum KubeObjectScope {
Namespace,
Cluster,
}
export type ScopedNamespace<Namespaced extends KubeObjectScope> = Namespaced extends KubeObjectScope.Namespace
? string
: Namespaced extends KubeObjectScope.Cluster
? undefined
: string | undefined;
export interface RawKubeObject<
Metadata extends KubeObjectMetadata = KubeObjectMetadata,
Status = Record<string, unknown>,
Spec = Record<string, unknown>,
> {
apiVersion: string;
kind: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
[otherFields: string]: unknown;
}
+266
View File
@@ -0,0 +1,266 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import moment from "moment";
import { formatDuration, isObject, isString } from "@k8slens/utilities";
import type { ItemObject } from "@k8slens/list-layout";
import type { Patch } from "rfc6902";
import autoBind from "auto-bind";
import type { KubeObjectMetadata, KubeObjectScope, KubeJsonApiData } from "./api-types";
import { KubeCreationError } from "./api-types";
import {
isKubeObjectNonSystem,
isJsonApiData,
isKubeJsonApiListMetadata,
isKubeJsonApiMetadata,
isPartialJsonApiMetadata,
isPartialJsonApiData,
isJsonApiDataList,
stringifyLabels,
filterOutResourceApplierAnnotations,
} from "./utils";
export function createKubeObject<
Metadata extends KubeObjectMetadata = KubeObjectMetadata,
Status = unknown,
Spec = unknown,
>(data: KubeJsonApiData<Metadata, Status, Spec>) {
return new KubeObject(data);
}
export class KubeObject<
Metadata extends KubeObjectMetadata<KubeObjectScope> = KubeObjectMetadata<KubeObjectScope>,
Status = unknown,
Spec = unknown,
> implements ItemObject
{
static readonly kind?: string;
static readonly namespaced?: boolean;
static readonly apiBase?: string;
apiVersion!: string;
kind!: string;
metadata!: Metadata;
status?: Status;
spec!: Spec;
/**
* @deprecated Switch to using {@link createKubeObject} instead
*/
static create = createKubeObject;
/**
* @deprecated Switch to using {@link isKubeObjectNonSystem} instead
*/
static isNonSystem = isKubeObjectNonSystem;
/**
* @deprecated Switch to using {@link isJsonApiData} instead
*/
static isJsonApiData = isJsonApiData;
/**
* @deprecated Switch to using {@link isKubeJsonApiListMetadata} instead
*/
static isKubeJsonApiListMetadata = isKubeJsonApiListMetadata;
/**
* @deprecated Switch to using {@link isKubeJsonApiMetadata} instead
*/
static isKubeJsonApiMetadata = isKubeJsonApiMetadata;
/**
* @deprecated Switch to using {@link isPartialJsonApiMetadata} instead
*/
static isPartialJsonApiMetadata = isPartialJsonApiMetadata;
/**
* @deprecated Switch to using {@link isPartialJsonApiData} instead
*/
static isPartialJsonApiData = isPartialJsonApiData;
/**
* @deprecated Switch to using {@link isJsonApiDataList} instead
*/
static isJsonApiDataList = isJsonApiDataList;
/**
* @deprecated Switch to using {@link stringifyLabels} instead
*/
static stringifyLabels = stringifyLabels;
constructor(data: KubeJsonApiData<Metadata, Status, Spec>) {
if (!isObject(data)) {
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
}
if (!isObject(data.metadata)) {
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data);
}
if (!isString(data.metadata.name)) {
throw new KubeCreationError(
`Cannot create a KubeObject from an object without metadata.name being a string`,
data,
);
}
if (!isString(data.metadata.uid)) {
throw new KubeCreationError(
`Cannot create a KubeObject from an object without metadata.uid being a string`,
data,
);
}
if (!isString(data.metadata.resourceVersion)) {
throw new KubeCreationError(
`Cannot create a KubeObject from an object without metadata.resourceVersion being a string`,
data,
);
}
if (!isString(data.metadata.selfLink)) {
throw new KubeCreationError(
`Cannot create a KubeObject from an object without metadata.selfLink being a string`,
data,
);
}
Object.assign(this, data);
autoBind(this);
}
get selfLink() {
return this.metadata.selfLink;
}
getId() {
return this.metadata.uid;
}
getResourceVersion() {
return this.metadata.resourceVersion;
}
getScopedName() {
return [this.getNs(), this.getName()].filter(Boolean).join("/");
}
getName() {
return this.metadata.name;
}
getNs(): Metadata["namespace"] {
// avoid "null" serialization via JSON.stringify when post data
return this.metadata.namespace || undefined;
}
/**
* This function computes the number of milliseconds from the UNIX EPOCH to the
* creation timestamp of this object.
*/
getCreationTimestamp() {
if (!this.metadata.creationTimestamp) {
return Date.now();
}
return new Date(this.metadata.creationTimestamp).getTime();
}
/**
* @deprecated This function computes a new "now". Switch to using {@link KubeObject.getCreationTimestamp} instead
*/
getTimeDiffFromNow(): number {
if (!this.metadata.creationTimestamp) {
return 0;
}
return Date.now() - new Date(this.metadata.creationTimestamp).getTime();
}
/**
* @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times
*
* NOTE: this function also is not reactive to updates in the current time so it should not be used for rendering
*/
getAge(humanize = true, compact = true, fromNow = false): string | number {
if (fromNow) {
return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used
}
const diff = this.getTimeDiffFromNow();
if (humanize) {
return formatDuration(diff, compact);
}
return diff;
}
getFinalizers(): string[] {
return this.metadata.finalizers || [];
}
getLabels(): string[] {
return KubeObject.stringifyLabels(this.metadata.labels);
}
getAnnotations(filter = false): string[] {
const labels = KubeObject.stringifyLabels(this.metadata.annotations);
if (!filter) {
return labels;
}
return labels.filter(filterOutResourceApplierAnnotations);
}
getOwnerRefs() {
const refs = this.metadata.ownerReferences || [];
const namespace = this.getNs();
return refs.map((ownerRef) => ({ ...ownerRef, namespace }));
}
getSearchFields() {
return [this.getName(), this.getNs(), this.getId(), ...this.getLabels(), ...this.getAnnotations(true)];
}
toPlainObject() {
return JSON.parse(JSON.stringify(this)) as Record<string, unknown>;
}
/**
* @deprecated use KubeApi.patch instead
*/
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
void patch;
throw new Error("KubeObject.patch() has been deprecated since v5.3.0, please switch to using KubeApi.patch()");
}
/**
* @deprecated use KubeApi.patch instead
*/
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
void data;
throw new Error("KubeObject.update() has been deprecated since v5.3.0, please switch to using KubeApi.patch()");
}
/**
* @deprecated use KubeApi.delete instead
*/
delete(params?: object) {
void params;
throw new Error("KubeObject.delete() has been deprecated since v5.3.0, please switch to using KubeApi.delete()");
}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isObject, hasTypedProperty, hasOptionalTypedProperty, isString, isNumber } from "@k8slens/utilities";
import type { KubeStatusData } from "./api-types";
/**
* Is the shape of {@link object} correct for {@link KubeStatusData}
* @param object Some object
* @returns
*/
export function isKubeStatusData(object: unknown): object is KubeStatusData {
return (
isObject(object) &&
hasTypedProperty(object, "kind", isString) &&
hasTypedProperty(object, "apiVersion", isString) &&
hasTypedProperty(object, "code", isNumber) &&
(hasOptionalTypedProperty(object, "message", isString) ||
hasOptionalTypedProperty(object, "reason", isString) ||
hasOptionalTypedProperty(object, "status", isString)) &&
object.kind === "Status"
);
}
export class KubeStatus {
public readonly kind = "Status";
public readonly apiVersion: string;
public readonly code: number;
public readonly message: string;
public readonly reason: string;
public readonly status: string;
constructor(data: KubeStatusData) {
this.apiVersion = data.apiVersion;
this.code = data.code;
this.message = data.message || "";
this.reason = data.reason || "";
this.status = data.status || "";
}
getExplanation(): string {
const { code, message, reason, status } = this;
return `${code}: ${message || reason || status}`;
}
}
@@ -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;
}
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LabelSelector } from "../api-types";
export interface AggregationRule {
clusterRoleSelectors?: LabelSelector;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* Adds and removes POSIX capabilities from running containers.
*/
export interface Capabilities {
/**
* Added capabilities
*/
add?: string[];
/**
* Removed capabilities
*/
drop?: string[];
}
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface Condition {
lastTransitionTime: string;
message: string;
observedGeneration?: number;
reason: string;
status: string;
type: string;
}
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ContainerPort {
containerPort: number;
hostIP?: string;
hostPort?: number;
name?: string;
protocol?: "UDP" | "TCP" | "SCTP";
}
+176
View File
@@ -0,0 +1,176 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Lifecycle } from "./lifecycle";
import type { ResourceRequirements } from "./resource-requirements";
import type { SecurityContext } from "./security-context";
import type { Probe } from "./probe";
import type { VolumeDevice } from "./volume-device";
import type { VolumeMount } from "./volume-mount";
import type { ContainerPort } from "./container-port";
import type { EnvFromSource } from "./env-from-source";
import type { EnvVar } from "./env-var";
/**
* A single application container that you want to run within a pod.
*/
export interface Container {
/**
* Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable
* references `$(VAR_NAME)` are expanded using the container's environment.
*
* If a variable cannot be resolved, the reference in the input string will be unchanged.
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
*
* Escaped references will never be expanded, regardless of whether the variable exists or not.
* Cannot be updated.
*
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
*/
args?: string[];
/**
* Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this
* is not provided. Variable references `$(VAR_NAME)` are expanded using the container's
* environment.
*
* If a variable cannot be resolved, the reference in the input string will be unchanged.
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
*
* Escaped references will never be expanded, regardless of whether the variable exists or not.
* Cannot be updated.
*
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
*/
command?: string[];
/**
* List of environment variables to set in the container. Cannot be updated.
*/
env?: EnvVar[];
/**
* List of sources to populate environment variables in the container. The keys defined within a
* source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the
* container is starting.
*
* When a key exists in multiple sources, the value associated with the last source will take
* precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be
* updated.
*/
envFrom?: EnvFromSource[];
/**
* Docker image name.
*
* More info: https://kubernetes.io/docs/concepts/containers/images
*/
image?: string;
/**
* Image pull policy. Defaults to `"Always"` if :latest tag is specified, or `"IfNotPresent"`
* otherwise. Cannot be updated.
*
* More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
*/
imagePullPolicy?: "Always" | "Never" | "IfNotPresent";
lifecycle?: Lifecycle;
livenessProbe?: Probe;
/**
* Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique
* name. Cannot be updated.
*/
name: string;
/**
* List of ports to expose from the container. Exposing a port here gives the system additional
* information about the network connections a container uses, but is primarily informational.
* Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is
* listening on the default `"0.0.0.0"` address inside a container will be accessible from the
* network. Cannot be updated.
*/
ports?: ContainerPort[];
readinessProbe?: Probe;
resources?: ResourceRequirements;
securityContext?: SecurityContext;
startupProbe?: Probe;
/**
* Whether this container should allocate a buffer for stdin in the container runtime. If this is
* not set, reads from stdin in the container will always result in EOF.
*
* @default false
*/
stdin?: boolean;
/**
* Whether the container runtime should close the stdin channel after it has been opened by a
* single attach. When stdin is true the stdin stream will remain open across multiple attach
* sessions.
*
* If stdinOnce is set to true, stdin is opened on container start, is empty until the first
* client attaches to stdin, and then remains open and accepts data until the client disconnects,
* at which time stdin is closed and remains closed until the container is restarted.
*
* If this flag is false, a container processes that reads from stdin will never receive an EOF.
*
* @default false
*/
stdinOnce?: boolean;
/**
* Path at which the file to which the container's termination message will be written
* is mounted into the container's filesystem. Message written is intended to be brief final
* status, such as an assertion failure message.
*
* Will be truncated by the node if greater than 4096 bytes.
* The total message length across all containers will be limited to 12kb. Cannot be updated.
*
* @default "/dev/termination-log"
*/
terminationMessagePath?: string;
/**
* Indicate how the termination message should be populated.
*
* - `File`: will use the contents of {@link terminationMessagePath} to populate the container
* status message on both success and failure.
*
* - `FallbackToLogsOnError`: will use the last chunk of container log output if the
* termination message file is empty and the container exited with an error.
*
* The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Cannot be updated.
*
* @default "File"
*/
terminationMessagePolicy?: "File" | "FallbackToLogsOnError";
/**
* Whether this container should allocate a TTY for itself, also requires 'stdin' to be true.
*
* @default false
*/
tty?: boolean;
/**
* volumeDevices is the list of block devices to be used by the container.
*/
volumeDevices?: VolumeDevice[];
/**
* Pod volumes to mount into the container's filesystem. Cannot be updated.
*/
volumeMounts?: VolumeMount[];
/**
* Container's working directory. If not specified, the container runtime's default will be used,
* which might be configured in the container image. Cannot be updated.
*/
workingDir?: string;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface CrossVersionObjectReference {
kind: string;
name: string;
apiVersion: string;
}
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvSource } from "./env-source";
export interface EnvFromSource {
configMapRef?: EnvSource;
/**
* An identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.
*/
prefix?: string;
secretRef?: EnvSource;
}
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LocalObjectReference } from "../api-types";
export interface EnvSource extends LocalObjectReference {
/**
* Whether the object must be defined
*/
optional?: boolean;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface EnvVarKeySelector {
key: string;
name?: string;
optional?: boolean;
}
@@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvVarKeySelector } from "./env-var-key-selector";
import type { ObjectFieldSelector } from "./object-field-selector";
import type { ResourceFieldSelector } from "./resource-field-selector";
export interface EnvVarSource {
configMapKeyRef?: EnvVarKeySelector;
fieldRef?: ObjectFieldSelector;
resourceFieldRef?: ResourceFieldSelector;
secretKeyRef?: EnvVarKeySelector;
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EnvVarSource } from "./env-var-source";
export interface EnvVar {
name: string;
value?: string;
valueFrom?: EnvVarSource;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* ExecAction describes a "run in container" action.
*/
export interface ExecAction {
/**
* Command is the command line to execute inside the container, the working directory for the
* command is root ('\\') in the container's filesystem. The command is simply exec'd, it is not
* run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell,
* you need to explicitly call out to that shell.
*
* Exit status of 0 is treated as live/healthy and non-zero is unhealthy.
*/
command?: string[];
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ExternalDocumentation {
description?: string;
url?: string;
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExecAction } from "./exec-action";
import type { HttpGetAction } from "./http-get-action";
import type { TcpSocketAction } from "./tcp-socket-action";
/**
* Handler defines a specific action that should be taken.
*/
export interface Handler {
exec?: ExecAction;
httpGet?: HttpGetAction;
tcpSocket?: TcpSocketAction;
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { HttpHeader } from "./http-header";
/**
* An action based on HTTP Get requests.
*/
export interface HttpGetAction {
/**
* Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.
*/
host?: string;
/**
* Custom headers to set in the request. HTTP allows repeated headers.
*/
httpHeaders?: HttpHeader[];
/**
* Path to access on the HTTP server.
*/
path?: string;
/**
* The PORT to request from.
*/
port: string | number;
/**
* Scheme to use for connecting to the host.
*
* @default "HTTP"
*/
scheme?: string;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* A custom header to be used in HTTP probes and get actions
*/
export interface HttpHeader {
/**
* Field name
*/
name: string;
/**
* The value of the field
*/
value: string;
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./aggregation-rule";
export * from "./capabilities";
export * from "./container";
export * from "./container-port";
export * from "./cross-version-object-reference";
export * from "./env-from-source";
export * from "./env-source";
export * from "./env-var-key-selector";
export * from "./env-var-source";
export * from "./env-var";
export * from "./exec-action";
export * from "./handler";
export * from "./http-get-action";
export * from "./http-header";
export * from "./job-template-spec";
export * from "./lifecycle";
export * from "./object-field-selector";
export * from "./persistent-volume-claim-template-spec";
export * from "./pod-security-context";
export * from "./pod-template-spec";
export * from "./policy-rule";
export * from "./probe";
export * from "./resource-field-selector";
export * from "./resource-requirements";
export * from "./role-ref";
export * from "./se-linux-options";
export * from "./seccomp-profile";
export * from "./subject";
export * from "./tcp-socket-action";
export * from "./volume-device";
export * from "./volume-mount";
export * from "./windows-security-context-options";
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types";
import type { JobSpec } from "../specifics/job";
export interface JobTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Namespace>;
spec?: JobSpec;
}
@@ -0,0 +1,120 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { JsonValue } from "type-fest";
import type { ExternalDocumentation } from "./external-documentation";
/**
* (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
*/
export type UUIDRegexString = string;
/**
* (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
*/
export type UUID3RegexString = string;
/**
* (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
*/
export type UUID4RegexString = string;
/**
* (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
*/
export type UUID5RegexString = string;
/* eslint-disable max-len */
/**
* ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$
*/
export type CreditCardRegexString = string;
/* eslint-enable max-len */
export interface JSONSchemaProps {
$ref?: string;
$schema?: string;
additionalItems?: JSONSchemaProps | boolean;
additionalProperties?: JSONSchemaProps | boolean;
allOf?: JSONSchemaProps[];
anyOf?: JSONSchemaProps[];
/**
* default is a default value for undefined object fields.
* Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
* Defaulting requires spec.preserveUnknownFields to be false.
*/
_default?: object;
definitions?: Partial<Record<string, JSONSchemaProps>>;
dependencies?: Partial<Record<string, object>>;
description?: string;
_enum?: object[];
example?: JsonValue;
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean;
externalDocs?: ExternalDocumentation;
/**
* format is an OpenAPI v3 format string.
* Unknown formats are ignored.
*
* The following formats are validated:
* - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
* - uri: an URI as parsed by Golang net/url.ParseRequestURI
* - email: an email address as parsed by Golang net/mail.ParseAddress
* - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
* - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
* - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
* - cidr: a CIDR as parsed by Golang net.ParseCIDR
* - mac: a MAC address as parsed by Golang net.ParseMAC
* - uuid: an UUID that allows uppercase defined by the regex {@link UUIDRegexString}
* - uuid3: an UUID3 that allows uppercase defined by the regex {@link UUID3RegexString}
* - uuid4: an UUID4 that allows uppercase defined by the regex {@link UUID4RegexString}
* - uuid5: an UUID5 that allows uppercase defined by the regex {@link UUID5RegexString}
* - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
* - isbn10: an ISBN10 number string like "0321751043"
* - isbn13: an ISBN13 number string like "978-0321751041"
* - creditcard: a credit card number defined by the regex {@link CreditCardRegexString}
* - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
* - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
* - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
* - byte: base64 encoded binary data
* - password: any kind of string
* - date: a date string like "2006-01-02" as defined by full-date in RFC3339
* - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or with Scala's duration format
* - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
*/
format?: string;
id?: string;
items?: JSONSchemaProps | JSONSchemaProps[];
maxItems?: number;
maxLength?: number;
maxProperties?: number;
maximum?: number;
minItems?: number;
minLength?: number;
minProperties?: number;
minimum?: number;
multipleOf?: number;
not?: JSONSchemaProps;
nullable?: boolean;
oneOf?: JSONSchemaProps[];
pattern?: string;
patternProperties?: Partial<Record<string, JSONSchemaProps>>;
properties?: Partial<Record<string, JSONSchemaProps>>;
required?: Array<string>;
title?: string;
type?: string;
uniqueItems?: boolean;
x_kubernetes_embedded_resource?: boolean;
x_kubernetes_int_or_string?: boolean;
x_kubernetes_list_map_keys?: string[];
x_kubernetes_list_type?: string;
x_kubernetes_map_type?: string;
x_kubernetes_preserve_unknown_fields?: boolean;
}
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Handler } from "./handler";
/**
* Lifecycle describes actions that the management system should take in response to container
* lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container
* blocks until the action is complete, unless the container process fails, in which case the
* handler is aborted.
*/
export interface Lifecycle {
postStart?: Handler;
preStop?: Handler;
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ObjectFieldSelector {
apiVersion?: string;
fieldPath: string;
}
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types";
import type { PersistentVolumeSpec } from "../specifics/persistent-volume";
export interface PersistentVolumeClaimTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Cluster>;
spec?: PersistentVolumeSpec;
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { SeLinuxOptions } from "./se-linux-options";
import type { SeccompProfile } from "./seccomp-profile";
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
import type { Sysctl } from "../specifics/pod";
export interface PodSecurityContext {
fsGroup?: number;
fsGroupChangePolicy?: string;
runAsGroup?: number;
runAsNonRoot?: boolean;
runAsUser?: number;
seLinuxOptions?: SeLinuxOptions;
seccompProfile?: SeccompProfile;
supplementalGroups?: number[];
sysctls?: Sysctl;
windowsOptions?: WindowsSecurityContextOptions;
}
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types";
import type { PodSpec } from "../specifics/pod";
export interface PodTemplateSpec {
metadata?: KubeTemplateObjectMetadata<KubeObjectScope.Namespace>;
spec?: PodSpec;
}
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface PolicyRule {
verbs: string[];
apiGroups?: string[];
resources?: string[];
resourceNames?: string[];
nonResourceURLs?: string[];
}
@@ -0,0 +1,6 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type PreemptionPolicy = "Never" | "PreemptLowerPriority";
+81
View File
@@ -0,0 +1,81 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExecAction } from "./exec-action";
import type { HttpGetAction } from "./http-get-action";
import type { TcpSocketAction } from "./tcp-socket-action";
/**
* Describes a health check to be performed against a container to determine whether
* it is alive or ready to receive traffic.
*/
export interface Probe {
exec?: ExecAction;
/**
* Minimum consecutive failures for the probe to be considered failed after having succeeded.
*
* @default 3
* @minimum 1
*/
failureThreshold?: number;
httpGet?: HttpGetAction;
/**
* Duration after the container has started before liveness probes are initiated.
*
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
*/
initialDelaySeconds?: number;
/**
* How often to perform the probe.
*
* @default 10
* @minimum 1
*/
periodSeconds?: number;
/**
* Minimum consecutive successes for the probe to be considered successful after having failed.
*
* Must be 1 for liveness and startup.
*
* @default 1
* @minimum 1
*/
successThreshold?: number;
tcpSocket?: TcpSocketAction;
/**
* Duration the pod needs to terminate gracefully upon probe failure.
*
* The grace period is the duration in seconds after the processes running in the pod are sent a
* termination signal and the time when the processes are forcibly halted with a kill signal.
*
* Set this value longer than the expected cleanup time for your process.
*
* If this value is not set, the pod's terminationGracePeriodSeconds will be used. Otherwise,
* this value overrides the value provided by the pod spec. Value must be non-negative integer.
* The value zero indicates stop immediately via the kill signal (no opportunity to shut down).
*
* This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
*
* @minimum 1
*/
terminationGracePeriodSeconds?: number;
/**
* Duration after which the probe times out.
*
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
*
* @default 1
* @minimum 1
*/
timeoutSeconds?: number;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ResourceFieldSelector {
containerName?: string;
divisor?: string;
resource: string;
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* ResourceRequirements describes the compute resource requirements.
*/
export interface ResourceRequirements {
/**
* Limits describes the maximum amount of compute resources allowed.
*
* More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
*/
limits?: Partial<Record<string, string>>;
/**
* Requests describes the minimum amount of compute resources required. If Requests is omitted
* for a container, it defaults to Limits if that is explicitly specified, otherwise to an
* implementation-defined value.
*
* More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
*/
requests?: Partial<Record<string, string>>;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface RoleRef {
apiGroup: string;
kind: string;
name: string;
}
@@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* SELinuxOptions are the labels to be applied to the container
*/
export interface SeLinuxOptions {
/**
* The SELinux `level` label that applies to the container.
*/
level?: string;
/**
* The SELinux `role` label that applies to the container.
*/
role?: string;
/**
* The SELinux `type` label that applies to the container.
*/
type?: string;
/**
* The SELinux `user` label that applies to the container.
*/
user?: string;
}
@@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* Defines a pod's or a container's seccomp profile settings. Only one profile source may be set.
*/
export interface SeccompProfile {
/**
* Indicates a profile defined in a file on the node should be used. The profile must be
* preconfigured on the node to work. Must be a descending path, relative to the kubelet's
* configured seccomp profile location. Must only be set if type is "Localhost".
*/
localhostProfile?: string;
/**
* Indicates which kind of seccomp profile will be applied.
*
* Options:
*
* | Value | Description |
* |--|--|
* | `Localhost` | A profile defined in a file on the node should be used. |
* | `RuntimeDefault` | The container runtime default profile should be used. |
* | `Unconfined` | No profile should be applied. |
*/
type: "Localhost" | "RuntimeDefault" | "Unconfined";
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) OpenLens Authors.
* All rights reserved.
* Licensed under MIT License.
* See LICENSE in root directory for more information.
*/
import type { Capabilities } from "./capabilities";
import type { SeLinuxOptions } from "./se-linux-options";
import type { SeccompProfile } from "./seccomp-profile";
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
/**
* SecurityContext holds security configuration that will be applied to a container.
* Some fields are present in both SecurityContext and PodSecurityContext.
* When both are set, the values in SecurityContext take precedence.
*/
export interface SecurityContext {
/**
* AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process.
* This bool directly controls if the no_new_privs flag will be set on the container process.
* AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN
*/
allowPrivilegeEscalation?: boolean;
capabilities?: Capabilities;
/**
* Run container in privileged mode.
* Processes in privileged containers are essentially equivalent to root on the host.
*
* @default false
*/
privileged?: boolean;
/**
* procMount denotes the type of proc mount to use for the containers.
* The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths.
* This requires the ProcMountType feature flag to be enabled.
*/
procMount?: string;
/**
* Whether this container has a read-only root filesystem.
* @default false
*/
readOnlyRootFilesystem?: boolean;
/**
* The GID to run the entrypoint of the container process.
* Uses runtime default if unset.
* May also be set in PodSecurityContext.
* If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsGroup?: number;
/**
* Indicates that the container must run as a non-root user.
* If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root)
* and fail to start the container if it does.
* If unset or false, no such validation will be performed.
* May also be set in PodSecurityContext.
* If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsNonRoot?: boolean;
/**
* The UID to run the entrypoint of the container process.
* Defaults to user specified in image metadata if unspecified.
* May also be set in PodSecurityContext.
* If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsUser?: number;
seLinuxOptions?: SeLinuxOptions;
seccompProfile?: SeccompProfile;
windowsOptions?: WindowsSecurityContextOptions;
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type SubjectKind = "Group" | "ServiceAccount" | "User";
export interface Subject {
apiGroup?: string;
kind: SubjectKind;
name: string;
namespace?: string;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* An action based on opening a socket
*/
export interface TcpSocketAction {
/**
* Host name to connect to, defaults to the pod IP.
*/
host?: string;
/**
* Port to connect to
*/
port: number | string;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* A mapping of a raw block device within a container.
*/
export interface VolumeDevice {
/**
* The path inside of the container that the device will be mapped to.
*/
devicePath: string;
/**
* Must match the name of a persistentVolumeClaim in the pod
*/
name: string;
}
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface VolumeMount {
name: string;
readOnly?: boolean;
mountPath: string;
mountPropagation?: string;
subPath?: string;
subPathExpr?: string;
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* Windows-specific options and credentials.
*/
export interface WindowsSecurityContextOptions {
/**
* The location of the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)
* inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.
*/
gmsaCredentialSpec?: string;
/**
* The name of the GMSA credential spec to use.
*/
gmsaCredentialSpecName?: string;
/**
* Determines if a container should be run as a 'Host Process' container.
*
* This field is alpha-level and will only be honored by components that enable the
* WindowsHostProcessContainers feature flag.
*
* Setting this field without the feature flag will result in errors when validating the Pod.
*
* All of a Pod's containers must have the same effective HostProcess value (it is not allowed to
* have a mix of HostProcess containers and non-HostProcess containers).
*
* In addition, if HostProcess is true then HostNetwork must also be set to true.
*/
hostProcess?: boolean;
/**
* The UserName in Windows to run the entrypoint of the container process.
* Defaults to the user specified in image metadata if unspecified.
* May also be set in PodSecurityContext.
* If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
*/
runAsUserName?: string;
}
+113
View File
@@ -0,0 +1,113 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import {
isObject,
hasTypedProperty,
isString,
hasOptionalTypedProperty,
bindPredicate,
isTypedArray,
isRecord,
} from "@k8slens/utilities";
import type {
KubeJsonApiData,
KubeObjectMetadata,
KubeObjectScope,
KubeJsonApiListMetadata,
KubeJsonApiObjectMetadata,
KubeJsonApiDataList,
} from "./api-types";
import type { KubeObject } from "./kube-object";
const resourceApplierAnnotationsForFiltering = ["kubectl.kubernetes.io/last-applied-configuration"];
export const filterOutResourceApplierAnnotations = (label: string) =>
!resourceApplierAnnotationsForFiltering.some((key) => label.startsWith(key));
export function isKubeObjectNonSystem(
item: KubeJsonApiData | KubeObject<KubeObjectMetadata<KubeObjectScope>, unknown, unknown>,
) {
return !item.metadata.name?.startsWith("system:");
}
export function isJsonApiData(object: unknown): object is KubeJsonApiData {
return (
isObject(object) &&
hasTypedProperty(object, "kind", isString) &&
hasTypedProperty(object, "apiVersion", isString) &&
hasTypedProperty(object, "metadata", isKubeJsonApiMetadata)
);
}
export function isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata {
return (
isObject(object) &&
hasOptionalTypedProperty(object, "resourceVersion", isString) &&
hasOptionalTypedProperty(object, "selfLink", isString)
);
}
export function isKubeJsonApiMetadata(object: unknown): object is KubeJsonApiObjectMetadata {
return (
isObject(object) &&
hasTypedProperty(object, "uid", isString) &&
hasTypedProperty(object, "name", isString) &&
hasTypedProperty(object, "resourceVersion", isString) &&
hasOptionalTypedProperty(object, "selfLink", isString) &&
hasOptionalTypedProperty(object, "namespace", isString) &&
hasOptionalTypedProperty(object, "creationTimestamp", isString) &&
hasOptionalTypedProperty(object, "continue", isString) &&
hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) &&
hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) &&
hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
export function isPartialJsonApiMetadata(object: unknown): object is Partial<KubeJsonApiObjectMetadata> {
return (
isObject(object) &&
hasOptionalTypedProperty(object, "uid", isString) &&
hasOptionalTypedProperty(object, "name", isString) &&
hasOptionalTypedProperty(object, "resourceVersion", isString) &&
hasOptionalTypedProperty(object, "selfLink", isString) &&
hasOptionalTypedProperty(object, "namespace", isString) &&
hasOptionalTypedProperty(object, "creationTimestamp", isString) &&
hasOptionalTypedProperty(object, "continue", isString) &&
hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) &&
hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) &&
hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString))
);
}
export function isPartialJsonApiData(object: unknown): object is Partial<KubeJsonApiData> {
return (
isObject(object) &&
hasOptionalTypedProperty(object, "kind", isString) &&
hasOptionalTypedProperty(object, "apiVersion", isString) &&
hasOptionalTypedProperty(object, "metadata", isPartialJsonApiMetadata)
);
}
export function isJsonApiDataList<T>(
object: unknown,
verifyItem: (val: unknown) => val is T,
): object is KubeJsonApiDataList<T> {
return (
isObject(object) &&
hasTypedProperty(object, "kind", isString) &&
hasTypedProperty(object, "apiVersion", isString) &&
hasTypedProperty(object, "metadata", isKubeJsonApiListMetadata) &&
hasTypedProperty(object, "items", bindPredicate(isTypedArray, verifyItem))
);
}
export function stringifyLabels(labels?: Partial<Record<string, string>>): string[] {
if (!labels) {
return [];
}
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "@k8slens/typescript/config/base.json",
"include": ["**/*.ts"]
}
+1
View File
@@ -0,0 +1 @@
module.exports = require("@k8slens/webpack").configForReact;