diff --git a/README.md b/README.md index adeb5084f..bf4963847 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Minikube tools to be installed and available on your PATH. * `vs-kubernetes.kubectl-path` - File path to the kubectl binary. Note this is the binary file itself, not just the directory containing the file. On Windows, this must contain the `.exe` extension. * `vs-kubernetes.helm-path` - File path to the helm binary. Note this is the binary file itself, not just the directory containing the file. On Windows, this must contain the `.exe` extension. * `vs-kubernetes.draft-path` - File path to the draft binary. Note this is the binary file itself, not just the directory containing the file. On Windows, this must contain the `.exe` extension (note current versions of Draft are not supported on Windows). + * `vs-kubernetes.minikube-path` - File path to the minikube binary. Note this is the binary file itself, not just the directory containing the file. On Windows, this must contain the `.exe` extension. * `vs-kubernetes.kubeconfig` - File path to the kubeconfig file you want to use. This overrides both the default kubeconfig and the KUBECONFIG environment variable. * `vs-kubernetes.knownKubeconfigs` - An array of file paths of kubeconfig files that you want to be able to quickly switch between using the Set Kubeconfig command. * `vs-kubernetes.autoCleanupOnDebugTerminate` - The flag to control whether to auto cleanup the created deployment and associated pod by the command "Kubernetes: Debug (Launch)". The cleanup action occurs when it failed to start debug session or debug session terminated. If not specified, the extension will prompt for whether to clean up or not. You might choose not to clean up if you wanted to view pod logs, etc. diff --git a/package.json b/package.json index b701f9ae9..2ede0538e 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,10 @@ "type": "string", "description": "File path to a draft binary." }, + "vs-kubernetes.minikube-path": { + "type": "string", + "description": "File path to a minikube binary." + }, "vs-kubernetes.kubeconfig": { "type": "string", "description": "File path to the kubeconfig file." @@ -134,6 +138,7 @@ "vs-kubernetes.kubectl-path": "", "vs-kubernetes.helm-path": "", "vs-kubernetes.draft-path": "", + "vs-kubernetes.minikube-path": "", "vs-kubernetes.outputFormat": "yaml", "vs-kubernetes.kubeconfig": "", "vs-kubernetes.knownKubeconfigs": [], diff --git a/src/components/clusterprovider/minikube/minikube.ts b/src/components/clusterprovider/minikube/minikube.ts index 5dbbc8199..b3613704e 100644 --- a/src/components/clusterprovider/minikube/minikube.ts +++ b/src/components/clusterprovider/minikube/minikube.ts @@ -7,6 +7,7 @@ import { FS } from '../../../fs'; import * as binutil from '../../../binutil'; import { Errorable } from '../../../errorable'; import { fromShellExitCodeOnly, Diagnostic } from '../../../wizard'; +import { getToolPath } from '../../config/config'; export class MinikubeInfo { readonly running: boolean; @@ -182,9 +183,9 @@ async function checkPresent(context: Context, mode: CheckPresentMode): Promise { const binName = 'minikube'; - const bin = context.host.getConfiguration('vs-kubernetes')[`vs-kubernetes.${binName}-path`]; + const bin = getToolPath(context.host, binName); - const inferFailedMessage = 'Could not find "minikube" binary.'; - const configuredFileMissingMessage = bin + ' does not exist!'; + const inferFailedMessage = `Could not find "${binName}" binary.`; + const configuredFileMissingMessage = `${bin} does not exist!`; return binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, mode === CheckPresentMode.Alert); } diff --git a/src/components/config/config.ts b/src/components/config/config.ts index 94aa85f9e..03022edd5 100644 --- a/src/components/config/config.ts +++ b/src/components/config/config.ts @@ -1,17 +1,19 @@ import * as vscode from 'vscode'; +import { Host } from '../../host'; const EXTENSION_CONFIG_KEY = "vs-kubernetes"; const KUBECONFIG_PATH_KEY = "vs-kubernetes.kubeconfig"; const KNOWN_KUBECONFIGS_KEY = "vs-kubernetes.knownKubeconfigs"; export async function addPathToConfig(configKey: string, value: string): Promise { - const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY); - await addPathToConfigAtScope(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true); - await addPathToConfigAtScope(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false); - await addPathToConfigAtScope(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false); + await setConfigValue(configKey, value); +} + +async function setConfigValue(configKey: string, value: any): Promise { + await atAllConfigScopes(addValueToConfigAtScope, configKey, value); } -async function addPathToConfigAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise { +async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise { if (!createIfNotExist) { if (!valueAtScope || !(valueAtScope[configKey])) { return; @@ -27,10 +29,7 @@ async function addPathToConfigAtScope(configKey: string, value: string, scope: v } async function addValueToConfigArray(configKey: string, value: string): Promise { - const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY); - await addValueToConfigArrayAtScope(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true); - await addValueToConfigArrayAtScope(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false); - await addValueToConfigArrayAtScope(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false); + await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value); } async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise { @@ -50,6 +49,15 @@ async function addValueToConfigArrayAtScope(configKey: string, value: string, sc await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope); } +type ConfigUpdater = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise; + +async function atAllConfigScopes(fn: ConfigUpdater, configKey: string, value: T): Promise { + const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY); + await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true); + await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false); + await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false); +} + // Functions for working with the list of known kubeconfigs export function getKnownKubeconfigs(): string[] { @@ -73,3 +81,39 @@ export async function setActiveKubeconfig(kubeconfig: string): Promise { export function getActiveKubeconfig(): string { return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY]; } + +// Functions for working with tool paths + +export function getToolPath(host: Host, tool: string): string | undefined { + return host.getConfiguration(EXTENSION_CONFIG_KEY)[toolPathKey(tool)]; +} + +export function toolPathKey(tool: string) { + return `vs-kubernetes.${tool}-path`; +} + +// Auto cleanup on debug terminate + +const AUTO_CLEANUP_DEBUG_KEY = "vs-kubernetes.autoCleanupOnDebugTerminate"; + +export function getAutoCompleteOnDebugTerminate(): boolean { + return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[AUTO_CLEANUP_DEBUG_KEY]; +} + +export async function setAlwaysCleanUp(): Promise { + await setConfigValue(AUTO_CLEANUP_DEBUG_KEY, true); +} + +// Other bits and bobs + +export function getOutputFormat(): string { + return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)['vs-kubernetes.outputFormat']; +} + +export function getConfiguredNamespace(): string | undefined { + return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)['vs-kubernetes.namespace']; +} + +export function affectsUs(change: vscode.ConfigurationChangeEvent) { + return change.affectsConfiguration(EXTENSION_CONFIG_KEY); +} diff --git a/src/components/installer/installer.ts b/src/components/installer/installer.ts index 10914ac36..c39c48547 100644 --- a/src/components/installer/installer.ts +++ b/src/components/installer/installer.ts @@ -8,7 +8,7 @@ import * as tar from 'tar'; import * as vscode from 'vscode'; import { Shell, Platform } from '../../shell'; import { Errorable, failed, succeeded } from '../../errorable'; -import { addPathToConfig } from '../config/config'; +import { addPathToConfig, toolPathKey } from '../config/config'; export async function installKubectl(shell: Shell): Promise> { const tool = 'kubectl'; @@ -34,7 +34,7 @@ export async function installKubectl(shell: Shell): Promise> { fs.chmodSync(downloadFile, '0777'); } - await addPathToConfig(`vs-kubernetes.${tool}-path`, downloadFile); + await addPathToConfig(toolPathKey(tool), downloadFile); return { succeeded: true, result: null }; } @@ -79,7 +79,7 @@ export async function installMinikube(shell: Shell): Promise> { if (shell.isUnix()) { await shell.exec(`chmod +x ${executableFullPath}`); } - const configKey = `vs-kubernetes.${tool}-path`; + const configKey = toolPathKey(tool); await addPathToConfig(configKey, executableFullPath); return { succeeded: true, result: null }; @@ -93,7 +93,7 @@ async function installToolFromTar(tool: string, urlTemplate: string, shell: Shel const installFolder = getInstallFolder(shell, tool); const executable = formatBin(tool, shell.platform()); const url = urlTemplate.replace('{os_placeholder}', os); - const configKey = `vs-kubernetes.${tool}-path`; + const configKey = toolPathKey(tool); return installFromTar(url, installFolder, executable, configKey); } diff --git a/src/debug/debugSession.ts b/src/debug/debugSession.ts index d991d5a6e..c375603e8 100644 --- a/src/debug/debugSession.ts +++ b/src/debug/debugSession.ts @@ -16,6 +16,7 @@ import { shell } from "../shell"; import { DockerfileParser } from "../docker/dockerfileParser"; import * as dockerUtils from "../docker/dockerUtils"; import { isPod, Pod, KubernetesCollection, Container } from "../kuberesources.objectmodel"; +import * as config from "../components/config/config"; const debugCommandDocumentationUrl = "https://github.com/Azure/vscode-kubernetes-tools/blob/master/debug-on-kubernetes.md"; @@ -289,7 +290,7 @@ export class DebugSession implements IDebugSession { } private async promptForCleanup(resourceId: string): Promise { - const autoCleanupFlag = vscode.workspace.getConfiguration("vs-kubernetes")["vs-kubernetes.autoCleanupOnDebugTerminate"]; + const autoCleanupFlag = config.getAutoCompleteOnDebugTerminate(); if (autoCleanupFlag) { return await this.cleanupResource(resourceId); } @@ -297,22 +298,7 @@ export class DebugSession implements IDebugSession { if (answer === "Clean Up") { return await this.cleanupResource(resourceId); } else if (answer === "Always Clean Up") { - // The object returned by VS Code API is readonly, clone it first. - const oldConfig = vscode.workspace.getConfiguration().inspect("vs-kubernetes"); - // Always update global config. - const globalConfig = Object.assign({}, oldConfig.globalValue); - globalConfig["vs-kubernetes.autoCleanupOnDebugTerminate"] = true; - await vscode.workspace.getConfiguration().update("vs-kubernetes", globalConfig, vscode.ConfigurationTarget.Global); - // If workspace folder value exists, update it. - if (oldConfig.workspaceFolderValue && oldConfig.workspaceFolderValue["vs-kubernetes.autoCleanupOnDebugTerminate"] === false) { - const workspaceFolderConfig = Object.assign({}, oldConfig.workspaceFolderValue); - workspaceFolderConfig["vs-kubernetes.autoCleanupOnDebugTerminate"] = true; - await vscode.workspace.getConfiguration().update("vs-kubernetes", workspaceFolderConfig, vscode.ConfigurationTarget.WorkspaceFolder); - } else if (oldConfig.workspaceValue && oldConfig.workspaceValue["vs-kubernetes.autoCleanupOnDebugTerminate"] === false) { // if workspace value exists, update it. - const workspaceConfig = Object.assign({}, oldConfig.workspaceValue); - workspaceConfig["vs-kubernetes.autoCleanupOnDebugTerminate"] = true; - await vscode.workspace.getConfiguration().update("vs-kubernetes", workspaceConfig, vscode.ConfigurationTarget.Workspace); - } + await config.setAlwaysCleanUp(); return await this.cleanupResource(resourceId); } } diff --git a/src/draft/draft.ts b/src/draft/draft.ts index bb1ae1110..fc140b499 100644 --- a/src/draft/draft.ts +++ b/src/draft/draft.ts @@ -4,6 +4,7 @@ import { FS } from '../fs'; import * as syspath from 'path'; import * as binutil from '../binutil'; import { Errorable } from '../errorable'; +import { getToolPath } from '../components/config/config'; export interface Draft { checkPresent(mode: CheckPresentMode): Promise; @@ -144,10 +145,10 @@ async function version(context: Context): Promise> { async function checkForDraftInternal(context: Context, mode: CheckPresentMode): Promise { const binName = 'draft'; - const bin = context.host.getConfiguration('vs-kubernetes')[`vs-kubernetes.${binName}-path`]; + const bin = getToolPath(context.host, binName); - const inferFailedMessage = 'Could not find "draft" binary.'; - const configuredFileMissingMessage = bin + ' does not exist!'; + const inferFailedMessage = `Could not find "${binName}" binary.`; + const configuredFileMissingMessage = `${bin} does not exist!`; return binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, mode === CheckPresentMode.Alert); } diff --git a/src/explorer.ts b/src/explorer.ts index 093e2dc6b..94800ad3b 100644 --- a/src/explorer.ts +++ b/src/explorer.ts @@ -8,6 +8,7 @@ import * as kuberesources from './kuberesources'; import { failed } from './errorable'; import * as helmexec from './helm.exec'; import { K8S_RESOURCE_SCHEME, KUBECTL_RESOURCE_AUTHORITY, kubefsUri } from './kuberesources.virtualfs'; +import { affectsUs } from './components/config/config'; const KUBERNETES_CLUSTER = "vsKubernetes.cluster"; const MINIKUBE_CLUSTER = "vsKubernetes.minikubeCluster"; @@ -64,7 +65,7 @@ export class KubernetesExplorer implements vscode.TreeDataProvider { - if (change.affectsConfiguration('vs-kubernetes')) { + if (affectsUs(change)) { this.refresh(); } }); diff --git a/src/extension.ts b/src/extension.ts index 3e1dc75e5..be0f88c81 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -528,7 +528,7 @@ function maybeRunKubernetesCommandForActiveWindow(command: string, progressMessa return false; // No open text editor } - const namespace = vscode.workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.namespace']; + const namespace = config.getConfiguredNamespace(); if (namespace) { command = `${command} --namespace ${namespace} `; } @@ -677,7 +677,7 @@ function loadKubernetes(explorerNode?: explorer.ResourceNode) { } function loadKubernetesCore(namespace: string | null, value: string) { - const outputFormat = vscode.workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.outputFormat']; + const outputFormat = config.getOutputFormat(); const uri = kubefsUri(namespace, value, outputFormat); vscode.workspace.openTextDocument(uri).then((doc) => { if (doc) { diff --git a/src/helm.exec.ts b/src/helm.exec.ts index 08e7bfa35..c4596146a 100644 --- a/src/helm.exec.ts +++ b/src/helm.exec.ts @@ -19,6 +19,8 @@ import { parseLineOutput } from './outputUtils'; import { sleep } from './sleep'; import { currentNamespace } from './kubectlUtils'; import { Kubectl } from './kubectl'; +import { getToolPath } from './components/config/config'; +import { host } from './host'; export interface PickChartUIOptions { readonly warnIfNoCharts: boolean; @@ -472,7 +474,7 @@ export function helmExec(args: string, fn) { if (!ensureHelm(EnsureMode.Alert)) { return; } - const configuredBin: string | undefined = vscode.workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.helm-path']; + const configuredBin: string | undefined = getToolPath(host, 'helm'); const bin = configuredBin ? `"${configuredBin}"` : "helm"; const cmd = `${bin} ${args}`; shell.exec(cmd, fn); @@ -483,7 +485,7 @@ export async function helmExecAsync(args: string): Promise { if (!ensureHelm(EnsureMode.Alert)) { return { code: -1, stdout: "", stderr: "" }; } - const configuredBin: string | undefined = vscode.workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.helm-path']; + const configuredBin: string | undefined = getToolPath(host, 'helm'); const bin = configuredBin ? `"${configuredBin}"` : "helm"; const cmd = `${bin} ${args}`; return await sh.exec(cmd); @@ -529,7 +531,7 @@ export async function helmListAll(namespace?: string): Promise { - const configuredBin: string | undefined = vscode.workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.helm-path']; + const configuredBin: string | undefined = getToolPath(host, 'helm'); const bin = sh.unquotedPath(configuredBin ? `"${configuredBin}"` : "helm"); const process = spawn(bin, [ "serve" ]); let ready = false; diff --git a/src/helm.repoExplorer.ts b/src/helm.repoExplorer.ts index 5aaca285a..bf4b63b45 100644 --- a/src/helm.repoExplorer.ts +++ b/src/helm.repoExplorer.ts @@ -7,6 +7,7 @@ import * as helm from './helm.exec'; import { HELM_OUTPUT_COLUMN_SEPARATOR } from './helm'; import { Errorable, failed } from './errorable'; import { parseLineOutput } from './outputUtils'; +import { affectsUs } from './components/config/config'; export function create(host: Host): HelmRepoExplorer { return new HelmRepoExplorer(host); @@ -56,7 +57,7 @@ export class HelmRepoExplorer implements vscode.TreeDataProvider { constructor(private readonly host: Host) { host.onDidChangeConfiguration((change) => { - if (change.affectsConfiguration('vs-kubernetes')) { + if (affectsUs(change)) { this.refresh(); } }); diff --git a/src/kubectl.ts b/src/kubectl.ts index 4d9b2bb8f..35b791c4c 100644 --- a/src/kubectl.ts +++ b/src/kubectl.ts @@ -7,6 +7,7 @@ import * as binutil from './binutil'; import { Errorable } from './errorable'; import { parseLineOutput } from './outputUtils'; import * as compatibility from './components/kubectl/compatibility'; +import { getToolPath, affectsUs } from './components/config/config'; const KUBECTL_OUTPUT_COLUMN_SEPARATOR = /\s+/g; @@ -97,7 +98,7 @@ class KubectlImpl implements Kubectl { } }); this.context.host.onDidChangeConfiguration((change) => { - if (change.affectsConfiguration('vs-kubernetes') && this.sharedTerminal) { + if (affectsUs(change) && this.sharedTerminal) { this.sharedTerminal.dispose(); } }); @@ -122,10 +123,10 @@ async function checkPresent(context: Context, errorMessageMode: CheckPresentMess async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise { const binName = 'kubectl'; - const bin = context.host.getConfiguration('vs-kubernetes')[`vs-kubernetes.${binName}-path`]; + const bin = getToolPath(context.host, binName); const contextMessage = getCheckKubectlContextMessage(errorMessageMode); - const inferFailedMessage = 'Could not find "kubectl" binary.' + contextMessage; + const inferFailedMessage = `Could not find "${binName}" binary.${contextMessage}`; const configuredFileMissingMessage = `${bin} does not exist! ${contextMessage}`; return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== 'silent'); @@ -229,7 +230,7 @@ function kubectlDone(context: Context): ShellHandler { } function baseKubectlPath(context: Context): string { - let bin = context.host.getConfiguration('vs-kubernetes')['vs-kubernetes.kubectl-path']; + let bin = getToolPath(context.host, 'kubectl'); if (!bin) { bin = 'kubectl'; } diff --git a/src/kuberesources.virtualfs.ts b/src/kuberesources.virtualfs.ts index c729757ea..c6c0b6278 100644 --- a/src/kuberesources.virtualfs.ts +++ b/src/kuberesources.virtualfs.ts @@ -7,6 +7,7 @@ import { Kubectl } from './kubectl'; import { Host } from './host'; import { ShellResult } from './shell'; import { helmExecAsync } from './helm.exec'; +import * as config from './components/config/config'; export const K8S_RESOURCE_SCHEME = "k8smsx"; export const KUBECTL_RESOURCE_AUTHORITY = "loadkubernetescore"; @@ -63,7 +64,7 @@ export class KubernetesResourceVirtualFileSystemProvider implements FileSystemPr async loadResource(uri: Uri): Promise { const query = querystring.parse(uri.query); - const outputFormat = workspace.getConfiguration('vs-kubernetes')['vs-kubernetes.outputFormat']; + const outputFormat = config.getOutputFormat(); const value = query.value; const ns = query.ns; const resourceAuthority = uri.authority; diff --git a/src/shell.ts b/src/shell.ts index 50d425be6..e98cf456d 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -3,7 +3,8 @@ import * as vscode from 'vscode'; import * as shelljs from 'shelljs'; import * as path from 'path'; -import { getActiveKubeconfig } from './components/config/config'; +import { getActiveKubeconfig, getToolPath } from './components/config/config'; +import { host } from './host'; export enum Platform { Windows, @@ -133,7 +134,7 @@ export function shellEnvironment(baseEnvironment: any): any { const env = Object.assign({}, baseEnvironment); const pathVariable = pathVariableName(env); for (const tool of ['kubectl', 'helm', 'draft', 'minikube']) { - const toolPath = vscode.workspace.getConfiguration('vs-kubernetes')[`vs-kubernetes.${tool}-path`]; + const toolPath = getToolPath(host, tool); if (toolPath) { const toolDirectory = path.dirname(toolPath); const currentPath = env[pathVariable];