mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Restructure to monorepo (#6907)
* wip: restructure to monorepo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor create-release-pr to a package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * github workflow fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix typo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add webpack-env types to core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix github workflows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor/fix integration tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * lint fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * yarn run dev Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * eslint settings for vscode Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * move templates to right package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * open-lens build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * integration test fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix nx task dependencies Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * use bash shell for unit tests in test workflow Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix test:unit for windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix build:app on windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove ELECTRON_BUILDER_EXTRA_ARGS Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * sync src/ from master Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove Makefile from core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
0066ca4ea2
commit
2657df2293
3321 files changed
+28699
-11447
No files matched your search
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import listHelmChartsInjectable from "../helm-service/list-helm-charts.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import { sortBySemverVersion } from "../../../common/utils";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
describe("Helm Service tests", () => {
|
||||
let listHelmCharts: () => Promise<any>;
|
||||
let getActiveHelmRepositoriesMock: jest.Mock<Promise<AsyncResult<HelmRepo[]>>>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
getActiveHelmRepositoriesMock = jest.fn();
|
||||
|
||||
di.override(
|
||||
helmChartManagerInjectable,
|
||||
(di, repo) => new HelmChartManagerFake(repo) as unknown,
|
||||
);
|
||||
|
||||
di.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock);
|
||||
|
||||
di.unoverride(listHelmChartsInjectable);
|
||||
di.permitSideEffects(listHelmChartsInjectable);
|
||||
|
||||
listHelmCharts = di.inject(listHelmChartsInjectable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("list charts with deprecated entries", async () => {
|
||||
getActiveHelmRepositoriesMock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{
|
||||
name: "stable",
|
||||
url: "stableurl",
|
||||
cacheFilePath: "/some-cache-file-path-for-stable",
|
||||
},
|
||||
{
|
||||
name: "experiment",
|
||||
url: "experimenturl",
|
||||
cacheFilePath: "/some-cache-file-path-for-experiment",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const charts = await listHelmCharts();
|
||||
|
||||
expect(charts).toEqual({
|
||||
stable: {
|
||||
"apm-server": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.7",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.6",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
"invalid-semver": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.4.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.3.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver but more",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
"redis": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "1.0.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "0.0.9",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
experiment: {
|
||||
"fairwind": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.2",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
deprecated: true,
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.1",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("list charts sorted by version in descending order", async () => {
|
||||
getActiveHelmRepositoriesMock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [{
|
||||
name: "bitnami",
|
||||
url: "bitnamiurl",
|
||||
cacheFilePath: "/some-cache-file-path-for-bitnami",
|
||||
}],
|
||||
}),
|
||||
);
|
||||
|
||||
const charts = await listHelmCharts();
|
||||
|
||||
expect(charts).toEqual({
|
||||
bitnami: {
|
||||
"hotdog": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.2",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
"pretzel": [
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const charts = new Map([
|
||||
["stable", {
|
||||
"invalid-semver": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.3.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver but more",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.4.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"apm-server": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.7",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.6",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"redis": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "1.0.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "0.0.9",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["experiment", {
|
||||
"fairwind": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.1",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.2",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
deprecated: true,
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["bitnami", {
|
||||
"hotdog": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.2",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"pretzel": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
]);
|
||||
|
||||
class HelmChartManagerFake {
|
||||
constructor(private repo: HelmRepo){ }
|
||||
|
||||
public async charts(): Promise<any> {
|
||||
return charts.get(this.repo.name) ?? {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface DeleteHelmReleaseData {
|
||||
name: string;
|
||||
namespace: string;
|
||||
}
|
||||
|
||||
export type DeleteHelmRelease = (kubeconfigPath: string, data: DeleteHelmReleaseData) => Promise<string>;
|
||||
|
||||
const deleteHelmReleaseInjectable = getInjectable({
|
||||
id: "delete-helm-release",
|
||||
instantiate: (di): DeleteHelmRelease => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (kubeconfigPath, { name, namespace }) => {
|
||||
const result = await execHelm([
|
||||
"delete",
|
||||
name,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
]);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return result.response;
|
||||
}
|
||||
|
||||
throw result.error;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default deleteHelmReleaseInjectable;
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { computed } from "mobx";
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import execHelmEnvInjectable from "./exec-env.injectable";
|
||||
|
||||
export default getGlobalOverride(execHelmEnvInjectable, () => computed(() => ({})));
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import httpsProxyConfigurationInjectable from "../../../common/user-store/https-proxy.injectable";
|
||||
|
||||
const execHelmEnvInjectable = getInjectable({
|
||||
id: "exec-helm-env",
|
||||
instantiate: (di) => {
|
||||
const httpsProxyConfiguration = di.inject(httpsProxyConfigurationInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const {
|
||||
HTTPS_PROXY = httpsProxyConfiguration.get(),
|
||||
...env
|
||||
} = process.env;
|
||||
|
||||
return { HTTPS_PROXY, ...env } as Partial<Record<string, string>>;
|
||||
});
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default execHelmEnvInjectable;
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 { ExecFileException } from "child_process";
|
||||
import execFileInjectable from "../../../common/fs/exec-file.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import helmBinaryPathInjectable from "../helm-binary-path.injectable";
|
||||
import execHelmEnvInjectable from "./exec-env.injectable";
|
||||
|
||||
export type ExecHelm = (args: string[]) => Promise<AsyncResult<string, ExecFileException & { stderr: string }>>;
|
||||
|
||||
const execHelmInjectable = getInjectable({
|
||||
id: "exec-helm",
|
||||
|
||||
instantiate: (di): ExecHelm => {
|
||||
const execFile = di.inject(execFileInjectable);
|
||||
const execHelmEnv = di.inject(execHelmEnvInjectable);
|
||||
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
||||
|
||||
return async (args) => execFile(helmBinaryPath, args, {
|
||||
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
||||
env: execHelmEnv.get(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default execHelmInjectable;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 execHelmInjectable from "../exec-helm/exec-helm.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
|
||||
export type HelmEnv = Record<string, string> & {
|
||||
HELM_REPOSITORY_CACHE?: string;
|
||||
HELM_REPOSITORY_CONFIG?: string;
|
||||
};
|
||||
|
||||
const getHelmEnvInjectable = getInjectable({
|
||||
id: "get-helm-env",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (): Promise<AsyncResult<HelmEnv>> => {
|
||||
const result = await execHelm(["env"]);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return { callWasSuccessful: false, error: result.error.stderr };
|
||||
}
|
||||
|
||||
const lines = result.response.split(/\r?\n/); // split by new line feed
|
||||
const env: HelmEnv = {};
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const [key, value] = line.split("=");
|
||||
|
||||
if (key && value) {
|
||||
env[key] = value.replace(/"/g, ""); // strip quotas
|
||||
}
|
||||
});
|
||||
|
||||
return { callWasSuccessful: true, response: env };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmEnvInjectable;
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 { HelmReleaseRevision } from "../../common/k8s-api/endpoints/helm-releases.api/request-history.injectable";
|
||||
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface GetHelmReleaseHistoryData {
|
||||
name: string;
|
||||
namespace: string;
|
||||
}
|
||||
|
||||
export type GetHelmReleaseHistory = (kubeconfigPath: string, data: GetHelmReleaseHistoryData) => Promise<HelmReleaseRevision[]>;
|
||||
|
||||
const getHelmReleaseHistoryInjectable = getInjectable({
|
||||
id: "get-helm-release-history",
|
||||
instantiate: (di): GetHelmReleaseHistory => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (kubeconfigPath, { name, namespace }) => {
|
||||
const result = await execHelm([
|
||||
"history",
|
||||
name,
|
||||
"--output", "json",
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
]);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return JSON.parse(result.response);
|
||||
}
|
||||
|
||||
throw result.error;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmReleaseHistoryInjectable;
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface GetHelmReleaseValuesData {
|
||||
name: string;
|
||||
namespace: string;
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
export type GetHelmReleaseValues = (kubeconfigPath: string, data: GetHelmReleaseValuesData) => Promise<string>;
|
||||
|
||||
const getHelmReleaseValuesInjectable = getInjectable({
|
||||
id: "get-helm-release-values",
|
||||
instantiate: (di): GetHelmReleaseValues => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (kubeconfigPath, { name, namespace, all = false }) => {
|
||||
const args = [
|
||||
"get",
|
||||
"values",
|
||||
name,
|
||||
];
|
||||
|
||||
if (all) {
|
||||
args.push("--all");
|
||||
}
|
||||
|
||||
args.push(
|
||||
"--output", "yaml",
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
);
|
||||
|
||||
const result = await execHelm(args);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return result.response;
|
||||
}
|
||||
|
||||
throw result.error;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmReleaseValuesInjectable;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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 bundledBinaryPathInjectable from "../../common/utils/bundled-binary-path.injectable";
|
||||
|
||||
const helmBinaryPathInjectable = getInjectable({
|
||||
id: "helm-binary-path",
|
||||
instantiate: (di) => di.inject(bundledBinaryPathInjectable, "helm"),
|
||||
});
|
||||
|
||||
export default helmBinaryPathInjectable;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
export interface ChartCacheEntry {
|
||||
data: string; // serialized JSON
|
||||
mtimeMs: number;
|
||||
}
|
||||
|
||||
export type HelmChartManagerCache = Map<string, ChartCacheEntry>;
|
||||
|
||||
const helmChartManagerCacheInjectable = getInjectable({
|
||||
id: "helm-chart-manager-cache",
|
||||
instantiate: (): HelmChartManagerCache => new Map(),
|
||||
});
|
||||
|
||||
export default helmChartManagerCacheInjectable;
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import { HelmChartManager } from "./helm-chart-manager";
|
||||
import helmChartManagerCacheInjectable from "./helm-chart-manager-cache.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import statInjectable from "../../common/fs/stat.injectable";
|
||||
|
||||
const helmChartManagerInjectable = getInjectable({
|
||||
id: "helm-chart-manager",
|
||||
|
||||
instantiate: (di, repo: HelmRepo) => new HelmChartManager({
|
||||
cache: di.inject(helmChartManagerCacheInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
execHelm: di.inject(execHelmInjectable),
|
||||
readFile: di.inject(readFileInjectable),
|
||||
stat: di.inject(statInjectable),
|
||||
}, repo),
|
||||
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, repo: HelmRepo) => repo.name,
|
||||
}),
|
||||
});
|
||||
|
||||
export default helmChartManagerInjectable;
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import * as yaml from "js-yaml";
|
||||
import { iter, put, sortBySemverVersion } from "../../common/utils";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api/request-charts.injectable";
|
||||
import type { ExecHelm } from "./exec-helm/exec-helm.injectable";
|
||||
import type { ReadFile } from "../../common/fs/read-file.injectable";
|
||||
import type { Stat } from "../../common/fs/stat.injectable";
|
||||
|
||||
export interface HelmCacheFile {
|
||||
apiVersion: string;
|
||||
entries: RepoHelmChartList;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
readonly cache: HelmChartManagerCache;
|
||||
readonly logger: Logger;
|
||||
execHelm: ExecHelm;
|
||||
readFile: ReadFile;
|
||||
stat: Stat;
|
||||
}
|
||||
|
||||
export class HelmChartManager {
|
||||
constructor(
|
||||
private readonly dependencies: Dependencies,
|
||||
protected readonly repo: HelmRepo,
|
||||
) {}
|
||||
|
||||
public async chartVersions(name: string) {
|
||||
const charts = await this.charts();
|
||||
|
||||
return charts[name];
|
||||
}
|
||||
|
||||
public async charts(): Promise<RepoHelmChartList> {
|
||||
try {
|
||||
return await this.cachedYaml();
|
||||
} catch(error) {
|
||||
this.dependencies.logger.error("HELM-CHART-MANAGER]: failed to list charts", { error });
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private async executeCommand(args: string[], name: string, version?: string) {
|
||||
args.push(`${this.repo.name}/${name}`);
|
||||
|
||||
if (version) {
|
||||
args.push("--version", version);
|
||||
}
|
||||
|
||||
return this.dependencies.execHelm(args);
|
||||
}
|
||||
|
||||
public async getReadme(name: string, version?: string) {
|
||||
return this.executeCommand(["show", "readme"], name, version);
|
||||
}
|
||||
|
||||
public async getValues(name: string, version?: string) {
|
||||
return this.executeCommand(["show", "values"], name, version);
|
||||
}
|
||||
|
||||
protected async updateYamlCache() {
|
||||
const cacheFile = await this.dependencies.readFile(this.repo.cacheFilePath);
|
||||
const cacheFileStats = await this.dependencies.stat(this.repo.cacheFilePath);
|
||||
const data = yaml.load(cacheFile) as string | number | HelmCacheFile;
|
||||
|
||||
if (!data || typeof data !== "object" || typeof data.entries !== "object") {
|
||||
throw Object.assign(new TypeError("Helm Cache file does not parse correctly"), { file: this.repo.cacheFilePath, data });
|
||||
}
|
||||
|
||||
const normalized = normalizeHelmCharts(this.repo.name, data.entries);
|
||||
|
||||
return put(
|
||||
this.dependencies.cache,
|
||||
this.repo.name,
|
||||
{
|
||||
data: JSON.stringify(normalized),
|
||||
mtimeMs: cacheFileStats.mtimeMs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected async cachedYaml(): Promise<RepoHelmChartList> {
|
||||
let cacheEntry = this.dependencies.cache.get(this.repo.name);
|
||||
|
||||
if (!cacheEntry) {
|
||||
cacheEntry = await this.updateYamlCache();
|
||||
} else {
|
||||
const newStats = await this.dependencies.stat(this.repo.cacheFilePath);
|
||||
|
||||
if (cacheEntry.mtimeMs < newStats.mtimeMs) {
|
||||
cacheEntry = await this.updateYamlCache();
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.parse(cacheEntry.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some initial preprocessing on the data, so as to avoid needing to do it later
|
||||
* 1. Set the repo name
|
||||
* 2. Normalize the created date
|
||||
* 3. Filter out charts that only have deprecated entries
|
||||
*/
|
||||
function normalizeHelmCharts(repoName: string, entries: RepoHelmChartList): RepoHelmChartList {
|
||||
return Object.fromEntries(
|
||||
iter.filter(
|
||||
iter.map(
|
||||
Object.entries(entries),
|
||||
([name, charts]) => [
|
||||
name,
|
||||
sortBySemverVersion(
|
||||
charts.map(chart => ({
|
||||
...chart,
|
||||
created: Date.parse(chart.created).toString(),
|
||||
repo: repoName,
|
||||
})),
|
||||
),
|
||||
] as const,
|
||||
),
|
||||
([, charts]) => !charts.every(chart => chart.deprecated),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { DeleteHelmReleaseData } from "../delete-helm-release.injectable";
|
||||
import deleteHelmReleaseInjectable from "../delete-helm-release.injectable";
|
||||
|
||||
const deleteClusterHelmReleaseInjectable = getInjectable({
|
||||
id: "delete-cluster-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const deleteHelmRelease = di.inject(deleteHelmReleaseInjectable);
|
||||
|
||||
return async (cluster: Cluster, data: DeleteHelmReleaseData) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug(`[CLUSTER]: Delete helm release`, data);
|
||||
|
||||
return deleteHelmRelease(proxyKubeconfig, data);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default deleteClusterHelmReleaseInjectable;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import getHelmChartReadmeInjectable from "./get-helm-chart-readme.injectable";
|
||||
|
||||
export default getGlobalOverride(getHelmChartReadmeInjectable, () => () => {
|
||||
throw new Error("tried to get a helm chart's readme without overriding");
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const getHelmChartReadmeInjectable = getInjectable({
|
||||
id: "get-helm-chart-readme",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getChartManager(repo).getReadme(chartName, version);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmChartReadmeInjectable;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const getHelmChartValuesInjectable = getInjectable({
|
||||
id: "get-helm-chart-values",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getChartManager(repo).getValues(chartName, version);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmChartValuesInjectable;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
|
||||
const getHelmChartVersionsInjectable = getInjectable({
|
||||
id: "get-helm-chart-versions",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string) => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getChartManager(repo).chartVersions(chartName);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmChartVersionsInjectable;
|
||||
@@ -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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { GetHelmReleaseHistoryData } from "../get-helm-release-history.injectable";
|
||||
import getHelmReleaseHistoryInjectable from "../get-helm-release-history.injectable";
|
||||
|
||||
const getClusterHelmReleaseHistoryInjectable = getInjectable({
|
||||
id: "get-cluster-helm-release-history",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const getHelmReleaseHistory = di.inject(getHelmReleaseHistoryInjectable);
|
||||
|
||||
return async (cluster: Cluster, data: GetHelmReleaseHistoryData) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug(`[CLUSTER]: Fetch release history for clusterId=${cluster.id}`, data);
|
||||
|
||||
return getHelmReleaseHistory(proxyKubeconfig, data);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getClusterHelmReleaseHistoryInjectable;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { AsyncResult } from "../../../../../common/utils/async-result";
|
||||
import execHelmInjectable from "../../../exec-helm/exec-helm.injectable";
|
||||
import yaml from "js-yaml";
|
||||
import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../../common/k8s-api/kube-json-api";
|
||||
|
||||
const callForHelmManifestInjectable = getInjectable({
|
||||
id: "call-for-helm-manifest",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (
|
||||
name: string,
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
): Promise<AsyncResult<(KubeJsonApiData | KubeJsonApiDataList)[]>> => {
|
||||
const result = await execHelm([
|
||||
"get",
|
||||
"manifest",
|
||||
name,
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
]);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return { callWasSuccessful: false, error: result.error.message };
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: yaml
|
||||
.loadAll(result.response)
|
||||
.filter((manifest) => !!manifest) as KubeJsonApiData[],
|
||||
};
|
||||
};
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
export default callForHelmManifestInjectable;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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 { JsonObject } from "type-fest";
|
||||
import { json } from "../../../../../common/utils";
|
||||
import yaml from "js-yaml";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input/exec-file-with-input.injectable";
|
||||
import { getErrorMessage } from "../../../../../common/utils/get-error-message";
|
||||
import { map } from "lodash/fp";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
|
||||
|
||||
export type CallForKubeResourcesByManifest = (
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
kubectlPath: string,
|
||||
resourceManifests: KubeJsonApiData[]
|
||||
) => Promise<JsonObject[]>;
|
||||
|
||||
const callForKubeResourcesByManifestInjectable = getInjectable({
|
||||
id: "call-for-kube-resources-by-manifest",
|
||||
|
||||
instantiate: (di): CallForKubeResourcesByManifest => {
|
||||
const execFileWithInput = di.inject(execFileWithInputInjectable);
|
||||
|
||||
return async (
|
||||
namespace,
|
||||
kubeconfigPath,
|
||||
kubectlPath,
|
||||
resourceManifests,
|
||||
) => {
|
||||
const input = pipeline(
|
||||
resourceManifests,
|
||||
map((manifest) => yaml.dump(manifest)),
|
||||
wideJoin("---\n"),
|
||||
);
|
||||
|
||||
const result = await execFileWithInput({
|
||||
filePath: kubectlPath,
|
||||
input,
|
||||
|
||||
commandArguments: [
|
||||
"get",
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
"-f",
|
||||
"-",
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--output",
|
||||
"json",
|
||||
],
|
||||
});
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
const errorMessage = getErrorMessage(result.error);
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const output = json.parse(result.response) as { items: JsonObject[] };
|
||||
|
||||
return output.items;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default callForKubeResourcesByManifestInjectable;
|
||||
|
||||
const wideJoin = (joiner: string) => (items: string[]) =>
|
||||
`${joiner}${items.join(joiner)}${joiner}`;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../../../../common/test-utils/get-global-override";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input.injectable";
|
||||
|
||||
export default getGlobalOverride(execFileWithInputInjectable, () => () => {
|
||||
throw new Error(
|
||||
"Tried to call exec file with input without explicit override",
|
||||
);
|
||||
});
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 { AsyncResult } from "../../../../../../common/utils/async-result";
|
||||
import nonPromiseExecFileInjectable from "./non-promise-exec-file.injectable";
|
||||
import { isNumber } from "../../../../../../common/utils";
|
||||
import assert from "assert";
|
||||
import type { ChildProcess } from "child_process";
|
||||
|
||||
export type ExecFileWithInput = (options: {
|
||||
filePath: string;
|
||||
commandArguments: string[];
|
||||
input: string;
|
||||
}) => Promise<AsyncResult<string, unknown>>;
|
||||
|
||||
const execFileWithInputInjectable = getInjectable({
|
||||
id: "exec-file-with-input",
|
||||
|
||||
instantiate: (di): ExecFileWithInput => {
|
||||
const execFile = di.inject(nonPromiseExecFileInjectable);
|
||||
|
||||
return async ({ filePath, commandArguments, input }) =>
|
||||
new Promise((resolve) => {
|
||||
let execution: ChildProcess;
|
||||
|
||||
try {
|
||||
execution = execFile(filePath, commandArguments, {
|
||||
maxBuffer: 8 * 1024 * 1024 * 1024, // 8 MiB
|
||||
});
|
||||
} catch (e) {
|
||||
resolve({ callWasSuccessful: false, error: e });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert(execution.stdout, "stdout is not defined");
|
||||
assert(execution.stderr, "stderr is not defined");
|
||||
assert(execution.stdin, "stdin is not defined");
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
execution.stdout.on("data", (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
execution.stderr.on("data", (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
execution.on("error", (error) =>
|
||||
resolve({ callWasSuccessful: false, error }),
|
||||
);
|
||||
|
||||
execution.on("exit", (code, signal) => {
|
||||
if (!isNumber(code)) {
|
||||
/**
|
||||
* According to https://nodejs.org/api/child_process.html#class-childprocess (section about the "exit" event)
|
||||
* it says the following:
|
||||
*
|
||||
* If the process exited, code is the final exit code of the process, otherwise null.
|
||||
* If the process terminated due to receipt of a signal, signal is the string name of the signal, otherwise null.
|
||||
* One of the two will always be non-null.
|
||||
*/
|
||||
resolve({
|
||||
callWasSuccessful: false,
|
||||
error: `Exited via ${signal}`,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (code !== 0) {
|
||||
resolve({
|
||||
callWasSuccessful: false,
|
||||
error: stderr ? stderr : `Failed with error: ${signal}`,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
resolve({ callWasSuccessful: true, response: stdout });
|
||||
});
|
||||
|
||||
execution.stdin.end(input);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default execFileWithInputInjectable;
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../../../../getDiForUnitTesting";
|
||||
import type { ExecFileWithInput } from "./exec-file-with-input.injectable";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input.injectable";
|
||||
import type { AsyncResult } from "../../../../../../common/utils/async-result";
|
||||
import nonPromiseExecFileInjectable from "./non-promise-exec-file.injectable";
|
||||
import { getPromiseStatus } from "../../../../../../common/test-utils/get-promise-status";
|
||||
import EventEmitter from "events";
|
||||
|
||||
describe("exec-file-with-input", () => {
|
||||
let execFileWithInput: ExecFileWithInput;
|
||||
let execFileMock: jest.Mock;
|
||||
|
||||
let executionStub: EventEmitter & {
|
||||
stdin: { end: (chunk: any) => void };
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.unoverride(execFileWithInputInjectable);
|
||||
|
||||
executionStub = Object.assign(new EventEmitter(), {
|
||||
stdin: { end: jest.fn() },
|
||||
stdout: new EventEmitter(),
|
||||
stderr: new EventEmitter(),
|
||||
});
|
||||
|
||||
execFileMock = jest.fn(() => executionStub);
|
||||
|
||||
di.override(nonPromiseExecFileInjectable, () => execFileMock as any);
|
||||
|
||||
execFileWithInput = di.inject(execFileWithInputInjectable);
|
||||
});
|
||||
|
||||
it("given call, when throws synchronously, resolves with failure", async () => {
|
||||
execFileMock.mockImplementation(() => {
|
||||
throw new Error("some-error");
|
||||
});
|
||||
|
||||
const actual = await execFileWithInput({
|
||||
filePath: "./irrelevant",
|
||||
commandArguments: ["irrelevant"],
|
||||
input: "irrelevant",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<AsyncResult<string, unknown>>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = execFileWithInput({
|
||||
filePath: "./some-file-path",
|
||||
commandArguments: ["some-arg", "some-other-arg"],
|
||||
input: "some-input",
|
||||
});
|
||||
});
|
||||
|
||||
it("calls for file with arguments", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"./some-file-path",
|
||||
[
|
||||
"some-arg",
|
||||
"some-other-arg",
|
||||
],
|
||||
{ "maxBuffer": 8589934592 },
|
||||
);
|
||||
});
|
||||
|
||||
it("calls with input", () => {
|
||||
expect(executionStub.stdin.end).toHaveBeenCalledWith("some-input");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stdout receives data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stdout.emit("data", "some-data");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stdout receives more data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stdout.emit("data", "some-other-data");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when execution exits with success, resolves with result", async () => {
|
||||
executionStub.emit("exit", 0);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: "some-datasome-other-data",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits without exit code, resolves with failure", async () => {
|
||||
executionStub.emit("exit", null, "SIGKILL");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Exited via SIGKILL",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits with failure, resolves with failure", async () => {
|
||||
executionStub.emit("exit", 42, "some-signal");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Failed with error: some-signal",
|
||||
});
|
||||
});
|
||||
|
||||
describe("when stderr receives data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stderr.emit("data", "some-error");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stderr receives more data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stderr.emit("data", "some-other-error");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when execution exits with success, resolves with result", async () => {
|
||||
executionStub.emit("exit", 0);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: "some-datasome-other-data",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits without exit code, resolves with failure", async () => {
|
||||
executionStub.emit("exit", null, "some-signal");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Exited via some-signal",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits with failure, resolves with errors", async () => {
|
||||
executionStub.emit("exit", 42, "irrelevant");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "some-errorsome-other-error",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution receives error, resolves with error", async () => {
|
||||
executionStub.emit("error", new Error("some-error"));
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { execFile } from "child_process";
|
||||
|
||||
const nonPromiseExecFileInjectable = getInjectable({
|
||||
id: "non-promise-exec-file",
|
||||
instantiate: () => execFile,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default nonPromiseExecFileInjectable;
|
||||
+41
@@ -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 callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable";
|
||||
import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../common/k8s-api/kube-json-api";
|
||||
import type { AsyncResult } from "../../../../common/utils/async-result";
|
||||
|
||||
export type GetHelmReleaseResources = (
|
||||
name: string,
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
) => Promise<AsyncResult<KubeJsonApiData[], string>>;
|
||||
|
||||
const getHelmReleaseResourcesInjectable = getInjectable({
|
||||
id: "get-helm-release-resources",
|
||||
|
||||
instantiate: (di): GetHelmReleaseResources => {
|
||||
const callForHelmManifest = di.inject(callForHelmManifestInjectable);
|
||||
|
||||
return async (name, namespace, kubeconfigPath) => {
|
||||
const result = await callForHelmManifest(name, namespace, kubeconfigPath);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: result.response.flatMap(item => (
|
||||
Array.isArray(item.items)
|
||||
? (item as KubeJsonApiDataList).items
|
||||
: item as KubeJsonApiData
|
||||
)),
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmReleaseResourcesInjectable;
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { GetHelmReleaseResources } from "./get-helm-release-resources.injectable";
|
||||
import getHelmReleaseResourcesInjectable from "./get-helm-release-resources.injectable";
|
||||
import type { ExecHelm } from "../../exec-helm/exec-helm.injectable";
|
||||
import execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { ExecFileWithInput } from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
|
||||
import execFileWithInputInjectable from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
|
||||
import type { AsyncResult } from "../../../../common/utils/async-result";
|
||||
import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api";
|
||||
|
||||
describe("get helm release resources", () => {
|
||||
let getHelmReleaseResources: GetHelmReleaseResources;
|
||||
let execHelmMock: AsyncFnMock<ExecHelm>;
|
||||
let execFileWithStreamInputMock: AsyncFnMock<ExecFileWithInput>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
execHelmMock = asyncFn();
|
||||
execFileWithStreamInputMock = asyncFn();
|
||||
|
||||
di.override(execHelmInjectable, () => execHelmMock);
|
||||
|
||||
di.override(
|
||||
execFileWithInputInjectable,
|
||||
() => execFileWithStreamInputMock,
|
||||
);
|
||||
|
||||
getHelmReleaseResources = di.inject(getHelmReleaseResourcesInjectable);
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<AsyncResult<KubeJsonApiData[], string>>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = getHelmReleaseResources(
|
||||
"some-release",
|
||||
"some-namespace",
|
||||
"/some-kubeconfig-path",
|
||||
);
|
||||
});
|
||||
|
||||
it("calls for release manifest", () => {
|
||||
expect(execHelmMock).toHaveBeenCalledWith([
|
||||
"get", "manifest", "some-release", "--namespace", "some-namespace", "--kubeconfig", "/some-kubeconfig-path",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not call for resources yet", () => {
|
||||
expect(execFileWithStreamInputMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when call for manifest resolves without resources, resolves without resources", async () => {
|
||||
await execHelmMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: "",
|
||||
});
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("when call to manifest resolves with resources, resolves with resources", async () => {
|
||||
await execHelmMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: `---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-same-namespace
|
||||
namespace: some-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: SomeOtherKind
|
||||
metadata:
|
||||
name: some-resource-without-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: collection-sumologic-fluentd-logs
|
||||
namespace: some-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-different-namespace
|
||||
namespace: some-other-namespace
|
||||
---
|
||||
`,
|
||||
});
|
||||
|
||||
expect(await actualPromise).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "SomeKind",
|
||||
metadata: {
|
||||
name: "some-resource-with-same-namespace",
|
||||
namespace: "some-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "SomeOtherKind",
|
||||
metadata: {
|
||||
name: "some-resource-without-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: "monitoring.coreos.com/v1",
|
||||
kind: "ServiceMonitor",
|
||||
metadata: {
|
||||
name: "collection-sumologic-fluentd-logs",
|
||||
namespace: "some-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "SomeKind",
|
||||
metadata: {
|
||||
name: "some-resource-with-different-namespace",
|
||||
namespace: "some-other-namespace",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { GetHelmReleaseValuesData } from "../get-helm-release-values.injectable";
|
||||
import getHelmReleaseValuesInjectable from "../get-helm-release-values.injectable";
|
||||
|
||||
const getClusterHelmReleaseValuesInjectable = getInjectable({
|
||||
id: "get-cluster-helm-release-values",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const getHelmReleaseValues = di.inject(getHelmReleaseValuesInjectable);
|
||||
|
||||
return async (cluster: Cluster, data: GetHelmReleaseValuesData) => {
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug(`[CLUSTER]: getting helm release values`, data);
|
||||
|
||||
return getHelmReleaseValues(pathToKubeconfig, data);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getClusterHelmReleaseValuesInjectable;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import getHelmReleaseInjectable from "./get-helm-release.injectable";
|
||||
|
||||
export default getGlobalOverride(getHelmReleaseInjectable, () => () => {
|
||||
throw new Error("Tried to get helm release without explicit override");
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import { isObject, json } from "../../../common/utils";
|
||||
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
|
||||
import getHelmReleaseResourcesInjectable from "./get-helm-release-resources/get-helm-release-resources.injectable";
|
||||
|
||||
const getHelmReleaseInjectable = getInjectable({
|
||||
id: "get-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const getHelmReleaseResources = di.inject(getHelmReleaseResourcesInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string) => {
|
||||
const kubeconfigPath = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Fetch release");
|
||||
|
||||
const result = await execHelm([
|
||||
"status",
|
||||
releaseName,
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
"--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;
|
||||
}
|
||||
|
||||
const resourcesResult = await getHelmReleaseResources(
|
||||
releaseName,
|
||||
namespace,
|
||||
kubeconfigPath,
|
||||
);
|
||||
|
||||
if (!resourcesResult.callWasSuccessful) {
|
||||
logger.warn(`Failed to get helm release resources: ${resourcesResult.error}`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...release,
|
||||
resources: resourcesResult.response,
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmReleaseInjectable;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 { JsonObject } from "type-fest";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import installHelmChartInjectable from "../install-helm-chart.injectable";
|
||||
|
||||
export interface InstallChartArgs {
|
||||
chart: string;
|
||||
values: JsonObject;
|
||||
name: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const installClusterHelmChartInjectable = getInjectable({
|
||||
id: "install-cluster-helm-chart",
|
||||
|
||||
instantiate: (di) => {
|
||||
const installHelmChart = di.inject(installHelmChartInjectable);
|
||||
|
||||
return async (cluster: Cluster, data: InstallChartArgs) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
return installHelmChart({
|
||||
...data,
|
||||
kubeconfigPath: proxyKubeconfig,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default installClusterHelmChartInjectable;
|
||||
@@ -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 assert from "assert";
|
||||
import { object } from "../../../common/utils";
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const listHelmChartsInjectable = getInjectable({
|
||||
id: "list-helm-charts",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async () => {
|
||||
const result = await getActiveHelmRepositories();
|
||||
|
||||
assert(result.callWasSuccessful);
|
||||
|
||||
const repositories = result.response;
|
||||
|
||||
return object.fromEntries(
|
||||
await Promise.all(
|
||||
repositories.map(
|
||||
async (repo) =>
|
||||
[
|
||||
repo.name,
|
||||
await getChartManager(repo).charts(),
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default listHelmChartsInjectable;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import listHelmReleasesInjectable from "../list-helm-releases.injectable";
|
||||
|
||||
const listClusterHelmReleasesInjectable = getInjectable({
|
||||
id: "list-cluster-helm-releases",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const listHelmReleases = di.inject(listHelmReleasesInjectable);
|
||||
|
||||
return async (cluster: Cluster, namespace?: string) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug(`[CLUSTER]: listing helm releases for clusterId=${cluster.id}`, { namespace });
|
||||
|
||||
return listHelmReleases(proxyKubeconfig, namespace);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default listClusterHelmReleasesInjectable;
|
||||
@@ -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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { RollbackHelmReleaseData } from "../rollback-helm-release.injectable";
|
||||
import rollbackHelmReleaseInjectable from "../rollback-helm-release.injectable";
|
||||
|
||||
const rollbackClusterHelmReleaseInjectable = getInjectable({
|
||||
id: "rollback-cluster-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const rollbackHelmRelease = di.inject(rollbackHelmReleaseInjectable);
|
||||
|
||||
return async (cluster: Cluster, data: RollbackHelmReleaseData) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug(`[CLUSTER]: rolling back helm release for clusterId=${cluster.id}`, data);
|
||||
|
||||
await rollbackHelmRelease(proxyKubeconfig, data);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default rollbackClusterHelmReleaseInjectable;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import updateHelmReleaseInjectable from "./update-helm-release.injectable";
|
||||
|
||||
export default getGlobalOverride(updateHelmReleaseInjectable, () => () => {
|
||||
throw new Error("Tried to update helm release without explicit override");
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 "../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import tempy from "tempy";
|
||||
import getHelmReleaseInjectable from "./get-helm-release.injectable";
|
||||
import writeFileInjectable from "../../../common/fs/write-file.injectable";
|
||||
import removePathInjectable from "../../../common/fs/remove.injectable";
|
||||
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface UpdateChartArgs {
|
||||
chart: string;
|
||||
values: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const updateHelmReleaseInjectable = getInjectable({
|
||||
id: "update-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const getHelmRelease = di.inject(getHelmReleaseInjectable);
|
||||
const writeFile = di.inject(writeFileInjectable);
|
||||
const removePath = di.inject(removePathInjectable);
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||
|
||||
logger.debug(`[HELM]: upgrading "${releaseName}" in "${namespace}" to ${data.version}`);
|
||||
|
||||
try {
|
||||
await writeFile(valuesFilePath, data.values);
|
||||
|
||||
const result = await execHelm([
|
||||
"upgrade",
|
||||
releaseName,
|
||||
data.chart,
|
||||
"--version", data.version,
|
||||
"--values", valuesFilePath,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", proxyKubeconfig,
|
||||
]);
|
||||
|
||||
if (result.callWasSuccessful === false) {
|
||||
throw result.error; // keep the same interface
|
||||
}
|
||||
|
||||
return {
|
||||
log: result.response,
|
||||
release: await getHelmRelease(cluster, releaseName, namespace),
|
||||
};
|
||||
} finally {
|
||||
await removePath(valuesFilePath);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default updateHelmReleaseInjectable;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 { dump } from "js-yaml";
|
||||
import tempy from "tempy";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import removePathInjectable from "../../common/fs/remove.injectable";
|
||||
import writeFileInjectable from "../../common/fs/write-file.injectable";
|
||||
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface InstallHelmChartData {
|
||||
chart: string;
|
||||
values: JsonValue;
|
||||
name: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
kubeconfigPath: string;
|
||||
}
|
||||
|
||||
export interface InstallHelmChartResult {
|
||||
log: string;
|
||||
release: {
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type InstallHelmChart = (data: InstallHelmChartData) => Promise<InstallHelmChartResult>;
|
||||
|
||||
const installHelmChartInjectable = getInjectable({
|
||||
id: "install-helm-chart",
|
||||
instantiate: (di): InstallHelmChart => {
|
||||
const writeFile = di.inject(writeFileInjectable);
|
||||
const removePath = di.inject(removePathInjectable);
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async ({
|
||||
chart,
|
||||
kubeconfigPath,
|
||||
name,
|
||||
namespace,
|
||||
values,
|
||||
version,
|
||||
}) => {
|
||||
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await writeFile(valuesFilePath, dump(values));
|
||||
|
||||
const args = ["install"];
|
||||
|
||||
if (name) {
|
||||
args.push(name);
|
||||
}
|
||||
|
||||
args.push(
|
||||
chart,
|
||||
"--version", version,
|
||||
"--values", valuesFilePath,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
);
|
||||
|
||||
if (!name) {
|
||||
args.push("--generate-name");
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await execHelm(args);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const output = result.response;
|
||||
const releaseName = output.split("\n")[0].split(" ")[1].trim();
|
||||
|
||||
return {
|
||||
log: output,
|
||||
release: {
|
||||
name: releaseName,
|
||||
namespace,
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
await removePath(valuesFilePath);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default installHelmChartInjectable;
|
||||
@@ -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 } from "@ogre-tools/injectable";
|
||||
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
import { isObject, json, toCamelCase } from "../../common/utils";
|
||||
|
||||
export type ListHelmReleases = (pathToKubeconfig: string, namespace?: string) => Promise<Record<string, any>[]>;
|
||||
|
||||
const listHelmReleasesInjectable = getInjectable({
|
||||
id: "list-helm-releases",
|
||||
instantiate: (di): ListHelmReleases => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (pathToKubeconfig, namespace) => {
|
||||
const args = [
|
||||
"ls",
|
||||
"--all",
|
||||
"--output", "json",
|
||||
];
|
||||
|
||||
if (namespace) {
|
||||
args.push("-n", namespace);
|
||||
} else {
|
||||
args.push("--all-namespaces");
|
||||
}
|
||||
|
||||
args.push("--kubeconfig", pathToKubeconfig);
|
||||
|
||||
const result = await execHelm(args);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const output = json.parse(result.response);
|
||||
|
||||
if (!Array.isArray(output) || output.length == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.filter(isObject).map(toCamelCase);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default listHelmReleasesInjectable;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { addHelmRepositoryChannel } from "../../../../common/helm/add-helm-repository-channel";
|
||||
import { getRequestChannelListenerInjectable } from "../../../utils/channel/channel-listeners/listener-tokens";
|
||||
import addHelmRepositoryInjectable from "./add-helm-repository.injectable";
|
||||
|
||||
const addHelmRepositoryChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||
channel: addHelmRepositoryChannel,
|
||||
handler: (di) => di.inject(addHelmRepositoryInjectable),
|
||||
});
|
||||
|
||||
export default addHelmRepositoryChannelListenerInjectable;
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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 execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import type { AddHelmRepositoryChannel } from "../../../../common/helm/add-helm-repository-channel";
|
||||
import type { RequestChannelHandler } from "../../../utils/channel/channel-listeners/listener-tokens";
|
||||
|
||||
const addHelmRepositoryInjectable = getInjectable({
|
||||
id: "add-helm-repository",
|
||||
|
||||
instantiate: (di): RequestChannelHandler<AddHelmRepositoryChannel> => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (repo) => {
|
||||
const {
|
||||
name,
|
||||
url,
|
||||
insecureSkipTlsVerify,
|
||||
username,
|
||||
password,
|
||||
caFile,
|
||||
keyFile,
|
||||
certFile,
|
||||
} = repo;
|
||||
|
||||
logger.info(`[HELM]: adding repo ${name} from ${url}`);
|
||||
|
||||
const args = ["repo", "add", name, url];
|
||||
|
||||
if (insecureSkipTlsVerify) {
|
||||
args.push("--insecure-skip-tls-verify");
|
||||
}
|
||||
|
||||
if (username) {
|
||||
args.push("--username", username);
|
||||
}
|
||||
|
||||
if (password) {
|
||||
args.push("--password", password);
|
||||
}
|
||||
|
||||
if (caFile) {
|
||||
args.push("--ca-file", caFile);
|
||||
}
|
||||
|
||||
if (keyFile) {
|
||||
args.push("--key-file", keyFile);
|
||||
}
|
||||
|
||||
if (certFile) {
|
||||
args.push("--cert-file", certFile);
|
||||
}
|
||||
|
||||
const result = await execHelm(args);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: result.error.stderr || result.error.message,
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default addHelmRepositoryInjectable;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getActiveHelmRepositoriesChannel } from "../../../../common/helm/get-active-helm-repositories-channel";
|
||||
import { getRequestChannelListenerInjectable } from "../../../utils/channel/channel-listeners/listener-tokens";
|
||||
import getActiveHelmRepositoriesInjectable from "./get-active-helm-repositories.injectable";
|
||||
|
||||
const getActiveHelmRepositoriesChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||
channel: getActiveHelmRepositoriesChannel,
|
||||
handler: (di) => di.inject(getActiveHelmRepositoriesInjectable),
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoriesChannelListenerInjectable;
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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 { HelmRepo } from "../../../../common/helm/helm-repo";
|
||||
import type { ReadYamlFile } from "../../../../common/fs/read-yaml-file.injectable";
|
||||
import readYamlFileInjectable from "../../../../common/fs/read-yaml-file.injectable";
|
||||
import getHelmEnvInjectable from "../../get-helm-env/get-helm-env.injectable";
|
||||
import execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import type { AsyncResult } from "../../../../common/utils/async-result";
|
||||
|
||||
interface HelmRepositoryFromYaml {
|
||||
name: string;
|
||||
url: string;
|
||||
caFile: string;
|
||||
certFile: string;
|
||||
insecure_skip_tls_verify: boolean;
|
||||
keyFile: string;
|
||||
pass_credentials_all: boolean;
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface HelmRepositoriesFromYaml {
|
||||
repositories: HelmRepositoryFromYaml[];
|
||||
}
|
||||
|
||||
const getActiveHelmRepositoriesInjectable = getInjectable({
|
||||
id: "get-helm-repositories",
|
||||
|
||||
instantiate: (di) => {
|
||||
const readYamlFile = di.inject(readYamlFileInjectable);
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const getHelmEnv = di.inject(getHelmEnvInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
const getRepositories = getRepositoriesFor(readYamlFile);
|
||||
|
||||
return async (): Promise<AsyncResult<HelmRepo[]>> => {
|
||||
const envResult = await getHelmEnv();
|
||||
|
||||
if (!envResult.callWasSuccessful) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error getting Helm configuration: ${envResult.error}`,
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
HELM_REPOSITORY_CONFIG: repositoryConfigFilePath,
|
||||
HELM_REPOSITORY_CACHE: helmRepositoryCacheDirPath,
|
||||
} = envResult.response;
|
||||
|
||||
if (!repositoryConfigFilePath) {
|
||||
const errorMessage = "Tried to get Helm repositories, but HELM_REPOSITORY_CONFIG was not present in `$ helm env`.";
|
||||
|
||||
logger.error(errorMessage);
|
||||
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error getting Helm configuration: ${errorMessage}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!helmRepositoryCacheDirPath) {
|
||||
const errorMessage = "Tried to get Helm repositories, but HELM_REPOSITORY_CACHE was not present in `$ helm env`.";
|
||||
|
||||
logger.error(errorMessage);
|
||||
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error getting Helm configuration: ${errorMessage}`,
|
||||
};
|
||||
}
|
||||
|
||||
const updateResult = await execHelm(["repo", "update"]);
|
||||
|
||||
if (!updateResult.callWasSuccessful) {
|
||||
if (!updateResult.error.stderr.includes(internalHelmErrorForNoRepositoriesFound)) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error updating Helm repositories: ${updateResult.error.stderr}`,
|
||||
};
|
||||
}
|
||||
const resultOfAddingDefaultRepository = await execHelm(["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"]);
|
||||
|
||||
if (!resultOfAddingDefaultRepository.callWasSuccessful) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error when adding default Helm repository: ${resultOfAddingDefaultRepository.error.stderr}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
|
||||
response: await getRepositories(
|
||||
repositoryConfigFilePath,
|
||||
helmRepositoryCacheDirPath,
|
||||
),
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoriesInjectable;
|
||||
|
||||
const getRepositoriesFor =
|
||||
(readYamlFile: ReadYamlFile) =>
|
||||
async (repositoryConfigFilePath: string, helmRepositoryCacheDirPath: string): Promise<HelmRepo[]> => {
|
||||
const { repositories } = (await readYamlFile(
|
||||
repositoryConfigFilePath,
|
||||
)) as HelmRepositoriesFromYaml;
|
||||
|
||||
return repositories.map((repository) => ({
|
||||
name: repository.name,
|
||||
url: repository.url,
|
||||
caFile: repository.caFile,
|
||||
certFile: repository.certFile,
|
||||
insecureSkipTlsVerify: repository.insecure_skip_tls_verify,
|
||||
keyFile: repository.keyFile,
|
||||
username: repository.username,
|
||||
password: repository.password,
|
||||
cacheFilePath: `${helmRepositoryCacheDirPath}/${repository.name}-index.yaml`,
|
||||
}));
|
||||
};
|
||||
|
||||
const internalHelmErrorForNoRepositoriesFound = "no repositories found. You must add one before updating";
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 assert from "assert";
|
||||
import getActiveHelmRepositoriesInjectable from "./get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
|
||||
const getActiveHelmRepositoryInjectable = getInjectable({
|
||||
id: "get-active-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
|
||||
|
||||
return async (name: string) => {
|
||||
const activeHelmRepositories = await getActiveHelmRepositories();
|
||||
|
||||
assert(activeHelmRepositories.callWasSuccessful);
|
||||
|
||||
return activeHelmRepositories.response.find(
|
||||
(repository) => repository.name === name,
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoryInjectable;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import removeHelmRepositoryInjectable from "./remove-helm-repository.injectable";
|
||||
import { removeHelmRepositoryChannel } from "../../../../common/helm/remove-helm-repository-channel";
|
||||
import { getRequestChannelListenerInjectable } from "../../../utils/channel/channel-listeners/listener-tokens";
|
||||
|
||||
const removeHelmRepositoryChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||
channel: removeHelmRepositoryChannel,
|
||||
handler: (di) => di.inject(removeHelmRepositoryInjectable),
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryChannelListenerInjectable;
|
||||
+41
@@ -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 execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import type { HelmRepo } from "../../../../common/helm/helm-repo";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import type { AsyncResult } from "../../../../common/utils/async-result";
|
||||
|
||||
const removeHelmRepositoryInjectable = getInjectable({
|
||||
id: "remove-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (repo: HelmRepo): Promise<AsyncResult<void, string>> => {
|
||||
logger.info(`[HELM]: removing repo ${repo.name} (${repo.url})`);
|
||||
|
||||
const result = await execHelm([
|
||||
"repo",
|
||||
"remove",
|
||||
repo.name,
|
||||
]);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: result.error.stderr,
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryInjectable;
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
|
||||
|
||||
export interface RollbackHelmReleaseData {
|
||||
name: string;
|
||||
namespace: string;
|
||||
revision: number;
|
||||
}
|
||||
|
||||
export type RollbackHelmRelease = (kubeconfigPath: string, data: RollbackHelmReleaseData) => Promise<void>;
|
||||
|
||||
const rollbackHelmReleaseInjectable = getInjectable({
|
||||
id: "rollback-helm-release",
|
||||
instantiate: (di): RollbackHelmRelease => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (kubeconfigPath, { name, namespace, revision }) => {
|
||||
const result = await execHelm([
|
||||
"rollback",
|
||||
name,
|
||||
`${revision}`,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
]);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
throw result.error;
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default rollbackHelmReleaseInjectable;
|
||||
Reference in new issue
Block a user