mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Move Cluster.apiUrl to seperate injectable (#7354)
* Refactor out Cluster.apiUrl to direct reading - Should help prevent using stale data - Removes some uses of synchronous FS operations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rename helper function to better communicate intent Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve prometheus handler tests to override less things Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
18d660ea77
commit
fe2fd4c1fd
2
package-lock.json
generated
2
package-lock.json
generated
@ -38059,7 +38059,6 @@
|
|||||||
"@astronautlabs/jsonpath": "^1.1.0",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/call": "^9.0.1",
|
"@hapi/call": "^9.0.1",
|
||||||
"@hapi/subtext": "^7.1.0",
|
"@hapi/subtext": "^7.1.0",
|
||||||
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
|
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
"@k8slens/react-application": "^1.0.0-alpha.0",
|
"@k8slens/react-application": "^1.0.0-alpha.0",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
@ -38266,6 +38265,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@k8slens/application": "^6.5.0-alpha.0",
|
"@k8slens/application": "^6.5.0-alpha.0",
|
||||||
"@k8slens/application-for-electron-main": "^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/legacy-extensions": "^1.0.0-alpha.0",
|
||||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
||||||
|
|||||||
@ -120,7 +120,6 @@
|
|||||||
"@astronautlabs/jsonpath": "^1.1.0",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/call": "^9.0.1",
|
"@hapi/call": "^9.0.1",
|
||||||
"@hapi/subtext": "^7.1.0",
|
"@hapi/subtext": "^7.1.0",
|
||||||
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
|
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
"@k8slens/react-application": "^1.0.0-alpha.0",
|
"@k8slens/react-application": "^1.0.0-alpha.0",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
@ -324,6 +323,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@k8slens/application": "^6.5.0-alpha.0",
|
"@k8slens/application": "^6.5.0-alpha.0",
|
||||||
"@k8slens/application-for-electron-main": "^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/legacy-extensions": "^1.0.0-alpha.0",
|
||||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
||||||
|
|||||||
@ -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
|
* The data representing a cluster's state, for passing between main and renderer
|
||||||
*/
|
*/
|
||||||
export interface ClusterState {
|
export interface ClusterState {
|
||||||
apiUrl: string;
|
|
||||||
online: boolean;
|
online: boolean;
|
||||||
disconnected: boolean;
|
disconnected: boolean;
|
||||||
accessible: boolean;
|
accessible: boolean;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { computed, observable, toJS, runInAction } from "mobx";
|
import { computed, observable, toJS, runInAction } from "mobx";
|
||||||
import type { KubeApiResource } from "../rbac";
|
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 { ClusterMetadataKey, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
||||||
import type { IObservableValue } from "mobx";
|
import type { IObservableValue } from "mobx";
|
||||||
import { replaceObservableObject } from "../utils/replace-observable-object";
|
import { replaceObservableObject } from "../utils/replace-observable-object";
|
||||||
@ -27,11 +27,6 @@ export class Cluster {
|
|||||||
*/
|
*/
|
||||||
readonly kubeConfigPath = observable.box() as IObservableValue<string>;
|
readonly kubeConfigPath = observable.box() as IObservableValue<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Kubernetes API server URL
|
|
||||||
*/
|
|
||||||
readonly apiUrl: IObservableValue<string>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes if we can detect that cluster is online
|
* 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);
|
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 });
|
const { error } = clusterModelIdChecker.validate({ id });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -131,7 +126,6 @@ export class Cluster {
|
|||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
this.apiUrl = observable.box(configData.clusterServerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,7 +181,6 @@ export class Cluster {
|
|||||||
*/
|
*/
|
||||||
getState(): ClusterState {
|
getState(): ClusterState {
|
||||||
return {
|
return {
|
||||||
apiUrl: this.apiUrl.get(),
|
|
||||||
online: this.online.get(),
|
online: this.online.get(),
|
||||||
ready: this.ready.get(),
|
ready: this.ready.get(),
|
||||||
disconnected: this.disconnected.get(),
|
disconnected: this.disconnected.get(),
|
||||||
@ -207,7 +200,6 @@ export class Cluster {
|
|||||||
this.accessible.set(state.accessible);
|
this.accessible.set(state.accessible);
|
||||||
this.allowedNamespaces.replace(state.allowedNamespaces);
|
this.allowedNamespaces.replace(state.allowedNamespaces);
|
||||||
this.resourcesToShow.replace(state.resourcesToShow);
|
this.resourcesToShow.replace(state.resourcesToShow);
|
||||||
this.apiUrl.set(state.apiUrl);
|
|
||||||
this.disconnected.set(state.disconnected);
|
this.disconnected.set(state.disconnected);
|
||||||
this.isAdmin.set(state.isAdmin);
|
this.isAdmin.set(state.isAdmin);
|
||||||
this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled);
|
this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled);
|
||||||
|
|||||||
@ -51,8 +51,6 @@ describe("ApiManager", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
apiManager = di.inject(apiManagerInjectable);
|
apiManager = di.inject(apiManagerInjectable);
|
||||||
|
|||||||
@ -43,8 +43,6 @@ describe("KubeApi", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
apiManager = di.inject(apiManagerInjectable);
|
apiManager = di.inject(apiManagerInjectable);
|
||||||
|
|||||||
@ -62,8 +62,6 @@ describe("createKubeApiForRemoteCluster", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fetchMock = asyncFn();
|
fetchMock = asyncFn();
|
||||||
@ -168,8 +166,6 @@ describe("KubeApi", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
di.override(apiKubeInjectable, () => createKubeJsonApi({
|
di.override(apiKubeInjectable, () => createKubeJsonApi({
|
||||||
|
|||||||
@ -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 {
|
export interface SplitConfigEntry {
|
||||||
config: KubeConfig;
|
config: KubeConfig;
|
||||||
validationResult: ValidateKubeConfigResult;
|
validationResult: ValidateKubeConfigResult;
|
||||||
|
|||||||
@ -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<ValidateKubeConfigResult>;
|
||||||
|
|
||||||
|
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;
|
||||||
@ -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<URL> => {
|
||||||
|
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;
|
||||||
@ -109,8 +109,6 @@ describe("Deleting a cluster", () => {
|
|||||||
clusterName: "some-current-context-cluster",
|
clusterName: "some-current-context-cluster",
|
||||||
},
|
},
|
||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
}, {
|
|
||||||
clusterServerUrl: currentClusterServerUrl,
|
|
||||||
});
|
});
|
||||||
nonCurrentCluster = new Cluster({
|
nonCurrentCluster = new Cluster({
|
||||||
id: "some-non-current-context-cluster",
|
id: "some-non-current-context-cluster",
|
||||||
@ -119,8 +117,6 @@ describe("Deleting a cluster", () => {
|
|||||||
clusterName: "some-non-current-context-cluster",
|
clusterName: "some-non-current-context-cluster",
|
||||||
},
|
},
|
||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
}, {
|
|
||||||
clusterServerUrl: currentClusterServerUrl,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -197,8 +193,6 @@ describe("Deleting a cluster", () => {
|
|||||||
clusterName: "some-cluster",
|
clusterName: "some-cluster",
|
||||||
},
|
},
|
||||||
kubeConfigPath: joinPaths(directoryForKubeConfigs, "some-cluster.json"),
|
kubeConfigPath: joinPaths(directoryForKubeConfigs, "some-cluster.json"),
|
||||||
}, {
|
|
||||||
clusterServerUrl: singleClusterServerUrl,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -233,8 +227,6 @@ describe("Deleting a cluster", () => {
|
|||||||
clusterName: "some-cluster",
|
clusterName: "some-cluster",
|
||||||
},
|
},
|
||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
}, {
|
|
||||||
clusterServerUrl: singleClusterServerUrl,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import type { WriteFileSync } from "../../../common/fs/write-file-sync.injectabl
|
|||||||
import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable";
|
import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable";
|
||||||
import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable";
|
import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable";
|
||||||
import writeBufferSyncInjectable 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 clustersPersistentStorageInjectable from "./common/storage.injectable";
|
||||||
import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable";
|
import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable";
|
||||||
import type { AddCluster } from "./common/add.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 getClusterByIdInjectable from "./common/get-by-id.injectable";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import clustersInjectable from "./common/clusters.injectable";
|
import clustersInjectable from "./common/clusters.injectable";
|
||||||
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
|
|
||||||
// NOTE: this is intended to read the actual file system
|
// NOTE: this is intended to read the actual file system
|
||||||
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
|
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
|
||||||
@ -102,7 +102,7 @@ describe("cluster storage technical tests", () => {
|
|||||||
|
|
||||||
describe("with foo cluster added", () => {
|
describe("with foo cluster added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const cluster = new Cluster({
|
addCluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
preferences: {
|
preferences: {
|
||||||
@ -114,11 +114,7 @@ describe("cluster storage technical tests", () => {
|
|||||||
getCustomKubeConfigFilePath("foo"),
|
getCustomKubeConfigFilePath("foo"),
|
||||||
kubeconfig,
|
kubeconfig,
|
||||||
),
|
),
|
||||||
}, {
|
|
||||||
clusterServerUrl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addCluster(cluster);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
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", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di.override(storeMigrationVersionInjectable, () => "3.6.0");
|
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({
|
const minimalValidKubeConfig = JSON.stringify({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
clusters: [
|
clusters: [
|
||||||
|
|||||||
@ -5,33 +5,23 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { action } from "mobx";
|
import { action } from "mobx";
|
||||||
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
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 type { ClusterModel } from "../../../../common/cluster-types";
|
||||||
import { Cluster } from "../../../../common/cluster/cluster";
|
import { Cluster } from "../../../../common/cluster/cluster";
|
||||||
import clustersStateInjectable from "./state.injectable";
|
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({
|
const addClusterInjectable = getInjectable({
|
||||||
id: "add-cluster",
|
id: "add-cluster",
|
||||||
instantiate: (di): AddCluster => {
|
instantiate: (di): AddCluster => {
|
||||||
const clustersState = di.inject(clustersStateInjectable);
|
const clustersState = di.inject(clustersStateInjectable);
|
||||||
const emitAppEvent = di.inject(emitAppEventInjectable);
|
const emitAppEvent = di.inject(emitAppEventInjectable);
|
||||||
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
|
|
||||||
|
|
||||||
return action((clusterOrModel) => {
|
return action((clusterModel) => {
|
||||||
emitAppEvent({ name: "cluster", action: "add" });
|
emitAppEvent({ name: "cluster", action: "add" });
|
||||||
|
|
||||||
const cluster = clusterOrModel instanceof Cluster
|
return setAndGet(clustersState, clusterModel.id, new Cluster(clusterModel));
|
||||||
? clusterOrModel
|
|
||||||
: new Cluster(
|
|
||||||
clusterOrModel,
|
|
||||||
readClusterConfigSync(clusterOrModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
clustersState.set(cluster.id, cluster);
|
|
||||||
|
|
||||||
return cluster;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -6,7 +6,6 @@ import { iter } from "@k8slens/utilities";
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { comparer, action } from "mobx";
|
import { comparer, action } from "mobx";
|
||||||
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
||||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
|
||||||
import type { ClusterId, ClusterModel } from "../../../../common/cluster-types";
|
import type { ClusterId, ClusterModel } from "../../../../common/cluster-types";
|
||||||
import { Cluster } from "../../../../common/cluster/cluster";
|
import { Cluster } from "../../../../common/cluster/cluster";
|
||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
@ -23,7 +22,6 @@ const clustersPersistentStorageInjectable = getInjectable({
|
|||||||
id: "clusters-persistent-storage",
|
id: "clusters-persistent-storage",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
|
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
|
||||||
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
|
|
||||||
const clustersState = di.inject(clustersStateInjectable);
|
const clustersState = di.inject(clustersStateInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
@ -39,7 +37,6 @@ const clustersPersistentStorageInjectable = getInjectable({
|
|||||||
const currentClusters = new Map(clustersState);
|
const currentClusters = new Map(clustersState);
|
||||||
const newClusters = new Map<ClusterId, Cluster>();
|
const newClusters = new Map<ClusterId, Cluster>();
|
||||||
|
|
||||||
// update new clusters
|
|
||||||
for (const clusterModel of clusters) {
|
for (const clusterModel of clusters) {
|
||||||
try {
|
try {
|
||||||
let cluster = currentClusters.get(clusterModel.id);
|
let cluster = currentClusters.get(clusterModel.id);
|
||||||
@ -47,11 +44,9 @@ const clustersPersistentStorageInjectable = getInjectable({
|
|||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.updateModel(clusterModel);
|
cluster.updateModel(clusterModel);
|
||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(
|
cluster = new Cluster(clusterModel);
|
||||||
clusterModel,
|
|
||||||
readClusterConfigSync(clusterModel),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newClusters.set(clusterModel.id, cluster);
|
newClusters.set(clusterModel.id, cluster);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* 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 { Kubectl } from "../kubectl/kubectl";
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
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 createRequestNamespaceListPermissionsInjectable from "../../common/cluster/create-request-namespace-list-permissions.injectable";
|
||||||
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.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", () => {
|
describe("create clusters", () => {
|
||||||
let cluster: Cluster;
|
let cluster: Cluster;
|
||||||
@ -26,7 +28,7 @@ describe("create clusters", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting();
|
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(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
||||||
@ -42,27 +44,45 @@ describe("create clusters", () => {
|
|||||||
setupPrometheus: jest.fn(),
|
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, () => ({
|
di.override(kubeconfigManagerInjectable, () => ({
|
||||||
ensurePath: async () => "/some-proxy-kubeconfig-file",
|
ensurePath: async () => "/some-proxy-kubeconfig-file",
|
||||||
} as Partial<KubeconfigManager> as KubeconfigManager));
|
} as Partial<KubeconfigManager> as KubeconfigManager));
|
||||||
|
|
||||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
|
||||||
|
|
||||||
cluster = new Cluster({
|
const addCluster = di.inject(addClusterInjectable);
|
||||||
|
|
||||||
|
cluster = addCluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "/minikube-config.yml",
|
||||||
}, {
|
|
||||||
clusterServerUrl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
clusterConnection = di.inject(clusterConnectionInjectable, cluster);
|
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", () => {
|
it("reconnect should not throw if contextHandler is missing", () => {
|
||||||
expect(() => clusterConnection.reconnect()).not.toThrowError();
|
expect(() => clusterConnection.reconnect()).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* 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 type { ChildProcess } from "child_process";
|
||||||
import { Kubectl } from "../kubectl/kubectl";
|
import { Kubectl } from "../kubectl/kubectl";
|
||||||
import type { DeepMockProxy } from "jest-mock-extended";
|
import type { DeepMockProxy } from "jest-mock-extended";
|
||||||
@ -12,7 +10,7 @@ import { mockDeep, mock } from "jest-mock-extended";
|
|||||||
import type { Readable } from "stream";
|
import type { Readable } from "stream";
|
||||||
import { EventEmitter } from "stream";
|
import { EventEmitter } from "stream";
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
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 createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.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 ensureDirInjectable from "../../common/fs/ensure-dir.injectable";
|
||||||
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
||||||
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
||||||
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
const clusterServerUrl = "https://192.168.64.3:8443";
|
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", () => {
|
describe("kube auth proxy tests", () => {
|
||||||
let createKubeAuthProxy: CreateKubeAuthProxy;
|
|
||||||
let spawnMock: jest.Mock;
|
let spawnMock: jest.Mock;
|
||||||
let waitUntilPortIsUsedMock: jest.Mock;
|
let waitUntilPortIsUsedMock: jest.Mock;
|
||||||
let broadcastMessageMock: jest.Mock;
|
let broadcastMessageMock: jest.Mock;
|
||||||
let getBasenameOfPath: GetBasenameOfPath;
|
let getBasenameOfPath: GetBasenameOfPath;
|
||||||
|
let cluster: Cluster;
|
||||||
|
let kubeAuthProxy: KubeAuthProxy;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting();
|
const di = getDiForUnitTesting();
|
||||||
@ -51,7 +51,7 @@ describe("kube auth proxy tests", () => {
|
|||||||
clusters: [{
|
clusters: [{
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
cluster: {
|
cluster: {
|
||||||
server: clusterServerUrl,
|
server: "https://192.168.64.3:8443",
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
"current-context": "minikube",
|
"current-context": "minikube",
|
||||||
@ -83,29 +83,25 @@ describe("kube auth proxy tests", () => {
|
|||||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
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 () => {
|
it("calling exit multiple times shouldn't throw", async () => {
|
||||||
const cluster = new Cluster({
|
kubeAuthProxy.exit();
|
||||||
id: "foobar",
|
kubeAuthProxy.exit();
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeAuthProxy.exit();
|
||||||
contextName: "minikube",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const kap = createKubeAuthProxy(cluster, {});
|
|
||||||
|
|
||||||
kap.exit();
|
|
||||||
kap.exit();
|
|
||||||
kap.exit();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("spawn tests", () => {
|
describe("spawn tests", () => {
|
||||||
let mockedCP: DeepMockProxy<ChildProcess>;
|
let mockedCP: DeepMockProxy<ChildProcess>;
|
||||||
let listeners: EventEmitter;
|
let listeners: EventEmitter;
|
||||||
let proxy: KubeAuthProxy;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockedCP = mockDeep<ChildProcess>();
|
mockedCP = mockDeep<ChildProcess>();
|
||||||
@ -184,16 +180,7 @@ describe("kube auth proxy tests", () => {
|
|||||||
});
|
});
|
||||||
waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve());
|
waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve());
|
||||||
|
|
||||||
const cluster = new Cluster({
|
await kubeAuthProxy.run();
|
||||||
id: "foobar",
|
|
||||||
kubeConfigPath: "minikube-config.yml",
|
|
||||||
contextName: "minikube",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
proxy = createKubeAuthProxy(cluster, {});
|
|
||||||
await proxy.run();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast errors", () => {
|
it("should call spawn and broadcast errors", () => {
|
||||||
|
|||||||
@ -89,8 +89,6 @@ describe("kubeconfig manager tests", () => {
|
|||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "/minikube-config.yml",
|
kubeConfigPath: "/minikube-config.yml",
|
||||||
}, {
|
|
||||||
clusterServerUrl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
kubeConfManager = di.inject(kubeconfigManagerInjectable, clusterFake);
|
kubeConfManager = di.inject(kubeconfigManagerInjectable, clusterFake);
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import { runInAction } from "mobx";
|
|||||||
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable";
|
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable";
|
||||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
||||||
import loadProxyKubeconfigInjectable from "../cluster/load-proxy-kubeconfig.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
|
||||||
|
|
||||||
enum ServiceResult {
|
enum ServiceResult {
|
||||||
Success,
|
Success,
|
||||||
@ -47,37 +47,46 @@ describe("PrometheusHandler", () => {
|
|||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let cluster: Cluster;
|
let cluster: Cluster;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
di.override(loadProxyKubeconfigInjectable, (di, cluster) => async () => {
|
di.override(createKubeAuthProxyInjectable, () => () => ({
|
||||||
const res = new KubeConfig();
|
apiPrefix: "/some-api-prefix",
|
||||||
|
exit: () => {},
|
||||||
res.addCluster({
|
run: async () => {},
|
||||||
name: "some-cluster-name",
|
port: 9191,
|
||||||
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(directoryForTempInjectable, () => "/some-temp-dir");
|
di.override(directoryForTempInjectable, () => "/some-temp-dir");
|
||||||
di.inject(lensProxyPortInjectable).set(12345);
|
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({
|
cluster = new Cluster({
|
||||||
contextName: "some-context-name",
|
contextName,
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some/path",
|
kubeConfigPath,
|
||||||
}, {
|
|
||||||
clusterServerUrl: "http://localhost:81",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -97,8 +97,8 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
const models = configToModels(config, "/bar");
|
const models = configToModels(config, "/bar");
|
||||||
|
|
||||||
expect(models.length).toBe(1);
|
expect(models.length).toBe(1);
|
||||||
expect(models[0][0].contextName).toBe("context-name");
|
expect(models[0].contextName).toBe("context-name");
|
||||||
expect(models[0][0].kubeConfigPath).toBe("/bar");
|
expect(models[0].kubeConfigPath).toBe("/bar");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -38,15 +38,15 @@ const computeKubeconfigDiffInjectable = getInjectable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rawModels = configToModels(config, filePath);
|
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 });
|
logger.debug(`File now has ${models.size} entries`, { filePath });
|
||||||
|
|
||||||
for (const [contextName, value] of source) {
|
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
|
// 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
|
// 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);
|
clustersThatAreBeingDeleted.delete(value[0].id);
|
||||||
|
|
||||||
@ -63,21 +63,16 @@ const computeKubeconfigDiffInjectable = getInjectable({
|
|||||||
// diff against that
|
// diff against that
|
||||||
|
|
||||||
// or update the model and mark it as not needed to be added
|
// 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);
|
models.delete(contextName);
|
||||||
logger.debug(`Updated old cluster from sync`, { filePath, 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
|
// add new clusters to the source
|
||||||
try {
|
try {
|
||||||
const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex");
|
const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex");
|
||||||
const cluster = getClusterById(clusterId) ?? new Cluster({ ...model, id: clusterId }, configData);
|
const cluster = getClusterById(clusterId) ?? new Cluster({ ...model, id: clusterId });
|
||||||
|
|
||||||
if (!cluster.apiUrl.get()) {
|
|
||||||
throw new Error("Cluster constructor failed, see above error");
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = catalogEntityFromCluster(cluster);
|
const entity = catalogEntityFromCluster(cluster);
|
||||||
|
|
||||||
if (!filePath.startsWith(directoryForKubeConfigs)) {
|
if (!filePath.startsWith(directoryForKubeConfigs)) {
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
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 { splitConfig } from "../../../common/kube-helpers";
|
||||||
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
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({
|
const configToModelsInjectable = getInjectable({
|
||||||
id: "config-to-models",
|
id: "config-to-models",
|
||||||
@ -22,15 +22,10 @@ const configToModelsInjectable = getInjectable({
|
|||||||
if (validationResult.error) {
|
if (validationResult.error) {
|
||||||
logger.debug(`context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath });
|
logger.debug(`context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath });
|
||||||
} else {
|
} else {
|
||||||
validConfigs.push([
|
validConfigs.push({
|
||||||
{
|
kubeConfigPath: filePath,
|
||||||
kubeConfigPath: filePath,
|
contextName: config.currentContext,
|
||||||
contextName: config.currentContext,
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
clusterServerUrl: validationResult.cluster.server,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,22 +9,24 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import k8SRequestInjectable from "../k8s-request.injectable";
|
import k8SRequestInjectable from "../k8s-request.injectable";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import requestClusterVersionInjectable from "./request-cluster-version.injectable";
|
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 isGKE = (version: string) => version.includes("gke");
|
||||||
const isEKS = (version: string) => version.includes("eks");
|
const isEKS = (version: string) => version.includes("eks");
|
||||||
const isIKS = (version: string) => version.includes("IKS");
|
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 isMirantis = (version: string) => version.includes("-mirantis-") || version.includes("-docker-");
|
||||||
const isDigitalOcean = (cluster: Cluster) => cluster.apiUrl.get().endsWith("k8s.ondigitalocean.com");
|
const isDigitalOcean = (apiUrl: URL) => apiUrl.hostname.endsWith("k8s.ondigitalocean.com");
|
||||||
const isMinikube = (cluster: Cluster) => cluster.contextName.get().startsWith("minikube");
|
const isMinikube = (contextName: string) => contextName.startsWith("minikube");
|
||||||
const isMicrok8s = (cluster: Cluster) => cluster.contextName.get().startsWith("microk8s");
|
const isMicrok8s = (contextName: string) => contextName.startsWith("microk8s");
|
||||||
const isKind = (cluster: Cluster) => cluster.contextName.get().startsWith("kubernetes-admin@kind-");
|
const isKind = (contextName: string) => contextName.startsWith("kubernetes-admin@kind-");
|
||||||
const isDockerDesktop = (cluster: Cluster) => cluster.contextName.get() === "docker-desktop";
|
const isDockerDesktop = (contextName: string) => contextName === "docker-desktop";
|
||||||
const isTke = (version: string) => version.includes("-tke.");
|
const isTke = (version: string) => version.includes("-tke.");
|
||||||
const isCustom = (version: string) => version.includes("+");
|
const isCustom = (version: string) => version.includes("+");
|
||||||
const isVMWare = (version: string) => version.includes("+vmware");
|
const isVMWare = (version: string) => version.includes("+vmware");
|
||||||
const isRke = (version: string) => version.includes("-rancher");
|
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 isK3s = (version: string) => version.includes("+k3s");
|
||||||
const isK0s = (version: string) => version.includes("-k0s") || version.includes("+k0s");
|
const isK0s = (version: string) => version.includes("-k0s") || version.includes("+k0s");
|
||||||
const isAlibaba = (version: string) => version.includes("-aliyun");
|
const isAlibaba = (version: string) => version.includes("-aliyun");
|
||||||
@ -49,12 +51,14 @@ const clusterDistributionDetectorInjectable = getInjectable({
|
|||||||
key: ClusterMetadataKey.DISTRIBUTION,
|
key: ClusterMetadataKey.DISTRIBUTION,
|
||||||
detect: async (cluster) => {
|
detect: async (cluster) => {
|
||||||
const version = await requestClusterVersion(cluster);
|
const version = await requestClusterVersion(cluster);
|
||||||
|
const apiUrl = await di.inject(clusterApiUrlInjectable, cluster)();
|
||||||
|
const contextName = cluster.contextName.get();
|
||||||
|
|
||||||
if (isRke(version)) {
|
if (isRke(version)) {
|
||||||
return { value: "rke", accuracy: 80 };
|
return { value: "rke", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRancherDesktop(cluster)) {
|
if (isRancherDesktop(contextName)) {
|
||||||
return { value: "rancher-desktop", accuracy: 80 };
|
return { value: "rancher-desktop", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +78,11 @@ const clusterDistributionDetectorInjectable = getInjectable({
|
|||||||
return { value: "iks", accuracy: 80 };
|
return { value: "iks", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAKS(cluster)) {
|
if (isAKS(apiUrl)) {
|
||||||
return { value: "aks", accuracy: 80 };
|
return { value: "aks", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDigitalOcean(cluster)) {
|
if (isDigitalOcean(apiUrl)) {
|
||||||
return { value: "digitalocean", accuracy: 90 };
|
return { value: "digitalocean", accuracy: 90 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,19 +110,19 @@ const clusterDistributionDetectorInjectable = getInjectable({
|
|||||||
return { value: "tencent", accuracy: 90 };
|
return { value: "tencent", accuracy: 90 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMinikube(cluster)) {
|
if (isMinikube(contextName)) {
|
||||||
return { value: "minikube", accuracy: 80 };
|
return { value: "minikube", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMicrok8s(cluster)) {
|
if (isMicrok8s(contextName)) {
|
||||||
return { value: "microk8s", accuracy: 80 };
|
return { value: "microk8s", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKind(cluster)) {
|
if (isKind(contextName)) {
|
||||||
return { value: "kind", accuracy: 70 };
|
return { value: "kind", accuracy: 70 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDockerDesktop(cluster)) {
|
if (isDockerDesktop(contextName)) {
|
||||||
return { value: "docker-desktop", accuracy: 80 };
|
return { value: "docker-desktop", accuracy: 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { ClusterMetadataKey } from "../../common/cluster-types";
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import k8SRequestInjectable from "../k8s-request.injectable";
|
import k8SRequestInjectable from "../k8s-request.injectable";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
|
import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable";
|
||||||
|
|
||||||
const clusterIdDetectorFactoryInjectable = getInjectable({
|
const clusterIdDetectorFactoryInjectable = getInjectable({
|
||||||
id: "cluster-id-detector-factory",
|
id: "cluster-id-detector-factory",
|
||||||
@ -28,7 +29,7 @@ const clusterIdDetectorFactoryInjectable = getInjectable({
|
|||||||
try {
|
try {
|
||||||
id = await getDefaultNamespaceId(cluster);
|
id = await getDefaultNamespaceId(cluster);
|
||||||
} catch(_) {
|
} catch(_) {
|
||||||
id = cluster.apiUrl.get();
|
id = (await di.inject(clusterApiUrlInjectable, cluster)()).toString();
|
||||||
}
|
}
|
||||||
const value = createHash("sha256").update(id).digest("hex");
|
const value = createHash("sha256").update(id).digest("hex");
|
||||||
|
|
||||||
|
|||||||
@ -67,8 +67,6 @@ describe("detect-cluster-metadata", () => {
|
|||||||
id: "some-id",
|
id: "some-id",
|
||||||
contextName: "some-context",
|
contextName: "some-context",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "foo",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type { Cluster } from "../../common/cluster/cluster";
|
|||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.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 type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import clusterApiUrlInjectable from "../../features/cluster/connections/main/api-url.injectable";
|
||||||
|
|
||||||
export interface KubeAuthProxyServer {
|
export interface KubeAuthProxyServer {
|
||||||
getApiTarget(isLongRunningRequest?: boolean): Promise<ServerOptions>;
|
getApiTarget(isLongRunningRequest?: boolean): Promise<ServerOptions>;
|
||||||
@ -24,23 +25,23 @@ const thirtySecondsInMs = 30 * 1000;
|
|||||||
const kubeAuthProxyServerInjectable = getInjectable({
|
const kubeAuthProxyServerInjectable = getInjectable({
|
||||||
id: "kube-auth-proxy-server",
|
id: "kube-auth-proxy-server",
|
||||||
instantiate: (di, cluster): KubeAuthProxyServer => {
|
instantiate: (di, cluster): KubeAuthProxyServer => {
|
||||||
const clusterUrl = new URL(cluster.apiUrl.get());
|
const clusterApiUrl = di.inject(clusterApiUrlInjectable, cluster);
|
||||||
|
const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable, cluster);
|
||||||
const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);
|
|
||||||
const certificate = di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname);
|
|
||||||
|
|
||||||
let kubeAuthProxy: KubeAuthProxy | undefined = undefined;
|
let kubeAuthProxy: KubeAuthProxy | undefined = undefined;
|
||||||
let apiTarget: ServerOptions | undefined = undefined;
|
let apiTarget: ServerOptions | undefined = undefined;
|
||||||
|
|
||||||
const ensureServerHelper = async (): Promise<KubeAuthProxy> => {
|
const ensureServerHelper = async (): Promise<KubeAuthProxy> => {
|
||||||
if (!kubeAuthProxy) {
|
if (!kubeAuthProxy) {
|
||||||
const proxyEnv = Object.assign({}, process.env);
|
const proxyEnv = {
|
||||||
|
...process.env,
|
||||||
|
};
|
||||||
|
|
||||||
if (cluster.preferences.httpsProxy) {
|
if (cluster.preferences.httpsProxy) {
|
||||||
proxyEnv.HTTPS_PROXY = cluster.preferences.httpsProxy;
|
proxyEnv.HTTPS_PROXY = cluster.preferences.httpsProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeAuthProxy = createKubeAuthProxy(cluster, proxyEnv);
|
kubeAuthProxy = createKubeAuthProxy(proxyEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
await kubeAuthProxy.run();
|
await kubeAuthProxy.run();
|
||||||
@ -49,31 +50,24 @@ const kubeAuthProxyServerInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newApiTarget = async (timeout: number): Promise<ServerOptions> => {
|
const newApiTarget = async (timeout: number): Promise<ServerOptions> => {
|
||||||
const kubeAuthProxy = await ensureServerHelper();
|
const { hostname } = await clusterApiUrl();
|
||||||
const headers: Record<string, string> = {};
|
const certificate = di.inject(kubeAuthProxyCertificateInjectable, hostname);
|
||||||
|
const { port, apiPrefix: path } = await ensureServerHelper();
|
||||||
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}]`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
target: {
|
target: {
|
||||||
protocol: "https:",
|
protocol: "https:",
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
port: kubeAuthProxy.port,
|
port,
|
||||||
path: kubeAuthProxy.apiPrefix,
|
path,
|
||||||
ca: certificate.cert,
|
ca: certificate.cert,
|
||||||
},
|
},
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
timeout,
|
timeout,
|
||||||
secure: true,
|
secure: true,
|
||||||
headers,
|
headers: {
|
||||||
|
Host: hostname,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import "../../common/ipc/cluster";
|
|||||||
import type { IComputedValue, IObservableValue, ObservableSet } from "mobx";
|
import type { IComputedValue, IObservableValue, ObservableSet } from "mobx";
|
||||||
import { action, makeObservable, observe, reaction, toJS } from "mobx";
|
import { action, makeObservable, observe, reaction, toJS } from "mobx";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import { isErrnoException } from "@k8slens/utilities";
|
|
||||||
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
|
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
|
||||||
import { ipcMainOn } from "../../common/ipc";
|
import { ipcMainOn } from "../../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
@ -158,26 +157,12 @@ export class ClusterManager {
|
|||||||
const cluster = this.dependencies.getClusterById(entity.getId());
|
const cluster = this.dependencies.getClusterById(entity.getId());
|
||||||
|
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
const model = {
|
this.dependencies.addCluster({
|
||||||
id: entity.getId(),
|
id: entity.getId(),
|
||||||
kubeConfigPath: entity.spec.kubeconfigPath,
|
kubeConfigPath: entity.spec.kubeconfigPath,
|
||||||
contextName: entity.spec.kubeconfigContext,
|
contextName: entity.spec.kubeconfigContext,
|
||||||
accessibleNamespaces: entity.spec.accessibleNamespaces ?? [],
|
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 {
|
} else {
|
||||||
cluster.kubeConfigPath.set(entity.spec.kubeconfigPath);
|
cluster.kubeConfigPath.set(entity.spec.kubeconfigPath);
|
||||||
cluster.contextName.set(entity.spec.kubeconfigContext);
|
cluster.contextName.set(entity.spec.kubeconfigContext);
|
||||||
|
|||||||
@ -34,8 +34,6 @@ describe("update-entity-metadata", () => {
|
|||||||
id: "some-id",
|
id: "some-id",
|
||||||
contextName: "some-context",
|
contextName: "some-context",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "foo",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
detectedMetadata = {
|
detectedMetadata = {
|
||||||
|
|||||||
@ -31,8 +31,6 @@ describe("update-entity-spec", () => {
|
|||||||
id: "some-id",
|
id: "some-id",
|
||||||
contextName: "some-context",
|
contextName: "some-context",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "foo",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
entity = new KubernetesCluster({
|
entity = new KubernetesCluster({
|
||||||
|
|||||||
@ -2,18 +2,22 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
|
||||||
import { KubeAuthProxyImpl } from "./kube-auth-proxy";
|
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable";
|
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||||
import lensK8sProxyPathInjectable from "./lens-k8s-proxy-path.injectable";
|
import lensK8sProxyPathInjectable from "./lens-k8s-proxy-path.injectable";
|
||||||
import getPortFromStreamInjectable from "../utils/get-port-from-stream.injectable";
|
import getPortFromStreamInjectable from "../utils/get-port-from-stream.injectable";
|
||||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.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 broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable";
|
||||||
|
import { TypedRegEx } from "typed-regex";
|
||||||
|
|
||||||
export interface KubeAuthProxy {
|
export interface KubeAuthProxy {
|
||||||
readonly apiPrefix: string;
|
readonly apiPrefix: string;
|
||||||
@ -22,31 +26,159 @@ export interface KubeAuthProxy {
|
|||||||
exit: () => void;
|
exit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateKubeAuthProxy = (cluster: Cluster, env: NodeJS.ProcessEnv) => KubeAuthProxy;
|
export type CreateKubeAuthProxy = (env: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||||
|
|
||||||
|
const startingServeMatcher = "starting to serve on (?<address>.+)";
|
||||||
|
const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), {
|
||||||
|
rawMatcher: startingServeMatcher,
|
||||||
|
});
|
||||||
|
|
||||||
const createKubeAuthProxyInjectable = getInjectable({
|
const createKubeAuthProxyInjectable = getInjectable({
|
||||||
id: "create-kube-auth-proxy",
|
id: "create-kube-auth-proxy",
|
||||||
|
|
||||||
instantiate: (di): CreateKubeAuthProxy => {
|
instantiate: (di, cluster): CreateKubeAuthProxy => {
|
||||||
const dependencies: Omit<KubeAuthProxyDependencies, "proxyCert" | "broadcastConnectionUpdate"> = {
|
const lensK8sProxyPath = di.inject(lensK8sProxyPathInjectable);
|
||||||
proxyBinPath: di.inject(lensK8sProxyPathInjectable),
|
const spawn = di.inject(spawnInjectable);
|
||||||
spawn: di.inject(spawnInjectable),
|
const logger = di.inject(loggerInjectable);
|
||||||
logger: di.inject(loggerInjectable),
|
const waitUntilPortIsUsed = di.inject(waitUntilPortIsUsedInjectable);
|
||||||
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
|
const getPortFromStream = di.inject(getPortFromStreamInjectable);
|
||||||
getPortFromStream: di.inject(getPortFromStreamInjectable),
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
dirname: di.inject(getDirnameOfPathInjectable),
|
const randomBytes = di.inject(randomBytesInjectable);
|
||||||
};
|
const clusterApiUrl = di.inject(clusterApiUrlInjectable, cluster);
|
||||||
|
const broadcastConnectionUpdate = di.inject(broadcastConnectionUpdateInjectable, cluster);
|
||||||
|
|
||||||
return (cluster, env) => {
|
return (env) => {
|
||||||
const clusterUrl = new URL(cluster.apiUrl.get());
|
let port: number | undefined;
|
||||||
|
let proxyProcess: ChildProcess | undefined;
|
||||||
|
const ready = observable.box(false);
|
||||||
|
const apiPrefix = `/${randomBytes(8).toString("hex")}`;
|
||||||
|
|
||||||
return new KubeAuthProxyImpl({
|
const exit = () => {
|
||||||
...dependencies,
|
ready.set(false);
|
||||||
proxyCert: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname),
|
|
||||||
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
|
if (proxyProcess) {
|
||||||
}, cluster, env);
|
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<void> => {
|
||||||
|
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;
|
export default createKubeAuthProxyInjectable;
|
||||||
|
|||||||
@ -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 (?<address>.+)";
|
|
||||||
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<void> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,24 +6,24 @@
|
|||||||
import { chunk } from "lodash";
|
import { chunk } from "lodash";
|
||||||
import type { ConnectionOptions } from "tls";
|
import type { ConnectionOptions } from "tls";
|
||||||
import { connect } from "tls";
|
import { connect } from "tls";
|
||||||
import url, { URL } from "url";
|
import url from "url";
|
||||||
import { apiKubePrefix } from "../../../common/vars";
|
import { apiKubePrefix } from "../../../common/vars";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { LensProxyApiRequest } from "../lens-proxy";
|
import type { LensProxyApiRequest } from "../lens-proxy";
|
||||||
import kubeAuthProxyServerInjectable from "../../cluster/kube-auth-proxy-server.injectable";
|
import kubeAuthProxyServerInjectable from "../../cluster/kube-auth-proxy-server.injectable";
|
||||||
import kubeAuthProxyCertificateInjectable from "../../kube-auth-proxy/kube-auth-proxy-certificate.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 skipRawHeaders = new Set(["Host", "Authorization"]);
|
||||||
|
|
||||||
const kubeApiUpgradeRequestInjectable = getInjectable({
|
const kubeApiUpgradeRequestInjectable = getInjectable({
|
||||||
id: "kube-api-upgrade-request",
|
id: "kube-api-upgrade-request",
|
||||||
instantiate: (di): LensProxyApiRequest => async ({ req, socket, head, cluster }) => {
|
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 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 proxyUrl = await kubeAuthProxyServer.ensureAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
||||||
const apiUrl = url.parse(cluster.apiUrl.get());
|
|
||||||
const pUrl = url.parse(proxyUrl);
|
const pUrl = url.parse(proxyUrl);
|
||||||
const connectOpts: ConnectionOptions = {
|
const connectOpts: ConnectionOptions = {
|
||||||
port: pUrl.port ? parseInt(pUrl.port) : undefined,
|
port: pUrl.port ? parseInt(pUrl.port) : undefined,
|
||||||
@ -33,7 +33,7 @@ const kubeApiUpgradeRequestInjectable = getInjectable({
|
|||||||
|
|
||||||
const proxySocket = connect(connectOpts, () => {
|
const proxySocket = connect(connectOpts, () => {
|
||||||
proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`);
|
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)) {
|
for (const [key, value] of chunk(req.rawHeaders, 2)) {
|
||||||
if (skipRawHeaders.has(key)) {
|
if (skipRawHeaders.has(key)) {
|
||||||
|
|||||||
@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
import { apiPrefix } from "../../../common/vars";
|
import { apiPrefix } from "../../../common/vars";
|
||||||
import { getRouteInjectable } from "../../router/router.injectable";
|
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 { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import { clusterRoute } from "../../router/route";
|
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 loadProxyKubeconfigInjectable from "../../cluster/load-proxy-kubeconfig.injectable";
|
||||||
|
import clusterApiUrlInjectable from "../../../features/cluster/connections/main/api-url.injectable";
|
||||||
|
|
||||||
const getServiceAccountRouteInjectable = getRouteInjectable({
|
const getServiceAccountRouteInjectable = getRouteInjectable({
|
||||||
id: "get-service-account-route",
|
id: "get-service-account-route",
|
||||||
@ -25,73 +24,57 @@ const getServiceAccountRouteInjectable = getRouteInjectable({
|
|||||||
const secretList = await client.listNamespacedSecret(params.namespace);
|
const secretList = await client.listNamespacedSecret(params.namespace);
|
||||||
|
|
||||||
const secret = secretList.body.items.find(secret => {
|
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 {
|
return {
|
||||||
error: "No secret found",
|
error: "No secret found",
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const kubeconfig = generateKubeConfig(params.account, secret, cluster);
|
const { token, "ca.crt": caCrt } = secret.data;
|
||||||
|
const apiUrl = (await di.inject(clusterApiUrlInjectable, cluster)()).toString();
|
||||||
if (!kubeconfig) {
|
const contextName = cluster.contextName.get();
|
||||||
return {
|
|
||||||
error: "No secret found",
|
|
||||||
statusCode: 404,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
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;
|
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(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -103,8 +103,6 @@ describe("technical unit tests for local shell sessions", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-kube-config-path",
|
kubeConfigPath: "/some-kube-config-path",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:9999",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await openLocalShellSession({
|
await openLocalShellSession({
|
||||||
|
|||||||
@ -53,8 +53,6 @@ describe("<HpaDetails/>", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|||||||
@ -31,8 +31,6 @@ describe("SecretDetails tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const secret = new Secret({
|
const secret = new Secret({
|
||||||
|
|||||||
@ -62,8 +62,6 @@ describe("<NamespaceSelectFilter />", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
namespaceStore = di.inject(namespaceStoreInjectable);
|
namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
|
|||||||
@ -115,8 +115,6 @@ describe("NamespaceStore", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
namespaceStore = di.inject(namespaceStoreInjectable);
|
namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
|
|||||||
@ -40,8 +40,6 @@ describe("ClusterRoleBindingDialog tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|||||||
@ -36,8 +36,6 @@ describe("RoleBindingDialog tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|||||||
@ -131,8 +131,6 @@ describe("CronJob Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
cronJobStore = di.inject(cronJobStoreInjectable);
|
cronJobStore = di.inject(cronJobStoreInjectable);
|
||||||
|
|||||||
@ -148,8 +148,6 @@ describe("DaemonSet Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const podStore = di.inject(podStoreInjectable);
|
const podStore = di.inject(podStoreInjectable);
|
||||||
|
|||||||
@ -220,8 +220,6 @@ describe("Deployment Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const podStore = di.inject(podStoreInjectable);
|
const podStore = di.inject(podStoreInjectable);
|
||||||
|
|||||||
@ -185,8 +185,6 @@ describe("Job Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jobStore = di.inject(jobStoreInjectable);
|
jobStore = di.inject(jobStoreInjectable);
|
||||||
|
|||||||
@ -131,8 +131,6 @@ describe("Pod Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
podStore = di.inject(podStoreInjectable);
|
podStore = di.inject(podStoreInjectable);
|
||||||
|
|||||||
@ -148,8 +148,6 @@ describe("ReplicaSet Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const podStore = di.inject(podStoreInjectable);
|
const podStore = di.inject(podStoreInjectable);
|
||||||
|
|||||||
@ -148,8 +148,6 @@ describe("StatefulSet Store tests", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
statefulSetStore = di.inject(statefulSetStoreInjectable);
|
statefulSetStore = di.inject(statefulSetStoreInjectable);
|
||||||
|
|||||||
@ -55,8 +55,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
|||||||
terminalCWD: "/foobar",
|
terminalCWD: "/foobar",
|
||||||
defaultNamespace: "kube-system",
|
defaultNamespace: "kube-system",
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
||||||
|
|
||||||
@ -76,8 +74,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
|||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/foobar",
|
terminalCWD: "/foobar",
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
||||||
@ -98,8 +94,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
|||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/foobar",
|
terminalCWD: "/foobar",
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
||||||
@ -129,8 +123,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some/path",
|
kubeConfigPath: "/some/path",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
||||||
@ -161,8 +153,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some/path",
|
kubeConfigPath: "/some/path",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
const dom = render(<ClusterLocalTerminalSetting cluster={cluster}/>);
|
||||||
|
|||||||
@ -17,32 +17,6 @@ import { clusterIconSettingsComponentInjectionToken, clusterIconSettingsMenuInje
|
|||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
import { getInjectable, type DiContainer } from "@ogre-tools/injectable";
|
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({
|
const newMenuItem = getInjectable({
|
||||||
id: "cluster-icon-settings-menu-test-item",
|
id: "cluster-icon-settings-menu-test-item",
|
||||||
|
|
||||||
@ -67,7 +41,7 @@ function CustomSettingsComponent(props: ClusterIconSettingComponentProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSettingsReactComponent = getInjectable({
|
const newSettingsReactComponent = getInjectable({
|
||||||
id: "cluster-icon-settings-react-component",
|
id: "cluster-icon-settings-react-component",
|
||||||
@ -86,8 +60,31 @@ describe("Icon settings", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
|
|
||||||
const render = renderFor(di);
|
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(
|
rendered = render(
|
||||||
<ClusterIconSetting cluster={cluster} entity={clusterEntity} />,
|
<ClusterIconSetting cluster={cluster} entity={clusterEntity} />,
|
||||||
|
|||||||
@ -38,8 +38,6 @@ describe("kube-object-list-layout", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|||||||
@ -528,8 +528,6 @@ export const getApplicationBuilder = () => {
|
|||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
kubeConfigPath: "/some-path-to-kube-config",
|
kubeConfigPath: "/some-path-to-kube-config",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:12345",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
windowDi.override(activeKubernetesClusterInjectable, () =>
|
windowDi.override(activeKubernetesClusterInjectable, () =>
|
||||||
|
|||||||
@ -48,16 +48,11 @@ describe("<ClusterFrame />", () => {
|
|||||||
|
|
||||||
testUsingFakeTime("2000-01-01 12:00:00am");
|
testUsingFakeTime("2000-01-01 12:00:00am");
|
||||||
|
|
||||||
cluster = new Cluster(
|
cluster = new Cluster({
|
||||||
{
|
contextName: "my-cluster",
|
||||||
contextName: "my-cluster",
|
id: "123456",
|
||||||
id: "123456",
|
kubeConfigPath: "/irrelavent",
|
||||||
kubeConfigPath: "/irrelavent",
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
clusterServerUrl: "https://localhost",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
di.override(hostedClusterInjectable, () => cluster);
|
di.override(hostedClusterInjectable, () => cluster);
|
||||||
di.override(hostedClusterIdInjectable, () => cluster.id);
|
di.override(hostedClusterIdInjectable, () => cluster.id);
|
||||||
|
|||||||
@ -85,6 +85,15 @@ export async function getOrInsertWithAsync<K, V>(map: Map<K, V>, key: K, asyncBu
|
|||||||
return map.get(key)!;
|
return map.get(key)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert `val` into `map` under `key` and then get the value back
|
||||||
|
*/
|
||||||
|
export function setAndGet<K, V>(map: Map<K, V>, 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
|
* Set the value associated with `key` iff there was not a previous value
|
||||||
* @param map The map to interact with
|
* @param map The map to interact with
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user