mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: extract kube-api from core
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
a22229ba60
commit
1ab5323f68
216 files changed
+1776
-1537
No files matched your search
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { KubeJsonApi } from "./kube-json-api";
|
||||
|
||||
export const apiKubeInjectionToken = getInjectionToken<KubeJsonApi>({
|
||||
id: "api-kube-injection-token",
|
||||
});
|
||||
@@ -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 { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { ClusterRoleBindingData } from "@k8slens/kube-object";
|
||||
import { ClusterRoleBinding } from "@k8slens/kube-object";
|
||||
|
||||
export class ClusterRoleBindingApi extends KubeApi<ClusterRoleBinding, ClusterRoleBindingData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: ClusterRoleBinding,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { ClusterRoleData } from "@k8slens/kube-object";
|
||||
import { ClusterRole } from "@k8slens/kube-object";
|
||||
|
||||
export class ClusterRoleApi extends KubeApi<ClusterRole, ClusterRoleData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: ClusterRole,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Cluster } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ClusterApi extends KubeApi<Cluster> {
|
||||
/**
|
||||
* @deprecated This field is legacy and never used.
|
||||
*/
|
||||
static kind = "Cluster";
|
||||
|
||||
/**
|
||||
* @deprecated This field is legacy and never used.
|
||||
*/
|
||||
static namespaced = true;
|
||||
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...(opts ?? {}),
|
||||
objectConstructor: Cluster,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { ComponentStatus } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ComponentStatusApi extends KubeApi<ComponentStatus> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: ComponentStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ConfigMapData } from "@k8slens/kube-object";
|
||||
import { ConfigMap } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ConfigMapApi extends KubeApi<ConfigMap, ConfigMapData> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
objectConstructor: ConfigMap,
|
||||
...(opts ?? {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { CronJob } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class CronJobApi extends KubeApi<CronJob> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: CronJob,
|
||||
});
|
||||
}
|
||||
|
||||
private requestSetSuspend(params: NamespacedResourceDescriptor, suspend: boolean) {
|
||||
return this.patch(
|
||||
params,
|
||||
{
|
||||
spec: {
|
||||
suspend,
|
||||
},
|
||||
},
|
||||
"strategic",
|
||||
);
|
||||
}
|
||||
|
||||
suspend(params: NamespacedResourceDescriptor) {
|
||||
return this.requestSetSuspend(params, true);
|
||||
}
|
||||
|
||||
resume(params: NamespacedResourceDescriptor) {
|
||||
return this.requestSetSuspend(params, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { CustomResourceDefinition } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class CustomResourceDefinitionApi extends KubeApi<CustomResourceDefinition> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: CustomResourceDefinition,
|
||||
checkPreferredVersion: true,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import moment from "moment";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { DaemonSet } from "@k8slens/kube-object";
|
||||
|
||||
export class DaemonSetApi extends KubeApi<DaemonSet> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: DaemonSet,
|
||||
});
|
||||
}
|
||||
|
||||
restart(params: NamespacedResourceDescriptor) {
|
||||
return this.patch(params, {
|
||||
spec: {
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
||||
},
|
||||
},
|
||||
},
|
||||
}, "strategic");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { Deployment } from "@k8slens/kube-object";
|
||||
|
||||
export class DeploymentApi extends KubeApi<Deployment> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Deployment,
|
||||
});
|
||||
}
|
||||
|
||||
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
|
||||
const { status } = await this.getResourceScale(params);
|
||||
|
||||
return status.replicas;
|
||||
}
|
||||
|
||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||
return this.scaleResource(params, { spec: { replicas }});
|
||||
}
|
||||
|
||||
restart(params: NamespacedResourceDescriptor) {
|
||||
return this.patch(params, {
|
||||
spec: {
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
||||
},
|
||||
},
|
||||
},
|
||||
}, "strategic");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { EndpointsData } from "@k8slens/kube-object";
|
||||
import { Endpoints } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class EndpointsApi extends KubeApi<Endpoints, EndpointsData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: Endpoints,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeEventData } from "@k8slens/kube-object";
|
||||
import { KubeEvent } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class KubeEventApi extends KubeApi<KubeEvent, KubeEventData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: KubeEvent,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { HorizontalPodAutoscaler } from "@k8slens/kube-object";
|
||||
|
||||
export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
allowedUsableVersions: {
|
||||
autoscaling: [
|
||||
"v2",
|
||||
"v2beta2",
|
||||
"v2beta1",
|
||||
"v1",
|
||||
],
|
||||
},
|
||||
...opts ?? {},
|
||||
objectConstructor: HorizontalPodAutoscaler,
|
||||
checkPreferredVersion: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Kubernetes apis
|
||||
// Docs: https://kubernetes.io/docs/reference/kubernetes-api/
|
||||
|
||||
export * from "./cluster.api";
|
||||
export * from "./cluster-role.api";
|
||||
export * from "./cluster-role-binding.api";
|
||||
export * from "./config-map.api";
|
||||
export * from "./custom-resource-definition.api";
|
||||
export * from "./cron-job.api";
|
||||
export * from "./component-status.api";
|
||||
export * from "./daemon-set.api";
|
||||
export * from "./deployment.api";
|
||||
export * from "./endpoint.api";
|
||||
export * from "./events.api";
|
||||
export * from "./horizontal-pod-autoscaler.api";
|
||||
export * from "./ingress.api";
|
||||
export * from "./ingress-class.api";
|
||||
export * from "./job.api";
|
||||
export * from "./lease.api";
|
||||
export * from "./limit-range.api";
|
||||
export * from "./mutating-webhook-configuration.api";
|
||||
export * from "./validating-webhook-configuration.api";
|
||||
export * from "./namespace.api";
|
||||
export * from "./network-policy.api";
|
||||
export * from "./node.api";
|
||||
export * from "./persistent-volume.api";
|
||||
export * from "./persistent-volume-claim.api";
|
||||
export * from "./pod.api";
|
||||
export * from "./pod-disruption-budget.api";
|
||||
export * from "./pod-metrics.api";
|
||||
export * from "./pod-security-policy.api";
|
||||
export * from "./priority-class.api";
|
||||
export * from "./replica-set.api";
|
||||
export * from "./replication-controller.api";
|
||||
export * from "./resource-quota.api";
|
||||
export * from "./role.api";
|
||||
export * from "./role-binding.api";
|
||||
export * from "./runtime-class.api";
|
||||
export * from "./secret.api";
|
||||
export * from "./self-subject-rules-reviews.api";
|
||||
export * from "./service.api";
|
||||
export * from "./service-account.api";
|
||||
export * from "./stateful-set.api";
|
||||
export * from "./storage-class.api";
|
||||
export * from "./vertical-pod-autoscaler.api";
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { IngressClass } from "@k8slens/kube-object";
|
||||
import type { KubeApiDependencies, ResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class IngressClassApi extends KubeApi<IngressClass> {
|
||||
constructor(dependencies: KubeApiDependencies) {
|
||||
super(dependencies, {
|
||||
objectConstructor: IngressClass,
|
||||
checkPreferredVersion: true,
|
||||
fallbackApiBases: ["/apis/extensions/v1beta1/ingressclasses"],
|
||||
});
|
||||
}
|
||||
|
||||
setAsDefault({ name }: ResourceDescriptor, isDefault = true) {
|
||||
return this.patch({ name }, {
|
||||
metadata: {
|
||||
annotations: {
|
||||
[IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault),
|
||||
},
|
||||
},
|
||||
}, "strategic");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Ingress } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class IngressApi extends KubeApi<Ingress> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Ingress,
|
||||
// Add fallback for Kubernetes <1.19
|
||||
checkPreferredVersion: true,
|
||||
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { Job } from "@k8slens/kube-object";
|
||||
|
||||
export class JobApi extends KubeApi<Job> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Job,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { Lease } from "@k8slens/kube-object";
|
||||
|
||||
export class LeaseApi extends KubeApi<Lease> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Lease,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { LimitRange } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
|
||||
export class LimitRangeApi extends KubeApi<LimitRange> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
objectConstructor: LimitRange,
|
||||
...opts ?? {},
|
||||
});
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { MutatingWebhookConfiguration } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class MutatingWebhookConfigurationApi extends KubeApi<MutatingWebhookConfiguration> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: MutatingWebhookConfiguration,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { Namespace } from "@k8slens/kube-object";
|
||||
|
||||
export class NamespaceApi extends KubeApi<Namespace> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Namespace,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { NetworkPolicy } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class NetworkPolicyApi extends KubeApi<NetworkPolicy> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: NetworkPolicy,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Node } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class NodeApi extends KubeApi<Node> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Node,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { PersistentVolumeClaim } from "@k8slens/kube-object";
|
||||
|
||||
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: PersistentVolumeClaim,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { PersistentVolume } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class PersistentVolumeApi extends KubeApi<PersistentVolume> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: PersistentVolume,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { PodDisruptionBudget } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class PodDisruptionBudgetApi extends KubeApi<PodDisruptionBudget> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: PodDisruptionBudget,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { PodMetricsData } from "@k8slens/kube-object";
|
||||
import { PodMetrics } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class PodMetricsApi extends KubeApi<PodMetrics, PodMetricsData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: PodMetrics,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { PodSecurityPolicy } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class PodSecurityPolicyApi extends KubeApi<PodSecurityPolicy> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: PodSecurityPolicy,
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
DeleteResourceDescriptor,
|
||||
DerivedKubeApiOptions,
|
||||
KubeApiDependencies,
|
||||
ResourceDescriptor,
|
||||
} from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object";
|
||||
import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object";
|
||||
|
||||
|
||||
export class PodApi extends KubeApi<Pod> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: Pod,
|
||||
});
|
||||
}
|
||||
|
||||
async evict(resource: DeleteResourceDescriptor) {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing(resource);
|
||||
let response: KubeStatusData;
|
||||
|
||||
try {
|
||||
response = await this.request.post<KubeStatusData>(`${apiUrl}/eviction`, {
|
||||
data: {
|
||||
apiVersion: "policy/v1",
|
||||
kind: "Eviction",
|
||||
metadata: {
|
||||
...resource,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
response = err as KubeStatusData;
|
||||
}
|
||||
|
||||
if (isKubeStatusData(response)) {
|
||||
const status = new KubeStatus(response);
|
||||
|
||||
if (status.code >= 200 && status.code < 300) {
|
||||
return status.getExplanation();
|
||||
} else {
|
||||
throw status.getExplanation();
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async getLogs(params: ResourceDescriptor, query?: PodLogsQuery): Promise<string> {
|
||||
const path = `${this.getUrl(params)}/log`;
|
||||
|
||||
const logs = await this.request.get(path, { query });
|
||||
|
||||
if (typeof logs !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { PriorityClassData } from "@k8slens/kube-object";
|
||||
import { PriorityClass } from "@k8slens/kube-object";
|
||||
|
||||
|
||||
export class PriorityClassApi extends KubeApi<PriorityClass, PriorityClassData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: PriorityClass,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { ReplicaSet } from "@k8slens/kube-object";
|
||||
|
||||
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: ReplicaSet,
|
||||
});
|
||||
}
|
||||
|
||||
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
|
||||
const { status } = await this.getResourceScale(params);
|
||||
|
||||
return status.replicas;
|
||||
}
|
||||
|
||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||
return this.scaleResource(params, { spec: { replicas }});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { ReplicationController } from "@k8slens/kube-object";
|
||||
import type { Scale } from "@k8slens/kube-object";
|
||||
|
||||
export class ReplicationControllerApi extends KubeApi<ReplicationController> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: ReplicationController,
|
||||
});
|
||||
}
|
||||
|
||||
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
|
||||
return `${this.formatUrlForNotListing(params)}/scale`;
|
||||
}
|
||||
|
||||
getScale(params: NamespacedResourceDescriptor): Promise<Scale> {
|
||||
return this.request.get(this.getScaleApiUrl(params));
|
||||
}
|
||||
|
||||
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
|
||||
return this.scaleResource(params, { spec: { replicas }});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { ResourceQuota } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ResourceQuotaApi extends KubeApi<ResourceQuota> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: ResourceQuota,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { RoleBindingData } from "@k8slens/kube-object";
|
||||
import { RoleBinding } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
|
||||
export class RoleBindingApi extends KubeApi<RoleBinding, RoleBindingData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: RoleBinding,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { RoleData } from "@k8slens/kube-object";
|
||||
import { Role } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class RoleApi extends KubeApi<Role, RoleData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: Role,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { RuntimeClassData } from "@k8slens/kube-object";
|
||||
import { RuntimeClass } from "@k8slens/kube-object";
|
||||
|
||||
export class RuntimeClassApi extends KubeApi<RuntimeClass, RuntimeClassData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
objectConstructor: RuntimeClass,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { Secret } from "@k8slens/kube-object";
|
||||
import type { SecretData } from "@k8slens/kube-object";
|
||||
|
||||
export class SecretApi extends KubeApi<Secret, SecretData> {
|
||||
constructor(deps: KubeApiDependencies, options: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...options,
|
||||
objectConstructor: Secret,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { SelfSubjectRulesReview } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: SelfSubjectRulesReview,
|
||||
});
|
||||
}
|
||||
|
||||
create({ namespace = "default" }) {
|
||||
return super.create({}, {
|
||||
spec: {
|
||||
namespace,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ServiceAccountData } from "@k8slens/kube-object";
|
||||
import { ServiceAccount } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ServiceAccountApi extends KubeApi<ServiceAccount, ServiceAccountData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: ServiceAccount,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Service } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ServiceApi extends KubeApi<Service> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: Service,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { StatefulSet } from "@k8slens/kube-object";
|
||||
|
||||
export class StatefulSetApi extends KubeApi<StatefulSet> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: StatefulSet,
|
||||
});
|
||||
}
|
||||
|
||||
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
|
||||
const { status } = await this.getResourceScale(params);
|
||||
|
||||
return status.replicas;
|
||||
}
|
||||
|
||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||
return this.scaleResource(params, { spec: { replicas }});
|
||||
}
|
||||
|
||||
restart(params: NamespacedResourceDescriptor) {
|
||||
return this.patch(params, {
|
||||
spec: {
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
||||
},
|
||||
},
|
||||
},
|
||||
}, "strategic");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { StorageClassData } from "@k8slens/kube-object";
|
||||
import { StorageClass } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class StorageClassApi extends KubeApi<StorageClass, StorageClassData> {
|
||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||
super(deps, {
|
||||
...opts,
|
||||
objectConstructor: StorageClass,
|
||||
});
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { ValidatingWebhookConfiguration } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ValidatingWebhookConfigurationApi extends KubeApi<ValidatingWebhookConfiguration> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: ValidatingWebhookConfiguration,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { VerticalPodAutoscaler } from "@k8slens/kube-object";
|
||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
|
||||
export class VerticalPodAutoscalerApi extends KubeApi<VerticalPodAutoscaler> {
|
||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||
super(deps, {
|
||||
...opts ?? {},
|
||||
objectConstructor: VerticalPodAutoscaler,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getMatchFor } from "./get-match-for";
|
||||
|
||||
describe("get-match-for", () => {
|
||||
it("given non-matching and matching regex, when called with a string, returns match", () => {
|
||||
const getMatch = getMatchFor(/some-match/, /some-non-match/);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const match = [...getMatch("some-match")!];
|
||||
|
||||
expect(match).toEqual(["some-match"]);
|
||||
});
|
||||
|
||||
it("given multiple matching regex, when called with a string, returns first match", () => {
|
||||
const getMatch = getMatchFor(/match/, /some-match/);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const match = [...getMatch("match")!];
|
||||
|
||||
expect(match).toEqual(["match"]);
|
||||
});
|
||||
|
||||
it("given multiple matching regex with one non-matching, when called with a string, returns first match", () => {
|
||||
const getMatch = getMatchFor(/non-match/, /some-match/, /match/);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const [...match] = getMatch("some-match")!;
|
||||
|
||||
expect(match).toEqual(["some-match"]);
|
||||
});
|
||||
|
||||
it("given no matching regex, when called with a string, returns undefined", () => {
|
||||
const getMatch = getMatchFor(/match/, /some-match/);
|
||||
|
||||
const match = getMatch("some-other-string");
|
||||
|
||||
expect(match).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export const getMatchFor =
|
||||
(...patterns: RegExp[]) =>
|
||||
(reference: string) => {
|
||||
for (const pattern of patterns) {
|
||||
const match = reference.match(pattern);
|
||||
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { KubeApi } from "./kube-api";
|
||||
|
||||
export const kubeApiInjectionToken = getInjectionToken<KubeApi<any, any>>({
|
||||
id: "kube-api-injection-token",
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IKubeApiParsed } from "./kube-api-parse";
|
||||
import { parseKubeApi } from "./kube-api-parse";
|
||||
|
||||
jest.mock("@k8slens/kube-api");
|
||||
|
||||
/**
|
||||
* [<input-url>, <expected-result>]
|
||||
*/
|
||||
type KubeApiParseTestData = [string, IKubeApiParsed];
|
||||
|
||||
const tests: KubeApiParseTestData[] = [
|
||||
[
|
||||
"/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||
{
|
||||
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "apiextensions.k8s.io",
|
||||
apiVersion: "v1beta1",
|
||||
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
|
||||
namespace: undefined,
|
||||
resource: "customresourcedefinitions",
|
||||
name: "prometheuses.monitoring.coreos.com",
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27",
|
||||
{
|
||||
apiBase: "/api/v1/pods",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
namespace: "kube-system",
|
||||
resource: "pods",
|
||||
name: "coredns-6955765f44-v8p27",
|
||||
},
|
||||
],
|
||||
[
|
||||
"/apis/stable.example.com/foo1/crontabs",
|
||||
{
|
||||
apiBase: "/apis/stable.example.com/foo1/crontabs",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "stable.example.com",
|
||||
apiVersion: "foo1",
|
||||
apiVersionWithGroup: "stable.example.com/foo1",
|
||||
resource: "crontabs",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||
{
|
||||
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||
apiPrefix: "/apis",
|
||||
apiGroup: "cluster.k8s.io",
|
||||
apiVersion: "v1alpha1",
|
||||
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
|
||||
resource: "clusters",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/namespaces",
|
||||
{
|
||||
apiBase: "/api/v1/namespaces",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "namespaces",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/secrets",
|
||||
{
|
||||
apiBase: "/api/v1/secrets",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "secrets",
|
||||
name: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/nodes/minikube",
|
||||
{
|
||||
apiBase: "/api/v1/nodes",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "nodes",
|
||||
name: "minikube",
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/foo-bar/nodes/minikube",
|
||||
{
|
||||
apiBase: "/api/foo-bar/nodes",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "foo-bar",
|
||||
apiVersionWithGroup: "foo-bar",
|
||||
resource: "nodes",
|
||||
name: "minikube",
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
"/api/v1/namespaces/kube-public",
|
||||
{
|
||||
apiBase: "/api/v1/namespaces",
|
||||
apiPrefix: "/api",
|
||||
apiGroup: "",
|
||||
apiVersion: "v1",
|
||||
apiVersionWithGroup: "v1",
|
||||
resource: "namespaces",
|
||||
name: "kube-public",
|
||||
namespace: undefined,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const invalidTests = [undefined, "", "ajklsmh"];
|
||||
|
||||
describe("parseApi unit tests", () => {
|
||||
it.each(tests)("testing %j", (url, expected) => {
|
||||
expect(parseKubeApi(url)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it.each(invalidTests)("testing %j should return undefined", (url) => {
|
||||
expect(parseKubeApi(url as never)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { compact, join } from "lodash/fp";
|
||||
import { getMatchFor } from "./get-match-for";
|
||||
import { prepend } from "./prepend";
|
||||
|
||||
// Parse kube-api path and get api-version, group, etc.
|
||||
|
||||
export interface IKubeApiLinkRef {
|
||||
apiPrefix?: string;
|
||||
apiVersion: string;
|
||||
resource: string;
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface IKubeApiParsed extends IKubeApiLinkRef {
|
||||
apiBase: string;
|
||||
apiPrefix: string;
|
||||
apiGroup: string;
|
||||
apiVersionWithGroup: string;
|
||||
}
|
||||
|
||||
const withoutDomainAddressOrParameters = (path: string) => new URL(path, "http://irrelevant").pathname;
|
||||
|
||||
const getKubeApiPathMatch = getMatchFor(
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||
);
|
||||
|
||||
const getParsedPath = (path: string) =>
|
||||
pipeline(path, withoutDomainAddressOrParameters, getKubeApiPathMatch, (match) => match?.groups);
|
||||
|
||||
const joinTruthy = (delimiter: string) => (toBeJoined: string[]) => pipeline(toBeJoined, compact, join(delimiter));
|
||||
|
||||
const getApiBase = (apiOrApis: string, apiGroup: string, apiVersion: string, resource: string) =>
|
||||
pipeline([apiOrApis, apiGroup, apiVersion, resource], joinTruthy("/"), prepend("/"));
|
||||
|
||||
const getApiPrefix = prepend("/");
|
||||
|
||||
const getApiVersionWithGroup = (apiGroup: string, apiVersion: string) => joinTruthy("/")([apiGroup, apiVersion]);
|
||||
|
||||
const getApiGroup = (apiGroup: string) => apiGroup || "";
|
||||
|
||||
export function parseKubeApi(path: string | undefined): IKubeApiParsed | undefined {
|
||||
if (!path) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsedPath = getParsedPath(path);
|
||||
|
||||
if (!parsedPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { apiOrApis, apiGroup, namespace, apiVersion, resource, name } = parsedPath;
|
||||
|
||||
return {
|
||||
apiBase: getApiBase(apiOrApis, apiGroup, apiVersion, resource),
|
||||
apiPrefix: getApiPrefix(apiOrApis),
|
||||
apiGroup: getApiGroup(apiGroup),
|
||||
apiVersion,
|
||||
apiVersionWithGroup: getApiVersionWithGroup(apiGroup, apiVersion),
|
||||
namespace,
|
||||
resource,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
||||
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
|
||||
}
|
||||
|
||||
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
||||
export function createKubeApiURL(linkParsed: IKubeApiParsed): string;
|
||||
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string {
|
||||
if (isIKubeApiParsed(ref)) {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: ref.apiPrefix,
|
||||
resource: ref.resource,
|
||||
name: ref.name,
|
||||
namespace: ref.namespace,
|
||||
apiVersion: `${ref.apiGroup}/${ref.apiVersion}`,
|
||||
});
|
||||
}
|
||||
|
||||
const { apiPrefix = "/apis", resource, apiVersion, name, namespace } = ref;
|
||||
const parts = [apiPrefix, apiVersion];
|
||||
|
||||
if (namespace) {
|
||||
parts.push("namespaces", namespace);
|
||||
}
|
||||
|
||||
parts.push(resource);
|
||||
|
||||
if (name) {
|
||||
parts.push(name);
|
||||
}
|
||||
|
||||
return parts.join("/");
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,887 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Base class for building all kubernetes apis
|
||||
|
||||
import { merge } from "lodash";
|
||||
import { stringify } from "querystring";
|
||||
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
||||
import type {
|
||||
KubeObjectConstructor,
|
||||
KubeJsonApiDataFor,
|
||||
KubeObjectMetadata,
|
||||
KubeJsonApiData,
|
||||
KubeObject,
|
||||
KubeObjectScope,
|
||||
Scale,
|
||||
} from "@k8slens/kube-object";
|
||||
import {
|
||||
isJsonApiData,
|
||||
isJsonApiDataList,
|
||||
isPartialJsonApiData,
|
||||
KubeStatus,
|
||||
isKubeStatusData,
|
||||
} from "@k8slens/kube-object";
|
||||
import byline from "byline";
|
||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||
import type { KubeJsonApi } from "./kube-json-api";
|
||||
import type { Disposer } from "@k8slens/utilities";
|
||||
import { isDefined, noop, WrappedAbortController } from "@k8slens/utilities";
|
||||
import type { RequestInit, Response } from "@k8slens/node-fetch";
|
||||
import type { Patch } from "rfc6902";
|
||||
import assert from "assert";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
import type { Logger } from "@k8slens/logger";
|
||||
import { matches } from "lodash/fp";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import type { ScaleCreateOptions } from "@k8slens/kube-object/src/types/scale";
|
||||
|
||||
/**
|
||||
* The options used for creating a `KubeApi`
|
||||
*/
|
||||
export interface KubeApiOptions<T extends KubeObject, Data extends KubeJsonApiDataFor<T> = KubeJsonApiDataFor<T>>
|
||||
extends DerivedKubeApiOptions {
|
||||
/**
|
||||
* base api-path for listing all resources, e.g. "/api/v1/pods"
|
||||
*
|
||||
* Must be provided either here or under `objectConstructor.apiBase`
|
||||
* @deprecated should be specified by `objectConstructor`
|
||||
*/
|
||||
apiBase?: string;
|
||||
|
||||
/**
|
||||
* The constructor for the kube objects returned from the API
|
||||
*/
|
||||
objectConstructor: KubeObjectConstructor<T, Data>;
|
||||
|
||||
/**
|
||||
* Must be provided either here or under `objectConstructor.namespaced`
|
||||
* @deprecated should be specified by `objectConstructor`
|
||||
*/
|
||||
isNamespaced?: boolean;
|
||||
|
||||
/**
|
||||
* Must be provided either here or under `objectConstructor.kind`
|
||||
* @deprecated should be specified by `objectConstructor`
|
||||
*/
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
export interface DerivedKubeApiOptions {
|
||||
/**
|
||||
* If the API uses a different API endpoint (e.g. apiBase) depending on the cluster version,
|
||||
* fallback API bases can be listed individually.
|
||||
*
|
||||
* The first (existing) API base is used in the requests, if apiBase is not found.
|
||||
*
|
||||
* This option only has effect if {@link DerivedKubeApiOptions.checkPreferredVersion} is `true`.
|
||||
*/
|
||||
fallbackApiBases?: string[];
|
||||
|
||||
/**
|
||||
* This option is useful for protecting against newer versions on the same apiBase from being
|
||||
* used. So that if a certain type only supports `v1`, or `v2` of some kind and then the `v3`
|
||||
* version becomes the `preferredVersion` on the server but still has `v2` then the `v2` version
|
||||
* will be used instead.
|
||||
*
|
||||
* This can help to prevent crashes in the future if the shape of a kind sufficently changes.
|
||||
*
|
||||
* The order is important. It should be sorted and the first entry should be the most preferable.
|
||||
*
|
||||
* This option only has effect if {@link DerivedKubeApiOptions.checkPreferredVersion} is `true`
|
||||
*/
|
||||
allowedUsableVersions?: Partial<Record<string, [string, ...string[]]>>;
|
||||
|
||||
/**
|
||||
* If `true` then will check all declared apiBases against the kube api server
|
||||
* for the first accepted one.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
checkPreferredVersion?: boolean;
|
||||
|
||||
/**
|
||||
* The api instance to use for making requests
|
||||
*
|
||||
* @default apiKube
|
||||
*/
|
||||
request?: KubeJsonApi;
|
||||
}
|
||||
|
||||
export interface KubeApiQueryParams {
|
||||
watch?: boolean | number;
|
||||
resourceVersion?: string;
|
||||
timeoutSeconds?: number;
|
||||
limit?: number; // doesn't work with ?watch
|
||||
continue?: string; // might be used with ?limit from second request
|
||||
labelSelector?: string | string[]; // restrict list of objects by their labels, e.g. labelSelector: ["label=value"]
|
||||
fieldSelector?: string | string[]; // restrict list of objects by their fields, e.g. fieldSelector: "field=name"
|
||||
}
|
||||
|
||||
export interface KubeApiListOptions {
|
||||
namespace?: string;
|
||||
reqInit?: RequestInit;
|
||||
}
|
||||
|
||||
export interface IKubePreferredVersion {
|
||||
preferredVersion?: {
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface KubeApiResource {
|
||||
categories?: string[];
|
||||
group?: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
namespaced: boolean;
|
||||
shortNames?: string[];
|
||||
singularName: string;
|
||||
storageVersionHash?: string;
|
||||
verbs: string[];
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface KubeApiResourceList {
|
||||
apiVersion?: string;
|
||||
groupVersion?: string;
|
||||
kind?: string;
|
||||
resources: KubeApiResource[];
|
||||
}
|
||||
|
||||
export interface KubeApiResourceVersion {
|
||||
groupVersion: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface KubeApiResourceVersionList {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
preferredVersion: KubeApiResourceVersion;
|
||||
versions: KubeApiResourceVersion[];
|
||||
}
|
||||
|
||||
const not =
|
||||
<T>(fn: (val: T) => boolean) =>
|
||||
(val: T) =>
|
||||
!fn(val);
|
||||
|
||||
const getOrderedVersions = (
|
||||
list: KubeApiResourceVersionList,
|
||||
allowedUsableVersions: string[] | undefined,
|
||||
): KubeApiResourceVersion[] =>
|
||||
[list.preferredVersion, ...list.versions.filter(not(matches(list.preferredVersion)))].filter(
|
||||
({ version }) => !allowedUsableVersions || allowedUsableVersions.includes(version),
|
||||
);
|
||||
|
||||
export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background";
|
||||
|
||||
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (
|
||||
data: IKubeWatchEvent<T> | null,
|
||||
error: KubeStatus | Response | null | Record<string, unknown>,
|
||||
) => void;
|
||||
|
||||
export interface KubeApiWatchOptions<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>> {
|
||||
/**
|
||||
* If the resource is namespaced then the default is `"default"`
|
||||
*/
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* This will be called when either an error occurs or some data is received
|
||||
*/
|
||||
callback?: KubeApiWatchCallback<Data>;
|
||||
|
||||
/**
|
||||
* This is a way of aborting the request
|
||||
*/
|
||||
abortController?: AbortController;
|
||||
|
||||
/**
|
||||
* The ID used for tracking within logs
|
||||
*/
|
||||
watchId?: string;
|
||||
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
retry?: boolean;
|
||||
|
||||
/**
|
||||
* timeout in seconds
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export type KubeApiPatchType = "merge" | "json" | "strategic";
|
||||
|
||||
export const patchTypeHeaders: Record<KubeApiPatchType, string> = {
|
||||
merge: "application/merge-patch+json",
|
||||
json: "application/json-patch+json",
|
||||
strategic: "application/strategic-merge-patch+json",
|
||||
};
|
||||
|
||||
export interface ResourceDescriptor {
|
||||
/**
|
||||
* The name of the kubernetes resource
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The namespace that the resource lives in (if the resource is namespaced)
|
||||
*
|
||||
* Note: if not provided and the resource kind is namespaced, then this defaults to `"default"`
|
||||
*/
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface SubResourceDescriptor {
|
||||
subResource: string;
|
||||
}
|
||||
|
||||
export type SpecificResourceDescriptor<Scope extends KubeObjectScope> = {
|
||||
/**
|
||||
* The name of the kubernetes resource
|
||||
*/
|
||||
name: string;
|
||||
} & (Scope extends KubeObjectScope.Cluster
|
||||
? {}
|
||||
: Scope extends KubeObjectScope.Namespace
|
||||
? {
|
||||
/**
|
||||
* The namespace that the resource lives in
|
||||
*/
|
||||
namespace: string;
|
||||
}
|
||||
: {
|
||||
namespace?: string;
|
||||
});
|
||||
|
||||
export type NamespacedResourceDescriptor = SpecificResourceDescriptor<KubeObjectScope.Namespace>;
|
||||
export type ClusterScopedResourceDescriptor = SpecificResourceDescriptor<KubeObjectScope.Cluster>;
|
||||
|
||||
export interface DeleteResourceDescriptor extends ResourceDescriptor {
|
||||
/**
|
||||
* This determinines how child resources should be handled by kubernetes
|
||||
*
|
||||
* @default "Background"
|
||||
*/
|
||||
propagationPolicy?: PropagationPolicy;
|
||||
}
|
||||
|
||||
export interface KubeApiDependencies {
|
||||
readonly logger: Logger;
|
||||
readonly maybeKubeApi: KubeJsonApi | undefined;
|
||||
}
|
||||
|
||||
export class KubeApi<
|
||||
Object extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object> = KubeJsonApiDataFor<Object>,
|
||||
> {
|
||||
readonly kind: string;
|
||||
|
||||
readonly apiVersion: string;
|
||||
|
||||
@observable apiBase: string;
|
||||
|
||||
apiPrefix: string;
|
||||
|
||||
apiGroup: string;
|
||||
|
||||
apiVersionPreferred: string | undefined;
|
||||
|
||||
readonly apiResource: string;
|
||||
|
||||
readonly isNamespaced: boolean;
|
||||
|
||||
public readonly objectConstructor: KubeObjectConstructor<Object, Data>;
|
||||
|
||||
protected readonly request: KubeJsonApi;
|
||||
|
||||
protected readonly resourceVersions = new Map<string, string>();
|
||||
|
||||
protected readonly watchDisposer: Disposer | undefined;
|
||||
|
||||
private watchId = 1;
|
||||
|
||||
protected readonly doCheckPreferredVersion: boolean;
|
||||
|
||||
protected readonly fullApiPathname: string;
|
||||
|
||||
protected readonly fallbackApiBases: string[] | undefined;
|
||||
|
||||
protected readonly allowedUsableVersions: Partial<Record<string, string[]>> | undefined;
|
||||
|
||||
constructor(protected readonly dependencies: KubeApiDependencies, opts: KubeApiOptions<Object, Data>) {
|
||||
const {
|
||||
objectConstructor,
|
||||
request = this.dependencies.maybeKubeApi,
|
||||
kind = objectConstructor.kind,
|
||||
isNamespaced,
|
||||
apiBase: fullApiPathname = objectConstructor.apiBase,
|
||||
checkPreferredVersion: doCheckPreferredVersion = false,
|
||||
fallbackApiBases,
|
||||
allowedUsableVersions,
|
||||
} = opts;
|
||||
|
||||
assert(
|
||||
fullApiPathname,
|
||||
"apiBase MUST be provided either via KubeApiOptions.apiBase or KubeApiOptions.objectConstructor.apiBase",
|
||||
);
|
||||
assert(request, "request MUST be provided if not in a cluster page frame context");
|
||||
|
||||
const parsedApi = parseKubeApi(fullApiPathname);
|
||||
|
||||
assert(parsedApi, "apiBase MUST be a valid kube api pathname");
|
||||
|
||||
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parsedApi;
|
||||
|
||||
assert(kind, "kind MUST be provided either via KubeApiOptions.kind or KubeApiOptions.objectConstructor.kind");
|
||||
assert(apiPrefix, "apiBase MUST be parsable as a kubeApi selfLink style string");
|
||||
|
||||
this.doCheckPreferredVersion = doCheckPreferredVersion;
|
||||
this.fallbackApiBases = fallbackApiBases;
|
||||
this.allowedUsableVersions = allowedUsableVersions;
|
||||
this.fullApiPathname = fullApiPathname;
|
||||
this.kind = kind;
|
||||
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
|
||||
this.apiBase = apiBase;
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = apiGroup;
|
||||
this.apiVersion = apiVersion;
|
||||
this.apiResource = resource;
|
||||
this.request = request;
|
||||
this.objectConstructor = objectConstructor;
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
get apiVersionWithGroup() {
|
||||
return [this.apiGroup, this.apiVersionPreferred ?? this.apiVersion].filter(Boolean).join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest API prefix/group that contains the required resource.
|
||||
* First tries fullApiPathname, then urls in order from fallbackApiBases.
|
||||
*/
|
||||
private async getLatestApiPrefixGroup() {
|
||||
// Note that this.fullApiPathname is the "full" url, whereas this.apiBase is parsed
|
||||
const rawApiBases = [this.fullApiPathname, this.objectConstructor.apiBase, ...(this.fallbackApiBases ?? [])].filter(
|
||||
isDefined,
|
||||
);
|
||||
const apiBases = new Set(rawApiBases);
|
||||
|
||||
for (const apiUrl of apiBases) {
|
||||
const parsedApi = parseKubeApi(apiUrl);
|
||||
|
||||
if (!parsedApi) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const { apiPrefix, apiGroup, resource } = parsedApi;
|
||||
const list = (await this.request.get(`${apiPrefix}/${apiGroup}`)) as KubeApiResourceVersionList;
|
||||
const resourceVersions = getOrderedVersions(list, this.allowedUsableVersions?.[apiGroup]);
|
||||
|
||||
for (const resourceVersion of resourceVersions) {
|
||||
const { resources } = (await this.request.get(
|
||||
`${apiPrefix}/${resourceVersion.groupVersion}`,
|
||||
)) as KubeApiResourceList;
|
||||
|
||||
if (resources.some(({ name }) => name === resource)) {
|
||||
return {
|
||||
apiPrefix,
|
||||
apiGroup,
|
||||
apiVersionPreferred: resourceVersion.version,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore exception to try next url
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Can't find working API for the Kubernetes resource ${this.apiResource}`);
|
||||
}
|
||||
|
||||
protected async checkPreferredVersion() {
|
||||
if (!this.doCheckPreferredVersion && (this.fallbackApiBases || this.allowedUsableVersions)) {
|
||||
throw new Error(
|
||||
"checkPreferredVersion must be enabled if either fallbackApiBases or allowedUsableVersions are set in KubeApi",
|
||||
);
|
||||
}
|
||||
|
||||
if (this.doCheckPreferredVersion && this.apiVersionPreferred === undefined) {
|
||||
const { apiPrefix, apiGroup, apiVersionPreferred } = await this.getLatestApiPrefixGroup();
|
||||
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = apiGroup;
|
||||
this.apiVersionPreferred = apiVersionPreferred;
|
||||
this.apiBase = this.computeApiBase();
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/default-param-last
|
||||
setResourceVersion(namespace = "", newVersion: string) {
|
||||
this.resourceVersions.set(namespace, newVersion);
|
||||
}
|
||||
|
||||
getResourceVersion(namespace = "") {
|
||||
return this.resourceVersions.get(namespace);
|
||||
}
|
||||
|
||||
async refreshResourceVersion(params?: KubeApiListOptions) {
|
||||
return this.list(params, { limit: 1 });
|
||||
}
|
||||
|
||||
private computeApiBase(): string {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method differs from {@link formatUrlForNotListing} because this treats `""` as "all namespaces"
|
||||
* NOTE: This is also useful for watching
|
||||
* @param namespace The namespace to list in or `""` for all namespaces
|
||||
*/
|
||||
formatUrlForListing(namespace: string | undefined, query?: Partial<KubeApiQueryParams>) {
|
||||
const resourcePath = createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
namespace: this.isNamespaced ? namespace ?? "default" : undefined,
|
||||
});
|
||||
|
||||
return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a URL pathname and query for acting upon a specific resource.
|
||||
*/
|
||||
formatUrlForNotListing({ name, namespace }: Partial<ResourceDescriptor> = {}) {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
namespace: this.isNamespaced ? namespace || "default" : undefined,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link formatUrlForNotListing} or {@link formatUrlForListing} instead
|
||||
*/
|
||||
getUrl(resource?: Partial<ResourceDescriptor>, query?: Partial<KubeApiQueryParams>) {
|
||||
if (query) {
|
||||
return this.formatUrlForListing(resource?.namespace, query);
|
||||
}
|
||||
|
||||
return this.formatUrlForNotListing(resource);
|
||||
}
|
||||
|
||||
protected normalizeQuery(query: Partial<KubeApiQueryParams> = {}) {
|
||||
if (query.labelSelector) {
|
||||
query.labelSelector = [query.labelSelector].flat().join(",");
|
||||
}
|
||||
|
||||
if (query.fieldSelector) {
|
||||
query.fieldSelector = [query.fieldSelector].flat().join(",");
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected parseResponse(data: unknown, namespace?: string): Object | Object[] | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const KubeObjectConstructor = this.objectConstructor;
|
||||
|
||||
// process items list response, check before single item since there is overlap
|
||||
if (isJsonApiDataList(data, isPartialJsonApiData)) {
|
||||
const { apiVersion, items, metadata } = data;
|
||||
|
||||
this.setResourceVersion(namespace, metadata.resourceVersion);
|
||||
this.setResourceVersion("", metadata.resourceVersion);
|
||||
|
||||
return items
|
||||
.map((item) => {
|
||||
if (item.metadata) {
|
||||
this.ensureMetadataSelfLink(item.metadata);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const object = new KubeObjectConstructor({
|
||||
...(item as Data),
|
||||
kind: this.kind,
|
||||
apiVersion,
|
||||
});
|
||||
|
||||
return object;
|
||||
})
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
// process a single item
|
||||
if (isJsonApiData(data)) {
|
||||
this.ensureMetadataSelfLink(data.metadata);
|
||||
|
||||
return new KubeObjectConstructor(data as Data);
|
||||
}
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// custom apis might return array for list response, e.g. users, groups, etc.
|
||||
return data
|
||||
.filter(isJsonApiData)
|
||||
.map((data) => (this.ensureMetadataSelfLink(data.metadata), data))
|
||||
.map((data) => new KubeObjectConstructor(data as Data));
|
||||
}
|
||||
|
||||
private ensureMetadataSelfLink<T extends { selfLink?: string; namespace?: string; name: string }>(
|
||||
metadata: T,
|
||||
): asserts metadata is T & { selfLink: string } {
|
||||
metadata.selfLink ||= createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
namespace: metadata.namespace,
|
||||
name: metadata.name,
|
||||
});
|
||||
}
|
||||
|
||||
async list(
|
||||
{ namespace = "", reqInit }: KubeApiListOptions = {},
|
||||
query?: KubeApiQueryParams,
|
||||
): Promise<Object[] | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const url = this.formatUrlForListing(namespace);
|
||||
const res = await this.request.get(url, { query }, reqInit);
|
||||
const parsed = this.parseResponse(res, namespace);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Error(`GET multiple request to ${url} returned not an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
async get(desc: ResourceDescriptor, query?: KubeApiQueryParams): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const url = this.formatUrlForNotListing(desc);
|
||||
const res = await this.request.get(url, { query });
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
throw new Error(`GET single request to ${url} returned an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async create(
|
||||
{ name, namespace }: Partial<ResourceDescriptor>,
|
||||
partialData?: PartialDeep<Object>,
|
||||
): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const apiUrl = this.formatUrlForNotListing({ namespace });
|
||||
const data = merge(partialData, {
|
||||
kind: this.kind,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
metadata: {
|
||||
name,
|
||||
namespace,
|
||||
},
|
||||
});
|
||||
const res = await this.request.post(apiUrl, { data });
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
throw new Error(`POST request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async update({ name, namespace }: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing({ namespace, name });
|
||||
|
||||
const res = await this.request.put(apiUrl, {
|
||||
data: merge(data, {
|
||||
metadata: {
|
||||
name,
|
||||
namespace,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
throw new Error(`PUT request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal method for requesting the `/scale` sub-resource if it exists
|
||||
*/
|
||||
protected async getResourceScale(desc: ResourceDescriptor): Promise<Scale> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
const res = await this.request.get(`${apiUrl}/scale`);
|
||||
|
||||
return res as Scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal method for requesting the `/scale` sub-resource if it exists
|
||||
*/
|
||||
protected async scaleResource(desc: ResourceDescriptor, data: ScaleCreateOptions): Promise<Scale> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
const res = await this.request.patch(
|
||||
`${apiUrl}/scale`,
|
||||
{
|
||||
data: { ...data },
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"content-type": patchTypeHeaders.merge,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return res as Scale;
|
||||
}
|
||||
|
||||
async patch(desc: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null>;
|
||||
async patch(
|
||||
desc: ResourceDescriptor,
|
||||
data: PartialDeep<Object>,
|
||||
strategy: "strategic" | "merge",
|
||||
): Promise<Object | null>;
|
||||
async patch(desc: ResourceDescriptor, data: Patch, strategy: "json"): Promise<Object | null>;
|
||||
async patch(
|
||||
desc: ResourceDescriptor,
|
||||
data: PartialDeep<Object> | Patch,
|
||||
strategy: KubeApiPatchType,
|
||||
): Promise<Object | null>;
|
||||
async patch(
|
||||
desc: ResourceDescriptor,
|
||||
data: PartialDeep<Object> | Patch,
|
||||
strategy: KubeApiPatchType = "strategic",
|
||||
): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
const res = await this.request.patch(
|
||||
apiUrl,
|
||||
{ data },
|
||||
{
|
||||
headers: {
|
||||
"content-type": patchTypeHeaders[strategy],
|
||||
},
|
||||
},
|
||||
);
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
throw new Error(`PATCH request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some k8s resources might implement special "delete" (e.g. pod.api)
|
||||
* See also: https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/
|
||||
* By default should work same as delete()
|
||||
*/
|
||||
async evict(desc: DeleteResourceDescriptor): Promise<KubeStatus | KubeObject | unknown> {
|
||||
return this.delete(desc);
|
||||
}
|
||||
|
||||
async delete({ propagationPolicy = "Background", ...desc }: DeleteResourceDescriptor) {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
return this.request.del(apiUrl, {
|
||||
query: {
|
||||
propagationPolicy,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWatchUrl(namespace?: string, query: KubeApiQueryParams = {}) {
|
||||
return this.formatUrlForListing(namespace, {
|
||||
watch: 1,
|
||||
resourceVersion: this.getResourceVersion(namespace),
|
||||
...query,
|
||||
});
|
||||
}
|
||||
|
||||
watch(opts?: KubeApiWatchOptions<Object, Data>): Disposer {
|
||||
let errorReceived = false;
|
||||
let timedRetry: NodeJS.Timeout;
|
||||
const {
|
||||
namespace,
|
||||
callback = noop as KubeApiWatchCallback<Data>,
|
||||
retry = false,
|
||||
timeout = 600,
|
||||
watchId = `${this.kind.toLowerCase()}-${this.watchId++}`,
|
||||
} = opts ?? {};
|
||||
|
||||
// Create AbortController for this request
|
||||
const abortController = new WrappedAbortController(opts?.abortController);
|
||||
const watchUrl = this.getWatchUrl(namespace);
|
||||
|
||||
abortController.signal.addEventListener("abort", () => {
|
||||
this.dependencies.logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
|
||||
clearTimeout(timedRetry);
|
||||
});
|
||||
|
||||
const requestParams = timeout ? { query: { timeoutSeconds: timeout } } : {};
|
||||
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
this.dependencies.logger.info(
|
||||
`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`,
|
||||
);
|
||||
|
||||
responsePromise
|
||||
.then((response) => {
|
||||
// True if the current watch request was retried
|
||||
let requestRetried = false;
|
||||
|
||||
if (!response.ok) {
|
||||
this.dependencies.logger.warn(`[KUBE-API] watch (${watchId}) error response ${watchUrl}`, {
|
||||
status: response.status,
|
||||
});
|
||||
|
||||
return callback(null, response);
|
||||
}
|
||||
|
||||
// Add mechanism to retry in case timeoutSeconds is set but the watch wasn't timed out.
|
||||
// This can happen if e.g. network is offline and AWS NLB is used.
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
// We only retry if we haven't retried, haven't aborted and haven't received k8s error
|
||||
if (requestRetried || abortController.signal.aborted || errorReceived) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close current request
|
||||
abortController.abort();
|
||||
|
||||
this.dependencies.logger.info(`[KUBE-API] Watch timeout set, but not retried, retrying now`);
|
||||
|
||||
requestRetried = true;
|
||||
|
||||
// Clearing out any possible timeout, although we don't expect this to be set
|
||||
clearTimeout(timedRetry);
|
||||
this.watch({ ...opts, namespace, callback, watchId, retry: true });
|
||||
// We wait longer than the timeout, as we expect the request to be retried with timeoutSeconds
|
||||
}, timeout * 1000 * 1.1);
|
||||
}
|
||||
|
||||
if (!response.body || !response.body.readable) {
|
||||
if (!response.body) {
|
||||
this.dependencies.logger.warn(`[KUBE-API]: watch (${watchId}) did not return a body`);
|
||||
}
|
||||
requestRetried = true;
|
||||
|
||||
clearTimeout(timedRetry);
|
||||
timedRetry = setTimeout(() => {
|
||||
// we did not get any kubernetes errors so let's retry
|
||||
this.watch({ ...opts, namespace, callback, watchId, retry: true });
|
||||
}, 1000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const eventName of ["end", "close", "error"]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
response.body.on(eventName, () => {
|
||||
// We only retry if we haven't retried, haven't aborted and haven't received k8s error
|
||||
// kubernetes errors (=errorReceived set) should be handled in a callback
|
||||
if (requestRetried || abortController.signal.aborted || errorReceived) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dependencies.logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`);
|
||||
|
||||
requestRetried = true;
|
||||
|
||||
clearTimeout(timedRetry);
|
||||
timedRetry = setTimeout(() => {
|
||||
// we did not get any kubernetes errors so let's retry
|
||||
this.watch({ ...opts, namespace, callback, watchId, retry: true });
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
byline(response.body).on("data", (line: string) => {
|
||||
try {
|
||||
const event = JSON.parse(line) as IKubeWatchEvent<Data>;
|
||||
|
||||
if (event.type === "ERROR" && isKubeStatusData(event.object)) {
|
||||
errorReceived = true;
|
||||
|
||||
return callback(null, new KubeStatus(event.object));
|
||||
}
|
||||
|
||||
this.modifyWatchEvent(event);
|
||||
callback(event, null);
|
||||
} catch (ignore) {
|
||||
// ignore parse errors
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (!abortController.signal.aborted) {
|
||||
this.dependencies.logger.error(`[KUBE-API] watch (${watchId}) threw ${watchUrl}`, error);
|
||||
}
|
||||
callback(null, error as Record<string, unknown>);
|
||||
});
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}
|
||||
|
||||
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData<KubeObjectMetadata>>) {
|
||||
if (event.type === "ERROR") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensureMetadataSelfLink(event.object.metadata);
|
||||
|
||||
const { namespace, resourceVersion } = event.object.metadata;
|
||||
|
||||
assert(resourceVersion, "watch events failed to return resourceVersion from kube api");
|
||||
|
||||
this.setResourceVersion(namespace, resourceVersion);
|
||||
this.setResourceVersion("", resourceVersion);
|
||||
}
|
||||
}
|
||||
@@ -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 { JsonApiError } from "@k8slens/json-api";
|
||||
import { JsonApi } from "@k8slens/json-api";
|
||||
import type { Response } from "@k8slens/node-fetch";
|
||||
import type { KubeJsonApiData } from "@k8slens/kube-object";
|
||||
|
||||
export interface KubeJsonApiError extends JsonApiError {
|
||||
code: number;
|
||||
status: string;
|
||||
message?: string;
|
||||
reason: string;
|
||||
details: {
|
||||
name: string;
|
||||
kind: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||
protected parseError(error: KubeJsonApiError | string, res: Response): string[] {
|
||||
if (typeof error === "string") {
|
||||
return [error];
|
||||
}
|
||||
|
||||
const { status, reason, message } = error;
|
||||
|
||||
if (status && reason) {
|
||||
return [message || `${status}: ${reason}`];
|
||||
}
|
||||
|
||||
return super.parseError(error, res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeStatusData } from "@k8slens/kube-object";
|
||||
|
||||
export type IKubeWatchEvent<T> =
|
||||
| {
|
||||
readonly type: "ADDED" | "MODIFIED" | "DELETED";
|
||||
readonly object: T;
|
||||
}
|
||||
| {
|
||||
readonly type: "ERROR";
|
||||
readonly object?: KubeStatusData;
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { Response, Headers as NodeFetchHeaders } from "@k8slens/node-fetch";
|
||||
import { PassThrough } from "stream";
|
||||
|
||||
export const createMockResponseFromString = (url: string, data: string, statusCode = 200) => {
|
||||
const res: jest.Mocked<Response> = {
|
||||
buffer: jest.fn(async () => {
|
||||
throw new Error("buffer() is not supported");
|
||||
}),
|
||||
clone: jest.fn(() => res),
|
||||
arrayBuffer: jest.fn(async () => {
|
||||
throw new Error("arrayBuffer() is not supported");
|
||||
}),
|
||||
blob: jest.fn(async () => {
|
||||
throw new Error("blob() is not supported");
|
||||
}),
|
||||
body: new PassThrough(),
|
||||
bodyUsed: false,
|
||||
headers: new NodeFetchHeaders(),
|
||||
json: jest.fn(async () => JSON.parse(await res.text())),
|
||||
ok: 200 <= statusCode && statusCode < 300,
|
||||
redirected: 300 <= statusCode && statusCode < 400,
|
||||
size: data.length,
|
||||
status: statusCode,
|
||||
statusText: "some-text",
|
||||
text: jest.fn(async () => data),
|
||||
type: "basic",
|
||||
url,
|
||||
formData: jest.fn(async () => {
|
||||
throw new Error("formData() is not supported");
|
||||
}),
|
||||
};
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => {
|
||||
const res: jest.Mocked<Response> = {
|
||||
buffer: jest.fn(async () => {
|
||||
throw new Error("buffer() is not supported");
|
||||
}),
|
||||
clone: jest.fn(() => res),
|
||||
arrayBuffer: jest.fn(async () => {
|
||||
throw new Error("arrayBuffer() is not supported");
|
||||
}),
|
||||
blob: jest.fn(async () => {
|
||||
throw new Error("blob() is not supported");
|
||||
}),
|
||||
body: stream,
|
||||
bodyUsed: false,
|
||||
headers: new NodeFetchHeaders(),
|
||||
json: jest.fn(async () => JSON.parse(await res.text())),
|
||||
ok: 200 <= statusCode && statusCode < 300,
|
||||
redirected: 300 <= statusCode && statusCode < 400,
|
||||
size: 10,
|
||||
status: statusCode,
|
||||
statusText: "some-text",
|
||||
text: jest.fn(() => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
stream.on("error", (err) => reject(err));
|
||||
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
||||
});
|
||||
}),
|
||||
type: "basic",
|
||||
url,
|
||||
formData: jest.fn(async () => {
|
||||
throw new Error("formData() is not supported");
|
||||
}),
|
||||
};
|
||||
|
||||
return res;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export const prepend = (prependWith: string) => (what: string) => `${prependWith}${what}`;
|
||||
Reference in new issue
Block a user