diff --git a/packages/core/src/common/cluster-env.injectable.ts b/packages/core/src/common/cluster-env.injectable.ts new file mode 100644 index 0000000000..b3f44f2db1 --- /dev/null +++ b/packages/core/src/common/cluster-env.injectable.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import type { Cluster } from "./cluster/cluster"; +import { object } from "./utils"; + +function isDefinedEntry(entry: readonly [T, string | undefined]): entry is [T, string] { + return Boolean(entry[1]); +} + +export interface ClusterEnvironment { + HTTPS_PROXY?: string; + NO_PROXY?: string; +} + +const clusterEnvironmentInjectable = getInjectable({ + id: "cluster-environment", + instantiate: (di, cluster) => computed(() => { + const { preferences } = cluster; + const entries = [ + ["HTTPS_PROXY", preferences.httpsProxy], + ["NO_PROXY", preferences.noProxy], + ] as const; + + return object.fromEntries(entries.filter(isDefinedEntry)) as ClusterEnvironment; + }), + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, cluster: Cluster) => cluster.id, + }), +}); + +export default clusterEnvironmentInjectable; diff --git a/packages/core/src/common/cluster-types.ts b/packages/core/src/common/cluster-types.ts index ba01489152..204349e317 100644 --- a/packages/core/src/common/cluster-types.ts +++ b/packages/core/src/common/cluster-types.ts @@ -106,6 +106,7 @@ export interface ClusterPreferences extends ClusterPrometheusPreferences { */ icon?: string | null; httpsProxy?: string; + noProxy?: string; hiddenMetrics?: string[]; nodeShellImage?: string; imagePullSecret?: string; diff --git a/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts b/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts index ff70af3a7b..d61fed7cb5 100644 --- a/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts +++ b/packages/core/src/main/cluster/kube-auth-proxy-server.injectable.ts @@ -9,6 +9,7 @@ import type { Cluster } from "../../common/cluster/cluster"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable"; import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; +import clusterEnvironmentInjectable from "../../common/cluster-env.injectable"; export interface KubeAuthProxyServer { getApiTarget(isLongRunningRequest?: boolean): Promise; @@ -26,6 +27,7 @@ const kubeAuthProxyServerInjectable = getInjectable({ instantiate: (di, cluster): KubeAuthProxyServer => { const clusterUrl = new URL(cluster.apiUrl.get()); + const clusterEnvironment = di.inject(clusterEnvironmentInjectable, cluster); const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable); const certificate = di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname); @@ -34,7 +36,10 @@ const kubeAuthProxyServerInjectable = getInjectable({ const ensureServerHelper = async (): Promise => { if (!kubeAuthProxy) { - const proxyEnv = Object.assign({}, process.env); + const proxyEnv = { + ...process.env, + ...clusterEnvironment.get(), + }; if (cluster.preferences.httpsProxy) { proxyEnv.HTTPS_PROXY = cluster.preferences.httpsProxy; diff --git a/packages/core/src/main/resource-applier/resource-applier.ts b/packages/core/src/main/resource-applier/resource-applier.ts index 6df02debc8..f6da5dc4b7 100644 --- a/packages/core/src/main/resource-applier/resource-applier.ts +++ b/packages/core/src/main/resource-applier/resource-applier.ts @@ -17,6 +17,8 @@ import type { JoinPaths } from "../../common/path/join-paths.injectable"; import type { CreateKubectl } from "../kubectl/create-kubectl.injectable"; import type { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager"; import type { AsyncResult } from "@k8slens/utilities"; +import type { IComputedValue } from "mobx"; +import type { ClusterEnvironment } from "../../common/cluster-env.injectable"; export interface ResourceApplierDependencies { emitAppEvent: EmitAppEvent; @@ -27,6 +29,7 @@ export interface ResourceApplierDependencies { createKubectl: CreateKubectl; readonly proxyKubeconfigManager: KubeconfigManager; readonly logger: Logger; + readonly clusterEnvironment: IComputedValue; } export class ResourceApplier { @@ -100,17 +103,17 @@ export class ResourceApplier { this.dependencies.logger.debug(`shooting manifests with ${kubectlPath}`, { args }); - const execEnv = { ...process.env }; - const httpsProxy = this.cluster.preferences?.httpsProxy; - - if (httpsProxy) { - execEnv.HTTPS_PROXY = httpsProxy; - } + const execEnv = { + ...process.env, + ...this.dependencies.clusterEnvironment.get(), + }; try { await this.dependencies.writeFile(fileName, content); - const result = await this.dependencies.execFile(kubectlPath, args); + const result = await this.dependencies.execFile(kubectlPath, args, { + env: execEnv, + }); if (result.callWasSuccessful) { return result; diff --git a/packages/core/src/main/shell-session/shell-session.ts b/packages/core/src/main/shell-session/shell-session.ts index ae1434287a..4ba81cc25b 100644 --- a/packages/core/src/main/shell-session/shell-session.ts +++ b/packages/core/src/main/shell-session/shell-session.ts @@ -19,6 +19,7 @@ import type { InitializableState } from "../../common/initializable-state/create import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable"; import type { Stat } from "../../common/fs/stat.injectable"; import type { IComputedValue } from "mobx"; +import type { ClusterEnvironment } from "../../common/cluster-env.injectable"; export class ShellOpenError extends Error { constructor(message: string, options?: ErrorOptions) { @@ -113,6 +114,7 @@ export interface ShellSessionDependencies { readonly buildVersion: InitializableState; readonly proxyKubeconfigPath: string; readonly directoryContainingKubectl: string; + readonly clusterEnvironment: IComputedValue; computeShellEnvironment: ComputeShellEnvironment; spawnPty: SpawnPty; emitAppEvent: EmitAppEvent; @@ -347,7 +349,10 @@ export abstract class ShellSession { return process.env; })(); - const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(rawEnv))); + const env: Partial> = { + ...clearKubeconfigEnvVars(JSON.parse(JSON.stringify(rawEnv))), + ...this.dependencies.clusterEnvironment.get(), + }; const pathStr = [this.dependencies.directoryContainingKubectl, ...this.getPathEntries(), env.PATH].join(path.delimiter); delete env.DEBUG; // don't pass DEBUG into shells @@ -380,10 +385,6 @@ export abstract class ShellSession { env.TERM_PROGRAM = this.dependencies.appName; env.TERM_PROGRAM_VERSION = this.dependencies.buildVersion.get(); - if (this.cluster.preferences.httpsProxy) { - env.HTTPS_PROXY = this.cluster.preferences.httpsProxy; - } - env.NO_PROXY = [ "localhost", "127.0.0.1", diff --git a/packages/core/src/renderer/components/cluster-settings/proxy-setting.tsx b/packages/core/src/renderer/components/cluster-settings/proxy-setting.tsx index 7210de36e4..2946d0b2da 100644 --- a/packages/core/src/renderer/components/cluster-settings/proxy-setting.tsx +++ b/packages/core/src/renderer/components/cluster-settings/proxy-setting.tsx @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import React from "react"; -import { observable, autorun, makeObservable } from "mobx"; -import { observer, disposeOnUnmount } from "mobx-react"; +import React, { useEffect, useState } from "react"; +import { action } from "mobx"; +import { observer } from "mobx-react"; import type { Cluster } from "../../../common/cluster/cluster"; import { Input, InputValidators } from "../input"; import { SubTitle } from "../layout/sub-title"; @@ -14,47 +14,47 @@ export interface ClusterProxySettingProps { cluster: Cluster; } -@observer -export class ClusterProxySetting extends React.Component { - @observable proxy = ""; +export const ClusterProxySetting = observer((props: ClusterProxySettingProps) => { + const { cluster } = props; - constructor(props: ClusterProxySettingProps) { - super(props); - makeObservable(this); - } + const [httpsProxy, setHttpsProxy] = useState(cluster.preferences.httpsProxy || ""); + const [noProxy, setNoProxy] = useState(cluster.preferences.noProxy || ""); - componentDidMount() { - disposeOnUnmount(this, - autorun(() => { - this.proxy = this.props.cluster.preferences.httpsProxy || ""; - }), - ); - } + useEffect(() => action(() => { + cluster.preferences.httpsProxy = httpsProxy; + cluster.preferences.noProxy = noProxy; + }), []); - save = () => { - this.props.cluster.preferences.httpsProxy = this.proxy; - }; - - onChange = (value: string) => { - this.proxy = value; - }; - - render() { - return ( - <> - - - - HTTP Proxy server. Used for communicating with Kubernetes API. - - - ); - } -} + return ( + <> + + { + props.cluster.preferences.httpsProxy = httpsProxy; + }} + placeholder="https://
:" + validators={httpsProxy ? InputValidators.isUrl : undefined} + /> + + HTTPS Proxy server. Used for communicating with Kubernetes API. + + + { + props.cluster.preferences.noProxy = noProxy; + }} + placeholder="" + validators={noProxy ? InputValidators.isUrl : undefined} + /> + + NO_PROXY configuration. Useful for when specifying that cluster communication shouldn't go through the default proxy. + + + ); +});