fix: Do not crash when opening details of a helm release

- Switch to using RequestChannel instead of LensProxy for better
  type safety

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2023-05-09 09:28:18 -04:00
1 parent b7ac14df73
commit 40af0d31c5
30 files changed
+664 -417

No files matched your search

@@ -0,0 +1,67 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncResult } from "@k8slens/utilities";
import { isObject, json } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import type { HelmReleaseData } from "../../../features/helm-releases/common/channels";
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
export type GetHelmReleaseData = (
name: string,
namespace: string,
kubeconfigPath: string,
) => AsyncResult<HelmReleaseData, string>;
const getHelmReleaseDataInjectable = getInjectable({
id: "get-helm-release-data",
instantiate: (di): GetHelmReleaseData => {
const execHelm = di.inject(execHelmInjectable);
return async (releaseName, namespace, proxyKubeconfigPath) => {
const result = await execHelm([
"status",
releaseName,
"--namespace",
namespace,
"--kubeconfig",
proxyKubeconfigPath,
"--output",
"json",
]);
if (!result.callWasSuccessful) {
return {
callWasSuccessful: false,
error: `Failed to execute helm: ${result.error}`,
};
}
const parseResult = json.parse(result.response);
if (!parseResult.callWasSuccessful) {
return {
callWasSuccessful: false,
error: `Failed to parse helm response: ${parseResult.error}`,
};
}
const release = parseResult.response;
if (!isObject(release) || Array.isArray(release)) {
return {
callWasSuccessful: false,
error: `Helm response is not an object: ${JSON.stringify(release)}`,
};
}
return {
callWasSuccessful: true,
response: release as unknown as HelmReleaseData,
};
};
},
});
export default getHelmReleaseDataInjectable;
@@ -4,12 +4,13 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncResult } from "@k8slens/utilities";
import { isObject } from "@k8slens/utilities";
import execHelmInjectable from "../../../exec-helm/exec-helm.injectable";
import yaml from "js-yaml";
import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object";
const callForHelmManifestInjectable = getInjectable({
id: "call-for-helm-manifest",
const requestHelmManifestInjectable = getInjectable({
id: "request-helm-manifest",
instantiate: (di) => {
const execHelm = di.inject(execHelmInjectable);
@@ -37,11 +38,11 @@ const callForHelmManifestInjectable = getInjectable({
callWasSuccessful: true,
response: yaml
.loadAll(result.response)
.filter((manifest) => !!manifest) as KubeJsonApiData[],
.filter(isObject) as unknown as KubeJsonApiData[],
};
};
},
});
export default callForHelmManifestInjectable;
export default requestHelmManifestInjectable;
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable";
import requestHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable";
import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object";
import type { AsyncResult } from "@k8slens/utilities";
@@ -17,10 +17,10 @@ const getHelmReleaseResourcesInjectable = getInjectable({
id: "get-helm-release-resources",
instantiate: (di): GetHelmReleaseResources => {
const callForHelmManifest = di.inject(callForHelmManifestInjectable);
const requestHelmManifest = di.inject(requestHelmManifestInjectable);
return async (name, namespace, kubeconfigPath) => {
const result = await callForHelmManifest(name, namespace, kubeconfigPath);
const result = await requestHelmManifest(name, namespace, kubeconfigPath);
if (!result.callWasSuccessful) {
return result;
@@ -4,47 +4,42 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import { loggerInjectionToken } from "@k8slens/logger";
import kubeconfigManagerInjectable from "../../kubeconfig-manager/kubeconfig-manager.injectable";
import { isObject, json } from "@k8slens/utilities";
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
import type { AsyncResult } from "@k8slens/utilities";
import getHelmReleaseResourcesInjectable from "./get-helm-release-resources/get-helm-release-resources.injectable";
import type { HelmReleaseDataWithResources } from "../../../features/helm-releases/common/channels";
import getHelmReleaseDataInjectable from "./get-helm-release-data.injectable";
export interface GetHelmReleaseArgs {
cluster: Cluster;
releaseName: string;
namespace: string;
}
export type GetHelmRelease = (args: GetHelmReleaseArgs) => AsyncResult<HelmReleaseDataWithResources, string>;
const getHelmReleaseInjectable = getInjectable({
id: "get-helm-release",
instantiate: (di) => {
const logger = di.inject(loggerInjectionToken);
const execHelm = di.inject(execHelmInjectable);
instantiate: (di): GetHelmRelease => {
const getHelmReleaseData = di.inject(getHelmReleaseDataInjectable);
const getHelmReleaseResources = di.inject(getHelmReleaseResourcesInjectable);
return async (cluster: Cluster, releaseName: string, namespace: string) => {
return async ({ cluster, namespace, releaseName }) => {
const proxyKubeconfigManager = di.inject(kubeconfigManagerInjectable, cluster);
const proxyKubeconfigPath = await proxyKubeconfigManager.ensurePath();
logger.debug("Fetch release");
const result = await execHelm([
"status",
const releaseResult = await getHelmReleaseData(
releaseName,
"--namespace",
namespace,
"--kubeconfig",
proxyKubeconfigPath,
"--output",
"json",
]);
);
if (!result.callWasSuccessful) {
logger.warn(`Failed to exectute helm: ${result.error}`);
return undefined;
}
const release = json.parse(result.response);
if (!isObject(release) || Array.isArray(release)) {
return undefined;
if (!releaseResult.callWasSuccessful) {
return {
callWasSuccessful: false,
error: `Failed to get helm release data: ${releaseResult.error}`,
};
}
const resourcesResult = await getHelmReleaseResources(
@@ -54,14 +49,18 @@ const getHelmReleaseInjectable = getInjectable({
);
if (!resourcesResult.callWasSuccessful) {
logger.warn(`Failed to get helm release resources: ${resourcesResult.error}`);
return undefined;
return {
callWasSuccessful: false,
error: `Failed to get helm release resources: ${resourcesResult.error}`,
};
}
return {
...release,
resources: resourcesResult.response,
callWasSuccessful: true,
response: {
...releaseResult.response,
resources: resourcesResult.response,
},
};
};
},
@@ -7,15 +7,19 @@ import type { Cluster } from "../../../common/cluster/cluster";
import { loggerInjectionToken } from "@k8slens/logger";
import kubeconfigManagerInjectable from "../../kubeconfig-manager/kubeconfig-manager.injectable";
import listHelmReleasesInjectable from "../list-helm-releases.injectable";
import type { AsyncResult } from "@k8slens/utilities";
import type { ListedHelmRelease } from "../../../features/helm-releases/common/channels";
export type ListClusterHelmReleases = (cluster: Cluster, namespace?: string) => AsyncResult<ListedHelmRelease[], string>;
const listClusterHelmReleasesInjectable = getInjectable({
id: "list-cluster-helm-releases",
instantiate: (di) => {
instantiate: (di): ListClusterHelmReleases => {
const logger = di.inject(loggerInjectionToken);
const listHelmReleases = di.inject(listHelmReleasesInjectable);
return async (cluster: Cluster, namespace?: string) => {
return async (cluster, namespace) => {
const proxyKubeconfigManager = di.inject(kubeconfigManagerInjectable, cluster);
const proxyKubeconfigPath = await proxyKubeconfigManager.ensurePath();
@@ -52,9 +52,15 @@ const updateHelmReleaseInjectable = getInjectable({
throw result.error; // keep the same interface
}
const releaseResult = await getHelmRelease({ cluster, releaseName, namespace });
if (!releaseResult.callWasSuccessful) {
throw releaseResult.error; // keep the same interface
}
return {
log: result.response,
release: await getHelmRelease(cluster, releaseName, namespace),
release: releaseResult.response,
};
} finally {
await removePath(valuesFilePath);
@@ -4,9 +4,11 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
import { toCamelCase, isObject } from "@k8slens/utilities";
import type { AsyncResult } from "@k8slens/utilities";
import { isObject } from "@k8slens/utilities";
import type { ListedHelmRelease } from "../../features/helm-releases/common/channels";
export type ListHelmReleases = (pathToKubeconfig: string, namespace?: string) => Promise<Record<string, any>[]>;
export type ListHelmReleases = (pathToKubeconfig: string, namespace?: string) => AsyncResult<ListedHelmRelease[], string>;
const listHelmReleasesInjectable = getInjectable({
id: "list-helm-releases",
@@ -33,16 +35,21 @@ const listHelmReleasesInjectable = getInjectable({
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
return {
callWasSuccessful: false,
error: `Failed to list helm releases: ${result.error}`,
};
}
const output = JSON.parse(result.response);
const rawOutput = JSON.parse(result.response);
const output = Array.isArray(rawOutput)
? rawOutput.filter(isObject)
: [];
if (!Array.isArray(output) || output.length == 0) {
return [];
}
return output.filter(isObject).map(toCamelCase);
return {
callWasSuccessful: true,
response: output as unknown as ListedHelmRelease[],
};
};
},
});
@@ -1,29 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import { clusterRoute } from "../../../router/route";
import getHelmReleaseInjectable from "../../../helm/helm-service/get-helm-release.injectable";
const getReleaseRouteInjectable = getRouteInjectable({
id: "get-release-route",
instantiate: (di) => {
const getHelmRelease = di.inject(getHelmReleaseInjectable);
return clusterRoute({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
})(async ({ cluster, params }) => ({
response: await getHelmRelease(
cluster,
params.release,
params.namespace,
),
}));
},
});
export default getReleaseRouteInjectable;
@@ -1,25 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import { clusterRoute } from "../../../router/route";
import listClusterHelmReleasesInjectable from "../../../helm/helm-service/list-helm-releases.injectable";
const listReleasesRouteInjectable = getRouteInjectable({
id: "list-releases-route",
instantiate: (di) => {
const listHelmReleases = di.inject(listClusterHelmReleasesInjectable);
return clusterRoute({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace?}`,
})(async ({ cluster, params }) => ({
response: await listHelmReleases(cluster, params.namespace),
}));
},
});
export default listReleasesRouteInjectable;