1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Copy detected metadata from cluster to catalog cluster (#7316)

* Copy detected metadata from cluster to catalog cluster

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>

* Remove duplicate copyright comment

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>

* Lint fixes

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>

* Typescript fix

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>

---------

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
Juho Heikka 2023-03-09 09:06:43 +02:00 committed by GitHub
parent 48ce558420
commit d5a124bd79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 415 additions and 37 deletions

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[];
}

View File

@ -8,6 +8,8 @@ import loggerInjectable from "../../common/logger.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable"; import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable"; import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable";
import { ClusterManager } from "./manager"; import { ClusterManager } from "./manager";
import updateEntityMetadataInjectable from "./update-entity-metadata.injectable";
import updateEntitySpecInjectable from "./update-entity-spec.injectable";
import visibleClusterInjectable from "./visible-cluster.injectable"; import visibleClusterInjectable from "./visible-cluster.injectable";
const clusterManagerInjectable = getInjectable({ const clusterManagerInjectable = getInjectable({
@ -19,6 +21,8 @@ const clusterManagerInjectable = getInjectable({
clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable), clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable),
visibleCluster: di.inject(visibleClusterInjectable), visibleCluster: di.inject(visibleClusterInjectable),
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
updateEntityMetadata: di.inject(updateEntityMetadataInjectable),
updateEntitySpec: di.inject(updateEntitySpecInjectable),
}), }),
}); });

View File

@ -8,7 +8,6 @@ import type { 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 "../../common/utils"; import { isErrnoException } from "../../common/utils";
import type { KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities/kubernetes-cluster";
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";
@ -16,6 +15,8 @@ import type { ClusterStore } from "../../common/cluster-store/cluster-store";
import type { ClusterId } from "../../common/cluster-types"; import type { ClusterId } from "../../common/cluster-types";
import type { CatalogEntityRegistry } from "../catalog"; import type { CatalogEntityRegistry } from "../catalog";
import type { Logger } from "../../common/logger"; import type { Logger } from "../../common/logger";
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
const logPrefix = "[CLUSTER-MANAGER]:"; const logPrefix = "[CLUSTER-MANAGER]:";
@ -27,6 +28,8 @@ interface Dependencies {
readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>; readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
readonly visibleCluster: IObservableValue<ClusterId | null>; readonly visibleCluster: IObservableValue<ClusterId | null>;
readonly logger: Logger; readonly logger: Logger;
readonly updateEntityMetadata: UpdateEntityMetadata;
readonly updateEntitySpec: UpdateEntitySpec;
} }
export class ClusterManager { export class ClusterManager {
@ -97,42 +100,8 @@ export class ClusterManager {
this.updateEntityStatus(entity, cluster); this.updateEntityStatus(entity, cluster);
entity.metadata.labels = { this.dependencies.updateEntityMetadata(entity, cluster);
...entity.metadata.labels, this.dependencies.updateEntitySpec(entity, cluster);
...cluster.labels,
};
entity.metadata.distro = cluster.distribution;
entity.metadata.kubeVersion = cluster.version;
if (cluster.preferences?.clusterName) {
/**
* Only set the name if the it is overriden in preferences. If it isn't
* set then the name of the entity has been explicitly set by its source
*/
entity.metadata.name = cluster.preferences.clusterName;
}
entity.spec.metrics ||= { source: "local" };
if (entity.spec.metrics.source === "local") {
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
prometheus.type = cluster.preferences.prometheusProvider?.type;
prometheus.address = cluster.preferences.prometheus;
entity.spec.metrics.prometheus = prometheus;
}
if (cluster.preferences.icon) {
entity.spec.icon ??= {};
entity.spec.icon.src = cluster.preferences.icon;
} else if (cluster.preferences.icon === null) {
/**
* NOTE: only clear the icon if set to `null` by ClusterIconSettings.
* We can then also clear that value too
*/
entity.spec.icon = undefined;
cluster.preferences.icon = undefined;
}
this.dependencies.catalogEntityRegistry.items.splice(index, 1, entity); this.dependencies.catalogEntityRegistry.items.splice(index, 1, entity);
} }

View File

@ -0,0 +1,42 @@
/**
* 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 { KubernetesCluster } from "../../common/catalog-entities";
import { ClusterMetadataKey } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster";
import { enumKeys } from "../../common/utils/enum";
export type UpdateEntityMetadata = (entity: KubernetesCluster, cluster: Cluster) => void;
const updateEntityMetadataInjectable = getInjectable({
id: "update-entity-metadata",
instantiate: (): UpdateEntityMetadata => {
return (entity, cluster) => {
entity.metadata.labels = {
...entity.metadata.labels,
...cluster.labels,
};
entity.metadata.distro = cluster.distribution;
entity.metadata.kubeVersion = cluster.version;
enumKeys(ClusterMetadataKey).forEach((key) => {
const metadataKey = ClusterMetadataKey[key];
entity.metadata[metadataKey] = cluster.metadata[metadataKey];
});
if (cluster.preferences?.clusterName) {
/**
* Only set the name if the it is overriden in preferences. If it isn't
* set then the name of the entity has been explicitly set by its source
*/
entity.metadata.name = cluster.preferences.clusterName;
}
};
},
});
export default updateEntityMetadataInjectable;

View File

@ -0,0 +1,160 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { KubernetesCluster } from "../../common/catalog-entities";
import { ClusterMetadataKey } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
import updateEntityMetadataInjectable from "./update-entity-metadata.injectable";
describe("update-entity-metadata", () => {
let cluster: Cluster;
let entity: KubernetesCluster;
let updateEntityMetadata: UpdateEntityMetadata;
let detectedMetadata: Record<ClusterMetadataKey, any>;
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(appPathsStateInjectable, () => ({
get: () => ({} as AppPaths),
set: () => {},
}));
const createCluster = di.inject(createClusterInjectionToken);
updateEntityMetadata = di.inject(updateEntityMetadataInjectable);
cluster = createCluster({
id: "some-id",
contextName: "some-context",
kubeConfigPath: "minikube-config.yml",
}, {
clusterServerUrl: "foo",
});
detectedMetadata = {
[ClusterMetadataKey.CLUSTER_ID]: "some-cluster-id",
[ClusterMetadataKey.DISTRIBUTION]: "some-distribution",
[ClusterMetadataKey.VERSION]: "some-version",
[ClusterMetadataKey.LAST_SEEN]: "some-date",
[ClusterMetadataKey.NODES_COUNT]: 42,
[ClusterMetadataKey.PROMETHEUS]: {
"some-parameter": "some-value",
},
};
cluster.metadata = {
...cluster.metadata,
};
entity = new KubernetesCluster({
metadata: {
uid: "some-uid",
name: "some-name",
labels: {},
},
spec: {
kubeconfigContext: "some-context",
kubeconfigPath: "/some/path/to/kubeconfig",
},
status: {
phase: "connecting",
},
});
});
it("given cluster metadata has no some last seen timestamp, does not update entity metadata with last seen timestamp", () => {
updateEntityMetadata(entity, cluster);
expect(entity.metadata.lastSeen).toEqual(undefined);
});
it("given cluster metadata has some last seen timestamp, updates entity metadata with last seen timestamp", () => {
cluster.metadata[ClusterMetadataKey.LAST_SEEN] = detectedMetadata[ClusterMetadataKey.LAST_SEEN];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.lastSeen).toEqual("some-date");
});
it("given cluster metadata has some version, updates entity metadata with version", () => {
cluster.metadata[ClusterMetadataKey.VERSION] = detectedMetadata[ClusterMetadataKey.VERSION];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.version).toEqual("some-version");
});
it("given cluster metadata has nodes count, updates entity metadata with node count", () => {
cluster.metadata[ClusterMetadataKey.NODES_COUNT] = detectedMetadata[ClusterMetadataKey.NODES_COUNT];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.nodes).toEqual(42);
});
it("given cluster metadata has prometheus data, updates entity metadata with prometheus data", () => {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = detectedMetadata[ClusterMetadataKey.PROMETHEUS];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.prometheus).toEqual({
"some-parameter": "some-value",
});
});
it("given cluster metadata has distribution, updates entity metadata with distribution", () => {
cluster.metadata[ClusterMetadataKey.DISTRIBUTION] = detectedMetadata[ClusterMetadataKey.DISTRIBUTION];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.distribution).toEqual("some-distribution");
});
it("given cluster metadata has cluster id, updates entity metadata with cluster id", () => {
cluster.metadata[ClusterMetadataKey.CLUSTER_ID] = detectedMetadata[ClusterMetadataKey.CLUSTER_ID];
updateEntityMetadata(entity, cluster);
expect(entity.metadata.id).toEqual("some-cluster-id");
});
it("given cluster metadata has no kubernetes version, updates entity metadata with 'unknown' kubernetes version", () => {
updateEntityMetadata(entity, cluster);
expect(entity.metadata.kubeVersion).toEqual("unknown");
});
it("given cluster metadata has kubernetes version, updates entity metadata with kubernetes version", () => {
cluster.metadata.version = "some-kubernetes-version";
updateEntityMetadata(entity, cluster);
expect(entity.metadata.kubeVersion).toEqual("some-kubernetes-version");
});
it("given cluster has labels, updates entity metadata with labels", () => {
cluster.labels = {
"some-label": "some-value",
};
entity.metadata.labels = {
"some-other-label": "some-other-value",
};
updateEntityMetadata(entity, cluster);
expect(entity.metadata.labels).toEqual({
"some-label": "some-value",
"some-other-label": "some-other-value",
});
});
it("given cluster has labels, overwrites entity metadata with cluster labels", () => {
cluster.labels = {
"some-label": "some-cluster-value",
};
entity.metadata.labels = {
"some-label": "some-entity-value",
};
updateEntityMetadata(entity, cluster);
expect(entity.metadata.labels).toEqual({
"some-label": "some-cluster-value",
});
});
it("give cluster preferences has name, updates entity metadata with name", () => {
cluster.preferences.clusterName = "some-cluster-name";
updateEntityMetadata(entity, cluster);
expect(entity.metadata.name).toEqual("some-cluster-name");
});
});

View File

@ -0,0 +1,41 @@
/**
* 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 { KubernetesCluster, KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities";
import type { Cluster } from "../../common/cluster/cluster";
export type UpdateEntitySpec = (entity: KubernetesCluster, cluster: Cluster) => void;
const updateEntitySpecInjectable = getInjectable({
id: "update-entity-spec",
instantiate: (): UpdateEntitySpec => {
return (entity, cluster) => {
entity.spec.metrics ||= { source: "local" };
if (entity.spec.metrics.source === "local") {
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
prometheus.type = cluster.preferences.prometheusProvider?.type;
prometheus.address = cluster.preferences.prometheus;
entity.spec.metrics.prometheus = prometheus;
}
if (cluster.preferences.icon) {
entity.spec.icon ??= {};
entity.spec.icon.src = cluster.preferences.icon;
} else if (cluster.preferences.icon === null) {
/**
* NOTE: only clear the icon if set to `null` by ClusterIconSettings.
* We can then also clear that value too
*/
entity.spec.icon = undefined;
cluster.preferences.icon = undefined;
}
};
},
});
export default updateEntitySpecInjectable;

View File

@ -0,0 +1,154 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { KubernetesCluster } from "../../common/catalog-entities";
import type { Cluster } from "../../common/cluster/cluster";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
import updateEntitySpecInjectable from "./update-entity-spec.injectable";
describe("update-entity-spec", () => {
let cluster: Cluster;
let entity: KubernetesCluster;
let updateEntitySpec: UpdateEntitySpec;
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(appPathsStateInjectable, () => ({
get: () => ({} as AppPaths),
set: () => {},
}));
const createCluster = di.inject(createClusterInjectionToken);
updateEntitySpec = di.inject(updateEntitySpecInjectable);
cluster = createCluster({
id: "some-id",
contextName: "some-context",
kubeConfigPath: "minikube-config.yml",
}, {
clusterServerUrl: "foo",
});
entity = new KubernetesCluster({
metadata: {
uid: "some-uid",
name: "some-name",
labels: {},
},
spec: {
kubeconfigContext: "some-context",
kubeconfigPath: "/some/path/to/kubeconfig",
},
status: {
phase: "connecting",
},
});
});
it("given cluster has icon, updates entity spec with icon", () => {
cluster.preferences.icon = "some-icon";
updateEntitySpec(entity, cluster);
expect(entity.spec.icon?.src).toEqual("some-icon");
});
it("given cluster icon is null, deletes icon from both", () => {
cluster.preferences.icon = null;
entity.spec.icon = { src : "some-icon" };
updateEntitySpec(entity, cluster);
expect(entity.spec.icon).toBeUndefined();
expect(cluster.preferences.icon).toBeUndefined();
});
it("given entity has no metrics, adds source as local", () => {
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.source).toEqual("local");
});
it("given entity has metrics, does not change source", () => {
entity.spec.metrics = { source: "some-source" };
entity.spec.metrics.prometheus = {
address: {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
},
};
cluster.preferences.prometheus = {
namespace: "some-other-namespace",
port: 666,
service: "some-other-service",
prefix: "some-other-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.source).toEqual("some-source");
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
});
it("given entity has local prometheus source, updates entity spec with prometheus provider", () => {
entity.spec.metrics = { source: "local" };
cluster.preferences.prometheusProvider = {
type: "some-prometheus-provider-type",
};
cluster.preferences.prometheus = {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
});
it("given entity has no metrics, updates entity spec with prometheus provider", () => {
expect(entity.spec.metrics).toBeUndefined();
cluster.preferences.prometheusProvider = {
type: "some-prometheus-provider-type",
};
cluster.preferences.prometheus = {
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
};
updateEntitySpec(entity, cluster);
expect(entity.spec.metrics?.prometheus?.address).toEqual({
namespace: "some-namespace",
port: 42,
service: "some-service",
prefix: "some-prefix",
});
expect(entity.spec.metrics?.prometheus?.type).toEqual("some-prometheus-provider-type");
});
});