From 5f1434e5c446de758145823f8c762685f4568c01 Mon Sep 17 00:00:00 2001 From: Ravikumar Palanisamy Date: Wed, 6 May 2020 16:01:25 -0700 Subject: [PATCH 1/5] Attach support for windows container --- src/debugging/netcore/NetCoreDebugHelper.ts | 48 +++++++++++++++------ src/utils/osUtils.ts | 8 ++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index 4ae1ce9a86..394612b095 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -13,6 +13,7 @@ import { localize } from '../../localize'; import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCoreTaskHelper'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { LocalOSProvider } from '../../utils/LocalOSProvider'; +import { ContainerOSType, getConatinerOSType } from '../../utils/osUtils'; import { pathNormalize } from '../../utils/pathNormalize'; import { PlatformOS } from '../../utils/platform'; import { unresolveWorkspaceFolder } from '../../utils/resolveVariables'; @@ -194,11 +195,15 @@ export class NetCoreDebugHelper implements DebugHelper { // If debugger path is not specified, then install the debugger if it doesn't exist in the container if (!debuggerPath) { - const debuggerDirectory = '/remote_debugger'; - debuggerPath = `${debuggerDirectory}/vsdbg`; - const isDebuggerInstalled: boolean = await this.isDebuggerInstalled(containerName, debuggerPath); + const conatinerOS = await getConatinerOSType(containerName); + const osProvider = new LocalOSProvider(); + const debuggerDirectory = conatinerOS === 'windows' ? 'C:\\remote_debugger' : '/remote_debugger'; + debuggerPath = conatinerOS === 'windows' + ? osProvider.pathJoin(osProvider.os, debuggerDirectory, 'win7-x64', 'latest', 'vsdbg.exe') + : osProvider.pathJoin(osProvider.os, debuggerDirectory, 'vsdbg'); + const isDebuggerInstalled: boolean = await this.isDebuggerInstalled(containerName, debuggerPath, conatinerOS); if (!isDebuggerInstalled) { - debuggerPath = await this.copyDebuggerToContainer(containerName, debuggerDirectory); + await this.copyDebuggerToContainer(context, containerName, debuggerDirectory, conatinerOS); } } @@ -287,7 +292,16 @@ export class NetCoreDebugHelper implements DebugHelper { return pathNormalize(result, platformOS); } - private async copyDebuggerToContainer(containerName: string, containerDebuggerDirectory: string): Promise { + private async copyDebuggerToContainer(context: DockerDebugContext, containerName: string, containerDebuggerDirectory: string, containerOS: ContainerOSType): Promise { + const dockerClient = new CliDockerClient(new ChildProcessProvider()); + if (containerOS === 'windows') { + const isolation = await dockerClient.inspectObject(containerName, { format: '{{ .HostConfig.Isolation}}' }); + if (isolation && isolation === 'hyperv') { + context.actionContext.errorHandling.suppressReportIssue = true; + throw new Error(localize('vscode-docker.debug.netcore.isolationNotSupported', 'Unable to copy debugger. This operation is not supported on Hyper-V container.')); + } + } + const yesItem: MessageItem = DialogResponses.yes; const message = localize('vscode-docker.debug.netcore.attachingRequiresDebugger', 'Attaching to container requires .NET Core debugger in the container. Do you want to copy the debugger to the container?'); const install = (yesItem === await window.showInformationMessage(message, ...[DialogResponses.yes, DialogResponses.no])); @@ -295,12 +309,15 @@ export class NetCoreDebugHelper implements DebugHelper { throw new UserCancelledError(); } - // TODO: Attach doesn't support Windows yet. - await this.acquireDebuggers('Linux'); + if (containerOS === 'windows') { + await this.acquireDebuggers('Windows'); + } else { + await this.acquireDebuggers('Linux'); + } + const hostDebuggerPath = await this.vsDbgClientFactory().getVsDbgFolder(); const containerDebuggerPath = `${containerName}:${containerDebuggerDirectory}`; const outputManager = new DefaultOutputManager(ext.outputChannel); - const dockerClient = new CliDockerClient(new ChildProcessProvider()); await outputManager.performOperation( localize('vscode-docker.debug.netcore.copyDebugger', 'Copying the .NET Core debugger to the container...'), @@ -310,15 +327,20 @@ export class NetCoreDebugHelper implements DebugHelper { localize('vscode-docker.debug.netcore.debuggerInstalled', 'Debugger copied'), localize('vscode-docker.debug.netcore.unableToInstallDebugger', 'Unable to copy the .NET Core debugger.') ); - return `${containerDebuggerDirectory}/vsdbg`; } - private async isDebuggerInstalled(containerName: string, debuggerPath: string): Promise { + private async isDebuggerInstalled(containerName: string, debuggerPath: string, containerOS: ContainerOSType): Promise { const dockerClient = new CliDockerClient(new ChildProcessProvider()); const osProvider = new LocalOSProvider(); - const command: string = osProvider.os === 'Windows' ? - `/bin/sh -c "if [ -f ${debuggerPath} ]; then echo true; fi;"` - : `/bin/sh -c 'if [ -f ${debuggerPath} ]; then echo true; fi;'` + let command: string; + + if (containerOS === 'windows') { + command = `cmd /C "IF EXIST "${debuggerPath}" (echo true) else (echo false)"`; + } else { + command = osProvider.os === 'Windows' ? + `/bin/sh -c "if [ -f ${debuggerPath} ]; then echo true; fi;"` + : `/bin/sh -c 'if [ -f ${debuggerPath} ]; then echo true; fi;'` + } const result: string = await dockerClient.exec(containerName, command, {}); return result === 'true'; } diff --git a/src/utils/osUtils.ts b/src/utils/osUtils.ts index cba25e06b5..3b491e4e23 100644 --- a/src/utils/osUtils.ts +++ b/src/utils/osUtils.ts @@ -7,6 +7,7 @@ import * as semver from 'semver'; import { IActionContext } from 'vscode-azureextensionui'; import { ext } from '../extensionVariables'; import { callDockerodeWithErrorHandling } from './callDockerode'; +import { execAsync } from './spawnAsync'; // Minimum Windows RS3 version number const windows10RS3MinVersion = '10.0.16299'; @@ -76,6 +77,7 @@ export function isMac(): boolean { } export type DockerOSType = "windows" | "linux"; +export type ContainerOSType = "windows" | "linux"; export async function getDockerOSType(context: IActionContext): Promise { if (!isWindows()) { @@ -88,3 +90,9 @@ export async function getDockerOSType(context: IActionContext): Promise { + const inspectCmd: string = `docker inspect --format="{{ .Platform}}" ${containerId}`; + const execResult = await execAsync(inspectCmd); + return execResult.stdout.trim().toLowerCase() === 'windows' ? 'windows' : 'linux'; +} From 790d06931b932d4e765916002fa4aab06aa36f58 Mon Sep 17 00:00:00 2001 From: Ravikumar Palanisamy Date: Thu, 7 May 2020 09:57:52 -0700 Subject: [PATCH 2/5] update message --- src/debugging/netcore/NetCoreDebugHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index 394612b095..0f043cd01a 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -298,7 +298,7 @@ export class NetCoreDebugHelper implements DebugHelper { const isolation = await dockerClient.inspectObject(containerName, { format: '{{ .HostConfig.Isolation}}' }); if (isolation && isolation === 'hyperv') { context.actionContext.errorHandling.suppressReportIssue = true; - throw new Error(localize('vscode-docker.debug.netcore.isolationNotSupported', 'Unable to copy debugger. This operation is not supported on Hyper-V container.')); + throw new Error(localize('vscode-docker.debug.netcore.isolationNotSupported', 'Attaching a debugger to a Hyper-V container is not supported.')); } } From 616669d420937f126b603ca5d320a7ff93688e9e Mon Sep 17 00:00:00 2001 From: Ravikumar Palanisamy Date: Thu, 7 May 2020 10:25:53 -0700 Subject: [PATCH 3/5] fix typo --- src/debugging/netcore/NetCoreDebugHelper.ts | 12 ++++++------ src/utils/osUtils.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index 0f043cd01a..42a9da52f0 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -13,7 +13,7 @@ import { localize } from '../../localize'; import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCoreTaskHelper'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { LocalOSProvider } from '../../utils/LocalOSProvider'; -import { ContainerOSType, getConatinerOSType } from '../../utils/osUtils'; +import { ContainerOSType, getContainerOSType } from '../../utils/osUtils'; import { pathNormalize } from '../../utils/pathNormalize'; import { PlatformOS } from '../../utils/platform'; import { unresolveWorkspaceFolder } from '../../utils/resolveVariables'; @@ -195,15 +195,15 @@ export class NetCoreDebugHelper implements DebugHelper { // If debugger path is not specified, then install the debugger if it doesn't exist in the container if (!debuggerPath) { - const conatinerOS = await getConatinerOSType(containerName); + const containerOS = await getContainerOSType(containerName); const osProvider = new LocalOSProvider(); - const debuggerDirectory = conatinerOS === 'windows' ? 'C:\\remote_debugger' : '/remote_debugger'; - debuggerPath = conatinerOS === 'windows' + const debuggerDirectory = containerOS === 'windows' ? 'C:\\remote_debugger' : '/remote_debugger'; + debuggerPath = containerOS === 'windows' ? osProvider.pathJoin(osProvider.os, debuggerDirectory, 'win7-x64', 'latest', 'vsdbg.exe') : osProvider.pathJoin(osProvider.os, debuggerDirectory, 'vsdbg'); - const isDebuggerInstalled: boolean = await this.isDebuggerInstalled(containerName, debuggerPath, conatinerOS); + const isDebuggerInstalled: boolean = await this.isDebuggerInstalled(containerName, debuggerPath, containerOS); if (!isDebuggerInstalled) { - await this.copyDebuggerToContainer(context, containerName, debuggerDirectory, conatinerOS); + await this.copyDebuggerToContainer(context, containerName, debuggerDirectory, containerOS); } } diff --git a/src/utils/osUtils.ts b/src/utils/osUtils.ts index 3b491e4e23..ef36cecde4 100644 --- a/src/utils/osUtils.ts +++ b/src/utils/osUtils.ts @@ -91,7 +91,7 @@ export async function getDockerOSType(context: IActionContext): Promise { +export async function getContainerOSType(containerId: string): Promise { const inspectCmd: string = `docker inspect --format="{{ .Platform}}" ${containerId}`; const execResult = await execAsync(inspectCmd); return execResult.stdout.trim().toLowerCase() === 'windows' ? 'windows' : 'linux'; From a423d81180012c34b94f1fe798137366f1a254ef Mon Sep 17 00:00:00 2001 From: Ravikumar Palanisamy Date: Thu, 7 May 2020 14:30:15 -0700 Subject: [PATCH 4/5] Use dockerode to get inspect info --- src/debugging/netcore/NetCoreDebugHelper.ts | 12 +++++++----- src/utils/osUtils.ts | 15 ++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index 42a9da52f0..dce0249922 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -12,6 +12,7 @@ import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCoreTaskHelper'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; +import { callDockerodeWithErrorHandling } from '../../utils/callDockerode'; import { LocalOSProvider } from '../../utils/LocalOSProvider'; import { ContainerOSType, getContainerOSType } from '../../utils/osUtils'; import { pathNormalize } from '../../utils/pathNormalize'; @@ -195,7 +196,7 @@ export class NetCoreDebugHelper implements DebugHelper { // If debugger path is not specified, then install the debugger if it doesn't exist in the container if (!debuggerPath) { - const containerOS = await getContainerOSType(containerName); + const containerOS = await getContainerOSType(containerName, context.actionContext); const osProvider = new LocalOSProvider(); const debuggerDirectory = containerOS === 'windows' ? 'C:\\remote_debugger' : '/remote_debugger'; debuggerPath = containerOS === 'windows' @@ -203,7 +204,7 @@ export class NetCoreDebugHelper implements DebugHelper { : osProvider.pathJoin(osProvider.os, debuggerDirectory, 'vsdbg'); const isDebuggerInstalled: boolean = await this.isDebuggerInstalled(containerName, debuggerPath, containerOS); if (!isDebuggerInstalled) { - await this.copyDebuggerToContainer(context, containerName, debuggerDirectory, containerOS); + await this.copyDebuggerToContainer(context.actionContext, containerName, debuggerDirectory, containerOS); } } @@ -292,12 +293,13 @@ export class NetCoreDebugHelper implements DebugHelper { return pathNormalize(result, platformOS); } - private async copyDebuggerToContainer(context: DockerDebugContext, containerName: string, containerDebuggerDirectory: string, containerOS: ContainerOSType): Promise { + private async copyDebuggerToContainer(context: IActionContext, containerName: string, containerDebuggerDirectory: string, containerOS: ContainerOSType): Promise { const dockerClient = new CliDockerClient(new ChildProcessProvider()); if (containerOS === 'windows') { - const isolation = await dockerClient.inspectObject(containerName, { format: '{{ .HostConfig.Isolation}}' }); + const containerInfo = await callDockerodeWithErrorHandling(async () => ext.dockerode.getContainer(containerName).inspect(), context); + const isolation = containerInfo.HostConfig.Isolation; if (isolation && isolation === 'hyperv') { - context.actionContext.errorHandling.suppressReportIssue = true; + context.errorHandling.suppressReportIssue = true; throw new Error(localize('vscode-docker.debug.netcore.isolationNotSupported', 'Attaching a debugger to a Hyper-V container is not supported.')); } } diff --git a/src/utils/osUtils.ts b/src/utils/osUtils.ts index ef36cecde4..26e24db525 100644 --- a/src/utils/osUtils.ts +++ b/src/utils/osUtils.ts @@ -7,7 +7,6 @@ import * as semver from 'semver'; import { IActionContext } from 'vscode-azureextensionui'; import { ext } from '../extensionVariables'; import { callDockerodeWithErrorHandling } from './callDockerode'; -import { execAsync } from './spawnAsync'; // Minimum Windows RS3 version number const windows10RS3MinVersion = '10.0.16299'; @@ -91,8 +90,14 @@ export async function getDockerOSType(context: IActionContext): Promise { - const inspectCmd: string = `docker inspect --format="{{ .Platform}}" ${containerId}`; - const execResult = await execAsync(inspectCmd); - return execResult.stdout.trim().toLowerCase() === 'windows' ? 'windows' : 'linux'; +export async function getContainerOSType(containerId: string, context: IActionContext): Promise { + if (!isWindows()) { + // On Linux or macOS, this can only ever be linux, + // so short-circuit the Docker call entirely. + return "linux"; + } else { + const info = await callDockerodeWithErrorHandling(async () => ext.dockerode.getContainer(containerId).inspect(), context); + // eslint-disable-next-line @typescript-eslint/tslint/config + return info.Platform === 'windows' ? 'windows' : 'linux' + } } From 2eda0901af312607fa4cd330ae1dfced6898bc38 Mon Sep 17 00:00:00 2001 From: Ravikumar Palanisamy Date: Thu, 14 May 2020 12:37:10 -0700 Subject: [PATCH 5/5] use docker os instead of contaier os --- src/debugging/netcore/NetCoreDebugHelper.ts | 9 ++++----- src/utils/osUtils.ts | 13 ------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index dce0249922..533f97df43 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -14,7 +14,7 @@ import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCo import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { callDockerodeWithErrorHandling } from '../../utils/callDockerode'; import { LocalOSProvider } from '../../utils/LocalOSProvider'; -import { ContainerOSType, getContainerOSType } from '../../utils/osUtils'; +import { DockerOSType, getDockerOSType } from '../../utils/osUtils'; import { pathNormalize } from '../../utils/pathNormalize'; import { PlatformOS } from '../../utils/platform'; import { unresolveWorkspaceFolder } from '../../utils/resolveVariables'; @@ -188,7 +188,6 @@ export class NetCoreDebugHelper implements DebugHelper { } public async resolveAttachDebugConfiguration(context: DockerDebugContext, debugConfiguration: DockerAttachConfiguration): Promise { - // TODO: Validate the target container OS and fail debugging // Get Container Name if missing const containerName: string = debugConfiguration.containerName ?? await this.getContainerNameToAttach(); @@ -196,7 +195,7 @@ export class NetCoreDebugHelper implements DebugHelper { // If debugger path is not specified, then install the debugger if it doesn't exist in the container if (!debuggerPath) { - const containerOS = await getContainerOSType(containerName, context.actionContext); + const containerOS = await getDockerOSType(context.actionContext); const osProvider = new LocalOSProvider(); const debuggerDirectory = containerOS === 'windows' ? 'C:\\remote_debugger' : '/remote_debugger'; debuggerPath = containerOS === 'windows' @@ -293,7 +292,7 @@ export class NetCoreDebugHelper implements DebugHelper { return pathNormalize(result, platformOS); } - private async copyDebuggerToContainer(context: IActionContext, containerName: string, containerDebuggerDirectory: string, containerOS: ContainerOSType): Promise { + private async copyDebuggerToContainer(context: IActionContext, containerName: string, containerDebuggerDirectory: string, containerOS: DockerOSType): Promise { const dockerClient = new CliDockerClient(new ChildProcessProvider()); if (containerOS === 'windows') { const containerInfo = await callDockerodeWithErrorHandling(async () => ext.dockerode.getContainer(containerName).inspect(), context); @@ -331,7 +330,7 @@ export class NetCoreDebugHelper implements DebugHelper { ); } - private async isDebuggerInstalled(containerName: string, debuggerPath: string, containerOS: ContainerOSType): Promise { + private async isDebuggerInstalled(containerName: string, debuggerPath: string, containerOS: DockerOSType): Promise { const dockerClient = new CliDockerClient(new ChildProcessProvider()); const osProvider = new LocalOSProvider(); let command: string; diff --git a/src/utils/osUtils.ts b/src/utils/osUtils.ts index 26e24db525..cba25e06b5 100644 --- a/src/utils/osUtils.ts +++ b/src/utils/osUtils.ts @@ -76,7 +76,6 @@ export function isMac(): boolean { } export type DockerOSType = "windows" | "linux"; -export type ContainerOSType = "windows" | "linux"; export async function getDockerOSType(context: IActionContext): Promise { if (!isWindows()) { @@ -89,15 +88,3 @@ export async function getDockerOSType(context: IActionContext): Promise { - if (!isWindows()) { - // On Linux or macOS, this can only ever be linux, - // so short-circuit the Docker call entirely. - return "linux"; - } else { - const info = await callDockerodeWithErrorHandling(async () => ext.dockerode.getContainer(containerId).inspect(), context); - // eslint-disable-next-line @typescript-eslint/tslint/config - return info.Platform === 'windows' ? 'windows' : 'linux' - } -}