diff --git a/package-lock.json b/package-lock.json index 539916d8a3..d398f4aef3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38059,7 +38059,6 @@ "@astronautlabs/jsonpath": "^1.1.0", "@hapi/call": "^9.0.1", "@hapi/subtext": "^7.1.0", - "@k8slens/cluster-settings": "^6.5.0-alpha.1", "@k8slens/node-fetch": "^6.5.0-alpha.1", "@k8slens/react-application": "^1.0.0-alpha.0", "@kubernetes/client-node": "^0.18.1", @@ -38266,6 +38265,7 @@ "peerDependencies": { "@k8slens/application": "^6.5.0-alpha.0", "@k8slens/application-for-electron-main": "^6.5.0-alpha.0", + "@k8slens/cluster-settings": "^6.5.0-alpha.1", "@k8slens/legacy-extensions": "^1.0.0-alpha.0", "@k8slens/messaging": "^1.0.0-alpha.1", "@k8slens/messaging-for-main": "^1.0.0-alpha.1", diff --git a/packages/core/package.json b/packages/core/package.json index 7b04d2ce06..be81f74f14 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -120,7 +120,6 @@ "@astronautlabs/jsonpath": "^1.1.0", "@hapi/call": "^9.0.1", "@hapi/subtext": "^7.1.0", - "@k8slens/cluster-settings": "^6.5.0-alpha.1", "@k8slens/node-fetch": "^6.5.0-alpha.1", "@k8slens/react-application": "^1.0.0-alpha.0", "@kubernetes/client-node": "^0.18.1", @@ -324,6 +323,7 @@ "peerDependencies": { "@k8slens/application": "^6.5.0-alpha.0", "@k8slens/application-for-electron-main": "^6.5.0-alpha.0", + "@k8slens/cluster-settings": "^6.5.0-alpha.1", "@k8slens/legacy-extensions": "^1.0.0-alpha.0", "@k8slens/messaging": "^1.0.0-alpha.1", "@k8slens/messaging-for-main": "^1.0.0-alpha.1", diff --git a/packages/core/src/common/cluster-types.ts b/packages/core/src/common/cluster-types.ts index 4095c3fc23..a8ce7da912 100644 --- a/packages/core/src/common/cluster-types.ts +++ b/packages/core/src/common/cluster-types.ts @@ -183,7 +183,6 @@ export const initialNodeShellImage = "docker.io/alpine:3.13"; * The data representing a cluster's state, for passing between main and renderer */ export interface ClusterState { - apiUrl: string; online: boolean; disconnected: boolean; accessible: boolean; diff --git a/packages/core/src/common/cluster/cluster.ts b/packages/core/src/common/cluster/cluster.ts index fc66d9aa29..f703ebc336 100644 --- a/packages/core/src/common/cluster/cluster.ts +++ b/packages/core/src/common/cluster/cluster.ts @@ -5,7 +5,7 @@ import { computed, observable, toJS, runInAction } from "mobx"; import type { KubeApiResource } from "../rbac"; -import type { ClusterState, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, ClusterConfigData } from "../cluster-types"; +import type { ClusterState, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../cluster-types"; import { ClusterMetadataKey, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types"; import type { IObservableValue } from "mobx"; import { replaceObservableObject } from "../utils/replace-observable-object"; @@ -27,11 +27,6 @@ export class Cluster { */ readonly kubeConfigPath = observable.box() as IObservableValue; - /** - * Kubernetes API server URL - */ - readonly apiUrl: IObservableValue; - /** * Describes if we can detect that cluster is online */ @@ -122,7 +117,7 @@ export class Cluster { */ readonly prometheusPreferences = computed(() => pick(toJS(this.preferences), "prometheus", "prometheusProvider") as ClusterPrometheusPreferences); - constructor({ id, ...model }: ClusterModel, configData: ClusterConfigData) { + constructor({ id, ...model }: ClusterModel) { const { error } = clusterModelIdChecker.validate({ id }); if (error) { @@ -131,7 +126,6 @@ export class Cluster { this.id = id; this.updateModel(model); - this.apiUrl = observable.box(configData.clusterServerUrl); } /** @@ -187,7 +181,6 @@ export class Cluster { */ getState(): ClusterState { return { - apiUrl: this.apiUrl.get(), online: this.online.get(), ready: this.ready.get(), disconnected: this.disconnected.get(), @@ -207,7 +200,6 @@ export class Cluster { this.accessible.set(state.accessible); this.allowedNamespaces.replace(state.allowedNamespaces); this.resourcesToShow.replace(state.resourcesToShow); - this.apiUrl.set(state.apiUrl); this.disconnected.set(state.disconnected); this.isAdmin.set(state.isAdmin); this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled); diff --git a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts index 48fdb3e544..ce117d9475 100644 --- a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts @@ -51,8 +51,6 @@ describe("ApiManager", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); apiManager = di.inject(apiManagerInjectable); diff --git a/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts index e3bcd70a81..e6efa606b9 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts @@ -43,8 +43,6 @@ describe("KubeApi", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); apiManager = di.inject(apiManagerInjectable); diff --git a/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts index e7240895cf..a5cce9f02c 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts @@ -62,8 +62,6 @@ describe("createKubeApiForRemoteCluster", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); fetchMock = asyncFn(); @@ -168,8 +166,6 @@ describe("KubeApi", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); di.override(apiKubeInjectable, () => createKubeJsonApi({ diff --git a/packages/core/src/common/kube-helpers.ts b/packages/core/src/common/kube-helpers.ts index 6deb9ab8a1..0e42425f1c 100644 --- a/packages/core/src/common/kube-helpers.ts +++ b/packages/core/src/common/kube-helpers.ts @@ -130,6 +130,16 @@ export function loadConfigFromString(content: string): ConfigResult { }; } +export function loadValidatedConfig(content: string, contextName: string): ValidateKubeConfigResult { + const { options, error } = loadToOptions(content); + + if (error) { + return { error }; + } + + return validateKubeConfig(loadFromOptions(options), contextName); +} + export interface SplitConfigEntry { config: KubeConfig; validationResult: ValidateKubeConfigResult; diff --git a/packages/core/src/common/kube-helpers/load-validated-config-from-file.injectable.ts b/packages/core/src/common/kube-helpers/load-validated-config-from-file.injectable.ts new file mode 100644 index 0000000000..ee0b9ed9b0 --- /dev/null +++ b/packages/core/src/common/kube-helpers/load-validated-config-from-file.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Cluster } from "../cluster/cluster"; +import readFileInjectable from "../fs/read-file.injectable"; +import type { ValidateKubeConfigResult } from "../kube-helpers"; +import { loadValidatedConfig } from "../kube-helpers"; +import resolveTildeInjectable from "../path/resolve-tilde.injectable"; + +export type LoadValidatedClusterConfig = (cluster: Cluster) => Promise; + +const loadValidatedClusterConfigInjectable = getInjectable({ + id: "load-validated-cluster-config", + instantiate: (di): LoadValidatedClusterConfig => { + const readFile = di.inject(readFileInjectable); + const resolveTilde = di.inject(resolveTildeInjectable); + + return async (cluster) => { + const data = await readFile(resolveTilde(cluster.kubeConfigPath.get())); + + return loadValidatedConfig(data, cluster.contextName.get()); + }; + }, +}); + +export default loadValidatedClusterConfigInjectable; diff --git a/packages/core/src/features/cluster/connections/main/api-url.injectable.ts b/packages/core/src/features/cluster/connections/main/api-url.injectable.ts new file mode 100644 index 0000000000..5cb0b761ec --- /dev/null +++ b/packages/core/src/features/cluster/connections/main/api-url.injectable.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { URL } from "url"; +import type { Cluster } from "../../../../common/cluster/cluster"; +import statInjectable from "../../../../common/fs/stat.injectable"; +import loadValidatedClusterConfigInjectable from "../../../../common/kube-helpers/load-validated-config-from-file.injectable"; + +interface ClusterApiUrlState { + url: URL; + lastReadMtimeMs: number; +} + +const clusterApiUrlInjectable = getInjectable({ + id: "cluster-api-url", + instantiate: (di, cluster): () => Promise => { + const loadValidatedClusterConfig = di.inject(loadValidatedClusterConfigInjectable); + const stat = di.inject(statInjectable); + + let state: ClusterApiUrlState | undefined; + + return async () => { + const stats = await stat(cluster.kubeConfigPath.get()); + + if (!state || state.lastReadMtimeMs >= stats.mtimeMs) { + const result = await loadValidatedClusterConfig(cluster); + + if (result.error) { + throw result.error; + } + + state = { + url: new URL(result.cluster.server), + lastReadMtimeMs: stats.mtimeMs, + }; + } + + return state.url; + }; + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, cluster: Cluster) => cluster.id, + }), +}); + +export default clusterApiUrlInjectable; diff --git a/packages/core/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx b/packages/core/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx index 255f545769..1ffad5df97 100644 --- a/packages/core/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx +++ b/packages/core/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx @@ -109,8 +109,6 @@ describe("Deleting a cluster", () => { clusterName: "some-current-context-cluster", }, kubeConfigPath: "./temp-kube-config", - }, { - clusterServerUrl: currentClusterServerUrl, }); nonCurrentCluster = new Cluster({ id: "some-non-current-context-cluster", @@ -119,8 +117,6 @@ describe("Deleting a cluster", () => { clusterName: "some-non-current-context-cluster", }, kubeConfigPath: "./temp-kube-config", - }, { - clusterServerUrl: currentClusterServerUrl, }); }); @@ -197,8 +193,6 @@ describe("Deleting a cluster", () => { clusterName: "some-cluster", }, kubeConfigPath: joinPaths(directoryForKubeConfigs, "some-cluster.json"), - }, { - clusterServerUrl: singleClusterServerUrl, }); }); @@ -233,8 +227,6 @@ describe("Deleting a cluster", () => { clusterName: "some-cluster", }, kubeConfigPath: "./temp-kube-config", - }, { - clusterServerUrl: singleClusterServerUrl, }); }); diff --git a/packages/core/src/features/cluster/storage/cluster-storage.test.ts b/packages/core/src/features/cluster/storage/cluster-storage.test.ts index f11ae3a4c5..4178b3e7d4 100644 --- a/packages/core/src/features/cluster/storage/cluster-storage.test.ts +++ b/packages/core/src/features/cluster/storage/cluster-storage.test.ts @@ -23,7 +23,6 @@ import type { WriteFileSync } from "../../../common/fs/write-file-sync.injectabl import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable"; import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable"; import writeBufferSyncInjectable from "../../../common/fs/write-buffer-sync.injectable"; -import { Cluster } from "../../../common/cluster/cluster"; import clustersPersistentStorageInjectable from "./common/storage.injectable"; import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable"; import type { AddCluster } from "./common/add.injectable"; @@ -32,6 +31,7 @@ import type { GetClusterById } from "./common/get-by-id.injectable"; import getClusterByIdInjectable from "./common/get-by-id.injectable"; import type { IComputedValue } from "mobx"; import clustersInjectable from "./common/clusters.injectable"; +import type { Cluster } from "../../../common/cluster/cluster"; // NOTE: this is intended to read the actual file system const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png"); @@ -102,7 +102,7 @@ describe("cluster storage technical tests", () => { describe("with foo cluster added", () => { beforeEach(() => { - const cluster = new Cluster({ + addCluster({ id: "foo", contextName: "foo", preferences: { @@ -114,11 +114,7 @@ describe("cluster storage technical tests", () => { getCustomKubeConfigFilePath("foo"), kubeconfig, ), - }, { - clusterServerUrl, }); - - addCluster(cluster); }); it("adds new cluster to store", async () => { @@ -232,47 +228,6 @@ describe("cluster storage technical tests", () => { }); }); - describe("config with invalid cluster kubeconfig", () => { - beforeEach(() => { - writeFileSync("/invalid-kube-config", invalidKubeconfig); - writeFileSync("/valid-kube-config", kubeconfig); - writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", { - __internal__: { - migrations: { - version: "99.99.99", - }, - }, - clusters: [ - { - id: "cluster1", - kubeConfigPath: "/invalid-kube-config", - contextName: "test", - preferences: { terminalCWD: "/foo" }, - workspace: "foo", - }, - { - id: "cluster2", - kubeConfigPath: "/valid-kube-config", - contextName: "foo", - preferences: { terminalCWD: "/foo" }, - workspace: "default", - }, - ], - }); - - getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable); - - clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable); - clustersPersistentStorage.loadAndStartSyncing(); - }); - - it("does not enable clusters with invalid kubeconfig", () => { - const storedClusters = clusters.get(); - - expect(storedClusters.length).toBe(1); - }); - }); - describe("pre 3.6.0-beta.1 config with an existing cluster", () => { beforeEach(() => { di.override(storeMigrationVersionInjectable, () => "3.6.0"); @@ -315,32 +270,6 @@ describe("cluster storage technical tests", () => { }); }); -const invalidKubeconfig = JSON.stringify({ - apiVersion: "v1", - clusters: [{ - cluster: { - server: "https://localhost", - }, - name: "test2", - }], - contexts: [{ - context: { - cluster: "test", - user: "test", - }, - name: "test", - }], - "current-context": "test", - kind: "Config", - preferences: {}, - users: [{ - user: { - token: "kubeconfig-user-q4lm4:xxxyyyy", - }, - name: "test", - }], -}); - const minimalValidKubeConfig = JSON.stringify({ apiVersion: "v1", clusters: [ diff --git a/packages/core/src/features/cluster/storage/common/add.injectable.ts b/packages/core/src/features/cluster/storage/common/add.injectable.ts index 051aa6d23b..ff4ad8d828 100644 --- a/packages/core/src/features/cluster/storage/common/add.injectable.ts +++ b/packages/core/src/features/cluster/storage/common/add.injectable.ts @@ -5,33 +5,23 @@ import { getInjectable } from "@ogre-tools/injectable"; import { action } from "mobx"; import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; -import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; import type { ClusterModel } from "../../../../common/cluster-types"; import { Cluster } from "../../../../common/cluster/cluster"; import clustersStateInjectable from "./state.injectable"; +import { setAndGet } from "@k8slens/utilities"; -export type AddCluster = (clusterOrModel: ClusterModel | Cluster) => Cluster; +export type AddCluster = (clusterModel: ClusterModel) => Cluster; const addClusterInjectable = getInjectable({ id: "add-cluster", instantiate: (di): AddCluster => { const clustersState = di.inject(clustersStateInjectable); const emitAppEvent = di.inject(emitAppEventInjectable); - const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable); - return action((clusterOrModel) => { + return action((clusterModel) => { emitAppEvent({ name: "cluster", action: "add" }); - const cluster = clusterOrModel instanceof Cluster - ? clusterOrModel - : new Cluster( - clusterOrModel, - readClusterConfigSync(clusterOrModel), - ); - - clustersState.set(cluster.id, cluster); - - return cluster; + return setAndGet(clustersState, clusterModel.id, new Cluster(clusterModel)); }); }, }); diff --git a/packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts b/packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts deleted file mode 100644 index 9b3be7733e..0000000000 --- a/packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import type { ClusterConfigData, ClusterModel } from "../../../../common/cluster-types"; -import readFileSyncInjectable from "../../../../common/fs/read-file-sync.injectable"; -import { loadConfigFromString, validateKubeConfig } from "../../../../common/kube-helpers"; - -export type ReadClusterConfigSync = (model: ClusterModel) => ClusterConfigData; - -const readClusterConfigSyncInjectable = getInjectable({ - id: "read-cluster-config-sync", - instantiate: (di): ReadClusterConfigSync => { - const readFileSync = di.inject(readFileSyncInjectable); - - return ({ kubeConfigPath, contextName }) => { - const kubeConfigData = readFileSync(kubeConfigPath); - const { config } = loadConfigFromString(kubeConfigData); - const result = validateKubeConfig(config, contextName); - - if (result.error) { - throw result.error; - } - - return { clusterServerUrl: result.cluster.server }; - }; - }, -}); - -export default readClusterConfigSyncInjectable; diff --git a/packages/core/src/features/cluster/storage/common/storage.injectable.ts b/packages/core/src/features/cluster/storage/common/storage.injectable.ts index eaac8376cf..eb11d4b4e4 100644 --- a/packages/core/src/features/cluster/storage/common/storage.injectable.ts +++ b/packages/core/src/features/cluster/storage/common/storage.injectable.ts @@ -6,7 +6,6 @@ import { iter } from "@k8slens/utilities"; import { getInjectable } from "@ogre-tools/injectable"; import { comparer, action } from "mobx"; import { clusterStoreMigrationInjectionToken } from "./migration-token"; -import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; import type { ClusterId, ClusterModel } from "../../../../common/cluster-types"; import { Cluster } from "../../../../common/cluster/cluster"; import loggerInjectable from "../../../../common/logger.injectable"; @@ -23,7 +22,6 @@ const clustersPersistentStorageInjectable = getInjectable({ id: "clusters-persistent-storage", instantiate: (di) => { const createPersistentStorage = di.inject(createPersistentStorageInjectable); - const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable); const clustersState = di.inject(clustersStateInjectable); const logger = di.inject(loggerInjectable); @@ -39,7 +37,6 @@ const clustersPersistentStorageInjectable = getInjectable({ const currentClusters = new Map(clustersState); const newClusters = new Map(); - // update new clusters for (const clusterModel of clusters) { try { let cluster = currentClusters.get(clusterModel.id); @@ -47,11 +44,9 @@ const clustersPersistentStorageInjectable = getInjectable({ if (cluster) { cluster.updateModel(clusterModel); } else { - cluster = new Cluster( - clusterModel, - readClusterConfigSync(clusterModel), - ); + cluster = new Cluster(clusterModel); } + newClusters.set(clusterModel.id, cluster); } catch (error) { logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`); diff --git a/packages/core/src/main/__test__/cluster.test.ts b/packages/core/src/main/__test__/cluster.test.ts index 205a23d5ab..dcc028afd6 100644 --- a/packages/core/src/main/__test__/cluster.test.ts +++ b/packages/core/src/main/__test__/cluster.test.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { Cluster } from "../../common/cluster/cluster"; +import type { Cluster } from "../../common/cluster/cluster"; import { Kubectl } from "../kubectl/kubectl"; import { getDiForUnitTesting } from "../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; @@ -19,6 +19,8 @@ import createCanIInjectable from "../../common/cluster/create-can-i.injectable"; import createRequestNamespaceListPermissionsInjectable from "../../common/cluster/create-request-namespace-list-permissions.injectable"; import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable"; +import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable"; +import addClusterInjectable from "../../features/cluster/storage/common/add.injectable"; describe("create clusters", () => { let cluster: Cluster; @@ -26,7 +28,7 @@ describe("create clusters", () => { beforeEach(() => { const di = getDiForUnitTesting(); - const clusterServerUrl = "https://192.168.64.3:8443"; + const writeJsonSync = di.inject(writeJsonSyncInjectable); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); @@ -42,27 +44,45 @@ describe("create clusters", () => { setupPrometheus: jest.fn(), })); + writeJsonSync("/minikube-config.yml", { + apiVersion: "v1", + clusters: [{ + name: "minikube", + cluster: { + server: "https://192.168.64.3:8443", + }, + }], + "current-context": "minikube", + contexts: [{ + context: { + cluster: "minikube", + user: "minikube", + }, + name: "minikube", + }], + users: [{ + name: "minikube", + }], + kind: "Config", + preferences: {}, + }); + di.override(kubeconfigManagerInjectable, () => ({ ensurePath: async () => "/some-proxy-kubeconfig-file", } as Partial as KubeconfigManager)); jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true)); - cluster = new Cluster({ + const addCluster = di.inject(addClusterInjectable); + + cluster = addCluster({ id: "foo", contextName: "minikube", - kubeConfigPath: "minikube-config.yml", - }, { - clusterServerUrl, + kubeConfigPath: "/minikube-config.yml", }); - clusterConnection = di.inject(clusterConnectionInjectable, cluster); }); - it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => { - expect(cluster.apiUrl.get()).toBe("https://192.168.64.3:8443"); - }); - it("reconnect should not throw if contextHandler is missing", () => { expect(() => clusterConnection.reconnect()).not.toThrowError(); }); diff --git a/packages/core/src/main/__test__/kube-auth-proxy.test.ts b/packages/core/src/main/__test__/kube-auth-proxy.test.ts index dc4878f806..35de680e77 100644 --- a/packages/core/src/main/__test__/kube-auth-proxy.test.ts +++ b/packages/core/src/main/__test__/kube-auth-proxy.test.ts @@ -3,8 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable"; -import { Cluster } from "../../common/cluster/cluster"; import type { ChildProcess } from "child_process"; import { Kubectl } from "../kubectl/kubectl"; import type { DeepMockProxy } from "jest-mock-extended"; @@ -12,7 +10,7 @@ import { mockDeep, mock } from "jest-mock-extended"; import type { Readable } from "stream"; import { EventEmitter } from "stream"; import { getDiForUnitTesting } from "../getDiForUnitTesting"; -import type { CreateKubeAuthProxy, KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; +import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import spawnInjectable from "../child-process/spawn.injectable"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; @@ -25,15 +23,17 @@ import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable" import ensureDirInjectable from "../../common/fs/ensure-dir.injectable"; import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable"; import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable"; - -const clusterServerUrl = "https://192.168.64.3:8443"; +import type { Cluster } from "../../common/cluster/cluster"; +import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable"; +import addClusterInjectable from "../../features/cluster/storage/common/add.injectable"; describe("kube auth proxy tests", () => { - let createKubeAuthProxy: CreateKubeAuthProxy; let spawnMock: jest.Mock; let waitUntilPortIsUsedMock: jest.Mock; let broadcastMessageMock: jest.Mock; let getBasenameOfPath: GetBasenameOfPath; + let cluster: Cluster; + let kubeAuthProxy: KubeAuthProxy; beforeEach(async () => { const di = getDiForUnitTesting(); @@ -51,7 +51,7 @@ describe("kube auth proxy tests", () => { clusters: [{ name: "minikube", cluster: { - server: clusterServerUrl, + server: "https://192.168.64.3:8443", }, }], "current-context": "minikube", @@ -83,29 +83,25 @@ describe("kube auth proxy tests", () => { di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(normalizedPlatformInjectable, () => "darwin"); - createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable); + const addCluster = di.inject(addClusterInjectable); + + cluster = addCluster({ + id: "foobar", + kubeConfigPath: "/minikube-config.yml", + contextName: "minikube", + }); + kubeAuthProxy = di.inject(createKubeAuthProxyInjectable, cluster)({}); }); it("calling exit multiple times shouldn't throw", async () => { - const cluster = new Cluster({ - id: "foobar", - kubeConfigPath: "minikube-config.yml", - contextName: "minikube", - }, { - clusterServerUrl, - }); - - const kap = createKubeAuthProxy(cluster, {}); - - kap.exit(); - kap.exit(); - kap.exit(); + kubeAuthProxy.exit(); + kubeAuthProxy.exit(); + kubeAuthProxy.exit(); }); describe("spawn tests", () => { let mockedCP: DeepMockProxy; let listeners: EventEmitter; - let proxy: KubeAuthProxy; beforeEach(async () => { mockedCP = mockDeep(); @@ -184,16 +180,7 @@ describe("kube auth proxy tests", () => { }); waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve()); - const cluster = new Cluster({ - id: "foobar", - kubeConfigPath: "minikube-config.yml", - contextName: "minikube", - }, { - clusterServerUrl, - }); - - proxy = createKubeAuthProxy(cluster, {}); - await proxy.run(); + await kubeAuthProxy.run(); }); it("should call spawn and broadcast errors", () => { diff --git a/packages/core/src/main/__test__/kubeconfig-manager.test.ts b/packages/core/src/main/__test__/kubeconfig-manager.test.ts index 1205ba4f8f..fb9b329b69 100644 --- a/packages/core/src/main/__test__/kubeconfig-manager.test.ts +++ b/packages/core/src/main/__test__/kubeconfig-manager.test.ts @@ -89,8 +89,6 @@ describe("kubeconfig manager tests", () => { id: "foo", contextName: "minikube", kubeConfigPath: "/minikube-config.yml", - }, { - clusterServerUrl, }); kubeConfManager = di.inject(kubeconfigManagerInjectable, clusterFake); diff --git a/packages/core/src/main/__test__/prometheus-handler.test.ts b/packages/core/src/main/__test__/prometheus-handler.test.ts index 74cab1ea74..df0e1b37c9 100644 --- a/packages/core/src/main/__test__/prometheus-handler.test.ts +++ b/packages/core/src/main/__test__/prometheus-handler.test.ts @@ -13,8 +13,8 @@ import { runInAction } from "mobx"; import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable"; import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable"; -import loadProxyKubeconfigInjectable from "../cluster/load-proxy-kubeconfig.injectable"; -import { KubeConfig } from "@kubernetes/client-node"; +import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; +import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable"; enum ServiceResult { Success, @@ -47,37 +47,46 @@ describe("PrometheusHandler", () => { let di: DiContainer; let cluster: Cluster; - beforeEach(() => { + beforeEach(async () => { di = getDiForUnitTesting(); - di.override(loadProxyKubeconfigInjectable, (di, cluster) => async () => { - const res = new KubeConfig(); - - res.addCluster({ - name: "some-cluster-name", - server: cluster.apiUrl.get(), - skipTLSVerify: false, - }); - res.addContext({ - cluster: "some-cluster-name", - name: "some-context-name", - user: "some-user-name", - }); - res.addUser({ - name: "some-user-name", - }); - res.setCurrentContext("some-context-name"); - - return res; - }); + di.override(createKubeAuthProxyInjectable, () => () => ({ + apiPrefix: "/some-api-prefix", + exit: () => {}, + run: async () => {}, + port: 9191, + })); di.override(directoryForTempInjectable, () => "/some-temp-dir"); di.inject(lensProxyPortInjectable).set(12345); + const writeJsonFile = di.inject(writeJsonFileInjectable); + const kubeConfigPath = "/some/path-to-a-config"; + const contextName = "some-context-name"; + + await writeJsonFile(kubeConfigPath, { + apiVersion: "v1", + kind: "Config", + clusters: [{ + name: "some-cluster-name", + cluster: { + server: "https://localhost:8989", + }, + }], + users: [{ + name: "some-user-name", + }], + contexts: [{ + name: contextName, + context: { + user: "some-user-name", + cluster: "some-cluster-name", + }, + }], + }); + cluster = new Cluster({ - contextName: "some-context-name", + contextName, id: "some-cluster-id", - kubeConfigPath: "/some/path", - }, { - clusterServerUrl: "http://localhost:81", + kubeConfigPath, }); }); diff --git a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 6d23610e79..6cc651f7e5 100644 --- a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -97,8 +97,8 @@ describe("kubeconfig-sync.source tests", () => { const models = configToModels(config, "/bar"); expect(models.length).toBe(1); - expect(models[0][0].contextName).toBe("context-name"); - expect(models[0][0].kubeConfigPath).toBe("/bar"); + expect(models[0].contextName).toBe("context-name"); + expect(models[0].kubeConfigPath).toBe("/bar"); }); }); diff --git a/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts b/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts index 87475931fe..d1e8f0daab 100644 --- a/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts +++ b/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts @@ -38,15 +38,15 @@ const computeKubeconfigDiffInjectable = getInjectable({ } const rawModels = configToModels(config, filePath); - const models = new Map(rawModels.map(([model, configData]) => [model.contextName, [model, configData] as const])); + const models = new Map(rawModels.map((model) => [model.contextName, model])); logger.debug(`File now has ${models.size} entries`, { filePath }); for (const [contextName, value] of source) { - const data = models.get(contextName); + const model = models.get(contextName); // remove and disconnect clusters that were removed from the config - if (!data) { + if (!model) { // remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting clustersThatAreBeingDeleted.delete(value[0].id); @@ -63,21 +63,16 @@ const computeKubeconfigDiffInjectable = getInjectable({ // diff against that // or update the model and mark it as not needed to be added - value[0].updateModel(data[0]); + value[0].updateModel(model); models.delete(contextName); logger.debug(`Updated old cluster from sync`, { filePath, contextName }); } - for (const [contextName, [model, configData]] of models) { + for (const [contextName, model] of models) { // add new clusters to the source try { const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex"); - const cluster = getClusterById(clusterId) ?? new Cluster({ ...model, id: clusterId }, configData); - - if (!cluster.apiUrl.get()) { - throw new Error("Cluster constructor failed, see above error"); - } - + const cluster = getClusterById(clusterId) ?? new Cluster({ ...model, id: clusterId }); const entity = catalogEntityFromCluster(cluster); if (!filePath.startsWith(directoryForKubeConfigs)) { diff --git a/packages/core/src/main/catalog-sources/kubeconfig-sync/config-to-models.injectable.ts b/packages/core/src/main/catalog-sources/kubeconfig-sync/config-to-models.injectable.ts index 8240d4e914..6baad196cf 100644 --- a/packages/core/src/main/catalog-sources/kubeconfig-sync/config-to-models.injectable.ts +++ b/packages/core/src/main/catalog-sources/kubeconfig-sync/config-to-models.injectable.ts @@ -4,11 +4,11 @@ */ import type { KubeConfig } from "@kubernetes/client-node"; import { getInjectable } from "@ogre-tools/injectable"; -import type { ClusterConfigData, UpdateClusterModel } from "../../../common/cluster-types"; +import type { UpdateClusterModel } from "../../../common/cluster-types"; import { splitConfig } from "../../../common/kube-helpers"; import kubeconfigSyncLoggerInjectable from "./logger.injectable"; -export type ConfigToModels = (rootConfig: KubeConfig, filePath: string) => [UpdateClusterModel, ClusterConfigData][]; +export type ConfigToModels = (rootConfig: KubeConfig, filePath: string) => UpdateClusterModel[]; const configToModelsInjectable = getInjectable({ id: "config-to-models", @@ -22,15 +22,10 @@ const configToModelsInjectable = getInjectable({ if (validationResult.error) { logger.debug(`context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath }); } else { - validConfigs.push([ - { - kubeConfigPath: filePath, - contextName: config.currentContext, - }, - { - clusterServerUrl: validationResult.cluster.server, - }, - ]); + validConfigs.push({ + kubeConfigPath: filePath, + contextName: config.currentContext, + }); } } diff --git a/packages/core/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts b/packages/core/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts index 44f1c88102..673cb3cf8d 100644 --- a/packages/core/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts +++ b/packages/core/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts @@ -9,22 +9,24 @@ import { getInjectable } from "@ogre-tools/injectable"; import k8SRequestInjectable from "../k8s-request.injectable"; import type { Cluster } from "../../common/cluster/cluster"; import requestClusterVersionInjectable from "./request-cluster-version.injectable"; +import type { URL } from "url"; +import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable"; const isGKE = (version: string) => version.includes("gke"); const isEKS = (version: string) => version.includes("eks"); const isIKS = (version: string) => version.includes("IKS"); -const isAKS = (cluster: Cluster) => cluster.apiUrl.get().includes("azmk8s.io"); +const isAKS = (apiUrl: URL) => apiUrl.hostname.includes("azmk8s.io"); const isMirantis = (version: string) => version.includes("-mirantis-") || version.includes("-docker-"); -const isDigitalOcean = (cluster: Cluster) => cluster.apiUrl.get().endsWith("k8s.ondigitalocean.com"); -const isMinikube = (cluster: Cluster) => cluster.contextName.get().startsWith("minikube"); -const isMicrok8s = (cluster: Cluster) => cluster.contextName.get().startsWith("microk8s"); -const isKind = (cluster: Cluster) => cluster.contextName.get().startsWith("kubernetes-admin@kind-"); -const isDockerDesktop = (cluster: Cluster) => cluster.contextName.get() === "docker-desktop"; +const isDigitalOcean = (apiUrl: URL) => apiUrl.hostname.endsWith("k8s.ondigitalocean.com"); +const isMinikube = (contextName: string) => contextName.startsWith("minikube"); +const isMicrok8s = (contextName: string) => contextName.startsWith("microk8s"); +const isKind = (contextName: string) => contextName.startsWith("kubernetes-admin@kind-"); +const isDockerDesktop = (contextName: string) => contextName === "docker-desktop"; const isTke = (version: string) => version.includes("-tke."); const isCustom = (version: string) => version.includes("+"); const isVMWare = (version: string) => version.includes("+vmware"); const isRke = (version: string) => version.includes("-rancher"); -const isRancherDesktop = (cluster: Cluster) => cluster.contextName.get() === "rancher-desktop"; +const isRancherDesktop = (contextName: string) => contextName === "rancher-desktop"; const isK3s = (version: string) => version.includes("+k3s"); const isK0s = (version: string) => version.includes("-k0s") || version.includes("+k0s"); const isAlibaba = (version: string) => version.includes("-aliyun"); @@ -49,12 +51,14 @@ const clusterDistributionDetectorInjectable = getInjectable({ key: ClusterMetadataKey.DISTRIBUTION, detect: async (cluster) => { const version = await requestClusterVersion(cluster); + const apiUrl = await di.inject(clusterApiUrlInjectable, cluster)(); + const contextName = cluster.contextName.get(); if (isRke(version)) { return { value: "rke", accuracy: 80 }; } - if (isRancherDesktop(cluster)) { + if (isRancherDesktop(contextName)) { return { value: "rancher-desktop", accuracy: 80 }; } @@ -74,11 +78,11 @@ const clusterDistributionDetectorInjectable = getInjectable({ return { value: "iks", accuracy: 80 }; } - if (isAKS(cluster)) { + if (isAKS(apiUrl)) { return { value: "aks", accuracy: 80 }; } - if (isDigitalOcean(cluster)) { + if (isDigitalOcean(apiUrl)) { return { value: "digitalocean", accuracy: 90 }; } @@ -106,19 +110,19 @@ const clusterDistributionDetectorInjectable = getInjectable({ return { value: "tencent", accuracy: 90 }; } - if (isMinikube(cluster)) { + if (isMinikube(contextName)) { return { value: "minikube", accuracy: 80 }; } - if (isMicrok8s(cluster)) { + if (isMicrok8s(contextName)) { return { value: "microk8s", accuracy: 80 }; } - if (isKind(cluster)) { + if (isKind(contextName)) { return { value: "kind", accuracy: 70 }; } - if (isDockerDesktop(cluster)) { + if (isDockerDesktop(contextName)) { return { value: "docker-desktop", accuracy: 80 }; } diff --git a/packages/core/src/main/cluster-detectors/cluster-id-detector.injectable.ts b/packages/core/src/main/cluster-detectors/cluster-id-detector.injectable.ts index 6699986805..51b65f0d90 100644 --- a/packages/core/src/main/cluster-detectors/cluster-id-detector.injectable.ts +++ b/packages/core/src/main/cluster-detectors/cluster-id-detector.injectable.ts @@ -9,6 +9,7 @@ import { ClusterMetadataKey } from "../../common/cluster-types"; import { getInjectable } from "@ogre-tools/injectable"; import k8SRequestInjectable from "../k8s-request.injectable"; import type { Cluster } from "../../common/cluster/cluster"; +import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable"; const clusterIdDetectorFactoryInjectable = getInjectable({ id: "cluster-id-detector-factory", @@ -28,7 +29,7 @@ const clusterIdDetectorFactoryInjectable = getInjectable({ try { id = await getDefaultNamespaceId(cluster); } catch(_) { - id = cluster.apiUrl.get(); + id = (await di.inject(clusterApiUrlInjectable, cluster)()).toString(); } const value = createHash("sha256").update(id).digest("hex"); diff --git a/packages/core/src/main/cluster-detectors/detect-cluster-metadata.test.ts b/packages/core/src/main/cluster-detectors/detect-cluster-metadata.test.ts index 9582b386c1..833b468738 100644 --- a/packages/core/src/main/cluster-detectors/detect-cluster-metadata.test.ts +++ b/packages/core/src/main/cluster-detectors/detect-cluster-metadata.test.ts @@ -67,8 +67,6 @@ describe("detect-cluster-metadata", () => { id: "some-id", contextName: "some-context", kubeConfigPath: "minikube-config.yml", - }, { - clusterServerUrl: "foo", }); }); diff --git a/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts b/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts index ff70af3a7b..df14cf3f93 100644 --- a/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts +++ b/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts @@ -9,6 +9,7 @@ import type { Cluster } from "../../common/cluster/cluster"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable"; import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; +import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable"; export interface KubeAuthProxyServer { getApiTarget(isLongRunningRequest?: boolean): Promise; @@ -24,23 +25,23 @@ const thirtySecondsInMs = 30 * 1000; const kubeAuthProxyServerInjectable = getInjectable({ id: "kube-auth-proxy-server", instantiate: (di, cluster): KubeAuthProxyServer => { - const clusterUrl = new URL(cluster.apiUrl.get()); - - const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable); - const certificate = di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname); + const clusterApiUrl = di.inject(clusterApiUrlInjectable, cluster); + const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable, cluster); let kubeAuthProxy: KubeAuthProxy | undefined = undefined; let apiTarget: ServerOptions | undefined = undefined; const ensureServerHelper = async (): Promise => { if (!kubeAuthProxy) { - const proxyEnv = Object.assign({}, process.env); + const proxyEnv = { + ...process.env, + }; if (cluster.preferences.httpsProxy) { proxyEnv.HTTPS_PROXY = cluster.preferences.httpsProxy; } - kubeAuthProxy = createKubeAuthProxy(cluster, proxyEnv); + kubeAuthProxy = createKubeAuthProxy(proxyEnv); } await kubeAuthProxy.run(); @@ -49,31 +50,24 @@ const kubeAuthProxyServerInjectable = getInjectable({ }; const newApiTarget = async (timeout: number): Promise => { - const kubeAuthProxy = await ensureServerHelper(); - const headers: Record = {}; - - if (clusterUrl.hostname) { - headers.Host = clusterUrl.hostname; - - // fix current IPv6 inconsistency in url.Parse() and httpProxy. - // with url.Parse the IPv6 Hostname has no Square brackets but httpProxy needs the Square brackets to work. - if (headers.Host.includes(":")) { - headers.Host = `[${headers.Host}]`; - } - } + const { hostname } = await clusterApiUrl(); + const certificate = di.inject(kubeAuthProxyCertificateInjectable, hostname); + const { port, apiPrefix: path } = await ensureServerHelper(); return { target: { protocol: "https:", host: "127.0.0.1", - port: kubeAuthProxy.port, - path: kubeAuthProxy.apiPrefix, + port, + path, ca: certificate.cert, }, changeOrigin: true, timeout, secure: true, - headers, + headers: { + Host: hostname, + }, }; }; diff --git a/packages/core/src/main/cluster/manager.ts b/packages/core/src/main/cluster/manager.ts index f9f844bd9f..800f981372 100644 --- a/packages/core/src/main/cluster/manager.ts +++ b/packages/core/src/main/cluster/manager.ts @@ -7,7 +7,6 @@ import "../../common/ipc/cluster"; import type { IComputedValue, IObservableValue, ObservableSet } from "mobx"; import { action, makeObservable, observe, reaction, toJS } from "mobx"; import type { Cluster } from "../../common/cluster/cluster"; -import { isErrnoException } from "@k8slens/utilities"; import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster"; import { ipcMainOn } from "../../common/ipc"; import { once } from "lodash"; @@ -158,26 +157,12 @@ export class ClusterManager { const cluster = this.dependencies.getClusterById(entity.getId()); if (!cluster) { - const model = { + this.dependencies.addCluster({ id: entity.getId(), kubeConfigPath: entity.spec.kubeconfigPath, contextName: entity.spec.kubeconfigContext, accessibleNamespaces: entity.spec.accessibleNamespaces ?? [], - }; - - try { - /** - * Add the bare minimum of data to ClusterStore. And especially no - * preferences, as those might be configured by the entity's source - */ - this.dependencies.addCluster(model); - } catch (error) { - if (isErrnoException(error) && error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) { - this.dependencies.logger.warn(`${logPrefix} kubeconfig file disappeared`, model); - } else { - this.dependencies.logger.error(`${logPrefix} failed to add cluster: ${error}`, model); - } - } + }); } else { cluster.kubeConfigPath.set(entity.spec.kubeconfigPath); cluster.contextName.set(entity.spec.kubeconfigContext); diff --git a/packages/core/src/main/cluster/update-entity-metadata.test.ts b/packages/core/src/main/cluster/update-entity-metadata.test.ts index 13683196bb..eca53c238e 100644 --- a/packages/core/src/main/cluster/update-entity-metadata.test.ts +++ b/packages/core/src/main/cluster/update-entity-metadata.test.ts @@ -34,8 +34,6 @@ describe("update-entity-metadata", () => { id: "some-id", contextName: "some-context", kubeConfigPath: "minikube-config.yml", - }, { - clusterServerUrl: "foo", }); detectedMetadata = { diff --git a/packages/core/src/main/cluster/update-entity-spec.test.ts b/packages/core/src/main/cluster/update-entity-spec.test.ts index a21e1fae46..fa2c14bb5a 100644 --- a/packages/core/src/main/cluster/update-entity-spec.test.ts +++ b/packages/core/src/main/cluster/update-entity-spec.test.ts @@ -31,8 +31,6 @@ describe("update-entity-spec", () => { id: "some-id", contextName: "some-context", kubeConfigPath: "minikube-config.yml", - }, { - clusterServerUrl: "foo", }); entity = new KubernetesCluster({ diff --git a/packages/core/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts b/packages/core/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts index fd4acf0d7a..df7108d8f1 100644 --- a/packages/core/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts +++ b/packages/core/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts @@ -2,18 +2,22 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; -import type { KubeAuthProxyDependencies } from "./kube-auth-proxy"; -import { KubeAuthProxyImpl } from "./kube-auth-proxy"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import type { Cluster } from "../../common/cluster/cluster"; import spawnInjectable from "../child-process/spawn.injectable"; -import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable"; import loggerInjectable from "../../common/logger.injectable"; import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable"; import lensK8sProxyPathInjectable from "./lens-k8s-proxy-path.injectable"; import getPortFromStreamInjectable from "../utils/get-port-from-stream.injectable"; import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable"; +import randomBytesInjectable from "../../common/utils/random-bytes.injectable"; +import type { ChildProcess } from "child_process"; +import { observable, when } from "mobx"; +import assert from "assert"; +import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable"; +import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable"; import broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable"; +import { TypedRegEx } from "typed-regex"; export interface KubeAuthProxy { readonly apiPrefix: string; @@ -22,31 +26,159 @@ export interface KubeAuthProxy { exit: () => void; } -export type CreateKubeAuthProxy = (cluster: Cluster, env: NodeJS.ProcessEnv) => KubeAuthProxy; +export type CreateKubeAuthProxy = (env: NodeJS.ProcessEnv) => KubeAuthProxy; + +const startingServeMatcher = "starting to serve on (?
.+)"; +const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), { + rawMatcher: startingServeMatcher, +}); const createKubeAuthProxyInjectable = getInjectable({ id: "create-kube-auth-proxy", - instantiate: (di): CreateKubeAuthProxy => { - const dependencies: Omit = { - proxyBinPath: di.inject(lensK8sProxyPathInjectable), - spawn: di.inject(spawnInjectable), - logger: di.inject(loggerInjectable), - waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable), - getPortFromStream: di.inject(getPortFromStreamInjectable), - dirname: di.inject(getDirnameOfPathInjectable), - }; + instantiate: (di, cluster): CreateKubeAuthProxy => { + const lensK8sProxyPath = di.inject(lensK8sProxyPathInjectable); + const spawn = di.inject(spawnInjectable); + const logger = di.inject(loggerInjectable); + const waitUntilPortIsUsed = di.inject(waitUntilPortIsUsedInjectable); + const getPortFromStream = di.inject(getPortFromStreamInjectable); + const getDirnameOfPath = di.inject(getDirnameOfPathInjectable); + const randomBytes = di.inject(randomBytesInjectable); + const clusterApiUrl = di.inject(clusterApiUrlInjectable, cluster); + const broadcastConnectionUpdate = di.inject(broadcastConnectionUpdateInjectable, cluster); - return (cluster, env) => { - const clusterUrl = new URL(cluster.apiUrl.get()); + return (env) => { + let port: number | undefined; + let proxyProcess: ChildProcess | undefined; + const ready = observable.box(false); + const apiPrefix = `/${randomBytes(8).toString("hex")}`; - return new KubeAuthProxyImpl({ - ...dependencies, - proxyCert: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname), - broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster), - }, cluster, env); + const exit = () => { + ready.set(false); + + if (proxyProcess) { + logger.debug("[KUBE-AUTH]: stopping local proxy", cluster.getMeta()); + proxyProcess.removeAllListeners(); + proxyProcess.stderr?.removeAllListeners(); + proxyProcess.stdout?.removeAllListeners(); + proxyProcess.kill(); + proxyProcess = undefined; + } + }; + + const run = async (): Promise => { + if (proxyProcess) { + return when(() => ready.get()); + } + + const apiUrl = await clusterApiUrl(); + const certificate = di.inject(kubeAuthProxyCertificateInjectable, apiUrl.hostname); + + proxyProcess = spawn(lensK8sProxyPath, [], { + env: { + ...env, + KUBECONFIG: cluster.kubeConfigPath.get(), + KUBECONFIG_CONTEXT: cluster.contextName.get(), + API_PREFIX: apiPrefix, + PROXY_KEY: certificate.private, + PROXY_CERT: certificate.cert, + }, + cwd: getDirnameOfPath(cluster.kubeConfigPath.get()), + }); + proxyProcess.on("error", (error) => { + broadcastConnectionUpdate({ + level: "error", + message: error.message, + }); + exit(); + }); + + proxyProcess.on("exit", (code) => { + if (code) { + broadcastConnectionUpdate({ + level: "error", + message: `proxy exited with code: ${code}`, + }); + } else { + broadcastConnectionUpdate({ + level: "info", + message: "proxy exited successfully", + }); + } + exit(); + }); + + proxyProcess.on("disconnect", () => { + broadcastConnectionUpdate({ + level: "error", + message: "Proxy disconnected communications", + }); + exit(); + }); + + assert(proxyProcess.stderr); + assert(proxyProcess.stdout); + + proxyProcess.stderr.on("data", (data: Buffer) => { + if (data.includes("http: TLS handshake error")) { + return; + } + + broadcastConnectionUpdate({ + level: "error", + message: data.toString(), + }); + }); + + proxyProcess.stdout.on("data", (data: Buffer) => { + if (typeof port === "number") { + broadcastConnectionUpdate({ + level: "info", + message: data.toString(), + }); + } + }); + + port = await getPortFromStream(proxyProcess.stdout, { + lineRegex: startingServeRegex, + onFind: () => broadcastConnectionUpdate({ + level: "info", + message: "Authentication proxy started", + }), + }); + + logger.info(`[KUBE-AUTH-PROXY]: found port=${port}`); + + try { + await waitUntilPortIsUsed(port, 500, 10000); + ready.set(true); + } catch (error) { + logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error); + broadcastConnectionUpdate({ + level: "error", + message: "Proxy port failed to be used within time limit, restarting...", + }); + exit(); + + return run(); + } + }; + + return { + apiPrefix, + exit, + run, + get port() { + assert(port, "port has not yet been initialized"); + + return port; + }, + }; }; }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, cluster: Cluster) => cluster.id, + }), }); export default createKubeAuthProxyInjectable; diff --git a/packages/core/src/main/kube-auth-proxy/kube-auth-proxy.ts b/packages/core/src/main/kube-auth-proxy/kube-auth-proxy.ts deleted file mode 100644 index 160566c3a5..0000000000 --- a/packages/core/src/main/kube-auth-proxy/kube-auth-proxy.ts +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { ChildProcess } from "child_process"; -import { randomBytes } from "crypto"; -import type { Cluster } from "../../common/cluster/cluster"; -import type { GetPortFromStream } from "../utils/get-port-from-stream.injectable"; -import { observable, when } from "mobx"; -import type { SelfSignedCert } from "selfsigned"; -import assert from "assert"; -import { TypedRegEx } from "typed-regex"; -import type { Spawn } from "../child-process/spawn.injectable"; -import type { Logger } from "../../common/logger"; -import type { WaitUntilPortIsUsed } from "./wait-until-port-is-used/wait-until-port-is-used.injectable"; -import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable"; -import type { BroadcastConnectionUpdate } from "../cluster/broadcast-connection-update.injectable"; -import type { KubeAuthProxy } from "./create-kube-auth-proxy.injectable"; - -const startingServeMatcher = "starting to serve on (?
.+)"; -const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), { - rawMatcher: startingServeMatcher, -}); - -export interface KubeAuthProxyDependencies { - readonly proxyBinPath: string; - readonly proxyCert: SelfSignedCert; - readonly logger: Logger; - spawn: Spawn; - waitUntilPortIsUsed: WaitUntilPortIsUsed; - getPortFromStream: GetPortFromStream; - dirname: GetDirnameOfPath; - broadcastConnectionUpdate: BroadcastConnectionUpdate; -} - -export class KubeAuthProxyImpl implements KubeAuthProxy { - public readonly apiPrefix = `/${randomBytes(8).toString("hex")}`; - - public get port(): number { - const port = this._port; - - assert(port, "port has not yet been initialized"); - - return port; - } - - protected _port?: number; - protected proxyProcess?: ChildProcess; - protected readonly ready = observable.box(false); - - constructor( - private readonly dependencies: KubeAuthProxyDependencies, - protected readonly cluster: Cluster, - protected readonly env: NodeJS.ProcessEnv, - ) {} - - public async run(): Promise { - if (this.proxyProcess) { - return when(() => this.ready.get()); - } - - const proxyBin = this.dependencies.proxyBinPath; - const cert = this.dependencies.proxyCert; - - this.proxyProcess = this.dependencies.spawn(proxyBin, [], { - env: { - ...this.env, - KUBECONFIG: this.cluster.kubeConfigPath.get(), - KUBECONFIG_CONTEXT: this.cluster.contextName.get(), - API_PREFIX: this.apiPrefix, - PROXY_KEY: cert.private, - PROXY_CERT: cert.cert, - }, - cwd: this.dependencies.dirname(this.cluster.kubeConfigPath.get()), - }); - this.proxyProcess.on("error", (error) => { - this.dependencies.broadcastConnectionUpdate({ - level: "error", - message: error.message, - }); - this.exit(); - }); - - this.proxyProcess.on("exit", (code) => { - if (code) { - this.dependencies.broadcastConnectionUpdate({ - level: "error", - message: `proxy exited with code: ${code}`, - }); - } else { - this.dependencies.broadcastConnectionUpdate({ - level: "info", - message: "proxy exited successfully", - }); - } - this.exit(); - }); - - this.proxyProcess.on("disconnect", () => { - this.dependencies.broadcastConnectionUpdate({ - level: "error", - message: "Proxy disconnected communications", - }); - this.exit(); - }); - - assert(this.proxyProcess.stderr); - assert(this.proxyProcess.stdout); - - this.proxyProcess.stderr.on("data", (data: Buffer) => { - if (data.includes("http: TLS handshake error")) { - return; - } - - this.dependencies.broadcastConnectionUpdate({ - level: "error", - message: data.toString(), - }); - }); - - this.proxyProcess.stdout.on("data", (data: Buffer) => { - if (typeof this._port === "number") { - this.dependencies.broadcastConnectionUpdate({ - level: "info", - message: data.toString(), - }); - } - }); - - this._port = await this.dependencies.getPortFromStream(this.proxyProcess.stdout, { - lineRegex: startingServeRegex, - onFind: () => this.dependencies.broadcastConnectionUpdate({ - level: "info", - message: "Authentication proxy started", - }), - }); - - this.dependencies.logger.info(`[KUBE-AUTH-PROXY]: found port=${this._port}`); - - try { - await this.dependencies.waitUntilPortIsUsed(this.port, 500, 10000); - this.ready.set(true); - } catch (error) { - this.dependencies.logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error); - this.dependencies.broadcastConnectionUpdate({ - level: "error", - message: "Proxy port failed to be used within time limit, restarting...", - }); - this.exit(); - - return this.run(); - } - } - - public exit() { - this.ready.set(false); - - if (this.proxyProcess) { - this.dependencies.logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta()); - this.proxyProcess.removeAllListeners(); - this.proxyProcess.stderr?.removeAllListeners(); - this.proxyProcess.stdout?.removeAllListeners(); - this.proxyProcess.kill(); - this.proxyProcess = undefined; - } - } -} diff --git a/packages/core/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.injectable.ts b/packages/core/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.injectable.ts index d6ddfca49a..a8f7b03b32 100644 --- a/packages/core/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.injectable.ts +++ b/packages/core/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.injectable.ts @@ -6,24 +6,24 @@ import { chunk } from "lodash"; import type { ConnectionOptions } from "tls"; import { connect } from "tls"; -import url, { URL } from "url"; +import url from "url"; import { apiKubePrefix } from "../../../common/vars"; import { getInjectable } from "@ogre-tools/injectable"; import type { LensProxyApiRequest } from "../lens-proxy"; import kubeAuthProxyServerInjectable from "../../cluster/kube-auth-proxy-server.injectable"; import kubeAuthProxyCertificateInjectable from "../../kube-auth-proxy/kube-auth-proxy-certificate.injectable"; +import clusterApiUrlInjectable from "../../../features/cluster/connections/main/api-url.injectable"; const skipRawHeaders = new Set(["Host", "Authorization"]); const kubeApiUpgradeRequestInjectable = getInjectable({ id: "kube-api-upgrade-request", instantiate: (di): LensProxyApiRequest => async ({ req, socket, head, cluster }) => { - const clusterUrl = new URL(cluster.apiUrl.get()); + const clusterApiUrl = await di.inject(clusterApiUrlInjectable, cluster)(); const kubeAuthProxyServer = di.inject(kubeAuthProxyServerInjectable, cluster); - const kubeAuthProxyCertificate = di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname); + const kubeAuthProxyCertificate = di.inject(kubeAuthProxyCertificateInjectable, clusterApiUrl.hostname); const proxyUrl = await kubeAuthProxyServer.ensureAuthProxyUrl() + req.url.replace(apiKubePrefix, ""); - const apiUrl = url.parse(cluster.apiUrl.get()); const pUrl = url.parse(proxyUrl); const connectOpts: ConnectionOptions = { port: pUrl.port ? parseInt(pUrl.port) : undefined, @@ -33,7 +33,7 @@ const kubeApiUpgradeRequestInjectable = getInjectable({ const proxySocket = connect(connectOpts, () => { proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`); - proxySocket.write(`Host: ${apiUrl.host}\r\n`); + proxySocket.write(`Host: ${clusterApiUrl.host}\r\n`); for (const [key, value] of chunk(req.rawHeaders, 2)) { if (skipRawHeaders.has(key)) { diff --git a/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts b/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts index 08aef8da1d..267a3eafe7 100644 --- a/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts +++ b/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts @@ -5,12 +5,11 @@ import { apiPrefix } from "../../../common/vars"; import { getRouteInjectable } from "../../router/router.injectable"; -import type { Cluster } from "../../../common/cluster/cluster"; -import type { V1Secret } from "@kubernetes/client-node"; import { CoreV1Api } from "@kubernetes/client-node"; import { clusterRoute } from "../../router/route"; -import { dump } from "js-yaml"; +import * as yaml from "js-yaml"; import loadProxyKubeconfigInjectable from "../../cluster/load-proxy-kubeconfig.injectable"; +import clusterApiUrlInjectable from "../../../features/cluster/connections/main/api-url.injectable"; const getServiceAccountRouteInjectable = getRouteInjectable({ id: "get-service-account-route", @@ -25,73 +24,57 @@ const getServiceAccountRouteInjectable = getRouteInjectable({ const secretList = await client.listNamespacedSecret(params.namespace); const secret = secretList.body.items.find(secret => { - const { annotations } = secret.metadata ?? {}; + const { annotations = {}} = secret.metadata ?? {}; - return annotations?.["kubernetes.io/service-account.name"] === params.account; + return annotations["kubernetes.io/service-account.name"] === params.account; }); - if (!secret) { + if (!secret || !secret.data || !secret.metadata) { return { error: "No secret found", statusCode: 404, }; } - const kubeconfig = generateKubeConfig(params.account, secret, cluster); - - if (!kubeconfig) { - return { - error: "No secret found", - statusCode: 404, - }; - } + const { token, "ca.crt": caCrt } = secret.data; + const apiUrl = (await di.inject(clusterApiUrlInjectable, cluster)()).toString(); + const contextName = cluster.contextName.get(); return { - response: kubeconfig, + response: yaml.dump({ + apiVersion: "v1", + kind: "Config", + clusters: [ + { + name: contextName, + cluster: { + server: apiUrl, + "certificate-authority-data": caCrt, + }, + }, + ], + users: [ + { + name: params.account, + user: { + token: Buffer.from(token, "base64").toString("utf8"), + }, + }, + ], + contexts: [ + { + name: `${contextName}-${params.account}`, + context: { + user: params.account, + cluster: contextName, + namespace: secret.metadata.namespace, + }, + }, + ], + "current-context": contextName, + }), }; }), }); export default getServiceAccountRouteInjectable; - -function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster): string | undefined { - if (!secret.data || !secret.metadata) { - return undefined; - } - - const { token, "ca.crt": caCrt } = secret.data; - const tokenData = Buffer.from(token, "base64"); - - return dump({ - "apiVersion": "v1", - "kind": "Config", - "clusters": [ - { - "name": cluster.contextName.get(), - "cluster": { - "server": cluster.apiUrl.get(), - "certificate-authority-data": caCrt, - }, - }, - ], - "users": [ - { - "name": username, - "user": { - "token": tokenData.toString("utf8"), - }, - }, - ], - "contexts": [ - { - "name": [cluster.contextName.get(), username].join("-"), - "context": { - "user": username, - "cluster": cluster.contextName.get(), - "namespace": secret.metadata.namespace, - }, - }, - ], - "current-context": cluster.contextName.get(), - }); -} diff --git a/packages/core/src/main/shell-session/local-shell-session/techincal.test.ts b/packages/core/src/main/shell-session/local-shell-session/techincal.test.ts index f37e690561..6d921ee5fe 100644 --- a/packages/core/src/main/shell-session/local-shell-session/techincal.test.ts +++ b/packages/core/src/main/shell-session/local-shell-session/techincal.test.ts @@ -103,8 +103,6 @@ describe("technical unit tests for local shell sessions", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-kube-config-path", - }, { - clusterServerUrl: "https://localhost:9999", }); await openLocalShellSession({ diff --git a/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx b/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx index aa2d2a3b32..3190be6edd 100644 --- a/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx +++ b/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx @@ -53,8 +53,6 @@ describe("", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); render = renderFor(di); diff --git a/packages/core/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/packages/core/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx index e110056ab0..795c204975 100644 --- a/packages/core/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx +++ b/packages/core/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -31,8 +31,6 @@ describe("SecretDetails tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); const secret = new Secret({ diff --git a/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx index b2369689e7..a067224811 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/+namespaces/namespace-select-filter.test.tsx @@ -62,8 +62,6 @@ describe("", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); namespaceStore = di.inject(namespaceStoreInjectable); diff --git a/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts b/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts index c4b26e9b8a..f274746b0f 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts +++ b/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts @@ -115,8 +115,6 @@ describe("NamespaceStore", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); namespaceStore = di.inject(namespaceStoreInjectable); diff --git a/packages/core/src/renderer/components/+user-management/+cluster-role-bindings/__tests__/dialog.test.tsx b/packages/core/src/renderer/components/+user-management/+cluster-role-bindings/__tests__/dialog.test.tsx index 04cf657606..3af6b8c8ff 100644 --- a/packages/core/src/renderer/components/+user-management/+cluster-role-bindings/__tests__/dialog.test.tsx +++ b/packages/core/src/renderer/components/+user-management/+cluster-role-bindings/__tests__/dialog.test.tsx @@ -40,8 +40,6 @@ describe("ClusterRoleBindingDialog tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); render = renderFor(di); diff --git a/packages/core/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx b/packages/core/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx index 96f7bb5b68..16e2b8e5d1 100644 --- a/packages/core/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx +++ b/packages/core/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx @@ -36,8 +36,6 @@ describe("RoleBindingDialog tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); render = renderFor(di); diff --git a/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts b/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts index 4e27c286eb..83bc2767e6 100644 --- a/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts @@ -131,8 +131,6 @@ describe("CronJob Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); cronJobStore = di.inject(cronJobStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts b/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts index bfd4861f48..72ab9e20b1 100644 --- a/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts @@ -148,8 +148,6 @@ describe("DaemonSet Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); const podStore = di.inject(podStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/deployments.store.test.ts b/packages/core/src/renderer/components/__tests__/deployments.store.test.ts index 23987ccd37..f1a8de27a0 100644 --- a/packages/core/src/renderer/components/__tests__/deployments.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/deployments.store.test.ts @@ -220,8 +220,6 @@ describe("Deployment Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); const podStore = di.inject(podStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/job.store.test.ts b/packages/core/src/renderer/components/__tests__/job.store.test.ts index 73a5cade8f..6f08fc5e7a 100644 --- a/packages/core/src/renderer/components/__tests__/job.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/job.store.test.ts @@ -185,8 +185,6 @@ describe("Job Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); jobStore = di.inject(jobStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/pods.store.test.ts b/packages/core/src/renderer/components/__tests__/pods.store.test.ts index 03a057458d..79d4f0d4de 100644 --- a/packages/core/src/renderer/components/__tests__/pods.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/pods.store.test.ts @@ -131,8 +131,6 @@ describe("Pod Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); podStore = di.inject(podStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts b/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts index aea690a507..18a0cb813e 100644 --- a/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts @@ -148,8 +148,6 @@ describe("ReplicaSet Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); const podStore = di.inject(podStoreInjectable); diff --git a/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts b/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts index 9e7ada1aa1..de6acf220a 100644 --- a/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts @@ -148,8 +148,6 @@ describe("StatefulSet Store tests", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); statefulSetStore = di.inject(statefulSetStoreInjectable); diff --git a/packages/core/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx b/packages/core/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx index ae807bab0a..7abaf8ac15 100644 --- a/packages/core/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx +++ b/packages/core/src/renderer/components/cluster-settings/__tests__/cluster-local-terminal-settings.test.tsx @@ -55,8 +55,6 @@ describe("ClusterLocalTerminalSettings", () => { terminalCWD: "/foobar", defaultNamespace: "kube-system", }, - }, { - clusterServerUrl: "https://localhost:12345", }); const dom = render(); @@ -76,8 +74,6 @@ describe("ClusterLocalTerminalSettings", () => { preferences: { terminalCWD: "/foobar", }, - }, { - clusterServerUrl: "https://localhost:12345", }); const dom = render(); @@ -98,8 +94,6 @@ describe("ClusterLocalTerminalSettings", () => { preferences: { terminalCWD: "/foobar", }, - }, { - clusterServerUrl: "https://localhost:12345", }); const dom = render(); @@ -129,8 +123,6 @@ describe("ClusterLocalTerminalSettings", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some/path", - }, { - clusterServerUrl: "https://localhost:12345", }); const dom = render(); @@ -161,8 +153,6 @@ describe("ClusterLocalTerminalSettings", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some/path", - }, { - clusterServerUrl: "https://localhost:12345", }); const dom = render(); diff --git a/packages/core/src/renderer/components/cluster-settings/__tests__/icon-settings.test.tsx b/packages/core/src/renderer/components/cluster-settings/__tests__/icon-settings.test.tsx index 31bf671854..4c88f34503 100644 --- a/packages/core/src/renderer/components/cluster-settings/__tests__/icon-settings.test.tsx +++ b/packages/core/src/renderer/components/cluster-settings/__tests__/icon-settings.test.tsx @@ -17,32 +17,6 @@ import { clusterIconSettingsComponentInjectionToken, clusterIconSettingsMenuInje import { runInAction } from "mobx"; import { getInjectable, type DiContainer } from "@ogre-tools/injectable"; -const cluster = new Cluster({ - contextName: "some-context", - id: "some-id", - kubeConfigPath: "/some/path/to/kubeconfig", - preferences: { - clusterName: "some-cluster-name", - }, -}, { - clusterServerUrl: "https://localhost:9999", -}); - -const clusterEntity = new KubernetesCluster({ - metadata: { - labels: {}, - name: "some-kubernetes-cluster", - uid: "some-entity-id", - }, - spec: { - kubeconfigContext: "some-context", - kubeconfigPath: "/some/path/to/kubeconfig", - }, - status: { - phase: "connecting", - }, -}); - const newMenuItem = getInjectable({ id: "cluster-icon-settings-menu-test-item", @@ -67,7 +41,7 @@ function CustomSettingsComponent(props: ClusterIconSettingComponentProps) { ); -} +} const newSettingsReactComponent = getInjectable({ id: "cluster-icon-settings-react-component", @@ -86,8 +60,31 @@ describe("Icon settings", () => { beforeEach(() => { di = getDiForUnitTesting(); - + const render = renderFor(di); + const cluster = new Cluster({ + contextName: "some-context", + id: "some-id", + kubeConfigPath: "/some/path/to/kubeconfig", + preferences: { + clusterName: "some-cluster-name", + }, + }); + + const clusterEntity = new KubernetesCluster({ + metadata: { + labels: {}, + name: "some-kubernetes-cluster", + uid: "some-entity-id", + }, + spec: { + kubeconfigContext: "some-context", + kubeconfigPath: "/some/path/to/kubeconfig", + }, + status: { + phase: "connecting", + }, + }); rendered = render( , diff --git a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.test.tsx b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.test.tsx index 74f31ff3dd..691000c887 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.test.tsx +++ b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.test.tsx @@ -38,8 +38,6 @@ describe("kube-object-list-layout", () => { contextName: "some-context-name", id: "some-cluster-id", kubeConfigPath: "/some-path-to-a-kubeconfig", - }, { - clusterServerUrl: "https://localhost:8080", })); render = renderFor(di); diff --git a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx index ce103d7a83..48dd102bf8 100644 --- a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx +++ b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx @@ -528,8 +528,6 @@ export const getApplicationBuilder = () => { id: "some-cluster-id", contextName: "some-context-name", kubeConfigPath: "/some-path-to-kube-config", - }, { - clusterServerUrl: "https://localhost:12345", }); windowDi.override(activeKubernetesClusterInjectable, () => diff --git a/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx b/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx index 4ad315ab98..e3ce80e13a 100644 --- a/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx +++ b/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx @@ -48,16 +48,11 @@ describe("", () => { testUsingFakeTime("2000-01-01 12:00:00am"); - cluster = new Cluster( - { - contextName: "my-cluster", - id: "123456", - kubeConfigPath: "/irrelavent", - }, - { - clusterServerUrl: "https://localhost", - }, - ); + cluster = new Cluster({ + contextName: "my-cluster", + id: "123456", + kubeConfigPath: "/irrelavent", + }); di.override(hostedClusterInjectable, () => cluster); di.override(hostedClusterIdInjectable, () => cluster.id); diff --git a/packages/utility-features/utilities/src/collection-functions.ts b/packages/utility-features/utilities/src/collection-functions.ts index 0c2d5cf119..9a50bb917f 100644 --- a/packages/utility-features/utilities/src/collection-functions.ts +++ b/packages/utility-features/utilities/src/collection-functions.ts @@ -85,6 +85,15 @@ export async function getOrInsertWithAsync(map: Map, key: K, asyncBu return map.get(key)!; } +/** + * Insert `val` into `map` under `key` and then get the value back + */ +export function setAndGet(map: Map, key: K, val: V): V { + map.set(key, val); + + return map.get(key)!; +} + /** * Set the value associated with `key` iff there was not a previous value * @param map The map to interact with