Skip to content

Commit

Permalink
Add setting for Docker CLI executable path (#2984)
Browse files Browse the repository at this point in the history
Provides a workaround for [issue 2894](#2894)
  • Loading branch information
karolz-ms authored Jun 3, 2021
1 parent a19ea5c commit 336b12d
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 29 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2201,6 +2201,12 @@
"type": "boolean",
"default": true,
"description": "%vscode-docker.config.docker.showStartPage%"
},
"docker.dockerPath": {
"type": "string",
"default": "docker",
"description": "%vscode-docker.config.docker.dockerPath%",
"scope": "machine-overridable"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"vscode-docker.config.docker.volumes.sortBy": "The property to use to sort volumes in Docker view: CreatedTime or Label",
"vscode-docker.config.docker.imageBuildContextPath": "Build context PATH to pass to Docker build command.",
"vscode-docker.config.docker.truncateLongRegistryPaths": "Set to true to truncate long image and container registry paths in Docker view",
"vscode-docker.config.docker.truncateMaxLength": "Maximum length of a registry paths displayed in Docker view, including elipsis. The truncateLongRegistryPaths setting must be set to true for truncateMaxLength setting to be effective.",
"vscode-docker.config.docker.truncateMaxLength": "Maximum length of a registry paths displayed in Docker view, including ellipsis. The truncateLongRegistryPaths setting must be set to true for truncateMaxLength setting to be effective.",
"vscode-docker.config.docker.dockerodeOptions": "If specified, this object will be passed to the Dockerode constructor. Takes precedence over DOCKER_HOST, the Docker Host setting, and any existing Docker contexts.",
"vscode-docker.config.docker.host": "Equivalent to setting the DOCKER_HOST environment variable, for example, ssh://myuser@mymachine or tcp://1.2.3.4.",
"vscode-docker.config.docker.context": "Equivalent to setting the DOCKER_CONTEXT environment variable.",
Expand All @@ -188,6 +188,7 @@
"vscode-docker.config.docker.showRemoteWorkspaceWarning": "Set to true to prompt to switch from \"UI\" extension mode to \"Workspace\" extension mode if an operation is not supported in UI mode.",
"vscode-docker.config.docker.scaffolding.templatePath": "The path to use for scaffolding templates.",
"vscode-docker.config.docker.showStartPage": "Show the Docker extension Start Page when a new update is released.",
"vscode-docker.config.docker.dockerPath": "Absolute path to Docker client executable ('docker' command). If the path contains whitespace, it needs to be quoted appropriately.",
"vscode-docker.config.deprecated": "This setting has been deprecated and will be removed in a future release.",
"vscode-docker.commands.compose.down": "Compose Down",
"vscode-docker.commands.compose.restart": "Compose Restart",
Expand Down
3 changes: 2 additions & 1 deletion src/commands/containers/attachShellContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DockerOSType } from '../../docker/Common';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { executeAsTask } from '../../utils/executeAsTask';
import { execAsync } from '../../utils/spawnAsync';
import { selectAttachCommand } from '../selectCommandTemplate';
Expand Down Expand Up @@ -46,7 +47,7 @@ export async function attachShellContainer(context: IActionContext, node?: Conta
// If so use it, otherwise use sh
try {
// If this succeeds, bash is present (exit code 0)
await execAsync(`docker exec -i ${node.containerId} sh -c "which bash"`);
await execAsync(`${dockerExePath(context)} exec -i ${node.containerId} sh -c "which bash"`);
shellCommand = 'bash';
} catch {
shellCommand = 'sh';
Expand Down
3 changes: 2 additions & 1 deletion src/commands/images/pullImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IActionContext } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { ImageTreeItem } from '../../tree/images/ImageTreeItem';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { executeAsTask } from '../../utils/executeAsTask';
import { multiSelectNodes } from '../../utils/multiSelectNodes';

Expand All @@ -33,6 +34,6 @@ export async function pullImage(context: IActionContext, node?: ImageTreeItem, n
continue;
}

await executeAsTask(context, `docker pull ${n.fullTag}`, 'docker pull', { addDockerEnv: true });
await executeAsTask(context, `${dockerExePath(context)} pull ${n.fullTag}`, 'docker pull', { addDockerEnv: true });
}
}
3 changes: 2 additions & 1 deletion src/commands/images/pushImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { localize } from '../../localize';
import { ImageTreeItem } from '../../tree/images/ImageTreeItem';
import { registryExpectedContextValues } from '../../tree/registries/registryContextValues';
import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { executeAsTask } from '../../utils/executeAsTask';
import { addImageTaggingTelemetry, tagImage } from './tagImage';

Expand Down Expand Up @@ -68,7 +69,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u
addImageTaggingTelemetry(context, finalTag, '');

// Finally push the image
await executeAsTask(context, `docker push ${finalTag}`, finalTag, { addDockerEnv: true });
await executeAsTask(context, `${dockerExePath(context)} push ${finalTag}`, finalTag, { addDockerEnv: true });
}

async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise<RegistryTreeItemBase | undefined> {
Expand Down
3 changes: 2 additions & 1 deletion src/commands/images/runAzureCliImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as os from 'os';
import * as vscode from 'vscode';
import { DialogResponses, IActionContext } from 'vscode-azureextensionui';
import { localize } from '../../localize';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { executeAsTask } from '../../utils/executeAsTask';
import { getDockerOSType } from '../../utils/osUtils';

Expand Down Expand Up @@ -44,6 +45,6 @@ export async function runAzureCliImage(context: IActionContext): Promise<void> {
vol += ` -v ${workspaceFolder.uri.fsPath}:/workspace`;
}

await executeAsTask(context, `docker run ${option} ${vol.trim()} -it --rm mcr.microsoft.com/azure-cli:latest`, 'Azure CLI', { addDockerEnv: true, focus: true });
await executeAsTask(context, `${dockerExePath(context)} run ${option} ${vol.trim()} -it --rm mcr.microsoft.com/azure-cli:latest`, 'Azure CLI', { addDockerEnv: true, focus: true });
}
}
13 changes: 7 additions & 6 deletions src/commands/registries/azure/deployImageToAci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { registryExpectedContextValues } from '../../../tree/registries/registry
import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem';
import { executeAsTask } from '../../../utils/executeAsTask';
import { execAsync } from '../../../utils/spawnAsync';
import { dockerExePath } from '../../../utils/dockerExePathProvider';
import { addImageTaggingTelemetry } from '../../images/tagImage';

export async function deployImageToAci(context: IActionContext, node?: RemoteTagTreeItem): Promise<void> {
Expand All @@ -35,13 +36,13 @@ export async function deployImageToAci(context: IActionContext, node?: RemoteTag
title: localize('vscode-docker.commands.registries.deployImageToAci.gettingPorts', 'Determining ports from image...'),
};
const ports = await vscode.window.withProgress(progressOptions, async () => {
return getImagePorts(node.fullTag);
return getImagePorts(node.fullTag, context);
});
const portsArg = ports.map(port => `-p ${port}:${port}`).join(' ');

addImageTaggingTelemetry(context, node.fullTag, '');

const command = `docker --context ${aciContext.name} run -d ${portsArg} ${node.fullTag}`;
const command = `${dockerExePath(context)} --context ${aciContext.name} run -d ${portsArg} ${node.fullTag}`;
const title = localize('vscode-docker.commands.registries.deployImageToAci.deploy', 'Deploy to ACI');
const options = {
addDockerEnv: false,
Expand All @@ -51,20 +52,20 @@ export async function deployImageToAci(context: IActionContext, node?: RemoteTag
await executeAsTask(context, command, title, { ...options, rejectOnError: true });
} catch {
// If it fails, try logging in and make one more attempt
await executeAsTask(context, `docker login azure --cloud-name ${await promptForAciCloud(context)}`, title, options);
await executeAsTask(context, `${dockerExePath(context)} login azure --cloud-name ${await promptForAciCloud(context)}`, title, options);
await executeAsTask(context, command, title, options);
}
}

async function getImagePorts(fullTag: string): Promise<number[]> {
async function getImagePorts(fullTag: string, context: IActionContext): Promise<number[]> {
try {
const result: number[] = [];

// 1. Pull the image to the default context
await execAsync(`docker --context default pull ${fullTag}`);
await execAsync(`${dockerExePath(context)} --context default pull ${fullTag}`);

// 2. Inspect it in the default context to find out the ports to map
const { stdout } = await execAsync(`docker --context default inspect ${fullTag} --format="{{ json .Config.ExposedPorts }}"`);
const { stdout } = await execAsync(`${dockerExePath(context)} --context default inspect ${fullTag} --format="{{ json .Config.ExposedPorts }}"`);

try {
const portsJson = <{ [key: string]: never }>JSON.parse(stdout);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/registries/logOutOfDockerCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IActionContext } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';
import { registryExpectedContextValues } from '../../tree/registries/registryContextValues';
import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { executeAsTask } from '../../utils/executeAsTask';

export async function logOutOfDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise<void> {
Expand All @@ -15,5 +16,5 @@ export async function logOutOfDockerCli(context: IActionContext, node?: Registry
}

const creds = await node.getDockerCliCredentials();
await executeAsTask(context, `docker logout ${creds.registryPath}`, 'Docker', { addDockerEnv: true });
await executeAsTask(context, `${dockerExePath(context)} logout ${creds.registryPath}`, 'Docker', { addDockerEnv: true });
}
13 changes: 7 additions & 6 deletions src/commands/registries/pullImages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IActionContext } from "vscode-azureextensionui";
import { IActionContext } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';
import { registryExpectedContextValues } from "../../tree/registries/registryContextValues";
import { registryExpectedContextValues } from '../../tree/registries/registryContextValues';
import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase';
import { RemoteRepositoryTreeItemBase } from "../../tree/registries/RemoteRepositoryTreeItemBase";
import { RemoteRepositoryTreeItemBase } from '../../tree/registries/RemoteRepositoryTreeItemBase';
import { RemoteTagTreeItem } from '../../tree/registries/RemoteTagTreeItem';
import { executeAsTask } from "../../utils/executeAsTask";
import { logInToDockerCli } from "./logInToDockerCli";
import { executeAsTask } from '../../utils/executeAsTask';
import { dockerExePath } from '../../utils/dockerExePathProvider';
import { logInToDockerCli } from './logInToDockerCli';

export async function pullRepository(context: IActionContext, node?: RemoteRepositoryTreeItemBase): Promise<void> {
if (!node) {
Expand All @@ -31,5 +32,5 @@ export async function pullImageFromRepository(context: IActionContext, node?: Re
async function pullImages(context: IActionContext, node: RegistryTreeItemBase, imageRequest: string): Promise<void> {
await logInToDockerCli(context, node);

await executeAsTask(context, `docker pull ${node.baseImagePath}/${imageRequest}`, 'Docker', { addDockerEnv: true });
await executeAsTask(context, `${dockerExePath(context)} pull ${node.baseImagePath}/${imageRequest}`, 'Docker', { addDockerEnv: true });
}
12 changes: 11 additions & 1 deletion src/commands/selectCommandTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ContextType } from '../docker/Contexts';
import { ext } from '../extensionVariables';
import { localize } from '../localize';
import { resolveVariables } from '../utils/resolveVariables';
import { dockerExePath, DefaultDockerPath } from '../utils/dockerExePathProvider';

type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown';

Expand Down Expand Up @@ -170,7 +171,16 @@ export async function selectCommandTemplate(
context.telemetry.properties.commandContextType = `[${selectedTemplate.contextTypes?.join(', ') ?? ''}]`;
context.telemetry.properties.currentContextType = currentContextType;

return resolveVariables(selectedTemplate.template, folder, additionalVariables);
let resolvedCommand = resolveVariables(selectedTemplate.template, folder, additionalVariables);

if (resolvedCommand.startsWith(DefaultDockerPath + ' ')) {
const dockerPath = dockerExePath(context);
if (dockerPath !== DefaultDockerPath) {
resolvedCommand = dockerPath + resolvedCommand.substring(DefaultDockerPath.length);
}
}

return resolvedCommand;
}

async function quickPickTemplate(templates: CommandTemplate[], templatePicker: TemplatePicker): Promise<CommandTemplate> {
Expand Down
11 changes: 6 additions & 5 deletions src/docker/ContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { localize } from '../localize';
import { AsyncLazy } from '../utils/lazy';
import { isWindows } from '../utils/osUtils';
import { execAsync, spawnAsync } from '../utils/spawnAsync';
import { dockerExePath } from '../utils/dockerExePathProvider';
import { ContextType, DockerContext, DockerContextInspection, isNewContextType } from './Contexts';

// CONSIDER
Expand Down Expand Up @@ -172,20 +173,20 @@ export class DockerContextManager implements ContextManager, Disposable {
}

public async inspect(actionContext: IActionContext, contextName: string): Promise<DockerContextInspection> {
const { stdout } = await execAsync(`docker context inspect ${contextName}`, { timeout: 10000 });
const { stdout } = await execAsync(`${dockerExePath(actionContext)} context inspect ${contextName}`, { timeout: 10000 });

// The result is an array with one entry
const result: DockerContextInspection[] = JSON.parse(stdout) as DockerContextInspection[];
return result[0];
}

public async use(actionContext: IActionContext, contextName: string): Promise<void> {
const useCmd: string = `docker context use ${contextName}`;
const useCmd: string = `${dockerExePath(actionContext)} context use ${contextName}`;
await execAsync(useCmd, ContextCmdExecOptions);
}

public async remove(actionContext: IActionContext, contextName: string): Promise<void> {
const removeCmd: string = `docker context rm ${contextName}`;
const removeCmd: string = `${dockerExePath(actionContext)} context rm ${contextName}`;
await spawnAsync(removeCmd, ContextCmdExecOptions);
}

Expand Down Expand Up @@ -261,7 +262,7 @@ export class DockerContextManager implements ContextManager, Disposable {
// return that specified context as Current = true. This way we don't need extra logic below in parsing.
// TODO: eventually change to `docker context ls --format json`
// eslint-disable-next-line @typescript-eslint/naming-convention
const { stdout } = await execAsync('docker context ls --format="{{json .}}"', { ...ContextCmdExecOptions, env: { ...process.env, DOCKER_CONTEXT: dockerContextEnv } });
const { stdout } = await execAsync(`${dockerExePath()} context ls --format="{{json .}}"`, { ...ContextCmdExecOptions, env: { ...process.env, DOCKER_CONTEXT: dockerContextEnv } });

try {
// Try parsing as-is; newer CLIs output a JSON object array
Expand Down Expand Up @@ -334,7 +335,7 @@ export class DockerContextManager implements ContextManager, Disposable {
} else {
// Otherwise we look at the output of `docker serve --help`
// TODO: this is not a very good heuristic
const { stdout } = await execAsync('docker serve --help');
const { stdout } = await execAsync(`${dockerExePath()} serve --help`);

if (/^\s*Start an api server/i.test(stdout)) {
result = true;
Expand Down
5 changes: 3 additions & 2 deletions src/tree/contexts/aci/AciContextCreateStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ext } from '../../../extensionVariables';
import { localize } from '../../../localize';
import { executeAsTask } from '../../../utils/executeAsTask';
import { execAsync } from '../../../utils/spawnAsync';
import { dockerExePath } from '../../../utils/dockerExePathProvider';
import { IAciWizardContext } from './IAciWizardContext';

export class AciContextCreateStep extends AzureWizardExecuteStep<IAciWizardContext> {
Expand All @@ -20,7 +21,7 @@ export class AciContextCreateStep extends AzureWizardExecuteStep<IAciWizardConte
ext.outputChannel.appendLine(creatingNewContext);
progress.report({ message: creatingNewContext });

const command = `docker context create aci ${wizardContext.contextName} --subscription-id ${wizardContext.subscriptionId} --resource-group ${wizardContext.resourceGroup.name}`;
const command = `${dockerExePath(wizardContext)} context create aci ${wizardContext.contextName} --subscription-id ${wizardContext.subscriptionId} --resource-group ${wizardContext.resourceGroup.name}`;

try {
await execAsync(command);
Expand All @@ -30,7 +31,7 @@ export class AciContextCreateStep extends AzureWizardExecuteStep<IAciWizardConte
if (error.errorType === '5' || /not logged in/i.test(error.message)) {
// If error is due to being not logged in, we'll go through login and try again
// Because login could involve device auth we do this step in the terminal
await executeAsTask(wizardContext, `docker login azure --cloud-name ${wizardContext.environment.name}`, localize('vscode-docker.commands.contexts.create.aci.azureLogin', 'Azure Login'), { rejectOnError: true });
await executeAsTask(wizardContext, `${dockerExePath(wizardContext)} login azure --cloud-name ${wizardContext.environment.name}`, localize('vscode-docker.commands.contexts.create.aci.azureLogin', 'Azure Login'), { rejectOnError: true });
await execAsync(command);
} else {
// Otherwise rethrow
Expand Down
8 changes: 5 additions & 3 deletions src/utils/DockerInstallStatusProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AsyncLazy } from "./lazy";
import { execAsync } from "./spawnAsync";
import { AsyncLazy } from './lazy';
import { execAsync } from './spawnAsync';
import { dockerExePath } from './dockerExePathProvider';

export interface IDockerInstallStatusProvider {
isDockerInstalled(): Promise<boolean>
Expand Down Expand Up @@ -32,7 +33,8 @@ class DockerInstallStatusProvider implements IDockerInstallStatusProvider {

public async isDockerInstalledRealTimeCheck(): Promise<boolean> {
try {
await execAsync('docker -v');

await execAsync(`${dockerExePath()} -v`);
return true; // As long as the docker command did't throw exception, assume it is installed.
} catch (error) {
return false; // docker not installed
Expand Down
Loading

0 comments on commit 336b12d

Please sign in to comment.