diff --git a/src/commands/images/buildImage.ts b/src/commands/images/buildImage.ts index db6660c458..9a504d3998 100644 --- a/src/commands/images/buildImage.ts +++ b/src/commands/images/buildImage.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils"; +import { quoted } from "@microsoft/vscode-container-client"; import * as path from "path"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; @@ -48,10 +49,10 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode. contextPath ); - // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. - if (tagRegex.test(terminalCommand.command)) { + const getImageName = async (): Promise => { const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); const dockerFileKey = `buildTag_${absFilePath}`; + const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); // Get imageName based previous entries, else on name of subfolder containing the Dockerfile @@ -65,7 +66,32 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode. addImageTaggingTelemetry(context, imageName, '.after'); await ext.context.workspaceState.update(dockerFileKey, imageName); - terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); + + return imageName; + }; + + // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. + if (!terminalCommand.args || terminalCommand.args.length === 0) { + // This is a customized command, so parse the tag from the command + if (tagRegex.test(terminalCommand.command)) { + const imageName = await getImageName(); + terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); + } + } else if (terminalCommand.args.some(arg => tagRegex.test(typeof (arg) === 'string' ? arg : arg.value))) { + // This is a default command, so look for ${tag} in the args + const imageName = await getImageName(); + + terminalCommand.args = terminalCommand.args.map(arg => { + if (typeof (arg) === 'string') { + if (tagRegex.test(arg)) { + arg = quoted(arg.replace(tagRegex, imageName)); + } + } else if (tagRegex.test(arg.value)) { + arg = quoted(arg.value.replace(tagRegex, imageName)); + } + + return arg; + }); } const client = await ext.runtimeManager.getClient(); diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 64f71e4ad4..66f4a061ad 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, IAzureQuickPickItem, IAzureQuickPickOptions, UserCancelledError } from '@microsoft/vscode-azext-utils'; -import { PortBinding, VoidCommandResponse } from '@microsoft/vscode-container-client'; +import { PortBinding, quoted, VoidCommandResponse } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from '../extensionVariables'; import { isDockerComposeClient } from '../runtimes/OrchestratorRuntimeManager'; @@ -168,14 +168,35 @@ export async function selectCommandTemplate( throw new Error(vscode.l10n.t('No command template was found for command \'{0}\'', command)); } - actionContext.telemetry.properties.isDefaultCommand = defaultTemplates.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; + const isDefault = defaultTemplates.some(t => t.template === selectedTemplate.template); + actionContext.telemetry.properties.isDefaultCommand = isDefault ? 'true' : 'false'; actionContext.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; - // This is not really ideal (putting the full command line into `command` instead of `command` + `args`), but parsing a string into a command + args like that is really hard - // Fortunately, `TaskCommandRunnerFactory` does not really care + + let resolvedCommand = resolveVariables(selectedTemplate.template, folder, additionalVariables); + + if (!isDefault) { + // This is not really ideal (putting the full command line into `command` instead of `command` + `args`), but parsing a string into a command + args like that is really hard + // Fortunately, `TaskCommandRunnerFactory` does not really care + return { + command: resolvedCommand, + args: undefined, + }; + } + + // For the default command, we can make assumptions that allow us to parse into command + args for better shell support + + const argsRegex = /(?:"[^"]*")|(?:[^"\s]*)/g; + const commandAndArgs = resolvedCommand.match(argsRegex).filter(arg => arg.length !== 0); + if (commandAndArgs.length > 0) { + resolvedCommand = commandAndArgs[0].replace(/"/g, ''); + } + + const resolvedArgs = commandAndArgs.slice(1).map(arg => arg.startsWith('"') ? quoted(arg.replace(/"/g, '')) : arg); + return { - command: resolveVariables(selectedTemplate.template, folder, additionalVariables), - args: undefined, + command: resolvedCommand, + args: resolvedArgs, }; }