mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Stop using HelmCli from Renderer (#4861)
* Introduce way for execute file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make typing of HelmRepo shared Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce way to get Helm environment values Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce function to read YAML file Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for listing active helm repositories in preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make sense in name of injectable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce helper for opening and selecting values of select Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for activating, deactivating public helm repositories in preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for deactivating helm repository from list of active repositories Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global overrides Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make some tests more deterministic by mocking tooltips Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for activating custom helm repository in preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove old implementation made redundant with competition for preferences of helm repositories Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add success notification for activating custom helm repository Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce way to get single active helm repository Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract responsibilities from god-class and switch to getting helm repositories using competition instead of another god class Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak position of spinner Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Start handling errors when accessing helm repositories Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Handle error about no helm repositories when updating repositories Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unwarranted function configuration Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global overrides Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication how to acquire binary path for helm Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant comment Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate naming to match Helm's internal Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate file closer to it's relatives Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
5420780ae0
commit
1393cc303d
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,387 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import execFileInjectable from "../../common/fs/exec-file.injectable";
|
||||
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
import isPathInjectable from "../../renderer/components/input/validators/is-path.injectable";
|
||||
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
||||
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
|
||||
// TODO: Make tooltips free of side effects by making it deterministic
|
||||
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
|
||||
withTooltip: (target: any) => target,
|
||||
}));
|
||||
|
||||
describe("add custom helm repository in preferences", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let showSuccessNotificationMock: jest.Mock;
|
||||
let showErrorNotificationMock: jest.Mock;
|
||||
let rendered: RenderResult;
|
||||
let execFileMock: AsyncFnMock<
|
||||
ReturnType<typeof execFileInjectable["instantiate"]>
|
||||
>;
|
||||
let getActiveHelmRepositoriesMock: AsyncFnMock<() => AsyncResult<HelmRepo[]>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers("modern");
|
||||
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
execFileMock = asyncFn();
|
||||
getActiveHelmRepositoriesMock = asyncFn();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
rendererDi.override(callForPublicHelmRepositoriesInjectable, () => async () => []);
|
||||
|
||||
showSuccessNotificationMock = jest.fn();
|
||||
|
||||
rendererDi.override(showSuccessNotificationInjectable, () => showSuccessNotificationMock);
|
||||
|
||||
showErrorNotificationMock = jest.fn();
|
||||
|
||||
rendererDi.override(showErrorNotificationInjectable, () => showErrorNotificationMock);
|
||||
|
||||
// TODO: Figure out how to make async validators unit testable
|
||||
rendererDi.override(isPathInjectable, () => ({ debounce: 0, validate: async () => {} }));
|
||||
|
||||
mainDi.override(
|
||||
getActiveHelmRepositoriesInjectable,
|
||||
() => getActiveHelmRepositoriesMock,
|
||||
);
|
||||
|
||||
mainDi.override(execFileInjectable, () => execFileMock);
|
||||
mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("when navigating to preferences containing helm repositories", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
applicationBuilder.preferences.navigation.click("kubernetes");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when active repositories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all([
|
||||
getActiveHelmRepositoriesMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{ name: "Some active repository", url: "some-url" },
|
||||
],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when selecting to add custom repository", () => {
|
||||
beforeEach(() => {
|
||||
const button = rendered.getByTestId("add-custom-helm-repo-button");
|
||||
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows dialog", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("add-custom-helm-repository-dialog"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// TODO: Figure out how to close dialog by clicking outside of it
|
||||
xdescribe("when closing the dialog by clicking outside", () => {
|
||||
beforeEach(() => {});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show dialog anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("add-custom-helm-repository-dialog"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when closing the dialog by clicking cancel", () => {
|
||||
beforeEach(() => {
|
||||
const button = rendered.getByTestId("custom-helm-repository-cancel-button");
|
||||
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show dialog anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("add-custom-helm-repository-dialog"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when inputted minimal options for the repository", () => {
|
||||
beforeEach(() => {
|
||||
getActiveHelmRepositoriesMock.mockClear();
|
||||
|
||||
const nameInput = rendered.getByTestId("custom-helm-repository-name-input");
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: "some-custom-repository" }});
|
||||
|
||||
const urlInput = rendered.getByTestId("custom-helm-repository-url-input");
|
||||
|
||||
fireEvent.change(urlInput, { target: { value: "http://some.url" }});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when submitted and some time passes", () => {
|
||||
beforeEach(() => {
|
||||
const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");
|
||||
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
// TODO: Remove when debounce is removed from WizardStep.submit
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("adds the repository", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "some-custom-repository", "http://some.url"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reload active repositories yet", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not show notification yet", () => {
|
||||
expect(showSuccessNotificationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when activation rejects", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.reject(
|
||||
"Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not show success notification", () => {
|
||||
expect(showSuccessNotificationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not show dialog anymore", () => {
|
||||
expect(rendered.queryByTestId("add-custom-helm-repository-dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not reload active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when activation resolves with success", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.resolveSpecific(
|
||||
[
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "some-custom-repository", "http://some.url"],
|
||||
],
|
||||
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show dialog anymore", () => {
|
||||
expect(rendered.queryByTestId("add-custom-helm-repository-dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reloads active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows success notification", () => {
|
||||
expect(showSuccessNotificationMock).toHaveBeenCalledWith(
|
||||
"Helm repository some-custom-repository has been added.",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when adding custom repository again", () => {
|
||||
beforeEach(() => {
|
||||
const button = rendered.getByTestId("add-custom-helm-repo-button");
|
||||
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("repository name is empty", () => {
|
||||
const input = rendered.getByTestId("custom-helm-repository-name-input") as HTMLInputElement;
|
||||
|
||||
expect(input.value).toBe("");
|
||||
});
|
||||
|
||||
it("repository url is empty", () => {
|
||||
const input = rendered.getByTestId("custom-helm-repository-url-input") as HTMLInputElement;
|
||||
|
||||
expect(input.value).toBe("");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when showing the maximal options", () => {
|
||||
beforeEach(() => {
|
||||
const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");
|
||||
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
it("shows maximal options", () => {
|
||||
const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");
|
||||
|
||||
expect(maximalOptions).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("given closing the dialog, when reopening the dialog, still shows maximal options", () => {
|
||||
const cancelButton = rendered.getByTestId("custom-helm-repository-cancel-button");
|
||||
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
const openButton = rendered.getByTestId("add-custom-helm-repo-button");
|
||||
|
||||
fireEvent.click(openButton);
|
||||
|
||||
const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");
|
||||
|
||||
expect(maximalOptions).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when hiding maximal options", () => {
|
||||
beforeEach(() => {
|
||||
const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");
|
||||
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show maximal options anymore", () => {
|
||||
const maximalOptions = rendered.queryByTestId("maximal-options-for-custom-helm-repository-dialog");
|
||||
|
||||
expect(maximalOptions).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when inputted maximal options", () => {
|
||||
beforeEach(async () => {
|
||||
[
|
||||
{ selector: "username-input", value: "some-username" },
|
||||
{ selector: "password-input", value: "some-password" },
|
||||
{ selector: "ca-cert-file-input", value: "some-ca-cert-file" },
|
||||
{ selector: "cert-file-input", value: "some-cert-file" },
|
||||
{ selector: "key-file-input", value: "some-key-file" },
|
||||
].forEach(({ selector, value }) => {
|
||||
const input = rendered.getByTestId(`custom-helm-repository-${selector}`);
|
||||
|
||||
fireEvent.change(input, { target: { value }});
|
||||
});
|
||||
|
||||
const checkbox = rendered.getByTestId(`custom-helm-repository-verify-tls-input`);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("when submitted and some time passes, adds the repository with maximal options", () => {
|
||||
const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");
|
||||
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
// TODO: Remove when debounce is removed from WizardStep.submit
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
[
|
||||
"repo",
|
||||
"add",
|
||||
"some-custom-repository",
|
||||
"http://some.url",
|
||||
"--insecure-skip-tls-verify",
|
||||
"--username",
|
||||
"some-username",
|
||||
"--password",
|
||||
"some-password",
|
||||
"--ca-file",
|
||||
"some-ca-cert-file",
|
||||
"--key-file",
|
||||
"some-key-file",
|
||||
"--cert-file",
|
||||
"some-cert-file",
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import execFileInjectable from "../../common/fs/exec-file.injectable";
|
||||
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
||||
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
|
||||
// TODO: Make tooltips free of side effects by making it deterministic
|
||||
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
|
||||
withTooltip: (target: any) => target,
|
||||
}));
|
||||
|
||||
describe("add helm repository from list in preferences", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let showSuccessNotificationMock: jest.Mock;
|
||||
let showErrorNotificationMock: jest.Mock;
|
||||
let rendered: RenderResult;
|
||||
let execFileMock: AsyncFnMock<
|
||||
ReturnType<typeof execFileInjectable["instantiate"]>
|
||||
>;
|
||||
let getActiveHelmRepositoriesMock: AsyncFnMock<() => AsyncResult<HelmRepo[]>>;
|
||||
let callForPublicHelmRepositoriesMock: AsyncFnMock<() => Promise<HelmRepo[]>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
execFileMock = asyncFn();
|
||||
getActiveHelmRepositoriesMock = asyncFn();
|
||||
callForPublicHelmRepositoriesMock = asyncFn();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
showSuccessNotificationMock = jest.fn();
|
||||
|
||||
rendererDi.override(showSuccessNotificationInjectable, () => showSuccessNotificationMock);
|
||||
|
||||
showErrorNotificationMock = jest.fn();
|
||||
|
||||
rendererDi.override(showErrorNotificationInjectable, () => showErrorNotificationMock);
|
||||
|
||||
rendererDi.override(
|
||||
callForPublicHelmRepositoriesInjectable,
|
||||
() => callForPublicHelmRepositoriesMock,
|
||||
);
|
||||
|
||||
mainDi.override(
|
||||
getActiveHelmRepositoriesInjectable,
|
||||
() => getActiveHelmRepositoriesMock,
|
||||
);
|
||||
mainDi.override(execFileInjectable, () => execFileMock);
|
||||
mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("when navigating to preferences containing helm repositories", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
applicationBuilder.preferences.navigation.click("kubernetes");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls for public repositories", () => {
|
||||
expect(callForPublicHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls for active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when both active and public repositories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all([
|
||||
callForPublicHelmRepositoriesMock.resolve([
|
||||
{ name: "Some already active repository", url: "some-url" },
|
||||
{ name: "Some to be added repository", url: "some-other-url" },
|
||||
]),
|
||||
|
||||
getActiveHelmRepositoriesMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{ name: "Some already active repository", url: "some-url" },
|
||||
],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when select for adding public repositories is clicked", () => {
|
||||
beforeEach(() => {
|
||||
applicationBuilder.select.openMenu(
|
||||
"selection-of-active-public-helm-repository",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when deactive public repository is selected", () => {
|
||||
beforeEach(async () => {
|
||||
getActiveHelmRepositoriesMock.mockClear();
|
||||
|
||||
applicationBuilder.select.selectOption(
|
||||
"selection-of-active-public-helm-repository",
|
||||
"Some to be added repository",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("adds the repository", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "Some to be added repository", "some-other-url"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reload active repositories yet", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when adding rejects", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.reject(
|
||||
"Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not show success notification", () => {
|
||||
expect(showSuccessNotificationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not show dialog anymore", () => {
|
||||
expect(rendered.queryByTestId("add-custom-helm-repository-dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not reload active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.resolveSpecific(
|
||||
[
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "Some to be added repository", "some-other-url"],
|
||||
],
|
||||
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("reloads active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows success notification", () => {
|
||||
expect(showSuccessNotificationMock).toHaveBeenCalledWith(
|
||||
"Helm repository Some to be added repository has been added.",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when active repositories resolve again", () => {
|
||||
beforeEach(async () => {
|
||||
await getActiveHelmRepositoriesMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{ name: "Some already active repository", url: "some-url" },
|
||||
{ name: "Some to be added repository", url: "some-other-url" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when select for selecting active repositories is clicked", () => {
|
||||
beforeEach(() => {
|
||||
applicationBuilder.select.openMenu(
|
||||
"selection-of-active-public-helm-repository",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when active repository is selected", () => {
|
||||
beforeEach(() => {
|
||||
execFileMock.mockClear();
|
||||
getActiveHelmRepositoriesMock.mockClear();
|
||||
|
||||
applicationBuilder.select.selectOption(
|
||||
"selection-of-active-public-helm-repository",
|
||||
"Some already active repository",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("removes the repository", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "Some already active repository"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reload active repositories yet", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when removing resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.resolveSpecific(
|
||||
[
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "Some already active repository"],
|
||||
],
|
||||
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("reloads active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,458 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { ReadYamlFile } from "../../common/fs/read-yaml-file.injectable";
|
||||
import readYamlFileInjectable from "../../common/fs/read-yaml-file.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { HelmRepositoriesFromYaml } from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import execFileInjectable from "../../common/fs/exec-file.injectable";
|
||||
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
|
||||
|
||||
// TODO: Make tooltips free of side effects by making it deterministic
|
||||
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
|
||||
withTooltip: (target: any) => target,
|
||||
}));
|
||||
|
||||
describe("listing active helm repositories in preferences", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let rendered: RenderResult;
|
||||
let readYamlFileMock: AsyncFnMock<ReadYamlFile>;
|
||||
let execFileMock: AsyncFnMock<ReturnType<typeof execFileInjectable["instantiate"]>>;
|
||||
let loggerStub: Logger;
|
||||
let showErrorNotificationMock: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
readYamlFileMock = asyncFn();
|
||||
execFileMock = asyncFn();
|
||||
|
||||
loggerStub = { error: jest.fn() } as unknown as Logger;
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
showErrorNotificationMock = jest.fn();
|
||||
|
||||
rendererDi.override(showErrorNotificationInjectable, () => showErrorNotificationMock);
|
||||
rendererDi.override(callForPublicHelmRepositoriesInjectable, () => async () => []);
|
||||
mainDi.override(readYamlFileInjectable, () => readYamlFileMock);
|
||||
mainDi.override(execFileInjectable, () => execFileMock);
|
||||
mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
|
||||
mainDi.override(loggerInjectable, () => loggerStub);
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("when navigating to preferences containing helm repositories", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
applicationBuilder.preferences.navigation.click("kubernetes");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows loader for repositories", () => {
|
||||
expect(
|
||||
rendered.getByTestId("helm-repositories-are-loading"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls for helm configuration", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["env"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not call for updating of repositories yet", () => {
|
||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "update"],
|
||||
);
|
||||
});
|
||||
|
||||
describe("when getting configuration rejects", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.reject("some-error");
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Error getting Helm configuration: some-error",
|
||||
);
|
||||
});
|
||||
|
||||
it("removes all helm controls", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-controls"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when configuration resolves without path to repository config file", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await execFileMock.resolveSpecific(
|
||||
["some-helm-binary-path", ["env"]],
|
||||
"HELM_REPOSITORY_CACHE=some-helm-repository-cache-path",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs error", () => {
|
||||
expect(loggerStub.error).toHaveBeenCalledWith(
|
||||
"Tried to get Helm repositories, but HELM_REPOSITORY_CONFIG was not present in `$ helm env`.",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CONFIG was not present in `$ helm env`.",
|
||||
);
|
||||
});
|
||||
|
||||
it("removes all helm controls", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-controls"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when configuration resolves without path to repository cache directory", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await execFileMock.resolveSpecific(
|
||||
["some-helm-binary-path", ["env"]],
|
||||
"HELM_REPOSITORY_CONFIG=some-helm-repository-config-file.yaml",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs error", () => {
|
||||
expect(loggerStub.error).toHaveBeenCalledWith(
|
||||
"Tried to get Helm repositories, but HELM_REPOSITORY_CACHE was not present in `$ helm env`.",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CACHE was not present in `$ helm env`.",
|
||||
);
|
||||
});
|
||||
|
||||
it("removes all helm controls", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-controls"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when configuration resolves", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await execFileMock.resolveSpecific(
|
||||
["some-helm-binary-path", ["env"]],
|
||||
|
||||
[
|
||||
"HELM_REPOSITORY_CONFIG=some-helm-repository-config-file.yaml",
|
||||
"HELM_REPOSITORY_CACHE=some-helm-repository-cache-path",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls for update of repositories", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "update"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not call for repositories yet", () => {
|
||||
expect(readYamlFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when updating repositories reject with any other error", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.reject("Some error");
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Error updating Helm repositories: Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("removes all helm controls", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-controls"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when updating repositories reject with error about no existing repositories", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await execFileMock.reject(
|
||||
"Error: no repositories found. You must add one before updating",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("still shows the loader for repositories", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('adds "bitnami" as default repository', () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||
);
|
||||
});
|
||||
|
||||
describe("when adding default repository reject", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.reject("Some error");
|
||||
});
|
||||
|
||||
it("shows error notification", () => {
|
||||
expect(showErrorNotificationMock).toHaveBeenCalledWith(
|
||||
"Error when adding default Helm repository: Some error",
|
||||
);
|
||||
});
|
||||
|
||||
it("removes all helm controls", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-controls"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding of default repository resolves", () => {
|
||||
beforeEach(async () => {
|
||||
readYamlFileMock.mockClear();
|
||||
|
||||
await execFileMock.resolveSpecific(
|
||||
[
|
||||
"some-helm-binary-path",
|
||||
|
||||
[
|
||||
"repo",
|
||||
"add",
|
||||
"bitnami",
|
||||
"https://charts.bitnami.com/bitnami",
|
||||
],
|
||||
],
|
||||
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("still shows the loader for repositories", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls for repositories again", () => {
|
||||
expect(readYamlFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-repository-config-file.yaml",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when another call for repositories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
await readYamlFileMock.resolveSpecific(
|
||||
["some-helm-repository-config-file.yaml"],
|
||||
|
||||
{
|
||||
repositories: [
|
||||
{
|
||||
name: "bitnami",
|
||||
url: "https://charts.bitnami.com/bitnami",
|
||||
caFile: "irrelevant",
|
||||
certFile: "irrelevant",
|
||||
insecure_skip_tls_verify: false,
|
||||
keyFile: "irrelevant",
|
||||
pass_credentials_all: false,
|
||||
password: "irrelevant",
|
||||
username: "irrelevant",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the added repository", () => {
|
||||
const actual = rendered.getByTestId("helm-repository-bitnami");
|
||||
|
||||
expect(actual).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when updating repositories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await execFileMock.resolveSpecific(
|
||||
["some-helm-binary-path", ["repo", "update"]],
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("loads repositories from file system", () => {
|
||||
expect(readYamlFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-repository-config-file.yaml",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when repositories resolves", () => {
|
||||
beforeEach(async () => {
|
||||
execFileMock.mockClear();
|
||||
|
||||
await readYamlFileMock.resolveSpecific(
|
||||
["some-helm-repository-config-file.yaml"],
|
||||
repositoryConfigStub,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not add default repository", () => {
|
||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show loader for repositories anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-repositories-are-loading"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows repositories in use", () => {
|
||||
const actual = rendered.getAllByTestId(
|
||||
/^helm-repository-(some-repository|some-other-repository)$/,
|
||||
);
|
||||
|
||||
expect(actual).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const repositoryConfigStub: HelmRepositoriesFromYaml = {
|
||||
repositories: [
|
||||
{
|
||||
name: "some-repository",
|
||||
url: "some-repository-url",
|
||||
caFile: "irrelevant",
|
||||
certFile: "irrelevant",
|
||||
insecure_skip_tls_verify: false,
|
||||
keyFile: "irrelevant",
|
||||
pass_credentials_all: false,
|
||||
password: "irrelevant",
|
||||
username: "irrelevant",
|
||||
},
|
||||
|
||||
{
|
||||
name: "some-other-repository",
|
||||
url: "some-other-repository-url",
|
||||
caFile: "irrelevant",
|
||||
certFile: "irrelevant",
|
||||
insecure_skip_tls_verify: false,
|
||||
keyFile: "irrelevant",
|
||||
pass_credentials_all: false,
|
||||
password: "irrelevant",
|
||||
username: "irrelevant",
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import execFileInjectable from "../../common/fs/exec-file.injectable";
|
||||
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
|
||||
// TODO: Make tooltips free of side effects by making it deterministic
|
||||
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
|
||||
withTooltip: (target: any) => target,
|
||||
}));
|
||||
|
||||
describe("remove helm repository from list of active repositories in preferences", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let rendered: RenderResult;
|
||||
let getActiveHelmRepositoriesMock: AsyncFnMock<() => AsyncResult<HelmRepo[]>>;
|
||||
let execFileMock: AsyncFnMock<
|
||||
ReturnType<typeof execFileInjectable["instantiate"]>
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
execFileMock = asyncFn();
|
||||
getActiveHelmRepositoriesMock = asyncFn();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
rendererDi.override(callForPublicHelmRepositoriesInjectable, () => async () => []);
|
||||
|
||||
mainDi.override(
|
||||
getActiveHelmRepositoriesInjectable,
|
||||
() => getActiveHelmRepositoriesMock,
|
||||
);
|
||||
|
||||
mainDi.override(execFileInjectable, () => execFileMock);
|
||||
mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("when navigating to preferences containing helm repositories", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
applicationBuilder.preferences.navigation.click("kubernetes");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when active repositories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
getActiveHelmRepositoriesMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [
|
||||
{ name: "some-active-repository", url: "some-url" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when removing repository", () => {
|
||||
beforeEach(() => {
|
||||
execFileMock.mockClear();
|
||||
getActiveHelmRepositoriesMock.mockClear();
|
||||
|
||||
const removeButton = rendered.getByTestId(
|
||||
"remove-helm-repository-some-active-repository",
|
||||
);
|
||||
|
||||
fireEvent.click(removeButton);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("removes the repository", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "some-active-repository"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reload active repositories yet", () => {
|
||||
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when removing resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await execFileMock.resolveSpecific(
|
||||
[
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "some-active-repository"],
|
||||
],
|
||||
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("reloads active repositories", () => {
|
||||
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -834,114 +834,121 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
|
||||
</h2>
|
||||
<div>
|
||||
<div
|
||||
class="flex gaps"
|
||||
data-testid="helm-controls"
|
||||
>
|
||||
<div
|
||||
class="Select theme-lens box grow Select--is-disabled css-3iigni-container"
|
||||
class="flex gaps"
|
||||
>
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
id="react-select-HelmRepoSelect-live-region"
|
||||
/>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class="Select__control Select__control--is-disabled css-1insrsq-control"
|
||||
class="Select theme-lens box grow Select--is-disabled css-3iigni-container"
|
||||
>
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
id="react-select-selection-of-active-public-helm-repository-live-region"
|
||||
/>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class="Select__value-container css-319lph-ValueContainer"
|
||||
class="Select__control Select__control--is-disabled css-1insrsq-control"
|
||||
>
|
||||
<div
|
||||
class="Select__placeholder css-14el2xx-placeholder"
|
||||
id="react-select-HelmRepoSelect-placeholder"
|
||||
class="Select__value-container css-319lph-ValueContainer"
|
||||
>
|
||||
Repositories
|
||||
</div>
|
||||
<div
|
||||
class="Select__input-container css-jzldcf-Input"
|
||||
data-value=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-describedby="react-select-HelmRepoSelect-placeholder"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class="Select__input"
|
||||
disabled=""
|
||||
id="HelmRepoSelect"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
|
||||
tabindex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="Select__indicator Select__loading-indicator css-at12u2-loadingIndicator"
|
||||
>
|
||||
<span
|
||||
class="css-1xtdfmb-LoadingDot"
|
||||
/>
|
||||
<span
|
||||
class="css-zoievk-LoadingDot"
|
||||
/>
|
||||
<span
|
||||
class="css-x748d8-LoadingDot"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="Select__indicator-separator css-109onse-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
<div
|
||||
class="Select__placeholder css-14el2xx-placeholder"
|
||||
id="react-select-selection-of-active-public-helm-repository-placeholder"
|
||||
>
|
||||
<path
|
||||
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||
Repositories
|
||||
</div>
|
||||
<div
|
||||
class="Select__input-container css-jzldcf-Input"
|
||||
data-value=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-describedby="react-select-selection-of-active-public-helm-repository-placeholder"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class="Select__input"
|
||||
disabled=""
|
||||
id="selection-of-active-public-helm-repository"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
|
||||
tabindex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="Select__indicator Select__loading-indicator css-at12u2-loadingIndicator"
|
||||
>
|
||||
<span
|
||||
class="css-1xtdfmb-LoadingDot"
|
||||
/>
|
||||
<span
|
||||
class="css-zoievk-LoadingDot"
|
||||
/>
|
||||
<span
|
||||
class="css-x748d8-LoadingDot"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="Select__indicator-separator css-109onse-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="Button primary"
|
||||
data-testid="add-custom-helm-repo-button"
|
||||
type="button"
|
||||
>
|
||||
Add Custom Helm Repo
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="Button primary"
|
||||
type="button"
|
||||
>
|
||||
Add Custom Helm Repo
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="repos"
|
||||
>
|
||||
<div
|
||||
class="pt-5 relative"
|
||||
class="repos"
|
||||
>
|
||||
<div
|
||||
class="Spinner singleColor center"
|
||||
/>
|
||||
class="pt-5 relative"
|
||||
>
|
||||
<div
|
||||
class="Spinner singleColor center"
|
||||
data-testid="helm-repositories-are-loading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
|
||||
describe("preferences - navigation to kubernetes preferences", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
@ -17,6 +19,15 @@ describe("preferences - navigation to kubernetes preferences", () => {
|
||||
let rendered: RenderResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.beforeApplicationStart(({ rendererDi, mainDi }) => {
|
||||
rendererDi.override(callForPublicHelmRepositoriesInjectable, () => async () => []);
|
||||
|
||||
mainDi.override(
|
||||
getActiveHelmRepositoriesInjectable,
|
||||
() => async () => ({ callWasSuccessful: true, response: [] }),
|
||||
);
|
||||
});
|
||||
|
||||
applicationBuilder.beforeRender(() => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
});
|
||||
|
||||
25
src/common/fs/exec-file.injectable.ts
Normal file
25
src/common/fs/exec-file.injectable.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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";
|
||||
import { promisify } from "util";
|
||||
|
||||
export type ExecFile = (filePath: string, args: string[]) => Promise<string>;
|
||||
|
||||
const execFileInjectable = getInjectable({
|
||||
id: "exec-file",
|
||||
|
||||
instantiate: (): ExecFile => async (filePath, args) => {
|
||||
const asyncExecFile = promisify(execFile);
|
||||
|
||||
const result = await asyncExecFile(filePath, args);
|
||||
|
||||
return result.stdout;
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default execFileInjectable;
|
||||
25
src/common/fs/read-yaml-file.injectable.ts
Normal file
25
src/common/fs/read-yaml-file.injectable.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 readFileInjectable from "./read-file.injectable";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
export type ReadYamlFile = (filePath: string) => Promise<unknown>;
|
||||
|
||||
const readYamlFileInjectable = getInjectable({
|
||||
id: "read-yaml-file",
|
||||
|
||||
instantiate: (di): ReadYamlFile => {
|
||||
const readFile = di.inject(readFileInjectable);
|
||||
|
||||
return async (filePath: string) => {
|
||||
const contents = await readFile(filePath);
|
||||
|
||||
return yaml.load(contents);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default readYamlFileInjectable;
|
||||
23
src/common/helm/add-helm-repository-channel.injectable.ts
Normal file
23
src/common/helm/add-helm-repository-channel.injectable.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 "./helm-repo";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
|
||||
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<string>>;
|
||||
|
||||
const addHelmRepositoryChannelInjectable = getInjectable({
|
||||
id: "add-helm-repository-channel",
|
||||
|
||||
instantiate: (): AddHelmRepositoryChannel => ({
|
||||
id: "add-helm-repository-channel",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default addHelmRepositoryChannelInjectable;
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
|
||||
export type GetHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
|
||||
|
||||
const getActiveHelmRepositoriesChannelInjectable = getInjectable({
|
||||
id: "get-active-helm-repositories-channel",
|
||||
|
||||
instantiate: (): GetHelmRepositoriesChannel => ({
|
||||
id: "get-helm-active-list-repositories",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoriesChannelInjectable;
|
||||
16
src/common/helm/helm-repo.ts
Normal file
16
src/common/helm/helm-repo.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type HelmRepo = {
|
||||
name: string;
|
||||
url: string;
|
||||
cacheFilePath?: string;
|
||||
caFile?: string;
|
||||
certFile?: string;
|
||||
insecureSkipTlsVerify?: boolean;
|
||||
keyFile?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
22
src/common/helm/remove-helm-repository-channel.injectable.ts
Normal file
22
src/common/helm/remove-helm-repository-channel.injectable.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
|
||||
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo>;
|
||||
|
||||
const removeHelmRepositoryChannelInjectable = getInjectable({
|
||||
id: "remove-helm-repository-channel",
|
||||
|
||||
instantiate: (): RemoveHelmRepositoryChannel => ({
|
||||
id: "remove-helm-repository-channel",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryChannelInjectable;
|
||||
7
src/common/utils/async-result.ts
Normal file
7
src/common/utils/async-result.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export type AsyncResult<Response, Error = string> =
|
||||
| { callWasSuccessful: true; response: Response }
|
||||
| { callWasSuccessful: false; error: Error };
|
||||
15
src/common/utils/get-error-message.ts
Normal file
15
src/common/utils/get-error-message.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export const getErrorMessage = (error: unknown): string => {
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return JSON.stringify(error);
|
||||
};
|
||||
@ -3,16 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import bundledBinariesNormalizedArchInjectable from "./bundled-binaries-normalized-arch.injectable";
|
||||
import bundledResourcesDirectoryInjectable from "./bundled-resources-dir.injectable";
|
||||
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
|
||||
import normalizedPlatformArchitectureInjectable from "./normalized-platform-architecture.injectable";
|
||||
|
||||
const baseBundeledBinariesDirectoryInjectable = getInjectable({
|
||||
id: "base-bundeled-binaries-directory",
|
||||
instantiate: (di) => path.join(
|
||||
di.inject(bundledResourcesDirectoryInjectable),
|
||||
di.inject(bundledBinariesNormalizedArchInjectable),
|
||||
),
|
||||
const baseBundledBinariesDirectoryInjectable = getInjectable({
|
||||
id: "base-bundled-binaries-directory",
|
||||
instantiate: (di) => {
|
||||
const bundledResourcesDirectory = di.inject(bundledResourcesDirectoryInjectable);
|
||||
const normalizedPlatformArchitecture = di.inject(normalizedPlatformArchitectureInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
|
||||
return getAbsolutePath(
|
||||
bundledResourcesDirectory,
|
||||
normalizedPlatformArchitecture,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default baseBundeledBinariesDirectoryInjectable;
|
||||
export default baseBundledBinariesDirectoryInjectable;
|
||||
|
||||
@ -3,19 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import isProductionInjectable from "./is-production.injectable";
|
||||
import normalizedPlatformInjectable from "./normalized-platform.injectable";
|
||||
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
|
||||
import lensResourcesDirInjectable from "./lens-resources-dir.injectable";
|
||||
|
||||
const bundledResourcesDirectoryInjectable = getInjectable({
|
||||
id: "bundled-resources-directory",
|
||||
instantiate: (di) => {
|
||||
const isProduction = di.inject(isProductionInjectable);
|
||||
const normalizedPlatform = di.inject(normalizedPlatformInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const lensResourcesDir = di.inject(lensResourcesDirInjectable);
|
||||
|
||||
return isProduction
|
||||
? process.resourcesPath
|
||||
: path.join(process.cwd(), "binaries", "client", normalizedPlatform);
|
||||
? lensResourcesDir
|
||||
: getAbsolutePath(lensResourcesDir, "binaries", "client", normalizedPlatform);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const bundledBinariesNormalizedArchInjectable = getInjectable({
|
||||
id: "bundled-binaries-normalized-arch",
|
||||
const normalizedPlatformArchitectureInjectable = getInjectable({
|
||||
id: "normalized-platform-architecture",
|
||||
instantiate: () => {
|
||||
switch (process.arch) {
|
||||
case "arm64":
|
||||
@ -24,4 +24,4 @@ const bundledBinariesNormalizedArchInjectable = getInjectable({
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default bundledBinariesNormalizedArchInjectable;
|
||||
export default normalizedPlatformArchitectureInjectable;
|
||||
@ -3,11 +3,15 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import platformInjectable from "./platform.injectable";
|
||||
|
||||
const normalizedPlatformInjectable = getInjectable({
|
||||
id: "normalized-platform",
|
||||
instantiate: () => {
|
||||
switch (process.platform) {
|
||||
|
||||
instantiate: (di) => {
|
||||
const platform = di.inject(platformInjectable);
|
||||
|
||||
switch (platform) {
|
||||
case "darwin":
|
||||
return "darwin";
|
||||
case "linux":
|
||||
@ -15,10 +19,9 @@ const normalizedPlatformInjectable = getInjectable({
|
||||
case "win32":
|
||||
return "windows";
|
||||
default:
|
||||
throw new Error(`platform=${process.platform} is unsupported`);
|
||||
throw new Error(`platform=${platform} is unsupported`);
|
||||
}
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default normalizedPlatformInjectable;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import glob from "glob";
|
||||
import { kebabCase, memoize, noop } from "lodash/fp";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
||||
@ -76,7 +76,7 @@ import quitAndInstallUpdateInjectable from "./electron-app/features/quit-and-ins
|
||||
import electronUpdaterIsActiveInjectable from "./electron-app/features/electron-updater-is-active.injectable";
|
||||
import publishIsConfiguredInjectable from "./application-update/publish-is-configured.injectable";
|
||||
import checkForPlatformUpdatesInjectable from "./application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||
import baseBundeledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import setUpdateOnQuitInjectable from "./electron-app/features/set-update-on-quit.injectable";
|
||||
import downloadPlatformUpdateInjectable from "./application-update/download-platform-update/download-platform-update.injectable";
|
||||
import startCatalogSyncInjectable from "./catalog-sync-to-renderer/start-catalog-sync.injectable";
|
||||
@ -84,6 +84,19 @@ import startKubeConfigSyncInjectable from "./start-main-application/runnables/ku
|
||||
import appVersionInjectable from "../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||
import periodicalCheckForUpdatesInjectable from "./application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
||||
import execFileInjectable from "../common/fs/exec-file.injectable";
|
||||
import normalizedPlatformArchitectureInjectable from "../common/vars/normalized-platform-architecture.injectable";
|
||||
import getHelmChartInjectable from "./helm/helm-service/get-helm-chart.injectable";
|
||||
import getHelmChartValuesInjectable from "./helm/helm-service/get-helm-chart-values.injectable";
|
||||
import listHelmChartsInjectable from "./helm/helm-service/list-helm-charts.injectable";
|
||||
import deleteHelmReleaseInjectable from "./helm/helm-service/delete-helm-release.injectable";
|
||||
import getHelmReleaseHistoryInjectable from "./helm/helm-service/get-helm-release-history.injectable";
|
||||
import getHelmReleaseInjectable from "./helm/helm-service/get-helm-release.injectable";
|
||||
import getHelmReleaseValuesInjectable from "./helm/helm-service/get-helm-release-values.injectable";
|
||||
import installHelmChartInjectable from "./helm/helm-service/install-helm-chart.injectable";
|
||||
import listHelmReleasesInjectable from "./helm/helm-service/list-helm-releases.injectable";
|
||||
import rollbackHelmReleaseInjectable from "./helm/helm-service/rollback-helm-release.injectable";
|
||||
import updateHelmReleaseInjectable from "./helm/helm-service/update-helm-release.injectable";
|
||||
|
||||
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
||||
const {
|
||||
@ -134,6 +147,24 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
|
||||
di.override(periodicalCheckForUpdatesInjectable, () => ({ start: () => {}, stop: () => {}, started: false }));
|
||||
|
||||
overrideFunctionalInjectables(di, [
|
||||
getHelmChartInjectable,
|
||||
getHelmChartValuesInjectable,
|
||||
listHelmChartsInjectable,
|
||||
deleteHelmReleaseInjectable,
|
||||
getHelmReleaseHistoryInjectable,
|
||||
getHelmReleaseInjectable,
|
||||
getHelmReleaseValuesInjectable,
|
||||
installHelmChartInjectable,
|
||||
listHelmReleasesInjectable,
|
||||
rollbackHelmReleaseInjectable,
|
||||
updateHelmReleaseInjectable,
|
||||
writeJsonFileInjectable,
|
||||
readJsonFileInjectable,
|
||||
readFileInjectable,
|
||||
execFileInjectable,
|
||||
]);
|
||||
|
||||
// TODO: Remove usages of globally exported appEventBus to get rid of this
|
||||
di.override(appEventBusInjectable, () => new EventEmitter<[AppEvent]>());
|
||||
|
||||
@ -141,7 +172,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.override(broadcastMessageInjectable, () => (channel) => {
|
||||
throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`);
|
||||
});
|
||||
di.override(baseBundeledBinariesDirectoryInjectable, () => "some-bin-directory");
|
||||
di.override(baseBundledBinariesDirectoryInjectable, () => "some-bin-directory");
|
||||
di.override(spawnInjectable, () => () => {
|
||||
return {
|
||||
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
|
||||
@ -150,18 +181,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
} as never;
|
||||
});
|
||||
|
||||
di.override(writeJsonFileInjectable, () => () => {
|
||||
throw new Error("Tried to write JSON file to file system without specifying explicit override.");
|
||||
});
|
||||
|
||||
di.override(readJsonFileInjectable, () => () => {
|
||||
throw new Error("Tried to read JSON file from file system without specifying explicit override.");
|
||||
});
|
||||
|
||||
di.override(readFileInjectable, () => () => {
|
||||
throw new Error("Tried to read file from file system without specifying explicit override.");
|
||||
});
|
||||
|
||||
di.override(loggerInjectable, () => ({
|
||||
warn: noop,
|
||||
debug: noop,
|
||||
@ -204,6 +223,7 @@ const overrideOperatingSystem = (di: DiContainer) => {
|
||||
di.override(platformInjectable, () => "darwin");
|
||||
di.override(getAbsolutePathInjectable, () => getAbsolutePathFake);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
di.override(normalizedPlatformArchitectureInjectable, () => "arm64");
|
||||
};
|
||||
|
||||
const overrideElectronFeatures = (di: DiContainer) => {
|
||||
@ -257,3 +277,11 @@ const overrideElectronFeatures = (di: DiContainer) => {
|
||||
di.override(publishIsConfiguredInjectable, () => false);
|
||||
di.override(electronUpdaterIsActiveInjectable, () => false);
|
||||
};
|
||||
|
||||
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
||||
injectables.forEach(injectable => {
|
||||
di.override(injectable, () => () => {
|
||||
throw new Error(`Tried to run "${injectable.id}" without explicit override.`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { sortCharts } from "../../../common/utils";
|
||||
import type { HelmRepo } from "../helm-repo-manager";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
|
||||
const charts = new Map([
|
||||
["stable", {
|
||||
|
||||
@ -3,27 +3,48 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { helmService } from "../helm-service";
|
||||
import { HelmRepoManager } from "../helm-repo-manager";
|
||||
|
||||
const mockHelmRepoManager = jest.spyOn(HelmRepoManager, "getInstance").mockImplementation();
|
||||
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";
|
||||
|
||||
jest.mock("../helm-chart-manager");
|
||||
|
||||
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(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock);
|
||||
|
||||
di.unoverride(listHelmChartsInjectable);
|
||||
di.permitSideEffects(listHelmChartsInjectable);
|
||||
|
||||
listHelmCharts = di.inject(listHelmChartsInjectable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("list charts with deprecated entries", async () => {
|
||||
mockHelmRepoManager.mockReturnValue({
|
||||
repositories: jest.fn().mockImplementation(async () => [
|
||||
{ name: "stable", url: "stableurl" },
|
||||
{ name: "experiment", url: "experimenturl" },
|
||||
]),
|
||||
} as any);
|
||||
getActiveHelmRepositoriesMock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
callWasSuccessful: true,
|
||||
|
||||
const charts = await helmService.listCharts();
|
||||
response: [
|
||||
{ name: "stable", url: "stableurl" },
|
||||
{ name: "experiment", url: "experimenturl" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const charts = await listHelmCharts();
|
||||
|
||||
expect(charts).toEqual({
|
||||
stable: {
|
||||
@ -123,15 +144,14 @@ describe("Helm Service tests", () => {
|
||||
});
|
||||
|
||||
it("list charts sorted by version in descending order", async () => {
|
||||
mockHelmRepoManager.mockReturnValue({
|
||||
repositories: jest.fn().mockImplementation(async () => {
|
||||
return [
|
||||
{ name: "bitnami", url: "bitnamiurl" },
|
||||
];
|
||||
getActiveHelmRepositoriesMock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: [{ name: "bitnami", url: "bitnamiurl" }],
|
||||
}),
|
||||
} as any);
|
||||
);
|
||||
|
||||
const charts = await helmService.listCharts();
|
||||
const charts = await listHelmCharts();
|
||||
|
||||
expect(charts).toEqual({
|
||||
bitnami: {
|
||||
|
||||
31
src/main/helm/exec-helm/exec-helm.injectable.ts
Normal file
31
src/main/helm/exec-helm/exec-helm.injectable.ts
Normal file
@ -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 execFileInjectable from "../../../common/fs/exec-file.injectable";
|
||||
import helmBinaryPathInjectable from "../helm-binary-path.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import { getErrorMessage } from "../../../common/utils/get-error-message";
|
||||
|
||||
const execHelmInjectable = getInjectable({
|
||||
id: "exec-helm",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execFile = di.inject(execFileInjectable);
|
||||
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
||||
|
||||
return async (...args: string[]): Promise<AsyncResult<string>> => {
|
||||
try {
|
||||
const response = await execFile(helmBinaryPath, args);
|
||||
|
||||
return { callWasSuccessful: true, response };
|
||||
} catch (error) {
|
||||
return { callWasSuccessful: false, error: getErrorMessage(error) };
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default execHelmInjectable;
|
||||
43
src/main/helm/get-helm-env/get-helm-env.injectable.ts
Normal file
43
src/main/helm/get-helm-env/get-helm-env.injectable.ts
Normal file
@ -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 };
|
||||
}
|
||||
|
||||
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;
|
||||
25
src/main/helm/helm-binary-path.injectable.ts
Normal file
25
src/main/helm/helm-binary-path.injectable.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 { getBinaryName } from "../../common/vars";
|
||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
|
||||
const helmBinaryPathInjectable = getInjectable({
|
||||
id: "helm-binary-path",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const normalizedPlatform = di.inject(normalizedPlatformInjectable);
|
||||
const baseBundledBinariesDirectory = di.inject(baseBundledBinariesDirectoryInjectable);
|
||||
|
||||
const helmBinaryName = getBinaryName("helm", { forPlatform: normalizedPlatform });
|
||||
|
||||
return getAbsolutePath(baseBundledBinariesDirectory, helmBinaryName);
|
||||
},
|
||||
});
|
||||
|
||||
export default helmBinaryPathInjectable;
|
||||
@ -5,13 +5,13 @@
|
||||
|
||||
import fs from "fs";
|
||||
import * as yaml from "js-yaml";
|
||||
import type { HelmRepo } from "./helm-repo-manager";
|
||||
import logger from "../logger";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { iter, put, sortCharts } from "../../common/utils";
|
||||
import { execHelm } from "./exec";
|
||||
import type { SetRequired } from "type-fest";
|
||||
import { assert } from "console";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
|
||||
interface ChartCacheEntry {
|
||||
data: string; // serialized JSON
|
||||
|
||||
@ -1,172 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import { readFile } from "fs-extra";
|
||||
import { customRequestPromise } from "../../common/request";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import logger from "../logger";
|
||||
import { execHelm } from "./exec";
|
||||
import type { HelmEnv, HelmRepo, HelmRepoConfig } from "./helm-repo-manager";
|
||||
|
||||
interface EnsuredHelmRepoManagerData {
|
||||
helmEnv: HelmEnv;
|
||||
didUpdateOnce: boolean;
|
||||
}
|
||||
|
||||
export class HelmRepoManager {
|
||||
protected helmEnv?: HelmEnv;
|
||||
protected didUpdateOnce?: boolean;
|
||||
|
||||
public async loadAvailableRepos(): Promise<HelmRepo[]> {
|
||||
const res = await customRequestPromise({
|
||||
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
return orderBy(res.body as HelmRepo[], repo => repo.name);
|
||||
}
|
||||
|
||||
private async ensureInitialized(): Promise<EnsuredHelmRepoManagerData> {
|
||||
this.helmEnv ??= await this.parseHelmEnv();
|
||||
|
||||
const repos = await this.list(this.helmEnv);
|
||||
|
||||
if (repos.length === 0) {
|
||||
await this.addRepo({
|
||||
name: "bitnami",
|
||||
url: "https://charts.bitnami.com/bitnami",
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.didUpdateOnce) {
|
||||
await this.update();
|
||||
this.didUpdateOnce = true;
|
||||
}
|
||||
|
||||
return {
|
||||
didUpdateOnce: this.didUpdateOnce,
|
||||
helmEnv: this.helmEnv,
|
||||
};
|
||||
}
|
||||
|
||||
protected async parseHelmEnv() {
|
||||
const output = await execHelm(["env"]);
|
||||
const lines = output.split(/\r?\n/); // split by new line feed
|
||||
const env: Partial<Record<string, string>> = {};
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const [key, value] = line.split("=");
|
||||
|
||||
if (key && value) {
|
||||
env[key] = value.replace(/"/g, ""); // strip quotas
|
||||
}
|
||||
});
|
||||
|
||||
return env as HelmEnv;
|
||||
}
|
||||
|
||||
public async repo(name: string): Promise<HelmRepo | undefined> {
|
||||
const repos = await this.repositories();
|
||||
|
||||
return repos.find(repo => repo.name === name);
|
||||
}
|
||||
|
||||
private async list(helmEnv: HelmEnv): Promise<HelmRepo[]> {
|
||||
try {
|
||||
const rawConfig = await readFile(helmEnv.HELM_REPOSITORY_CONFIG, "utf8");
|
||||
const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig;
|
||||
|
||||
if (typeof parsedConfig === "object" && parsedConfig) {
|
||||
return parsedConfig.repositories;
|
||||
}
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public async repositories(): Promise<HelmRepo[]> {
|
||||
try {
|
||||
const { helmEnv } = await this.ensureInitialized();
|
||||
|
||||
const repos = await this.list(helmEnv);
|
||||
|
||||
return repos.map(repo => ({
|
||||
...repo,
|
||||
cacheFilePath: `${helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error(`[HELM]: repositories listing error`, error);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async update() {
|
||||
return execHelm([
|
||||
"repo",
|
||||
"update",
|
||||
]);
|
||||
}
|
||||
|
||||
public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
|
||||
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);
|
||||
}
|
||||
|
||||
return execHelm(args);
|
||||
}
|
||||
|
||||
public async removeRepo({ name, url }: HelmRepo): Promise<string> {
|
||||
logger.info(`[HELM]: removing repo ${name} (${url})`);
|
||||
|
||||
return execHelm([
|
||||
"repo",
|
||||
"remove",
|
||||
name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const helmRepoManagerInjectable = getInjectable({
|
||||
id: "helm-repo-manager",
|
||||
|
||||
instantiate: () => new HelmRepoManager(),
|
||||
});
|
||||
|
||||
export default helmRepoManagerInjectable;
|
||||
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import {
|
||||
asLegacyGlobalSingletonForExtensionApi,
|
||||
} from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-singleton-object-for-extension-api";
|
||||
|
||||
import helmRepoManagerInjectable from "./helm-repo-manager.injectable";
|
||||
|
||||
export type HelmEnv = Partial<Record<string, string>> & {
|
||||
HELM_REPOSITORY_CACHE: string;
|
||||
HELM_REPOSITORY_CONFIG: string;
|
||||
};
|
||||
|
||||
export interface HelmRepoConfig {
|
||||
repositories: HelmRepo[];
|
||||
}
|
||||
|
||||
export interface HelmRepo {
|
||||
name: string;
|
||||
url: string;
|
||||
cacheFilePath?: string;
|
||||
caFile?: string;
|
||||
certFile?: string;
|
||||
insecureSkipTlsVerify?: boolean;
|
||||
keyFile?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export const HelmRepoManager = asLegacyGlobalSingletonForExtensionApi(helmRepoManagerInjectable);
|
||||
@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import logger from "../logger";
|
||||
import { HelmRepoManager } from "./helm-repo-manager";
|
||||
import { HelmChartManager } from "./helm-chart-manager";
|
||||
import { deleteRelease, getHistory, getRelease, getValues, installChart, listReleases, rollback, upgradeRelease } from "./helm-release-manager";
|
||||
import type { JsonObject } from "type-fest";
|
||||
import { object } from "../../common/utils";
|
||||
|
||||
interface GetReleaseValuesArgs {
|
||||
cluster: Cluster;
|
||||
namespace: string;
|
||||
all: boolean;
|
||||
}
|
||||
|
||||
export interface InstallChartArgs {
|
||||
chart: string;
|
||||
values: JsonObject;
|
||||
name: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface UpdateChartArgs {
|
||||
chart: string;
|
||||
values: JsonObject;
|
||||
version: string;
|
||||
}
|
||||
|
||||
class HelmService {
|
||||
public async installChart(cluster: Cluster, data: InstallChartArgs) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
return installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async listCharts() {
|
||||
const repositories = await HelmRepoManager.getInstance().repositories();
|
||||
|
||||
return object.fromEntries(
|
||||
await Promise.all(repositories.map(async repo => [repo.name, await HelmChartManager.forRepo(repo).charts()] as const)),
|
||||
);
|
||||
}
|
||||
|
||||
public async getChart(repoName: string, chartName: string, version = "") {
|
||||
const repo = await HelmRepoManager.getInstance().repo(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chartManager = HelmChartManager.forRepo(repo);
|
||||
|
||||
return {
|
||||
readme: await chartManager.getReadme(chartName, version),
|
||||
versions: await chartManager.chartVersions(chartName),
|
||||
};
|
||||
}
|
||||
|
||||
public async getChartValues(repoName: string, chartName: string, version = "") {
|
||||
const repo = await HelmRepoManager.getInstance().repo(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return HelmChartManager.forRepo(repo).getValues(chartName, version);
|
||||
}
|
||||
|
||||
public async listReleases(cluster: Cluster, namespace?: string) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("list releases");
|
||||
|
||||
return listReleases(proxyKubeconfig, namespace);
|
||||
}
|
||||
|
||||
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
const kubeconfigPath = await cluster.getProxyKubeconfigPath();
|
||||
const kubectl = await cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
|
||||
logger.debug("Fetch release");
|
||||
|
||||
return getRelease(releaseName, namespace, kubeconfigPath, kubectlPath);
|
||||
}
|
||||
|
||||
public async getReleaseValues(releaseName: string, { cluster, namespace, all }: GetReleaseValuesArgs) {
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Fetch release values");
|
||||
|
||||
return getValues(releaseName, { namespace, all, kubeconfigPath: pathToKubeconfig });
|
||||
}
|
||||
|
||||
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Fetch release history");
|
||||
|
||||
return getHistory(releaseName, namespace, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async deleteRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Delete release");
|
||||
|
||||
return deleteRelease(releaseName, namespace, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const kubectl = await cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
|
||||
logger.debug("Upgrade release");
|
||||
|
||||
return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, proxyKubeconfig, kubectlPath);
|
||||
}
|
||||
|
||||
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Rollback release");
|
||||
await rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
}
|
||||
}
|
||||
|
||||
export const helmService = new HelmService();
|
||||
28
src/main/helm/helm-service/delete-helm-release.injectable.ts
Normal file
28
src/main/helm/helm-service/delete-helm-release.injectable.ts
Normal file
@ -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 { deleteRelease } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
const deleteHelmReleaseInjectable = getInjectable({
|
||||
id: "delete-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Delete release");
|
||||
|
||||
return deleteRelease(releaseName, namespace, proxyKubeconfig);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default deleteHelmReleaseInjectable;
|
||||
@ -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 { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
|
||||
const getHelmChartValuesInjectable = getInjectable({
|
||||
id: "get-helm-chart-values",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return HelmChartManager.forRepo(repo).getValues(chartName, version);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmChartValuesInjectable;
|
||||
34
src/main/helm/helm-service/get-helm-chart.injectable.ts
Normal file
34
src/main/helm/helm-service/get-helm-chart.injectable.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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 { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
|
||||
const getHelmChartInjectable = getInjectable({
|
||||
id: "get-helm-chart",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chartManager = HelmChartManager.forRepo(repo);
|
||||
|
||||
return {
|
||||
readme: await chartManager.getReadme(chartName, version),
|
||||
versions: await chartManager.chartVersions(chartName),
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmChartInjectable;
|
||||
@ -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 { getHistory } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
const getHelmReleaseHistoryInjectable = getInjectable({
|
||||
id: "get-helm-release-history",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Fetch release history");
|
||||
|
||||
return getHistory(releaseName, namespace, proxyKubeconfig);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmReleaseHistoryInjectable;
|
||||
@ -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 { getValues } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
|
||||
interface GetReleaseValuesArgs {
|
||||
cluster: Cluster;
|
||||
namespace: string;
|
||||
all: boolean;
|
||||
}
|
||||
|
||||
const getHelmReleaseValuesInjectable = getInjectable({
|
||||
id: "get-helm-release-values",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (
|
||||
releaseName: string,
|
||||
{ cluster, namespace, all }: GetReleaseValuesArgs,
|
||||
) => {
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Fetch release values");
|
||||
|
||||
return getValues(releaseName, {
|
||||
namespace,
|
||||
all,
|
||||
kubeconfigPath: pathToKubeconfig,
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmReleaseValuesInjectable;
|
||||
30
src/main/helm/helm-service/get-helm-release.injectable.ts
Normal file
30
src/main/helm/helm-service/get-helm-release.injectable.ts
Normal file
@ -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 { Cluster } from "../../../common/cluster/cluster";
|
||||
import { getRelease } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
const getHelmReleaseInjectable = getInjectable({
|
||||
id: "get-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string) => {
|
||||
const kubeconfigPath = await cluster.getProxyKubeconfigPath();
|
||||
const kubectl = await cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
|
||||
logger.debug("Fetch release");
|
||||
|
||||
return getRelease(releaseName, namespace, kubeconfigPath, kubectlPath);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmReleaseInjectable;
|
||||
30
src/main/helm/helm-service/install-helm-chart.injectable.ts
Normal file
30
src/main/helm/helm-service/install-helm-chart.injectable.ts
Normal file
@ -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 { JsonObject } from "type-fest";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import { installChart } from "../helm-release-manager";
|
||||
|
||||
export interface InstallChartArgs {
|
||||
chart: string;
|
||||
values: JsonObject;
|
||||
name: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const installHelmChartInjectable = getInjectable({
|
||||
id: "install-helm-chart",
|
||||
|
||||
instantiate: () => async (cluster: Cluster, data: InstallChartArgs) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
return installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default installHelmChartInjectable;
|
||||
41
src/main/helm/helm-service/list-helm-charts.injectable.ts
Normal file
41
src/main/helm/helm-service/list-helm-charts.injectable.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { object } from "../../../common/utils";
|
||||
import { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
|
||||
const listHelmChartsInjectable = getInjectable({
|
||||
id: "list-helm-charts",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
|
||||
|
||||
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 HelmChartManager.forRepo(repo).charts(),
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default listHelmChartsInjectable;
|
||||
28
src/main/helm/helm-service/list-helm-releases.injectable.ts
Normal file
28
src/main/helm/helm-service/list-helm-releases.injectable.ts
Normal file
@ -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 { listReleases } from "../helm-release-manager";
|
||||
|
||||
const listHelmReleasesInjectable = getInjectable({
|
||||
id: "list-helm-releases",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster: Cluster, namespace?: string) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("list releases");
|
||||
|
||||
return listReleases(proxyKubeconfig, namespace);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default listHelmReleasesInjectable;
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 { rollback } from "../helm-release-manager";
|
||||
|
||||
const rollbackHelmReleaseInjectable = getInjectable({
|
||||
id: "rollback-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (
|
||||
cluster: Cluster,
|
||||
releaseName: string,
|
||||
namespace: string,
|
||||
revision: number,
|
||||
) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Rollback release");
|
||||
await rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default rollbackHelmReleaseInjectable;
|
||||
45
src/main/helm/helm-service/update-helm-release.injectable.ts
Normal file
45
src/main/helm/helm-service/update-helm-release.injectable.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 { upgradeRelease } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { JsonObject } from "type-fest";
|
||||
|
||||
export interface UpdateChartArgs {
|
||||
chart: string;
|
||||
values: JsonObject;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const updateHelmReleaseInjectable = getInjectable({
|
||||
id: "update-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) => {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const kubectl = await cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
|
||||
logger.debug("Upgrade release");
|
||||
|
||||
return upgradeRelease(
|
||||
releaseName,
|
||||
data.chart,
|
||||
data.values,
|
||||
namespace,
|
||||
data.version,
|
||||
proxyKubeconfig,
|
||||
kubectlPath,
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default updateHelmReleaseInjectable;
|
||||
@ -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 addHelmRepositoryChannelInjectable from "../../../../common/helm/add-helm-repository-channel.injectable";
|
||||
import addHelmRepositoryInjectable from "./add-helm-repository.injectable";
|
||||
import { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
|
||||
const addHelmRepositoryChannelListenerInjectable = getInjectable({
|
||||
id: "add-helm-repository-channel-listener",
|
||||
|
||||
instantiate: (di) => {
|
||||
const addHelmRepository = di.inject(addHelmRepositoryInjectable);
|
||||
const channel = di.inject(addHelmRepositoryChannelInjectable);
|
||||
|
||||
return {
|
||||
channel,
|
||||
handler: addHelmRepository,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default addHelmRepositoryChannelListenerInjectable;
|
||||
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
const addHelmRepositoryInjectable = getInjectable({
|
||||
id: "add-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (repo: HelmRepo) => {
|
||||
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);
|
||||
}
|
||||
|
||||
return await execHelm(...args);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default addHelmRepositoryInjectable;
|
||||
@ -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 { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
import getActiveHelmRepositoriesChannelInjectable from "../../../../common/helm/get-active-helm-repositories-channel.injectable";
|
||||
import getActiveHelmRepositoriesInjectable from "./get-active-helm-repositories.injectable";
|
||||
|
||||
const getActiveHelmRepositoriesChannelListenerInjectable = getInjectable({
|
||||
id: "get-active-helm-repositories-channel-listener",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
|
||||
|
||||
return {
|
||||
channel: di.inject(getActiveHelmRepositoriesChannelInjectable),
|
||||
|
||||
handler: getActiveHelmRepositories,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoriesChannelListenerInjectable;
|
||||
@ -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.includes(internalHelmErrorForNoRepositoriesFound)) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: `Error updating Helm repositories: ${updateResult.error}`,
|
||||
};
|
||||
}
|
||||
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}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -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 { requestChannelListenerInjectionToken } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
||||
import removeHelmRepositoryInjectable from "./remove-helm-repository.injectable";
|
||||
import removeHelmRepositoryChannelInjectable from "../../../../common/helm/remove-helm-repository-channel.injectable";
|
||||
|
||||
const removeHelmRepositoryChannelListenerInjectable = getInjectable({
|
||||
id: "remove-helm-repository-channel-listener",
|
||||
|
||||
instantiate: (di) => {
|
||||
const removeHelmRepository = di.inject(removeHelmRepositoryInjectable);
|
||||
const channel = di.inject(removeHelmRepositoryChannelInjectable);
|
||||
|
||||
return {
|
||||
channel,
|
||||
handler: removeHelmRepository,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: requestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryChannelListenerInjectable;
|
||||
@ -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 execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import type { HelmRepo } from "../../../../common/helm/helm-repo";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
|
||||
const removeHelmRepositoryInjectable = getInjectable({
|
||||
id: "remove-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (repo: HelmRepo) => {
|
||||
logger.info(`[HELM]: removing repo ${repo.name} (${repo.url})`);
|
||||
|
||||
return execHelm(
|
||||
"repo",
|
||||
"remove",
|
||||
repo.name,
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryInjectable;
|
||||
@ -12,7 +12,7 @@ import { getBinaryName } from "../../common/vars";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
|
||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
|
||||
@ -25,7 +25,7 @@ const createKubeAuthProxyInjectable = getInjectable({
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||
const clusterUrl = new URL(cluster.apiUrl);
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(baseBundeledBinariesDirectoryInjectable), binaryName),
|
||||
proxyBinPath: path.join(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
|
||||
@ -4,13 +4,13 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import kubectlBinaryNameInjectable from "./binary-name.injectable";
|
||||
|
||||
const bundledKubectlBinaryPathInjectable = getInjectable({
|
||||
id: "bundled-kubectl-binary-path",
|
||||
instantiate: (di) => path.join(
|
||||
di.inject(baseBundeledBinariesDirectoryInjectable),
|
||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
di.inject(kubectlBinaryNameInjectable),
|
||||
),
|
||||
});
|
||||
|
||||
@ -11,7 +11,7 @@ import kubectlDownloadingNormalizedArchInjectable from "./normalized-arch.inject
|
||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "./binary-name.injectable";
|
||||
import bundledKubectlBinaryPathInjectable from "./bundled-binary-path.injectable";
|
||||
import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
|
||||
const createKubectlInjectable = getInjectable({
|
||||
id: "create-kubectl",
|
||||
@ -24,7 +24,7 @@ const createKubectlInjectable = getInjectable({
|
||||
normalizedDownloadPlatform: di.inject(normalizedPlatformInjectable),
|
||||
kubectlBinaryName: di.inject(kubectlBinaryNameInjectable),
|
||||
bundledKubectlBinaryPath: di.inject(bundledKubectlBinaryPathInjectable),
|
||||
baseBundeledBinariesDirectory: di.inject(baseBundeledBinariesDirectoryInjectable),
|
||||
baseBundeledBinariesDirectory: di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
};
|
||||
|
||||
return (clusterVersion: string) => new Kubectl(dependencies, clusterVersion);
|
||||
|
||||
@ -5,25 +5,29 @@
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import getHelmChartInjectable from "../../../helm/helm-service/get-helm-chart.injectable";
|
||||
|
||||
const getChartRouteInjectable = getRouteInjectable({
|
||||
id: "get-chart-route",
|
||||
|
||||
instantiate: () => route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}`,
|
||||
})(async ({ params, query }) => {
|
||||
const { repo, chart } = params;
|
||||
instantiate: (di) => {
|
||||
const getHelmChart = di.inject(getHelmChartInjectable);
|
||||
|
||||
return {
|
||||
response: await helmService.getChart(
|
||||
repo,
|
||||
chart,
|
||||
query.get("version") ?? undefined,
|
||||
),
|
||||
};
|
||||
}),
|
||||
return route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}`,
|
||||
})(async ({ params, query }) => {
|
||||
const { repo, chart } = params;
|
||||
|
||||
return {
|
||||
response: await getHelmChart(
|
||||
repo,
|
||||
chart,
|
||||
query.get("version") ?? undefined,
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default getChartRouteInjectable;
|
||||
|
||||
@ -3,23 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import getHelmChartValuesInjectable from "../../../helm/helm-service/get-helm-chart-values.injectable";
|
||||
|
||||
const getChartRouteValuesInjectable = getRouteInjectable({
|
||||
id: "get-chart-route-values",
|
||||
|
||||
instantiate: () => route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}/values`,
|
||||
})(async ({ params, query }) => ({
|
||||
response: await helmService.getChartValues(
|
||||
params.repo,
|
||||
params.chart,
|
||||
query.get("version") ?? undefined,
|
||||
),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const getHelmChartValues = di.inject(getHelmChartValuesInjectable);
|
||||
|
||||
return route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}/values`,
|
||||
})(async ({ params, query }) => ({
|
||||
response: await getHelmChartValues(
|
||||
params.repo,
|
||||
params.chart,
|
||||
query.get("version") ?? undefined,
|
||||
),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default getChartRouteValuesInjectable;
|
||||
|
||||
@ -3,19 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import listHelmChartsInjectable from "../../../helm/helm-service/list-helm-charts.injectable";
|
||||
|
||||
const listChartsRouteInjectable = getRouteInjectable({
|
||||
id: "list-charts-route",
|
||||
|
||||
instantiate: () => route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts`,
|
||||
})(async () => ({
|
||||
response: await helmService.listCharts(),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const listHelmCharts = di.inject(listHelmChartsInjectable);
|
||||
|
||||
return route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts`,
|
||||
})(async () => ({
|
||||
response: await listHelmCharts(),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default listChartsRouteInjectable;
|
||||
|
||||
@ -3,23 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { clusterRoute } from "../../../router/route";
|
||||
import deleteHelmReleaseInjectable from "../../../helm/helm-service/delete-helm-release.injectable";
|
||||
|
||||
const deleteReleaseRouteInjectable = getRouteInjectable({
|
||||
id: "delete-release-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
method: "delete",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
|
||||
})(async ({ cluster, params: { release, namespace }}) => ({
|
||||
response: await helmService.deleteRelease(
|
||||
cluster,
|
||||
release,
|
||||
namespace,
|
||||
),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const deleteHelmRelease = di.inject(deleteHelmReleaseInjectable);
|
||||
|
||||
return clusterRoute({
|
||||
method: "delete",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
|
||||
})(async ({ cluster, params: { release, namespace }}) => ({
|
||||
response: await deleteHelmRelease(cluster, release, namespace),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default deleteReleaseRouteInjectable;
|
||||
|
||||
@ -3,23 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { clusterRoute } from "../../../router/route";
|
||||
import getHelmReleaseHistoryInjectable from "../../../helm/helm-service/get-helm-release-history.injectable";
|
||||
|
||||
const getReleaseRouteHistoryInjectable = getRouteInjectable({
|
||||
id: "get-release-history-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/history`,
|
||||
})(async ({ cluster, params }) => ({
|
||||
response: await helmService.getReleaseHistory(
|
||||
cluster,
|
||||
params.release,
|
||||
params.namespace,
|
||||
),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const getHelmReleaseHistory = di.inject(getHelmReleaseHistoryInjectable);
|
||||
|
||||
return clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/history`,
|
||||
})(async ({ cluster, params }) => ({
|
||||
response: await getHelmReleaseHistory(
|
||||
cluster,
|
||||
params.release,
|
||||
params.namespace,
|
||||
),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default getReleaseRouteHistoryInjectable;
|
||||
|
||||
@ -3,23 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
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: () => clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
|
||||
})(async ({ cluster, params }) => ({
|
||||
response: await helmService.getRelease(
|
||||
cluster,
|
||||
params.release,
|
||||
params.namespace,
|
||||
),
|
||||
})),
|
||||
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;
|
||||
|
||||
@ -3,27 +3,31 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { getBoolean } from "../../../utils/parse-query";
|
||||
import { contentTypes } from "../../../router/router-content-types";
|
||||
import { clusterRoute } from "../../../router/route";
|
||||
import getHelmReleaseValuesInjectable from "../../../helm/helm-service/get-helm-release-values.injectable";
|
||||
|
||||
const getReleaseRouteValuesInjectable = getRouteInjectable({
|
||||
id: "get-release-values-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/values`,
|
||||
})(async ({ cluster, params: { namespace, release }, query }) => ({
|
||||
response: await helmService.getReleaseValues(release, {
|
||||
cluster,
|
||||
namespace,
|
||||
all: getBoolean(query, "all"),
|
||||
}),
|
||||
instantiate: (di) => {
|
||||
const getHelmReleaseValues = di.inject(getHelmReleaseValuesInjectable);
|
||||
|
||||
contentType: contentTypes.txt,
|
||||
})),
|
||||
return clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/values`,
|
||||
})(async ({ cluster, params: { namespace, release }, query }) => ({
|
||||
response: await getHelmReleaseValues(release, {
|
||||
cluster,
|
||||
namespace,
|
||||
all: getBoolean(query, "all"),
|
||||
}),
|
||||
|
||||
contentType: contentTypes.txt,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default getReleaseRouteValuesInjectable;
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import type { InstallChartArgs } from "../../../helm/helm-service";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import Joi from "joi";
|
||||
import { payloadValidatedClusterRoute } from "../../../router/route";
|
||||
import type { InstallChartArgs } from "../../../helm/helm-service/install-helm-chart.injectable";
|
||||
import installHelmChartInjectable from "../../../helm/helm-service/install-helm-chart.injectable";
|
||||
|
||||
const installChartArgsValidator = Joi.object<InstallChartArgs, true, InstallChartArgs>({
|
||||
chart: Joi
|
||||
@ -31,14 +31,18 @@ const installChartArgsValidator = Joi.object<InstallChartArgs, true, InstallChar
|
||||
const installChartRouteInjectable = getRouteInjectable({
|
||||
id: "install-chart-route",
|
||||
|
||||
instantiate: () => payloadValidatedClusterRoute({
|
||||
method: "post",
|
||||
path: `${apiPrefix}/v2/releases`,
|
||||
payloadValidator: installChartArgsValidator,
|
||||
})(async ({ payload, cluster }) => ({
|
||||
response: await helmService.installChart(cluster, payload),
|
||||
statusCode: 201,
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const installHelmChart = di.inject(installHelmChartInjectable);
|
||||
|
||||
return payloadValidatedClusterRoute({
|
||||
method: "post",
|
||||
path: `${apiPrefix}/v2/releases`,
|
||||
payloadValidator: installChartArgsValidator,
|
||||
})(async ({ payload, cluster }) => ({
|
||||
response: await installHelmChart(cluster, payload),
|
||||
statusCode: 201,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default installChartRouteInjectable;
|
||||
|
||||
@ -3,19 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { clusterRoute } from "../../../router/route";
|
||||
import listHelmReleasesInjectable from "../../../helm/helm-service/list-helm-releases.injectable";
|
||||
|
||||
const listReleasesRouteInjectable = getRouteInjectable({
|
||||
id: "list-releases-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace?}`,
|
||||
})(async ({ cluster, params }) => ({
|
||||
response: await helmService.listReleases(cluster, params.namespace),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const listHelmReleases = di.inject(listHelmReleasesInjectable);
|
||||
|
||||
return clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/releases/{namespace?}`,
|
||||
})(async ({ cluster, params }) => ({
|
||||
response: await listHelmReleases(cluster, params.namespace),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default listReleasesRouteInjectable;
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import Joi from "joi";
|
||||
import { payloadValidatedClusterRoute } from "../../../router/route";
|
||||
import rollbackHelmReleaseInjectable from "../../../helm/helm-service/rollback-helm-release.injectable";
|
||||
|
||||
interface RollbackReleasePayload {
|
||||
revision: number;
|
||||
@ -21,13 +21,17 @@ const rollbackReleasePayloadValidator = Joi.object<RollbackReleasePayload, true,
|
||||
const rollbackReleaseRouteInjectable = getRouteInjectable({
|
||||
id: "rollback-release-route",
|
||||
|
||||
instantiate: () => payloadValidatedClusterRoute({
|
||||
method: "put",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback`,
|
||||
payloadValidator: rollbackReleasePayloadValidator,
|
||||
})(async ({ cluster, params: { release, namespace }, payload }) => {
|
||||
await helmService.rollback(cluster, release, namespace, payload.revision);
|
||||
}),
|
||||
instantiate: (di) => {
|
||||
const rollbackRelease = di.inject(rollbackHelmReleaseInjectable);
|
||||
|
||||
return payloadValidatedClusterRoute({
|
||||
method: "put",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback`,
|
||||
payloadValidator: rollbackReleasePayloadValidator,
|
||||
})(async ({ cluster, params: { release, namespace }, payload }) => {
|
||||
await rollbackRelease(cluster, release, namespace, payload.revision);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default rollbackReleaseRouteInjectable;
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import type { UpdateChartArgs } from "../../../helm/helm-service";
|
||||
import { helmService } from "../../../helm/helm-service";
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { payloadValidatedClusterRoute } from "../../../router/route";
|
||||
import Joi from "joi";
|
||||
import type { UpdateChartArgs } from "../../../helm/helm-service/update-helm-release.injectable";
|
||||
import updateHelmReleaseInjectable from "../../../helm/helm-service/update-helm-release.injectable";
|
||||
|
||||
const updateChartArgsValidator = Joi.object<UpdateChartArgs, true, UpdateChartArgs>({
|
||||
chart: Joi
|
||||
@ -24,18 +24,22 @@ const updateChartArgsValidator = Joi.object<UpdateChartArgs, true, UpdateChartAr
|
||||
const updateReleaseRouteInjectable = getRouteInjectable({
|
||||
id: "update-release-route",
|
||||
|
||||
instantiate: () => payloadValidatedClusterRoute({
|
||||
method: "put",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
|
||||
payloadValidator: updateChartArgsValidator,
|
||||
})(async ({ cluster, params, payload }) => ({
|
||||
response: await helmService.updateRelease(
|
||||
cluster,
|
||||
params.release,
|
||||
params.namespace,
|
||||
payload,
|
||||
),
|
||||
})),
|
||||
instantiate: (di) => {
|
||||
const updateRelease = di.inject(updateHelmReleaseInjectable);
|
||||
|
||||
return payloadValidatedClusterRoute({
|
||||
method: "put",
|
||||
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
|
||||
payloadValidator: updateChartArgsValidator,
|
||||
})(async ({ cluster, params, payload }) => ({
|
||||
response: await updateRelease(
|
||||
cluster,
|
||||
params.release,
|
||||
params.namespace,
|
||||
payload,
|
||||
),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default updateReleaseRouteInjectable;
|
||||
|
||||
@ -15,7 +15,6 @@ import * as LensExtensionsCommonApi from "../extensions/common-api";
|
||||
import * as LensExtensionsRendererApi from "../extensions/renderer-api";
|
||||
import { delay } from "../common/utils";
|
||||
import { isMac, isDevelopment } from "../common/vars";
|
||||
import { HelmRepoManager } from "../main/helm/helm-repo-manager";
|
||||
import { DefaultProps } from "./mui-base-theme";
|
||||
import configurePackages from "../common/configure-packages";
|
||||
import * as initializers from "./initializers";
|
||||
@ -151,8 +150,6 @@ export async function bootstrap(di: DiContainer) {
|
||||
|
||||
extensionInstallationStateStore.bindIpcListeners();
|
||||
|
||||
HelmRepoManager.createInstance(); // initialize the manager
|
||||
|
||||
// Register additional store listeners
|
||||
clusterStore.registerIpcListener();
|
||||
|
||||
|
||||
@ -1,220 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./add-helm-repo-dialog.scss";
|
||||
|
||||
import React from "react";
|
||||
import type { FileFilter } from "electron";
|
||||
import { observable, makeObservable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import type { DialogProps } from "../dialog";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Wizard, WizardStep } from "../wizard";
|
||||
import { Input } from "../input";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { Button } from "../button";
|
||||
import { systemName, isUrl, isPath } from "../input/input_validators";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { Icon } from "../icon";
|
||||
import { Notifications } from "../notifications";
|
||||
import { type HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
|
||||
import { requestOpenFilePickingDialog } from "../../ipc";
|
||||
|
||||
export interface AddHelmRepoDialogProps extends Partial<DialogProps> {
|
||||
onAddRepo: () => void;
|
||||
}
|
||||
|
||||
enum FileType {
|
||||
CaFile = "caFile",
|
||||
KeyFile = "keyFile",
|
||||
CertFile = "certFile",
|
||||
}
|
||||
|
||||
const dialogState = observable.object({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
function getEmptyRepo(): HelmRepo {
|
||||
return { name: "", url: "", username: "", password: "", insecureSkipTlsVerify: false, caFile: "", keyFile: "", certFile: "" };
|
||||
}
|
||||
@observer
|
||||
export class AddHelmRepoDialog extends React.Component<AddHelmRepoDialogProps> {
|
||||
private static keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"];
|
||||
private static certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"];
|
||||
|
||||
constructor(props: AddHelmRepoDialogProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
static open() {
|
||||
dialogState.isOpen = true;
|
||||
}
|
||||
|
||||
static close() {
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
@observable helmRepo = getEmptyRepo();
|
||||
@observable showOptions = false;
|
||||
|
||||
@action
|
||||
close = () => {
|
||||
AddHelmRepoDialog.close();
|
||||
this.helmRepo = getEmptyRepo();
|
||||
this.showOptions = false;
|
||||
};
|
||||
|
||||
setFilepath(type: FileType, value: string) {
|
||||
this.helmRepo[type] = value;
|
||||
}
|
||||
|
||||
getFilePath(type: FileType) {
|
||||
return this.helmRepo[type];
|
||||
}
|
||||
|
||||
async selectFileDialog(type: FileType, fileFilter: FileFilter) {
|
||||
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
||||
defaultPath: this.getFilePath(type),
|
||||
properties: ["openFile", "showHiddenFiles"],
|
||||
message: `Select file`,
|
||||
buttonLabel: `Use file`,
|
||||
filters: [
|
||||
fileFilter,
|
||||
{ name: "Any", extensions: ["*"] },
|
||||
],
|
||||
});
|
||||
|
||||
if (!canceled && filePaths.length) {
|
||||
this.setFilepath(type, filePaths[0]);
|
||||
}
|
||||
}
|
||||
|
||||
async addCustomRepo() {
|
||||
try {
|
||||
await HelmRepoManager.getInstance().addRepo(this.helmRepo);
|
||||
Notifications.ok((
|
||||
<>
|
||||
{"Helm repository "}
|
||||
<b>{this.helmRepo.name}</b>
|
||||
{" has been added."}
|
||||
</>
|
||||
));
|
||||
this.props.onAddRepo();
|
||||
this.close();
|
||||
} catch (err) {
|
||||
Notifications.error((
|
||||
<>
|
||||
{"Adding helm branch "}
|
||||
<b>{this.helmRepo.name}</b>
|
||||
{" has failed: "}
|
||||
{String(err)}
|
||||
</>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
renderFileInput(placeholder:string, fileType:FileType, fileExtensions:string[]){
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
validators={isPath}
|
||||
className="box grow"
|
||||
value={this.getFilePath(fileType)}
|
||||
onChange={v => this.setFilepath(fileType, v)}
|
||||
/>
|
||||
<Icon
|
||||
material="folder"
|
||||
onClick={() => this.selectFileDialog(fileType, { name: placeholder, extensions: fileExtensions })}
|
||||
tooltip="Browse"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderOptions() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Security settings" />
|
||||
<Checkbox
|
||||
label="Skip TLS certificate checks for the repository"
|
||||
value={this.helmRepo.insecureSkipTlsVerify}
|
||||
onChange={v => this.helmRepo.insecureSkipTlsVerify = v}
|
||||
/>
|
||||
{this.renderFileInput("Key file", FileType.KeyFile, AddHelmRepoDialog.keyExtensions)}
|
||||
{this.renderFileInput("Ca file", FileType.CaFile, AddHelmRepoDialog.certExtensions)}
|
||||
{this.renderFileInput("Certificate file", FileType.CertFile, AddHelmRepoDialog.certExtensions)}
|
||||
<SubTitle title="Chart Repository Credentials" />
|
||||
<Input
|
||||
placeholder="Username"
|
||||
value={this.helmRepo.username}
|
||||
onChange= {v => this.helmRepo.username = v}
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={this.helmRepo.password}
|
||||
onChange={v => this.helmRepo.password = v}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ...dialogProps } = this.props;
|
||||
|
||||
const header = <h5>Add custom Helm Repo</h5>;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
className="AddHelmRepoDialog"
|
||||
isOpen={dialogState.isOpen}
|
||||
close={this.close}
|
||||
>
|
||||
<Wizard header={header} done={this.close}>
|
||||
<WizardStep
|
||||
contentClass="flow column"
|
||||
nextLabel="Add"
|
||||
next={() => this.addCustomRepo()}
|
||||
>
|
||||
<div className="flex column gaps">
|
||||
<Input
|
||||
autoFocus
|
||||
required
|
||||
placeholder="Helm repo name"
|
||||
trim
|
||||
validators={systemName}
|
||||
value={this.helmRepo.name}
|
||||
onChange={v => this.helmRepo.name = v}
|
||||
/>
|
||||
<Input
|
||||
required
|
||||
placeholder="URL"
|
||||
validators={isUrl}
|
||||
value={this.helmRepo.url}
|
||||
onChange={v => this.helmRepo.url = v}
|
||||
/>
|
||||
<Button
|
||||
plain
|
||||
className="accordion"
|
||||
onClick={() => this.showOptions = !this.showOptions}
|
||||
>
|
||||
More
|
||||
<Icon
|
||||
small
|
||||
tooltip="More"
|
||||
material={this.showOptions ? "remove" : "add"}
|
||||
/>
|
||||
</Button>
|
||||
{this.showOptions && this.renderOptions()}
|
||||
</div>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,202 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "./helm-charts.module.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observable, makeObservable, computed } from "mobx";
|
||||
|
||||
import type { HelmRepo } from "../../../main/helm/helm-repo-manager";
|
||||
import { HelmRepoManager } from "../../../main/helm/helm-repo-manager";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Notifications } from "../notifications";
|
||||
import type { SelectOption } from "../select";
|
||||
import { Select } from "../select";
|
||||
import { AddHelmRepoDialog } from "./add-helm-repo-dialog";
|
||||
import { observer } from "mobx-react";
|
||||
import { RemovableItem } from "./removable-item";
|
||||
import { Notice } from "../+extensions/notice";
|
||||
import { Spinner } from "../spinner";
|
||||
import { noop } from "../../utils";
|
||||
import type { SingleValue } from "react-select";
|
||||
|
||||
@observer
|
||||
export class HelmCharts extends React.Component {
|
||||
@observable loadingRepos = false;
|
||||
@observable loadingAvailableRepos = false;
|
||||
@observable repos: HelmRepo[] = [];
|
||||
@observable addedRepos = observable.map<string, HelmRepo>();
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get repoOptions() {
|
||||
return this.repos.map(repo => ({
|
||||
value: repo,
|
||||
label: repo.name,
|
||||
isSelected: this.addedRepos.has(repo.name),
|
||||
}));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadAvailableRepos().catch(noop);
|
||||
this.loadRepos().catch(noop);
|
||||
}
|
||||
|
||||
async loadAvailableRepos() {
|
||||
this.loadingAvailableRepos = true;
|
||||
|
||||
try {
|
||||
if (!this.repos.length) {
|
||||
this.repos = await HelmRepoManager.getInstance().loadAvailableRepos();
|
||||
}
|
||||
} catch (err) {
|
||||
Notifications.error(String(err));
|
||||
}
|
||||
|
||||
this.loadingAvailableRepos = false;
|
||||
}
|
||||
|
||||
async loadRepos() {
|
||||
this.loadingRepos = true;
|
||||
|
||||
try {
|
||||
const repos = await HelmRepoManager.getInstance().repositories(); // via helm-cli
|
||||
|
||||
this.addedRepos.replace(repos.map(repo => [repo.name, repo]));
|
||||
} catch (err) {
|
||||
Notifications.error(String(err));
|
||||
}
|
||||
|
||||
this.loadingRepos = false;
|
||||
}
|
||||
|
||||
async addRepo(repo: HelmRepo) {
|
||||
try {
|
||||
await HelmRepoManager.getInstance().addRepo(repo);
|
||||
this.addedRepos.set(repo.name, repo);
|
||||
} catch (err) {
|
||||
Notifications.error((
|
||||
<>
|
||||
{"Adding helm branch "}
|
||||
<b>{repo.name}</b>
|
||||
{" has failed: "}
|
||||
{String(err)}
|
||||
</>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
async removeRepo(repo: HelmRepo) {
|
||||
try {
|
||||
await HelmRepoManager.getInstance().removeRepo(repo);
|
||||
this.addedRepos.delete(repo.name);
|
||||
} catch (err) {
|
||||
Notifications.error(
|
||||
<>
|
||||
{"Removing helm branch "}
|
||||
<b>{repo.name}</b>
|
||||
{" has failed: "}
|
||||
{String(err)}
|
||||
</>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onRepoSelect = async (option: SingleValue<{ value: HelmRepo }>): Promise<void> => {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.addedRepos.has(option.value.name)) {
|
||||
return void Notifications.ok((
|
||||
<>
|
||||
{"Helm repo "}
|
||||
<b>{option.value.name}</b>
|
||||
{" already in use."}
|
||||
</>
|
||||
));
|
||||
}
|
||||
|
||||
await this.addRepo(option.value);
|
||||
};
|
||||
|
||||
formatOptionLabel = ({ value, isSelected }: SelectOption<HelmRepo>) => (
|
||||
<div className="flex gaps">
|
||||
<span>{value.name}</span>
|
||||
{isSelected && (
|
||||
<Icon
|
||||
small
|
||||
material="check"
|
||||
className="box right" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
renderRepositories() {
|
||||
const repos = Array.from(this.addedRepos);
|
||||
|
||||
if (this.loadingRepos) {
|
||||
return <div className="pt-5 relative"><Spinner center/></div>;
|
||||
}
|
||||
|
||||
if (!repos.length) {
|
||||
return (
|
||||
<Notice>
|
||||
<div className="flex-grow text-center">The repositories have not been added yet</div>
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
return repos.map(([name, repo]) => {
|
||||
return (
|
||||
<RemovableItem
|
||||
key={name}
|
||||
onRemove={() => this.removeRepo(repo)}
|
||||
className="mt-3"
|
||||
>
|
||||
<div>
|
||||
<div data-testid="repository-name" className={styles.repoName}>{name}</div>
|
||||
<div className={styles.repoUrl}>{repo.url}</div>
|
||||
</div>
|
||||
</RemovableItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex gaps">
|
||||
<Select
|
||||
id="HelmRepoSelect"
|
||||
placeholder="Repositories"
|
||||
isLoading={this.loadingAvailableRepos}
|
||||
isDisabled={this.loadingAvailableRepos}
|
||||
options={this.repoOptions}
|
||||
onChange={this.onRepoSelect}
|
||||
value={this.repos}
|
||||
formatOptionLabel={this.formatOptionLabel}
|
||||
controlShouldRenderValue={false}
|
||||
className="box grow"
|
||||
themeName="lens"
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
label="Add Custom Helm Repo"
|
||||
onClick={AddHelmRepoDialog.open}
|
||||
/>
|
||||
</div>
|
||||
<AddHelmRepoDialog onAddRepo={() => this.loadRepos()}/>
|
||||
<div className={styles.repos}>
|
||||
{this.renderRepositories()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,7 @@
|
||||
*/
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { HelmCharts } from "./helm-charts";
|
||||
import { HelmCharts } from "./kubernetes/helm-charts/helm-charts";
|
||||
import { KubeconfigSyncs } from "./kubeconfig-syncs";
|
||||
import { KubectlBinaries } from "./kubectl-binaries";
|
||||
import { Preferences } from "./preferences";
|
||||
|
||||
@ -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 { asyncComputed } from "@ogre-tools/injectable-react";
|
||||
import getActiveHelmRepositoriesChannelInjectable from "../../../../../common/helm/get-active-helm-repositories-channel.injectable";
|
||||
import { requestFromChannelInjectionToken } from "../../../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
|
||||
import helmRepositoriesErrorStateInjectable from "./helm-repositories-error-state.injectable";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
const activeHelmRepositoriesInjectable = getInjectable({
|
||||
id: "active-helm-repositories",
|
||||
|
||||
instantiate: (di) => {
|
||||
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
const getHelmRepositoriesChannel = di.inject(getActiveHelmRepositoriesChannelInjectable);
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
const helmRepositoriesErrorState = di.inject(helmRepositoriesErrorStateInjectable);
|
||||
|
||||
return asyncComputed(async () => {
|
||||
const result = await requestFromChannel(getHelmRepositoriesChannel);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return result.response;
|
||||
} else {
|
||||
showErrorNotification(result.error);
|
||||
|
||||
runInAction(() =>
|
||||
helmRepositoriesErrorState.set({
|
||||
controlsAreShown: false,
|
||||
errorMessage: result.error,
|
||||
}),
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
});
|
||||
|
||||
export default activeHelmRepositoriesInjectable;
|
||||
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./add-helm-repo-dialog.scss";
|
||||
|
||||
import React from "react";
|
||||
import { Wizard, WizardStep } from "../../../../wizard";
|
||||
import { Input } from "../../../../input";
|
||||
import { systemName, isUrl } from "../../../../input/input_validators";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import customHelmRepoInjectable from "./custom-helm-repo.injectable";
|
||||
import type { HelmRepo } from "../../../../../../common/helm/helm-repo";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IObservableValue } from "mobx";
|
||||
import { action } from "mobx";
|
||||
import submitCustomHelmRepositoryInjectable from "./submit-custom-helm-repository.injectable";
|
||||
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
|
||||
import { Button } from "../../../../button";
|
||||
import { Icon } from "../../../../icon";
|
||||
import maximalCustomHelmRepoOptionsAreShownInjectable from "./maximal-custom-helm-repo-options-are-shown.injectable";
|
||||
import { SubTitle } from "../../../../layout/sub-title";
|
||||
import { Checkbox } from "../../../../checkbox";
|
||||
import { HelmFileInput } from "./helm-file-input/helm-file-input";
|
||||
|
||||
interface Dependencies {
|
||||
helmRepo: HelmRepo;
|
||||
hideDialog: () => void;
|
||||
submitCustomRepository: (repository: HelmRepo) => Promise<void>;
|
||||
maximalOptionsAreShown: IObservableValue<boolean>;
|
||||
}
|
||||
|
||||
const NonInjectedActivationOfCustomHelmRepositoryDialogContent = observer(({ helmRepo, submitCustomRepository, maximalOptionsAreShown, hideDialog } : Dependencies) => (
|
||||
<Wizard header={<h5>Add custom Helm Repo</h5>} done={hideDialog}>
|
||||
<WizardStep
|
||||
contentClass="flow column"
|
||||
nextLabel="Add"
|
||||
next={() => submitCustomRepository(helmRepo)}
|
||||
testIdForNext="custom-helm-repository-submit-button"
|
||||
testIdForPrev="custom-helm-repository-cancel-button"
|
||||
>
|
||||
<div className="flex column gaps" data-testid="add-custom-helm-repository-dialog">
|
||||
<Input
|
||||
autoFocus
|
||||
required
|
||||
placeholder="Helm repo name"
|
||||
trim
|
||||
validators={systemName}
|
||||
value={helmRepo.name}
|
||||
onChange={action(v => helmRepo.name = v)}
|
||||
data-testid="custom-helm-repository-name-input"
|
||||
/>
|
||||
<Input
|
||||
required
|
||||
placeholder="URL"
|
||||
validators={isUrl}
|
||||
value={helmRepo.url}
|
||||
onChange={action(v => helmRepo.url = v)}
|
||||
data-testid="custom-helm-repository-url-input"
|
||||
/>
|
||||
<Button
|
||||
plain
|
||||
className="accordion"
|
||||
data-testid="toggle-maximal-options-for-custom-helm-repository-button"
|
||||
onClick={action(() => maximalOptionsAreShown.set(!maximalOptionsAreShown.get()))}
|
||||
>
|
||||
More
|
||||
<Icon
|
||||
small
|
||||
tooltip="More"
|
||||
material={maximalOptionsAreShown.get() ? "remove" : "add"}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{maximalOptionsAreShown.get() && (
|
||||
<div data-testid="maximal-options-for-custom-helm-repository-dialog">
|
||||
<SubTitle title="Security settings" />
|
||||
|
||||
<Checkbox
|
||||
label="Skip TLS certificate checks for the repository"
|
||||
value={helmRepo.insecureSkipTlsVerify}
|
||||
onChange={action(v => {
|
||||
helmRepo.insecureSkipTlsVerify = v;
|
||||
})}
|
||||
data-testid="custom-helm-repository-verify-tls-input"
|
||||
/>
|
||||
|
||||
<HelmFileInput
|
||||
placeholder="Key file"
|
||||
value={helmRepo.keyFile || ""}
|
||||
setValue={action((value) => helmRepo.keyFile = value)}
|
||||
fileExtensions={keyExtensions}
|
||||
data-testid="custom-helm-repository-key-file-input"
|
||||
/>
|
||||
|
||||
<HelmFileInput
|
||||
placeholder="Ca file"
|
||||
value={helmRepo.caFile || ""}
|
||||
setValue={action((value) => helmRepo.caFile = value)}
|
||||
fileExtensions={certExtensions}
|
||||
data-testid="custom-helm-repository-ca-cert-file-input"
|
||||
/>
|
||||
|
||||
<HelmFileInput
|
||||
placeholder="Certificate file"
|
||||
value={helmRepo.certFile || ""}
|
||||
setValue={action((value) => helmRepo.certFile = value)}
|
||||
fileExtensions={certExtensions}
|
||||
data-testid="custom-helm-repository-cert-file-input"
|
||||
/>
|
||||
|
||||
<SubTitle title="Chart Repository Credentials" />
|
||||
|
||||
<Input
|
||||
placeholder="Username"
|
||||
value={helmRepo.username}
|
||||
onChange= {action(v => helmRepo.username = v)}
|
||||
data-testid="custom-helm-repository-username-input"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={helmRepo.password}
|
||||
onChange={action(v => helmRepo.password = v)}
|
||||
data-testid="custom-helm-repository-password-input"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
));
|
||||
|
||||
|
||||
|
||||
export const AddingOfCustomHelmRepositoryDialogContent = withInjectables<Dependencies>(
|
||||
NonInjectedActivationOfCustomHelmRepositoryDialogContent,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
helmRepo: di.inject(customHelmRepoInjectable),
|
||||
hideDialog: di.inject(hideDialogForAddingCustomHelmRepositoryInjectable),
|
||||
submitCustomRepository: di.inject(submitCustomHelmRepositoryInjectable),
|
||||
maximalOptionsAreShown: di.inject(maximalCustomHelmRepoOptionsAreShownInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"];
|
||||
const certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"];
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./add-helm-repo-dialog.scss";
|
||||
|
||||
import React from "react";
|
||||
import { Dialog } from "../../../../dialog";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { AddingOfCustomHelmRepositoryDialogContent } from "./adding-of-custom-helm-repository-dialog-content";
|
||||
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./dialog-visibility/adding-of-custom-helm-repository-dialog-is-visible.injectable";
|
||||
import type { IObservableValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
contentIsVisible: IObservableValue<boolean>;
|
||||
hideDialog: () => void;
|
||||
}
|
||||
|
||||
const NonInjectedActivationOfCustomHelmRepositoryDialog = observer(({
|
||||
contentIsVisible,
|
||||
hideDialog,
|
||||
}: Dependencies) => (
|
||||
<div>
|
||||
<Dialog
|
||||
className="AddHelmRepoDialog"
|
||||
isOpen={contentIsVisible.get()}
|
||||
close={hideDialog}
|
||||
>
|
||||
{contentIsVisible.get() && <AddingOfCustomHelmRepositoryDialogContent />}
|
||||
</Dialog>
|
||||
</div>
|
||||
));
|
||||
|
||||
|
||||
export const AddingOfCustomHelmRepositoryDialog = withInjectables<Dependencies>(
|
||||
NonInjectedActivationOfCustomHelmRepositoryDialog,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
contentIsVisible: di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable),
|
||||
hideDialog: di.inject(hideDialogForAddingCustomHelmRepositoryInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import { Button } from "../../../../button";
|
||||
import showDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/show-dialog-for-adding-custom-helm-repository.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
showDialog: () => void;
|
||||
}
|
||||
|
||||
const NonInjectedActivationOfCustomHelmRepositoryOpenButton = ({ showDialog }: Dependencies) => (
|
||||
<Button
|
||||
primary
|
||||
label="Add Custom Helm Repo"
|
||||
onClick={showDialog}
|
||||
data-testid="add-custom-helm-repo-button"
|
||||
/>
|
||||
);
|
||||
|
||||
export const AddingOfCustomHelmRepositoryOpenButton = withInjectables<Dependencies>(
|
||||
NonInjectedActivationOfCustomHelmRepositoryOpenButton,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
showDialog: di.inject(showDialogForAddingCustomHelmRepositoryInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 { observable } from "mobx";
|
||||
|
||||
const customHelmRepoInjectable = getInjectable({
|
||||
id: "custom-helm-repo",
|
||||
|
||||
instantiate: () => observable({
|
||||
name: "",
|
||||
url: "",
|
||||
username: "",
|
||||
password: "",
|
||||
insecureSkipTlsVerify: false,
|
||||
caFile: "",
|
||||
keyFile: "",
|
||||
certFile: "",
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default customHelmRepoInjectable;
|
||||
@ -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 { observable } from "mobx";
|
||||
|
||||
const addingOfCustomHelmRepositoryDialogIsVisibleInjectable = getInjectable({
|
||||
id: "adding-of-custom-helm-repository-dialog-is-visible",
|
||||
instantiate: () => observable.box(false),
|
||||
});
|
||||
|
||||
export default addingOfCustomHelmRepositoryDialogIsVisibleInjectable;
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { action } from "mobx";
|
||||
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./adding-of-custom-helm-repository-dialog-is-visible.injectable";
|
||||
|
||||
const hideDialogForAddingCustomHelmRepositoryInjectable = getInjectable({
|
||||
id: "hide-dialog-for-adding-custom-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const state = di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable);
|
||||
|
||||
return action(() => {
|
||||
state.set(false);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default hideDialogForAddingCustomHelmRepositoryInjectable;
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { action } from "mobx";
|
||||
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./adding-of-custom-helm-repository-dialog-is-visible.injectable";
|
||||
|
||||
const showDialogForAddingCustomHelmRepositoryInjectable = getInjectable({
|
||||
id: "show-dialog-for-adding-custom-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const state = di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable);
|
||||
|
||||
return action(() => {
|
||||
state.set(true);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default showDialogForAddingCustomHelmRepositoryInjectable;
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { FileFilter } from "electron";
|
||||
import { requestOpenFilePickingDialog } from "../../../../../../ipc";
|
||||
|
||||
const getFilePathsInjectable = getInjectable({
|
||||
id: "get-file-paths",
|
||||
|
||||
instantiate: () => async (fileFilter: FileFilter) =>
|
||||
await requestOpenFilePickingDialog({
|
||||
properties: ["openFile", "showHiddenFiles"],
|
||||
message: `Select file`,
|
||||
buttonLabel: `Use file`,
|
||||
filters: [fileFilter, { name: "Any", extensions: ["*"] }],
|
||||
}),
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getFilePathsInjectable;
|
||||
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { InputValidator } from "../../../../../input";
|
||||
import { Input } from "../../../../../input";
|
||||
import { Icon } from "../../../../../icon";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import getFilePathsInjectable from "./get-file-paths.injectable";
|
||||
import type { FileFilter } from "electron";
|
||||
import isPathInjectable from "../../../../../input/validators/is-path.injectable";
|
||||
|
||||
interface HelmFileInputProps {
|
||||
placeholder: string;
|
||||
fileExtensions: string[];
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
"data-testid"?: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
getFilePaths: (fileFilter: FileFilter) => Promise<{ canceled: boolean; filePaths: string[] }>;
|
||||
isPath: InputValidator<true>;
|
||||
}
|
||||
|
||||
const NonInjectedHelmFileInput = ({
|
||||
placeholder,
|
||||
value,
|
||||
setValue,
|
||||
fileExtensions,
|
||||
getFilePaths,
|
||||
isPath,
|
||||
"data-testid": testId,
|
||||
}: Dependencies & HelmFileInputProps) => (
|
||||
<div className="flex gaps align-center">
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
validators={isPath}
|
||||
className="box grow"
|
||||
value={value}
|
||||
onChange={(v) => setValue(v)}
|
||||
data-testid={testId}
|
||||
/>
|
||||
<Icon
|
||||
material="folder"
|
||||
|
||||
onClick={async () => {
|
||||
const { canceled, filePaths } = await getFilePaths({
|
||||
name: placeholder,
|
||||
extensions: fileExtensions,
|
||||
});
|
||||
|
||||
if (!canceled && filePaths.length) {
|
||||
setValue(filePaths[0]);
|
||||
}
|
||||
}}
|
||||
|
||||
tooltip="Browse"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const HelmFileInput = withInjectables<Dependencies, HelmFileInputProps>(
|
||||
NonInjectedHelmFileInput,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
getFilePaths: di.inject(getFilePathsInjectable),
|
||||
isPath: di.inject(isPathInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -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 { observable } from "mobx";
|
||||
|
||||
const maximalCustomHelmRepoOptionsAreShownInjectable = getInjectable({
|
||||
id: "maximal-custom-helm-repo-options-are-shown",
|
||||
instantiate: () => observable.box(false),
|
||||
});
|
||||
|
||||
export default maximalCustomHelmRepoOptionsAreShownInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 addHelmRepositoryInjectable from "../adding-of-public-helm-repository/select-helm-repository/add-helm-repository.injectable";
|
||||
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
|
||||
|
||||
const submitCustomHelmRepositoryInjectable = getInjectable({
|
||||
id: "submit-custom-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const addHelmRepository = di.inject(addHelmRepositoryInjectable);
|
||||
const hideDialog = di.inject(hideDialogForAddingCustomHelmRepositoryInjectable);
|
||||
|
||||
return async (repository: HelmRepo) => {
|
||||
await addHelmRepository(repository);
|
||||
|
||||
hideDialog();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default submitCustomHelmRepositoryInjectable;
|
||||
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import publicHelmRepositoriesInjectable from "./public-helm-repositories/public-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../../../../../common/helm/helm-repo";
|
||||
import type { SelectOption } from "../../../../select";
|
||||
import { Select } from "../../../../select";
|
||||
import { Icon } from "../../../../icon";
|
||||
import { observer } from "mobx-react";
|
||||
import type { SingleValue } from "react-select";
|
||||
import selectHelmRepositoryInjectable from "./select-helm-repository/select-helm-repository.injectable";
|
||||
import { matches } from "lodash/fp";
|
||||
import activeHelmRepositoriesInjectable from "../active-helm-repositories.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
publicRepositories: IAsyncComputed<HelmRepo[]>;
|
||||
activeRepositories: IAsyncComputed<HelmRepo[]>;
|
||||
selectRepository: (value: SingleValue<SelectOption<HelmRepo>>) => void;
|
||||
}
|
||||
|
||||
const NonInjectedAddingOfPublicHelmRepository = observer(({
|
||||
publicRepositories,
|
||||
activeRepositories,
|
||||
selectRepository,
|
||||
}: Dependencies) => {
|
||||
const dereferencesPublicRepositories = publicRepositories.value.get();
|
||||
const dereferencesActiveRepositories = activeRepositories.value.get();
|
||||
|
||||
const valuesAreLoading = publicRepositories.pending.get() || activeRepositories.pending.get();
|
||||
|
||||
const repositoryOptions = dereferencesPublicRepositories.map(repository => ({
|
||||
value: repository,
|
||||
label: repository.name,
|
||||
isSelected: !!dereferencesActiveRepositories.find(matches({ name: repository.name })),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
id="selection-of-active-public-helm-repository"
|
||||
placeholder="Repositories"
|
||||
isLoading={valuesAreLoading}
|
||||
isDisabled={valuesAreLoading}
|
||||
options={repositoryOptions}
|
||||
onChange={selectRepository}
|
||||
value={dereferencesPublicRepositories}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
controlShouldRenderValue={false}
|
||||
className="box grow"
|
||||
themeName="lens"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const AddingOfPublicHelmRepository = withInjectables<Dependencies>(
|
||||
NonInjectedAddingOfPublicHelmRepository,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
publicRepositories: di.inject(publicHelmRepositoriesInjectable),
|
||||
activeRepositories: di.inject(activeHelmRepositoriesInjectable),
|
||||
selectRepository: di.inject(selectHelmRepositoryInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const formatOptionLabel = ({ value, isSelected }: SelectOption<HelmRepo>) => (
|
||||
<div className="flex gaps">
|
||||
<span>{value.name}</span>
|
||||
{isSelected && (
|
||||
<Icon
|
||||
small
|
||||
material="check"
|
||||
className="box right" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@ -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 { sortBy } from "lodash/fp";
|
||||
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
|
||||
import { customRequestPromise } from "../../../../../../../common/request";
|
||||
|
||||
const callForPublicHelmRepositoriesInjectable = getInjectable({
|
||||
id: "call-for-public-helm-repositories",
|
||||
|
||||
instantiate: () => async (): Promise<HelmRepo[]> => {
|
||||
const res = await customRequestPromise({
|
||||
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const repositories = res.body as HelmRepo[];
|
||||
|
||||
return sortBy(repo => repo.name, repositories);
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default callForPublicHelmRepositoriesInjectable;
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { asyncComputed } from "@ogre-tools/injectable-react";
|
||||
import callForPublicHelmRepositoriesInjectable from "./call-for-public-helm-repositories.injectable";
|
||||
|
||||
const publicHelmRepositoriesInjectable = getInjectable({
|
||||
id: "public-helm-repositories",
|
||||
|
||||
instantiate: (di) => {
|
||||
const callForPublicHelmRepositories = di.inject(callForPublicHelmRepositoriesInjectable);
|
||||
|
||||
return asyncComputed(async () => {
|
||||
return await callForPublicHelmRepositories();
|
||||
}, []);
|
||||
},
|
||||
});
|
||||
|
||||
export default publicHelmRepositoriesInjectable;
|
||||
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import addHelmRepositoryChannelInjectable from "../../../../../../../common/helm/add-helm-repository-channel.injectable";
|
||||
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
|
||||
import { requestFromChannelInjectionToken } from "../../../../../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import activeHelmRepositoriesInjectable from "../../active-helm-repositories.injectable";
|
||||
import showErrorNotificationInjectable from "../../../../../notifications/show-error-notification.injectable";
|
||||
import showSuccessNotificationInjectable from "../../../../../notifications/show-success-notification.injectable";
|
||||
|
||||
const addHelmRepositoryInjectable = getInjectable({
|
||||
id: "add-public-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
const addHelmRepositoryChannel = di.inject(addHelmRepositoryChannelInjectable);
|
||||
const activeHelmRepositories = di.inject(activeHelmRepositoriesInjectable);
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
||||
|
||||
return async (repository: HelmRepo) => {
|
||||
const result = await requestFromChannel(
|
||||
addHelmRepositoryChannel,
|
||||
repository,
|
||||
);
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
showSuccessNotification(
|
||||
`Helm repository ${repository.name} has been added.`,
|
||||
);
|
||||
|
||||
activeHelmRepositories.invalidate();
|
||||
} else {
|
||||
showErrorNotification(result.error);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default addHelmRepositoryInjectable;
|
||||
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 addHelmRepositoryInjectable from "./add-helm-repository.injectable";
|
||||
import type { SelectOption } from "../../../../../select";
|
||||
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
|
||||
import type { SingleValue } from "react-select";
|
||||
import removeHelmRepositoryInjectable from "../../remove-helm-repository.injectable";
|
||||
|
||||
const selectHelmRepositoryInjectable = getInjectable({
|
||||
id: "select-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const addHelmRepository = di.inject(addHelmRepositoryInjectable);
|
||||
const removeHelmRepository = di.inject(removeHelmRepositoryInjectable);
|
||||
|
||||
return (selected: SingleValue<SelectOption<HelmRepo>>) => {
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selected.isSelected) {
|
||||
addHelmRepository(selected.value);
|
||||
} else {
|
||||
removeHelmRepository(selected.value);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default selectHelmRepositoryInjectable;
|
||||
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { HelmRepositories } from "./helm-repositories";
|
||||
import { AddingOfPublicHelmRepository } from "./adding-of-public-helm-repository/adding-of-public-helm-repository";
|
||||
import { AddingOfCustomHelmRepositoryOpenButton } from "./adding-of-custom-helm-repository/adding-of-custom-helm-repository-open-button";
|
||||
import { AddingOfCustomHelmRepositoryDialog } from "./adding-of-custom-helm-repository/adding-of-custom-helm-repository-dialog";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { HelmRepositoriesErrorState } from "./helm-repositories-error-state.injectable";
|
||||
import helmRepositoriesErrorStateInjectable from "./helm-repositories-error-state.injectable";
|
||||
import type { IObservableValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Notice } from "../../../+extensions/notice";
|
||||
|
||||
interface Dependencies {
|
||||
helmRepositoriesErrorState: IObservableValue<HelmRepositoriesErrorState>;
|
||||
}
|
||||
|
||||
const NonInjectedHelmCharts = observer(
|
||||
({ helmRepositoriesErrorState }: Dependencies) => {
|
||||
const state = helmRepositoriesErrorState.get();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!state.controlsAreShown && (
|
||||
<Notice>
|
||||
<div className="flex-grow text-center">{state.errorMessage}</div>
|
||||
</Notice>
|
||||
)}
|
||||
|
||||
{state.controlsAreShown && (
|
||||
<div data-testid="helm-controls">
|
||||
<div className="flex gaps">
|
||||
<AddingOfPublicHelmRepository />
|
||||
|
||||
<AddingOfCustomHelmRepositoryOpenButton />
|
||||
</div>
|
||||
|
||||
<HelmRepositories />
|
||||
|
||||
<AddingOfCustomHelmRepositoryDialog />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const HelmCharts = withInjectables<Dependencies>(
|
||||
NonInjectedHelmCharts,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
helmRepositoriesErrorState: di.inject(helmRepositoriesErrorStateInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -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";
|
||||
import { observable } from "mobx";
|
||||
|
||||
export type HelmRepositoriesErrorState =
|
||||
| { controlsAreShown: true }
|
||||
| { controlsAreShown: false; errorMessage: string };
|
||||
|
||||
const helmRepositoriesErrorStateInjectable = getInjectable({
|
||||
id: "helm-repositories-error-state",
|
||||
|
||||
instantiate: () =>
|
||||
observable.box<HelmRepositoriesErrorState>({ controlsAreShown: true }),
|
||||
});
|
||||
|
||||
export default helmRepositoriesErrorStateInjectable;
|
||||
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "./helm-charts.module.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import activeHelmRepositoriesInjectable from "./active-helm-repositories.injectable";
|
||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { Spinner } from "../../../spinner";
|
||||
import type { HelmRepo } from "../../../../../common/helm/helm-repo";
|
||||
import { RemovableItem } from "../../removable-item";
|
||||
import removeHelmRepositoryInjectable from "./remove-helm-repository.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
activeHelmRepositories: IAsyncComputed<HelmRepo[]>;
|
||||
removeRepository: (repository: HelmRepo) => Promise<void>;
|
||||
}
|
||||
|
||||
const NonInjectedActiveHelmRepositories = observer(({ activeHelmRepositories, removeRepository }: Dependencies) => {
|
||||
if (activeHelmRepositories.pending.get()) {
|
||||
return (
|
||||
<div className={styles.repos}>
|
||||
<div className="pt-5 relative">
|
||||
<Spinner center data-testid="helm-repositories-are-loading" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const repositories = activeHelmRepositories.value.get();
|
||||
|
||||
return (
|
||||
<div className={styles.repos}>
|
||||
{repositories.map((repository) => (
|
||||
<RemovableItem
|
||||
key={repository.name}
|
||||
onRemove={() => removeRepository(repository)}
|
||||
className="mt-3"
|
||||
data-testid={`remove-helm-repository-${repository.name}`}
|
||||
>
|
||||
<div data-testid={`helm-repository-${repository.name}`} className={styles.repoName}>
|
||||
{repository.name}
|
||||
</div>
|
||||
|
||||
<div className={styles.repoUrl}>{repository.url}</div>
|
||||
</RemovableItem>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
export const HelmRepositories = withInjectables<Dependencies>(
|
||||
NonInjectedActiveHelmRepositories,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
activeHelmRepositories: di.inject(activeHelmRepositoriesInjectable),
|
||||
removeRepository: di.inject(removeHelmRepositoryInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -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 { HelmRepo } from "../../../../../common/helm/helm-repo";
|
||||
import { requestFromChannelInjectionToken } from "../../../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import activeHelmRepositoriesInjectable from "./active-helm-repositories.injectable";
|
||||
import removeHelmRepositoryChannelInjectable from "../../../../../common/helm/remove-helm-repository-channel.injectable";
|
||||
|
||||
const removePublicHelmRepositoryInjectable = getInjectable({
|
||||
id: "remove-public-helm-repository",
|
||||
|
||||
instantiate: (di) => {
|
||||
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
const removeHelmRepositoryChannel = di.inject(removeHelmRepositoryChannelInjectable);
|
||||
const activeHelmRepositories = di.inject(activeHelmRepositoriesInjectable);
|
||||
|
||||
return async (repository: HelmRepo) => {
|
||||
await requestFromChannel(removeHelmRepositoryChannel, repository);
|
||||
|
||||
activeHelmRepositories.invalidate();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default removePublicHelmRepositoryInjectable;
|
||||
@ -14,9 +14,10 @@ export interface RemovableItemProps extends DOMAttributes<any>{
|
||||
icon?: string;
|
||||
onRemove: () => void;
|
||||
className?: string;
|
||||
"data-testid"?: string;
|
||||
}
|
||||
|
||||
export function RemovableItem({ icon, onRemove, children, className, ...rest }: RemovableItemProps) {
|
||||
export function RemovableItem({ icon, onRemove, children, className, "data-testid": testId, ...rest }: RemovableItemProps) {
|
||||
return (
|
||||
<div className={cssNames(styles.item, "flex gaps align-center justify-space-between", className)} {...rest}>
|
||||
{icon && (
|
||||
@ -27,6 +28,7 @@ export function RemovableItem({ icon, onRemove, children, className, ...rest }:
|
||||
material="delete"
|
||||
onClick={onRemove}
|
||||
tooltip="Remove"
|
||||
data-testid={testId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 { AsyncInputValidationError, inputValidator } from "../input_validators";
|
||||
import pathExistsInjectable from "../../../../common/fs/path-exists.injectable";
|
||||
|
||||
const isPathInjectable = getInjectable({
|
||||
id: "is-path",
|
||||
|
||||
instantiate: (di) => {
|
||||
const pathExists = di.inject(pathExistsInjectable);
|
||||
|
||||
return inputValidator<true>({
|
||||
debounce: 100,
|
||||
condition: ({ type }) => type === "text",
|
||||
|
||||
validate: async (value) => {
|
||||
try {
|
||||
await pathExists(value);
|
||||
} catch {
|
||||
throw new AsyncInputValidationError(
|
||||
`${value} is not a valid file path`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default isPathInjectable;
|
||||
@ -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 type { NotificationMessage, Notification } from "./notifications.store";
|
||||
import { NotificationStatus } from "./notifications.store";
|
||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||
|
||||
const showErrorNotificationInjectable = getInjectable({
|
||||
id: "show-error-notification",
|
||||
|
||||
instantiate: (di) => {
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
|
||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
||||
notificationsStore.add({
|
||||
status: NotificationStatus.ERROR,
|
||||
timeout: 5000,
|
||||
message,
|
||||
...customOpts,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default showErrorNotificationInjectable;
|
||||
@ -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 type { NotificationMessage, Notification } from "./notifications.store";
|
||||
import { NotificationStatus } from "./notifications.store";
|
||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||
|
||||
const showSuccessNotificationInjectable = getInjectable({
|
||||
id: "show-success-notification",
|
||||
|
||||
instantiate: (di) => {
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
|
||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
||||
notificationsStore.add({
|
||||
status: NotificationStatus.OK,
|
||||
timeout: 5000,
|
||||
message,
|
||||
...customOpts,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default showSuccessNotificationInjectable;
|
||||
@ -233,7 +233,9 @@ class NonInjectedSelect<
|
||||
Menu: ({ className, ...props }) => (
|
||||
<WrappedMenu
|
||||
{...props}
|
||||
className={cssNames(menuClass, this.themeClass, className)}
|
||||
className={cssNames(menuClass, this.themeClass, className, {
|
||||
[`${inputId}-options`]: !!inputId,
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@ -15,7 +15,7 @@ import { Observer } from "mobx-react";
|
||||
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
|
||||
import allowedResourcesInjectable from "../../../common/cluster-store/allowed-resources.injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import { getByText, fireEvent } from "@testing-library/react";
|
||||
import type { KubeResource } from "../../../common/rbac";
|
||||
import { Sidebar } from "../layout/sidebar";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
@ -52,6 +52,9 @@ import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTest
|
||||
import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels";
|
||||
import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token";
|
||||
import trayIconPathsInjectable from "../../../main/tray/tray-icon-path.injectable";
|
||||
import assert from "assert";
|
||||
import { openMenu } from "react-select-event";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||
|
||||
@ -85,6 +88,11 @@ export interface ApplicationBuilder {
|
||||
helmCharts: {
|
||||
navigate: () => void;
|
||||
};
|
||||
|
||||
select: {
|
||||
openMenu: (id: string) => void;
|
||||
selectOption: (menuId: string, labelText: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
interface DiContainers {
|
||||
@ -433,6 +441,30 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
return rendered;
|
||||
},
|
||||
|
||||
select: {
|
||||
openMenu: (menuId) => {
|
||||
const selector = rendered.container.querySelector<HTMLElement>(
|
||||
`#${menuId}`,
|
||||
);
|
||||
|
||||
assert(selector);
|
||||
|
||||
openMenu(selector);
|
||||
},
|
||||
|
||||
selectOption: (menuId, labelText) => {
|
||||
const menuOptions = rendered.baseElement.querySelector<HTMLElement>(
|
||||
`.${menuId}-options`,
|
||||
);
|
||||
|
||||
assert(menuOptions);
|
||||
|
||||
const option = getByText(menuOptions, labelText);
|
||||
|
||||
userEvent.click(option);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return builder;
|
||||
|
||||
@ -129,6 +129,8 @@ export interface WizardStepProps<D> extends WizardCommonProps<D> {
|
||||
skip?: boolean; // don't render the step
|
||||
scrollable?: boolean;
|
||||
children?: React.ReactNode | React.ReactNode[];
|
||||
testIdForNext?: string;
|
||||
testIdForPrev?: string;
|
||||
}
|
||||
|
||||
interface WizardStepState {
|
||||
@ -214,7 +216,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
|
||||
step, isFirst, isLast, children,
|
||||
loading, customButtons, disabledNext, scrollable,
|
||||
hideNextBtn, hideBackBtn, beforeContent, afterContent, noValidate, skip, moreButtons,
|
||||
waiting, className, contentClass, prevLabel, nextLabel,
|
||||
waiting, className, contentClass, prevLabel, nextLabel, testIdForNext, testIdForPrev,
|
||||
} = this.props;
|
||||
|
||||
if (skip) {
|
||||
@ -242,6 +244,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
|
||||
label={prevLabel || (isFirst?.() ? "Cancel" : "Back")}
|
||||
hidden={hideBackBtn}
|
||||
onClick={this.prev}
|
||||
data-testid={testIdForPrev}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
@ -250,6 +253,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
|
||||
hidden={hideNextBtn}
|
||||
waiting={waiting ?? this.state.waiting}
|
||||
disabled={disabledNext}
|
||||
data-testid={testIdForNext}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -5,7 +5,12 @@
|
||||
|
||||
import glob from "glob";
|
||||
import { memoize, noop } from "lodash/fp";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import type {
|
||||
DiContainer,
|
||||
Injectable } from "@ogre-tools/injectable";
|
||||
import {
|
||||
createContainer,
|
||||
} from "@ogre-tools/injectable";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import requestFromChannelInjectable from "./utils/channel/request-from-channel.injectable";
|
||||
import loggerInjectable from "../common/logger.injectable";
|
||||
@ -45,6 +50,8 @@ import appVersionInjectable from "../common/get-configuration-file-model/app-ver
|
||||
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
|
||||
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
|
||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||
import getFilePathsInjectable from "./components/+preferences/kubernetes/helm-charts/adding-of-custom-helm-repository/helm-file-input/get-file-paths.injectable";
|
||||
import callForPublicHelmRepositoriesInjectable from "./components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
|
||||
|
||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||
const {
|
||||
@ -91,9 +98,11 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
on: () => {},
|
||||
}) as unknown as IpcRenderer);
|
||||
|
||||
di.override(broadcastMessageInjectable, () => () => {
|
||||
throw new Error("Tried to broadcast message over IPC without explicit override.");
|
||||
});
|
||||
overrideFunctionalInjectables(di, [
|
||||
broadcastMessageInjectable,
|
||||
getFilePathsInjectable,
|
||||
callForPublicHelmRepositoriesInjectable,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
||||
@ -147,3 +156,11 @@ const getInjectableFilePaths = memoize(() => [
|
||||
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
]);
|
||||
|
||||
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
||||
injectables.forEach(injectable => {
|
||||
di.override(injectable, () => () => {
|
||||
throw new Error(`Tried to run "${injectable.id}" without explicit override.`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user