From a711499bb6a60fb792c4fdd9ac3009f7c4e91ac1 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Fri, 17 Dec 2021 17:28:26 +0200 Subject: [PATCH 1/6] Update ogre-tools for improved types of withInjectables (#4587) --- package.json | 4 +- .../components/+extensions/extensions.tsx | 56 +++++++-------- .../kube-object-menu/kube-object-menu.tsx | 70 +++++++++---------- yarn.lock | 18 ++--- 4 files changed, 68 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 352ee617a8..52f521edb7 100644 --- a/package.json +++ b/package.json @@ -196,8 +196,8 @@ "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.16.1", - "@ogre-tools/injectable": "^1.4.1", - "@ogre-tools/injectable-react": "^1.4.1", + "@ogre-tools/injectable": "1.5.0", + "@ogre-tools/injectable-react": "1.5.2", "@sentry/electron": "^2.5.4", "@sentry/integrations": "^6.15.0", "abort-controller": "^3.0.0", diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 10438e4aca..0ba9deba65 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -49,34 +49,28 @@ import type { LensExtensionId } from "../../../extensions/lens-extension"; import installOnDropInjectable from "./install-on-drop/install-on-drop.injectable"; import { supportedExtensionFormats } from "./supported-extension-formats"; -interface Props { - dependencies: { - userExtensions: IComputedValue; - enableExtension: (id: LensExtensionId) => void; - disableExtension: (id: LensExtensionId) => void; - confirmUninstallExtension: (extension: InstalledExtension) => Promise; - installFromInput: (input: string) => Promise; - installFromSelectFileDialog: () => Promise; - installOnDrop: (files: File[]) => Promise; - }; +interface Dependencies { + userExtensions: IComputedValue; + enableExtension: (id: LensExtensionId) => void; + disableExtension: (id: LensExtensionId) => void; + confirmUninstallExtension: (extension: InstalledExtension) => Promise; + installFromInput: (input: string) => Promise; + installFromSelectFileDialog: () => Promise; + installOnDrop: (files: File[]) => Promise; } @observer -class NonInjectedExtensions extends React.Component { +class NonInjectedExtensions extends React.Component { @observable installPath = ""; - constructor(props: Props) { + constructor(props: Dependencies) { super(props); makeObservable(this); } - - get dependencies() { - return this.props.dependencies; - } componentDidMount() { disposeOnUnmount(this, [ - reaction(() => this.dependencies.userExtensions.get().length, (curSize, prevSize) => { + reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => { if (curSize > prevSize) { disposeOnUnmount(this, [ when(() => !ExtensionInstallationStateStore.anyInstalling, () => this.installPath = ""), @@ -87,10 +81,10 @@ class NonInjectedExtensions extends React.Component { } render() { - const userExtensions = this.dependencies.userExtensions.get(); + const userExtensions = this.props.userExtensions.get(); return ( - +

Extensions

@@ -106,8 +100,8 @@ class NonInjectedExtensions extends React.Component { (this.installPath = value)} - installFromInput={() => this.dependencies.installFromInput(this.installPath)} - installFromSelectFileDialog={this.dependencies.installFromSelectFileDialog} + installFromInput={() => this.props.installFromInput(this.installPath)} + installFromSelectFileDialog={this.props.installFromSelectFileDialog} installPath={this.installPath} /> @@ -115,9 +109,9 @@ class NonInjectedExtensions extends React.Component {
@@ -126,10 +120,10 @@ class NonInjectedExtensions extends React.Component { } } - -export const Extensions = withInjectables(NonInjectedExtensions, { - getProps: di => ({ - dependencies: { +export const Extensions = withInjectables( + NonInjectedExtensions, + { + getProps: (di) => ({ userExtensions: di.inject(userExtensionsInjectable), enableExtension: di.inject(enableExtensionInjectable), disableExtension: di.inject(disableExtensionInjectable), @@ -140,6 +134,6 @@ export const Extensions = withInjectables(NonInjectedExtensions, { installFromSelectFileDialog: di.inject( installFromSelectFileDialogInjectable, ), - }, - }), -}); + }), + }, +); diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 989094023f..29c822be89 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -32,38 +32,28 @@ import hideDetailsInjectable from "./dependencies/hide-details.injectable"; import kubeObjectMenuItemsInjectable from "./dependencies/kube-object-menu-items/kube-object-menu-items.injectable"; import apiManagerInjectable from "./dependencies/api-manager.injectable"; -// TODO: Replace with KubeObjectMenuProps2 -export interface KubeObjectMenuProps extends MenuActionsProps { +export interface KubeObjectMenuProps extends MenuActionsProps { object: TKubeObject | null | undefined; editable?: boolean; removable?: boolean; } -interface KubeObjectMenuProps2 extends MenuActionsProps { - object: KubeObject | null | undefined; - editable?: boolean; - removable?: boolean; - - dependencies: { - apiManager: ApiManager; - kubeObjectMenuItems: React.ElementType[]; - clusterName: string; - hideDetails: () => void; - editResourceTab: (kubeObject: KubeObject) => void; - }; +interface Dependencies { + apiManager: ApiManager; + kubeObjectMenuItems: React.ElementType[]; + clusterName: string; + hideDetails: () => void; + editResourceTab: (kubeObject: KubeObject) => void; } -class NonInjectedKubeObjectMenu extends React.Component { - get dependencies() { - return this.props.dependencies; - } - +class NonInjectedKubeObjectMenu extends React.Component & Dependencies> { + get store() { const { object } = this.props; if (!object) return null; - return this.props.dependencies.apiManager.getStore(object.selfLink); + return this.props.apiManager.getStore(object.selfLink); } get isEditable() { @@ -76,13 +66,13 @@ class NonInjectedKubeObjectMenu extends React.Component { @boundMethod async update() { - this.props.dependencies.hideDetails(); - this.props.dependencies.editResourceTab(this.props.object); + this.props.hideDetails(); + this.props.editResourceTab(this.props.object); } @boundMethod async remove() { - this.props.dependencies.hideDetails(); + this.props.hideDetails(); const { object, removeAction } = this.props; if (removeAction) await removeAction(); @@ -103,7 +93,7 @@ class NonInjectedKubeObjectMenu extends React.Component { return (

- Remove {object.kind} {breadcrumb} from {this.dependencies.clusterName}? + Remove {object.kind} {breadcrumb} from {this.props.clusterName}?

); } @@ -111,7 +101,7 @@ class NonInjectedKubeObjectMenu extends React.Component { getMenuItems(): React.ReactChild[] { const { object, toolbar } = this.props; - return this.props.dependencies.kubeObjectMenuItems.map((MenuItem, index) => ( + return this.props.kubeObjectMenuItems.map((MenuItem, index) => ( )); } @@ -134,19 +124,23 @@ class NonInjectedKubeObjectMenu extends React.Component { } } -export const KubeObjectMenu = withInjectables(NonInjectedKubeObjectMenu, { - getProps: (di, props) => ({ - dependencies: { - clusterName: di.inject(clusterNameInjectable), - apiManager: di.inject(apiManagerInjectable), - editResourceTab: di.inject(editResourceTabInjectable), - hideDetails: di.inject(hideDetailsInjectable), +export function KubeObjectMenu( + props: KubeObjectMenuProps, +) { + return withInjectables>( + NonInjectedKubeObjectMenu, + { + getProps: (di, props) => ({ + clusterName: di.inject(clusterNameInjectable), + apiManager: di.inject(apiManagerInjectable), + editResourceTab: di.inject(editResourceTabInjectable), + hideDetails: di.inject(hideDetailsInjectable), - kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, { - kubeObject: props.object, + kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, { + kubeObject: props.object, + }), + ...props, }), }, - - ...props, - }), -}); + )(props); +} diff --git a/yarn.lock b/yarn.lock index 5e1c8442aa..952789bf84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -979,19 +979,19 @@ dependencies: lodash "^4.17.21" -"@ogre-tools/injectable-react@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-1.4.1.tgz#48d8633462189939292596a66631d6717e39e47f" - integrity sha512-SRk3QXvFCEQk4MeVG8TAomGcOt0Pf06hZ5kBh+iNIug3FLYeyWagH6OSVylZRu4u2Izd89J0taS1GmSfYDoHaA== +"@ogre-tools/injectable-react@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-1.5.2.tgz#7df925ca5abda86210f527333774ddbf027f0693" + integrity sha512-n26NyGLYjwyIbfwsLj1tg0uNPK6SI/H0vGisMNpNfrcdci2QiLQBSKemnLMMRn7LF/+JhA/NQClTetiMkcSCuw== dependencies: "@ogre-tools/fp" "^1.4.0" - "@ogre-tools/injectable" "^1.4.1" + "@ogre-tools/injectable" "^1.5.0" lodash "^4.17.21" -"@ogre-tools/injectable@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-1.4.1.tgz#45414c6e13c870d7d84f4fa8e0dd67b33f6cc23e" - integrity sha512-vX4QXS/2d3g7oUenOKcv3mZRnJ5XewUMPsSsELjCyhL2caJlD0eB9J7y3y0eeFu/I18L8GC3DRs9o3QNshwN5Q== +"@ogre-tools/injectable@1.5.0", "@ogre-tools/injectable@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-1.5.0.tgz#edf911f360e73bb5f10ac669f147108d1465fc45" + integrity sha512-k0Wgc8QqB+p/gHcWPVWJV8N8xX5cMpagEjZ1C5bPVeRyB+73od/yHgb1HOgL2pPzlE6qOtTdkiUrWuTAoqnqUw== dependencies: "@ogre-tools/fp" "^1.4.0" lodash "^4.17.21" From 78678bdf2f03b3add559d9c9ef78a20908f04ee8 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 17 Dec 2021 10:28:59 -0500 Subject: [PATCH 2/6] Add support for customizing the extension install registry URL (#4503) --- src/common/user-store/preferences-helpers.ts | 31 +++++++++ src/common/user-store/user-store.ts | 5 +- src/common/utils/index.ts | 1 + .../attempt-install-by-info.tsx | 41 +++++++----- .../components/+extensions/extensions.tsx | 4 ++ .../get-base-registry-url.injectable.ts | 36 ++++++++++ .../get-base-registry-url.tsx | 57 ++++++++++++++++ .../components/+preferences/application.tsx | 65 +++++++++++++++---- 8 files changed, 208 insertions(+), 32 deletions(-) create mode 100644 src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts create mode 100644 src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts index 1f6050017e..450c75ae89 100644 --- a/src/common/user-store/preferences-helpers.ts +++ b/src/common/user-store/preferences-helpers.ts @@ -306,6 +306,36 @@ const updateChannel: PreferenceDescription = { }, }; +export enum ExtensionRegistryLocation { + DEFAULT = "default", + NPMRC = "npmrc", + CUSTOM = "custom", +} +export type ExtensionRegistry = { + location: ExtensionRegistryLocation.DEFAULT | ExtensionRegistryLocation.NPMRC; + customUrl?: undefined; +} | { + location: ExtensionRegistryLocation.CUSTOM, + customUrl: string; +}; + +export const defaultExtensionRegistryUrl = "https://registry.npmjs.org"; + +const extensionRegistryUrl: PreferenceDescription = { + fromStore(val) { + return val ?? { + location: ExtensionRegistryLocation.DEFAULT, + }; + }, + toStore(val) { + if (val.location === ExtensionRegistryLocation.DEFAULT) { + return undefined; + } + + return val; + }, +}; + type PreferencesModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; type UserStoreModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; @@ -335,6 +365,7 @@ export const DESCRIPTORS = { editorConfiguration, terminalCopyOnSelect, updateChannel, + extensionRegistryUrl, }; export const CONSTANTS = { diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index 3de94113d6..31238f0ded 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -29,7 +29,7 @@ import { kubeConfigDefaultPath } from "../kube-helpers"; import { appEventBus } from "../event-bus"; import path from "path"; import { ObservableToggleSet, toJS } from "../../renderer/utils"; -import { DESCRIPTORS, EditorConfiguration, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; +import { DESCRIPTORS, EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; import logger from "../../main/logger"; import { AppPaths } from "../app-paths"; @@ -75,6 +75,7 @@ export class UserStore extends BaseStore /* implements UserStore @observable kubectlBinariesPath?: string; @observable terminalCopyOnSelect: boolean; @observable updateChannel?: string; + @observable extensionRegistryUrl: ExtensionRegistry; /** * Download kubectl binaries matching cluster version @@ -201,6 +202,7 @@ export class UserStore extends BaseStore /* implements UserStore this.editorConfiguration = DESCRIPTORS.editorConfiguration.fromStore(preferences?.editorConfiguration); this.terminalCopyOnSelect = DESCRIPTORS.terminalCopyOnSelect.fromStore(preferences?.terminalCopyOnSelect); this.updateChannel = DESCRIPTORS.updateChannel.fromStore(preferences?.updateChannel); + this.extensionRegistryUrl = DESCRIPTORS.extensionRegistryUrl.fromStore(preferences?.extensionRegistryUrl); } toJSON(): UserStoreModel { @@ -224,6 +226,7 @@ export class UserStore extends BaseStore /* implements UserStore editorConfiguration: DESCRIPTORS.editorConfiguration.toStore(this.editorConfiguration), terminalCopyOnSelect: DESCRIPTORS.terminalCopyOnSelect.toStore(this.terminalCopyOnSelect), updateChannel: DESCRIPTORS.updateChannel.toStore(this.updateChannel), + extensionRegistryUrl: DESCRIPTORS.extensionRegistryUrl.toStore(this.extensionRegistryUrl), }, }; diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 733867f1cb..ea2f742bc7 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -48,6 +48,7 @@ export * from "./n-fircate"; export * from "./objects"; export * from "./openExternal"; export * from "./paths"; +export * from "./promise-exec"; export * from "./reject-promise"; export * from "./singleton"; export * from "./sort-compare"; diff --git a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx index bc5f54c147..c898fa0cc0 100644 --- a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx +++ b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx @@ -36,32 +36,39 @@ export interface ExtensionInfo { } export interface Dependencies { - attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise + attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise; + getBaseRegistryUrl: () => Promise; } -export const attemptInstallByInfo = ({ attemptInstall }: Dependencies) => async ({ +export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl }: Dependencies) => async ({ name, version, requireConfirmation = false, }: ExtensionInfo) => { const disposer = ExtensionInstallationStateStore.startPreInstall(); - const registryUrl = new URLParse("https://registry.npmjs.com") - .set("pathname", name) - .toString(); - const { promise } = downloadJson({ url: registryUrl }); - const json = await promise.catch(console.error); + const baseUrl = await getBaseRegistryUrl(); + const registryUrl = new URLParse(baseUrl).set("pathname", name).toString(); + let json: any; - if ( - !json || - json.error || - typeof json.versions !== "object" || - !json.versions - ) { - const message = json?.error ? `: ${json.error}` : ""; + try { + json = await downloadJson({ url: registryUrl }).promise; - Notifications.error( - `Failed to get registry information for that extension${message}`, - ); + if (!json || json.error || typeof json.versions !== "object" || !json.versions) { + const message = json?.error ? `: ${json.error}` : ""; + + Notifications.error(`Failed to get registry information for that extension${message}`); + + return disposer(); + } + } catch (error) { + if (error instanceof SyntaxError) { + // assume invalid JSON + console.warn("Set registry has invalid json", { url: baseUrl }, error); + Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON"); + } else { + console.error("Failed to download registry information", error); + Notifications.error(`Failed to get valid registry information for that extension. ${error}`); + } return disposer(); } diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 0ba9deba65..838635660d 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -68,6 +68,10 @@ class NonInjectedExtensions extends React.Component { makeObservable(this); } + get dependencies() { + return this.props.dependencies; + } + componentDidMount() { disposeOnUnmount(this, [ reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => { diff --git a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts new file mode 100644 index 0000000000..7f46d4a7b8 --- /dev/null +++ b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { UserStore } from "../../../../common/user-store"; +import { Dependencies, getBaseRegistryUrl } from "./get-base-registry-url"; + +const getBaseRegistryUrlInjectable: Injectable<() => Promise, Dependencies> = { + getDependencies: () => ({ + // TODO: use injection + getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl, + }), + + instantiate: getBaseRegistryUrl, + lifecycle: lifecycleEnum.singleton, +}; + +export default getBaseRegistryUrlInjectable; diff --git a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx new file mode 100644 index 0000000000..0d012ccf55 --- /dev/null +++ b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import React from "react"; +import { defaultExtensionRegistryUrl, ExtensionRegistry, ExtensionRegistryLocation } from "../../../../common/user-store/preferences-helpers"; +import { promiseExecFile } from "../../../utils"; +import { Notifications } from "../../notifications"; + +export interface Dependencies { + getRegistryUrlPreference: () => ExtensionRegistry, +} + +export const getBaseRegistryUrl = ({ getRegistryUrlPreference }: Dependencies) => async () => { + const extensionRegistryUrl = getRegistryUrlPreference(); + + switch (extensionRegistryUrl.location) { + case ExtensionRegistryLocation.CUSTOM: + return extensionRegistryUrl.customUrl; + + case ExtensionRegistryLocation.NPMRC: { + try { + const filteredEnv = Object.fromEntries( + Object.entries(process.env) + .filter(([key]) => !key.startsWith("npm")), + ); + const { stdout } = await promiseExecFile("npm", ["config", "get", "registry"], { env: filteredEnv }); + + return stdout.trim(); + } catch (error) { + Notifications.error(

Failed to get configured registry from .npmrc. Falling back to default registry

); + console.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", error); + // fallthrough + } + } + default: + case ExtensionRegistryLocation.DEFAULT: + return defaultExtensionRegistryUrl; + } +}; diff --git a/src/renderer/components/+preferences/application.tsx b/src/renderer/components/+preferences/application.tsx index 0fd1087953..40859cd5e0 100644 --- a/src/renderer/components/+preferences/application.tsx +++ b/src/renderer/components/+preferences/application.tsx @@ -29,7 +29,9 @@ import { Input } from "../input"; import { isWindows } from "../../../common/vars"; import { FormSwitch, Switcher } from "../switch"; import moment from "moment-timezone"; -import { CONSTANTS } from "../../../common/user-store/preferences-helpers"; +import { CONSTANTS, defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../common/user-store/preferences-helpers"; +import { action } from "mobx"; +import { isUrl } from "../input/input_validators"; import { AppPreferenceRegistry } from "../../../extensions/registries"; import { ExtensionSettings } from "./extension-settings"; @@ -43,6 +45,7 @@ const updateChannelOptions: SelectOption[] = Array.from( ); export const Application = observer(() => { + const userStore = UserStore.getInstance(); const defaultShell = process.env.SHELL || process.env.PTYSHELL || ( @@ -51,7 +54,8 @@ export const Application = observer(() => { : "System default shell" ); - const [shell, setShell] = React.useState(UserStore.getInstance().shell || ""); + const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || ""); + const [shell, setShell] = React.useState(userStore.shell || ""); const extensionSettings = AppPreferenceRegistry.getInstance().getItems().filter((preference) => preference.showInPreferencesTab === "application"); return ( @@ -61,8 +65,8 @@ export const Application = observer(() => { { + userStore.extensionRegistryUrl.location = value; + + if (userStore.extensionRegistryUrl.location === ExtensionRegistryLocation.CUSTOM) { + userStore.extensionRegistryUrl.customUrl = ""; + } + })} + themeName="lens" + /> +

+ This setting is to change the registry URL for installing extensions by name.{" "} + If you are unable to access the default registry ({defaultExtensionRegistryUrl}){" "} + you can change it in your .npmrc file or in the input below. +

+ + userStore.extensionRegistryUrl.customUrl = customUrl} + placeholder="Custom Extension Registry URL..." + disabled={userStore.extensionRegistryUrl.location !== ExtensionRegistryLocation.CUSTOM} + /> + + +
+
UserStore.getInstance().openAtLogin = v.target.checked} + checked={userStore.openAtLogin} + onChange={v => userStore.openAtLogin = v.target.checked} name="startup" /> } @@ -120,8 +157,8 @@ export const Application = observer(() => { UserStore.getInstance().setLocaleTimezone(value)} + value={userStore.localeTimezone} + onChange={({ value }) => userStore.setLocaleTimezone(value)} themeName="lens" />
From e9d99d8485d46be1bc6786ceffafbc4d5cbd954f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 17 Dec 2021 10:29:09 -0500 Subject: [PATCH 3/6] Replace all uses of promiseExec with promiseExecFile (#4514) --- .../__tests__/app-preferences.tests.ts | 3 +- src/common/system-ca.ts | 22 +- src/common/utils/promise-exec.ts | 3 +- src/main/helm/helm-release-manager.ts | 264 +++++++++++------- src/main/helm/helm-repo-manager.ts | 104 ++++--- src/main/helm/helm-service.ts | 16 +- src/main/kubectl.ts | 9 +- .../+preferences/add-helm-repo-dialog.tsx | 4 +- 8 files changed, 270 insertions(+), 155 deletions(-) diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index 47dffd6a90..8cdf19a548 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -71,7 +71,8 @@ describe("preferences page tests", () => { } }, 10*60*1000); - utils.itIf(process.platform !== "win32")("ensures helm repos", async () => { + // Skipping, but will turn it on again in the follow up PR + it.skip("ensures helm repos", async () => { await window.click("[data-testid=kubernetes-tab]"); await window.waitForSelector("[data-testid=repository-name]", { timeout: 140_000, diff --git a/src/common/system-ca.ts b/src/common/system-ca.ts index 22d85e3b42..bc7231a5f6 100644 --- a/src/common/system-ca.ts +++ b/src/common/system-ca.ts @@ -22,7 +22,7 @@ import { isMac, isWindows } from "./vars"; import wincaAPI from "win-ca/api"; import https from "https"; -import { promiseExec } from "./utils/promise-exec"; +import { promiseExecFile } from "./utils/promise-exec"; // DST Root CA X3, which was expired on 9.30.2021 export const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; @@ -33,19 +33,25 @@ export function isCertActive(cert: string) { return !isExpired; } +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions +const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g; + +async function execSecurity(...args: string[]): Promise { + const { stdout } = await promiseExecFile("/usr/bin/security", args); + + return stdout.split(certSplitPattern); +} + /** * Get root CA certificate from MacOSX system keychain * Only return non-expred certificates. */ export async function getMacRootCA() { // inspired mac-ca https://github.com/jfromaniello/mac-ca - const args = "find-certificate -a -p"; - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions - const splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g; - const systemRootCertsPath = "/System/Library/Keychains/SystemRootCertificates.keychain"; - const bin = "/usr/bin/security"; - const trusted = (await promiseExec(`${bin} ${args}`)).stdout.toString().split(splitPattern); - const rootCA = (await promiseExec(`${bin} ${args} ${systemRootCertsPath}`)).stdout.toString().split(splitPattern); + const [trusted, rootCA] = await Promise.all([ + execSecurity("find-certificate", "-a", "-p"), + execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"), + ]); return [...new Set([...trusted, ...rootCA])].filter(isCertActive); } diff --git a/src/common/utils/promise-exec.ts b/src/common/utils/promise-exec.ts index 8b9d5b72cc..b2d39ee110 100644 --- a/src/common/utils/promise-exec.ts +++ b/src/common/utils/promise-exec.ts @@ -20,7 +20,6 @@ */ import * as util from "util"; -import { exec, execFile } from "child_process"; +import { execFile } from "child_process"; -export const promiseExec = util.promisify(exec); export const promiseExecFile = util.promisify(execFile); diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 4f96562e5d..5a5f1e6ef2 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -22,161 +22,229 @@ import * as tempy from "tempy"; import fse from "fs-extra"; import * as yaml from "js-yaml"; -import { promiseExec } from "../../common/utils/promise-exec"; +import { promiseExecFile } from "../../common/utils/promise-exec"; import { helmCli } from "./helm-cli"; -import type { Cluster } from "../cluster"; import { toCamelCase } from "../../common/utils/camelCase"; +import type { BaseEncodingOptions } from "fs"; +import { execFile, ExecFileOptions } from "child_process"; -export async function listReleases(pathToKubeconfig: string, namespace?: string) { - const helm = await helmCli.binaryPath(); - const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces"; +async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise { + const helmCliPath = await helmCli.binaryPath(); try { - const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`); - const output = JSON.parse(stdout); + const { stdout } = await promiseExecFile(helmCliPath, args, options); - if (output.length == 0) { - return output; - } - output.forEach((release: any, index: number) => { - output[index] = toCamelCase(release); - }); - - return output; + return stdout; } catch (error) { throw error?.stderr || error; } } +export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise[]> { + const args = [ + "ls", + "--output", "json", + ]; -export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string) { - const helm = await helmCli.binaryPath(); - const fileName = tempy.file({ name: "values.yaml" }); + if (namespace) { + args.push("-n", namespace); + } else { + args.push("--all-namespaces"); + } - await fse.writeFile(fileName, yaml.dump(values)); + args.push("--kubeconfig", pathToKubeconfig); + + const output = JSON.parse(await execHelm(args)); + + if (!Array.isArray(output) || output.length == 0) { + return []; + } + + return output.map(toCamelCase); +} + + +export async function installChart(chart: string, values: any, name: string | undefined = "", namespace: string, version: string, kubeconfigPath: string) { + const valuesFilePath = tempy.file({ name: "values.yaml" }); + + await fse.writeFile(valuesFilePath, yaml.dump(values)); + + const args = ["install"]; + + if (name) { + args.push(name); + } + + args.push( + chart, + "--version", version, + "--values", valuesFilePath, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ); + + if (!name) { + args.push("--generate-name"); + } try { - let generateName = ""; - - if (!name) { - generateName = "--generate-name"; - name = ""; - } - const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`); - const releaseName = stdout.split("\n")[0].split(" ")[1].trim(); + const output = await execHelm(args); + const releaseName = output.split("\n")[0].split(" ")[1].trim(); return { - log: stdout, + log: output, release: { name: releaseName, namespace, }, }; - } catch (error) { - throw error?.stderr || error; } finally { - await fse.unlink(fileName); + await fse.unlink(valuesFilePath); } } -export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) { - const helm = await helmCli.binaryPath(); - const fileName = tempy.file({ name: "values.yaml" }); +export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, kubeconfigPath: string, kubectlPath: string) { + const valuesFilePath = tempy.file({ name: "values.yaml" }); - await fse.writeFile(fileName, yaml.dump(values)); + await fse.writeFile(valuesFilePath, yaml.dump(values)); + + const args = [ + "upgrade", + name, + chart, + "--version", version, + "--values", valuesFilePath, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ]; try { - const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); - const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`); + const output = await execHelm(args); return { - log: stdout, - release: getRelease(name, namespace, cluster), + log: output, + release: getRelease(name, namespace, kubeconfigPath, kubectlPath), }; - } catch (error) { - throw error?.stderr || error; } finally { - await fse.unlink(fileName); + await fse.unlink(valuesFilePath); } } -export async function getRelease(name: string, namespace: string, cluster: Cluster) { - try { - const helm = await helmCli.binaryPath(); - const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); +export async function getRelease(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) { + const args = [ + "status", + name, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + "--output", "json", + ]; - const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`, { - maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB - }); - const release = JSON.parse(stdout); + const release = JSON.parse(await execHelm(args, { + maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB + })); - release.resources = await getResources(name, namespace, cluster); + release.resources = await getResources(name, namespace, kubeconfigPath, kubectlPath); - return release; - } catch (error) { - throw error?.stderr || error; - } + return release; } -export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) { - try { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); - - return stdout; - } catch (error) { - throw error?.stderr || error; - } +export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string) { + return execHelm([ + "delete", + name, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ]); } interface GetValuesOptions { namespace: string; all?: boolean; - pathToKubeconfig: string; + kubeconfigPath: string; } -export async function getValues(name: string, { namespace, all = false, pathToKubeconfig }: GetValuesOptions) { - try { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all" : ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); +export async function getValues(name: string, { namespace, all = false, kubeconfigPath }: GetValuesOptions) { + const args = [ + "get", + "values", + name, + ]; - return stdout; - } catch (error) { - throw error?.stderr || error; + if (all) { + args.push("--all"); } + + args.push( + "--output", "yaml", + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ); + + return execHelm(args); } -export async function getHistory(name: string, namespace: string, pathToKubeconfig: string) { - try { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); - - return JSON.parse(stdout); - } catch (error) { - throw error?.stderr || error; - } +export async function getHistory(name: string, namespace: string, kubeconfigPath: string) { + return JSON.parse(await execHelm([ + "history", + name, + "--output", "json", + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ])); } -export async function rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) { - try { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); - - return stdout; - } catch (error) { - throw error?.stderr || error; - } +export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) { + return JSON.parse(await execHelm([ + "rollback", + name, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ])); } -async function getResources(name: string, namespace: string, cluster: Cluster) { - try { - const helm = await helmCli.binaryPath(); - const kubectl = await cluster.ensureKubectl(); - const kubectlPath = await kubectl.getPath(); - const pathToKubeconfig = await cluster.getProxyKubeconfigPath(); - const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectlPath}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`); +async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) { + const helmArgs = [ + "get", + "manifest", + name, + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + ]; + const kubectlArgs = [ + "get", + "--namespace", namespace, + "--kubeconfig", kubeconfigPath, + "-f", "-", + "--output", "json", + ]; - return JSON.parse(stdout).items; + try { + const helmOutput = await execHelm(helmArgs); + + return new Promise((resolve, reject) => { + let stdout = ""; + let stderr = ""; + const kubectl = execFile(kubectlPath, kubectlArgs); + + kubectl + .on("exit", (code, signal) => { + if (typeof code === "number") { + if (code === 0) { + resolve(JSON.parse(stdout).items); + } else { + reject(stderr); + } + } else { + reject(new Error(`Kubectl exited with signal ${signal}`)); + } + }) + .on("error", reject); + + kubectl.stderr.on("data", output => stderr += output); + kubectl.stdout.on("data", output => stdout += output); + kubectl.stdin.write(helmOutput); + kubectl.stdin.end(); + }); } catch { return []; } diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 1c36bef2be..f8edc12e00 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -20,13 +20,14 @@ */ import yaml from "js-yaml"; -import { readFile } from "fs-extra"; -import { promiseExec } from "../../common/utils/promise-exec"; +import { BaseEncodingOptions, readFile } from "fs-extra"; +import { promiseExecFile } from "../../common/utils/promise-exec"; import { helmCli } from "./helm-cli"; import { Singleton } from "../../common/utils/singleton"; import { customRequestPromise } from "../../common/request"; import orderBy from "lodash/orderBy"; import logger from "../logger"; +import type { ExecFileOptions } from "child_process"; export type HelmEnv = Record & { HELM_REPOSITORY_CACHE?: string; @@ -49,6 +50,18 @@ export interface HelmRepo { password?: string, } +async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise { + const helmCliPath = await helmCli.binaryPath(); + + try { + const { stdout } = await promiseExecFile(helmCliPath, args, options); + + return stdout; + } catch (error) { + throw error?.stderr || error; + } +} + export class HelmRepoManager extends Singleton { protected repos: HelmRepo[]; protected helmEnv: HelmEnv; @@ -77,11 +90,8 @@ export class HelmRepoManager extends Singleton { } protected static async parseHelmEnv() { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" env`).catch((error) => { - throw(error.stderr); - }); - const lines = stdout.split(/\r?\n/); // split by new line feed + const output = await execHelm(["env"]); + const lines = output.split(/\r?\n/); // split by new line feed const env: HelmEnv = {}; lines.forEach((line: string) => { @@ -135,57 +145,73 @@ export class HelmRepoManager extends Singleton { cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`, })); } catch (error) { - logger.error(`[HELM]: repositories listing error "${error}"`); + logger.error(`[HELM]: repositories listing error`, error); return []; } } public static async update() { - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" repo update`).catch((error) => { - return { stdout: error.stdout }; - }); - - return stdout; + return execHelm([ + "repo", + "update", + ]); } public static async addRepo({ name, url }: HelmRepo) { logger.info(`[HELM]: adding repo "${name}" from ${url}`); - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" repo add ${name} ${url}`).catch((error) => { - throw(error.stderr); - }); - return stdout; + return execHelm([ + "repo", + "add", + name, + url, + ]); } - public static async addCustomRepo(repoAttributes : HelmRepo) { - logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`); - const helm = await helmCli.binaryPath(); + public static async addCustomRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) { + logger.info(`[HELM]: adding repo ${name} from ${url}`); + const args = [ + "repo", + "add", + name, + url, + ]; - const insecureSkipTlsVerify = repoAttributes.insecureSkipTlsVerify ? " --insecure-skip-tls-verify" : ""; - const username = repoAttributes.username ? ` --username "${repoAttributes.username}"` : ""; - const password = repoAttributes.password ? ` --password "${repoAttributes.password}"` : ""; - const caFile = repoAttributes.caFile ? ` --ca-file "${repoAttributes.caFile}"` : ""; - const keyFile = repoAttributes.keyFile ? ` --key-file "${repoAttributes.keyFile}"` : ""; - const certFile = repoAttributes.certFile ? ` --cert-file "${repoAttributes.certFile}"` : ""; + if (insecureSkipTlsVerify) { + args.push("--insecure-skip-tls-verify"); + } - const addRepoCommand = `"${helm}" repo add ${repoAttributes.name} ${repoAttributes.url}${insecureSkipTlsVerify}${username}${password}${caFile}${keyFile}${certFile}`; - const { stdout } = await promiseExec(addRepoCommand).catch((error) => { - throw(error.stderr); - }); + if (username) { + args.push("--username", username); + } - return stdout; + 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 static async removeRepo({ name, url }: HelmRepo): Promise { - logger.info(`[HELM]: removing repo "${name}" from ${url}`); - const helm = await helmCli.binaryPath(); - const { stdout } = await promiseExec(`"${helm}" repo remove ${name}`).catch((error) => { - throw(error.stderr); - }); + logger.info(`[HELM]: removing repo ${name} (${url})`); - return stdout; + return execHelm([ + "repo", + "remove", + name, + ]); } } diff --git a/src/main/helm/helm-service.ts b/src/main/helm/helm-service.ts index 2e86e36513..4d4637b3b1 100644 --- a/src/main/helm/helm-service.ts +++ b/src/main/helm/helm-service.ts @@ -65,13 +65,19 @@ class HelmService { public async listReleases(cluster: Cluster, namespace: string = null) { 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, cluster); + return getRelease(releaseName, namespace, kubeconfigPath, kubectlPath); } public async getReleaseValues(releaseName: string, { cluster, namespace, all }: GetReleaseValuesArgs) { @@ -79,7 +85,7 @@ class HelmService { logger.debug("Fetch release values"); - return getValues(releaseName, { namespace, all, pathToKubeconfig }); + return getValues(releaseName, { namespace, all, kubeconfigPath: pathToKubeconfig }); } public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) { @@ -99,9 +105,13 @@ class HelmService { } public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) { + 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, cluster); + return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, proxyKubeconfig, kubectlPath); } public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) { diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index dbe4506082..2ac1831c41 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -21,7 +21,7 @@ import path from "path"; import fs from "fs"; -import { promiseExec } from "../common/utils/promise-exec"; +import { promiseExecFile } from "../common/utils/promise-exec"; import logger from "./logger"; import { ensureDir, pathExists } from "fs-extra"; import * as lockFile from "proper-lockfile"; @@ -199,7 +199,12 @@ export class Kubectl { if (exists) { try { - const { stdout } = await promiseExec(`"${path}" version --client=true -o json`); + const args = [ + "version", + "--client", "true", + "--output", "json", + ]; + const { stdout } = await promiseExecFile(path, args); const output = JSON.parse(stdout); if (!checkVersion) { diff --git a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx index d6760fa561..608c33bad2 100644 --- a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx +++ b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx @@ -121,7 +121,7 @@ export class AddHelmRepoDialog extends React.Component {
this.setFilepath(fileType, v)} @@ -172,7 +172,7 @@ export class AddHelmRepoDialog extends React.Component { close={this.close} > - {this.addCustomRepo();}}> + this.addCustomRepo()}>
Date: Fri, 17 Dec 2021 11:01:34 -0500 Subject: [PATCH 4/6] Fix not building (#4588) --- src/renderer/components/+extensions/extensions.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 838635660d..0ba9deba65 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -68,10 +68,6 @@ class NonInjectedExtensions extends React.Component { makeObservable(this); } - get dependencies() { - return this.props.dependencies; - } - componentDidMount() { disposeOnUnmount(this, [ reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => { From 8f194b87c3535d38749bf3cd7f868f64bbc870c9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 17 Dec 2021 11:53:00 -0500 Subject: [PATCH 5/6] Add getBaseRegistryUrl dep to attemptInstallByInfoInjectable (#4590) --- .../attempt-install-by-info.injectable.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts index 0db5c1677c..34fd491fe9 100644 --- a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts +++ b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts @@ -22,10 +22,12 @@ import type { Injectable } from "@ogre-tools/injectable"; import { lifecycleEnum } from "@ogre-tools/injectable"; import { attemptInstallByInfo, ExtensionInfo } from "./attempt-install-by-info"; import attemptInstallInjectable from "../attempt-install/attempt-install.injectable"; +import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable"; const attemptInstallByInfoInjectable: Injectable<(extensionInfo: ExtensionInfo) => Promise, {}> = { getDependencies: di => ({ attemptInstall: di.inject(attemptInstallInjectable), + getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable), }), instantiate: attemptInstallByInfo, From f8fa33ec8cde5dcc5b56f77262b6ccb233f13115 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Sun, 19 Dec 2021 23:18:01 -0500 Subject: [PATCH 6/6] Upgrade to latest version of @typescript-eslint/* pkgs (#4576) Signed-off-by: Sebastian Malton --- package.json | 4 +- yarn.lock | 117 +++++++++++++++++++++++++++------------------------ 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 52f521edb7..93af1a9bdf 100644 --- a/package.json +++ b/package.json @@ -324,8 +324,8 @@ "@types/webpack-dev-server": "^3.11.6", "@types/webpack-env": "^1.16.3", "@types/webpack-node-externals": "^1.7.1", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", + "@typescript-eslint/eslint-plugin": "^5.7.0", + "@typescript-eslint/parser": "^5.7.0", "ansi_up": "^5.1.0", "chart.js": "^2.9.4", "circular-dependency-plugin": "^5.2.2", diff --git a/yarn.lock b/yarn.lock index 952789bf84..e791116113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1558,7 +1558,7 @@ "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@^7.0.4": +"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== @@ -1568,11 +1568,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== -"@types/json-schema@^7.0.7": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== - "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -2081,75 +2076,75 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== +"@typescript-eslint/eslint-plugin@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.7.0.tgz#12d54709f8ea1da99a01d8a992cd0474ad0f0aa9" + integrity sha512-8RTGBpNn5a9M628wBPrCbJ+v3YTEOE2qeZb7TDkGKTDXSj36KGRg92SpFFaR/0S3rSXQxM0Og/kV9EyadsYSBg== dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/experimental-utils" "5.7.0" + "@typescript-eslint/scope-manager" "5.7.0" + debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/experimental-utils@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.7.0.tgz#2b1633e6613c3238036156f70c32634843ad034f" + integrity sha512-u57eZ5FbEpzN5kSjmVrSesovWslH2ZyNPnaXQMXWgH57d5+EVHEt76W75vVuI9qKZ5BMDKNfRN+pxcPEjQjb2A== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.7.0" + "@typescript-eslint/types" "5.7.0" + "@typescript-eslint/typescript-estree" "5.7.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== +"@typescript-eslint/parser@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.7.0.tgz#4dca6de463d86f02d252e681136a67888ea3b181" + integrity sha512-m/gWCCcS4jXw6vkrPQ1BjZ1vomP01PArgzvauBqzsoZ3urLbsRChexB8/YV8z9HwE3qlJM35FxfKZ1nfP/4x8g== dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/scope-manager" "5.7.0" + "@typescript-eslint/types" "5.7.0" + "@typescript-eslint/typescript-estree" "5.7.0" + debug "^4.3.2" -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== +"@typescript-eslint/scope-manager@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.7.0.tgz#70adf960e5a58994ad50438ba60d98ecadd79452" + integrity sha512-7mxR520DGq5F7sSSgM0HSSMJ+TFUymOeFRMfUfGFAVBv8BR+Jv1vHgAouYUvWRZeszVBJlLcc9fDdktxb5kmxA== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" + "@typescript-eslint/types" "5.7.0" + "@typescript-eslint/visitor-keys" "5.7.0" -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== +"@typescript-eslint/types@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.7.0.tgz#2d4cae0105ba7d08bffa69698197a762483ebcbe" + integrity sha512-5AeYIF5p2kAneIpnLFve8g50VyAjq7udM7ApZZ9JYjdPjkz0LvODfuSHIDUVnIuUoxafoWzpFyU7Sqbxgi79mA== -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== +"@typescript-eslint/typescript-estree@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.7.0.tgz#968fad899050ccce4f08a40cd5fabc0798525006" + integrity sha512-aO1Ql+izMrTnPj5aFFlEJkpD4jRqC4Gwhygu2oHK2wfVQpmOPbyDSveJ+r/NQo+PWV43M6uEAeLVbTi09dFLhg== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" + "@typescript-eslint/types" "5.7.0" + "@typescript-eslint/visitor-keys" "5.7.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== +"@typescript-eslint/visitor-keys@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.7.0.tgz#e05164239eb7cb8aa9fa06c516ede480ce260178" + integrity sha512-hdohahZ4lTFcglZSJ3DGdzxQHBSxsLVqHzkiOmKi7xVAWC4y2c1bIMKmPJSrA4aOEoRUPOKQ87Y/taC7yVHpFg== dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.7.0" + eslint-visitor-keys "^3.0.0" "@webassemblyjs/ast@1.9.0": version "1.9.0" @@ -5547,6 +5542,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== +eslint-visitor-keys@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" + integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== + eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -6594,7 +6594,7 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -globby@^11.0.1, globby@^11.0.3: +globby@^11.0.1, globby@^11.0.4: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -11864,6 +11864,11 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + registry-auth-token@^3.0.1: version "3.4.0" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"