Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debugging support for .NET Core in Docker containers #500

Merged

Conversation

philliphoff
Copy link
Contributor

Adds support for debugging .NET Core applications within Docker containers, in a manner similar to that performed by Visual Studio's Docker-related tooling.

The basic feature set is:

  • Adds a palette command to scaffold a “Docker .NET Core” debug launch configuration.
    • The intent of this configuration is to minimize input/configuration from/by the user; properties will all be inferred
    • Optional properties can be used to override image tags, names, arguments, variables, etc.
  • Adds a debug configuration provider that:
    • Pulls down the latest .NET Core debugger from the Cloud, as needed
    • Generates a debuggable Docker image for the application (e.g. building just the first stage of the multi-stage Dockerfile)
    • Runs the Docker image in a container, applying shared volumes as necessary to expose debugger/application to the container
    • Dynamically generates a .NET Core debug launch configuration allowing VS Code to initiate and attach the debugger within the container
    • Launches the browser to the running application via the container’s mapped ports
    • Cleans up containers when debugging is done

NOTE: Only debugging of Linux containers is supported today (in VS Code running on either Mac or Windows). We cannot debug Windows containers because the .NET Core debugger (vsdbg) is not supported on Windows containers, and VS Code cannot use msvsmon (used by Visual Studio in that scenario).

The feature is disabled by default, using the new docker.debugging.enabled setting.

Copy link
Member

@ejizba ejizba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding some initial comments, but still in the process of reviewing.

Also I got this error when I F5'ed:

Building Docker image...
> Sending build context to Docker daemon  2.504MB
> 
> 
> Step 1/16 : FROM microsoft/aspnetcore:2.0 AS base
> 2.0: Pulling from microsoft/aspnetcore
> 55cbf04beb70: Pulling fs layer
> 2ad9fb8b9d3d: Pulling fs layer
> 7debd121b442: Pulling fs layer
> 76fbad01d0bd: Pulling fs layer
> 1cba46fa0c00: Pulling fs layer
> 76fbad01d0bd: Waiting
> 1cba46fa0c00: Waiting
> 7debd121b442: Download complete
> 76fbad01d0bd: Verifying Checksum
> 76fbad01d0bd: Download complete
> 55cbf04beb70: Verifying Checksum
> 55cbf04beb70: Download complete
> 1cba46fa0c00: Verifying Checksum
> 1cba46fa0c00: Download complete
> 2ad9fb8b9d3d: Verifying Checksum
> 2ad9fb8b9d3d: Download complete
> 55cbf04beb70: Pull complete
> 2ad9fb8b9d3d: Pull complete
> 7debd121b442: Pull complete
> 76fbad01d0bd: Pull complete
> 1cba46fa0c00: Pull complete
> Digest: sha256:8bd7808981568a92b7f1d0aa4bab07ef811e6417898847d9caf698a4a788ab11
> Status: Downloaded newer image for microsoft/aspnetcore:2.0
>  ---> db030c19e94b
> Step 2/16 : WORKDIR /app
>  ---> 78f936151ff7
> Removing intermediate container 62a22a04608f
> Step 3/16 : EXPOSE 80
>  ---> Running in 721a55e2bb1e
>  ---> cab04bc7c223
> Removing intermediate container 721a55e2bb1e
> Successfully built cab04bc7c223
> Successfully tagged hellodotnetcore.csproj:dev
Docker image cab04bc7c223 built.
Acquiring the latest .NET Core debugger...
Debugger acquired.
Starting container...
Unable to start container: Error: Command failed: docker run -dt -P --name hellodotnetcore.csproj-dev -v "/Users/ericjizba/TestRepos/hellodotnetcore:/app:rw" -v "/Users/ericjizba/.vsdbg/debian.8-x64/vs2017u5:/remote_debugger:ro" -v "/Users/ericjizba/.nuget/packages:/root/.nuget/packages:ro" -v "/usr/local/share/dotnet/sdk/NuGetFallbackFolder:/root/.nuget/fallbackpackages:ro" --entrypoint tail cab04bc7c223 -f /dev/null
docker: Error response from daemon: Mounts denied: 
The path /usr/local/share/dotnet/sdk/NuGetFallbackFolder
is not shared from OS X and is not known to Docker.
You can configure shared paths from Docker -> Preferences... -> File Sharing.
See https://docs.docker.com/docker-for-mac/osxfs/#namespaces for more info.
.

Does that mean anything to you? I admittedly haven't investigated much and plan to look into it further.

CHANGELOG.md Outdated Show resolved Hide resolved
dockerExtension.ts Outdated Show resolved Hide resolved
debugging/netcoreapp/dockerDebugConfigurationProvider.ts Outdated Show resolved Hide resolved
dockerExtension.ts Outdated Show resolved Hide resolved
ctx.subscriptions.push(
vscode.debug.onDidChangeActiveDebugSession(
session => {
if (session === undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get if (session === undefined). How do you know the session was a docker debug session? Doesn't this event fire for any debug session?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent is to shutdown any containers used for Docker debugging still running when debugging ends in VS Code, whether from the ending session or a previous one. I'm not sure it matters which kind of session was ending, in that if there are no containers running it's a no-op and if there are, then we'd still want them shutdown.

(As far as I know, VS Code only supports one debugging session at a time.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just concerned because this code will be run for every user that has the docker extension installed - even if they never touch the 'docker-netcoreapp' experience (well - every user once we get rid of the feature flag). No matter how perfect the code is today (which could change in the future) I would prefer to have some checking here to prevent the docker extension from affecting users that are debugging entirely different things.

Can you use onDidTerminateDebugSession and check session.type === 'docker-netcoreapp'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into issues with onDidTerminateDebugSession when the VS Live Share extension was installed, as it may "wrap" other debug adaptors, even when not actively used. In that case, the returned type is the Live Share adaptor. It exposes the underlying adaptor type via a custom request, but when a session is terminated you cannot make custom requests.

Instead, I add a "session manager" that's informed by my debug adaptor when debugging is about to start, at which time it starts listening to the termination event. Then, on termination, it performs the necessary cleanup and stops listening for future events. This should limit it the cleanup to running only when the user has attempted to debug within a Docker container, but no more than once per attempt.

}

export class LocalFileSystemProvider implements FileSystemProvider {
public async dirExists(path: string): Promise<boolean> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the 'fs-extra' npm module (already a dependency of this extension). It has promisified versions of all the 'fs' methods, plus some extra helper methods. Hopefully it's enough that you can get rid of LocalFileSystemProvider completely, but it should at least make this a lot smaller.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, hadn't realized that was already in use. Updated to use that instead. (The use of the provider structure itself is an intentional abstraction of the file system for (future) testing purposes.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the desire to plan ahead, but I'm also not a fan of over-complicating code before it's actually needed. How soon do you plan to write the tests? Are you sure the tests need an abstraction and can't just run against the real thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstracting/mocking the file system tends to be handy, in that it can simplify/eliminate test initialization/cleanup code as well as avoids collisions if/when tests run in parallel. I've added some sample tests; I'd love to add more as time allows.

debugging/netcoreapp/osProvider.ts Outdated Show resolved Hide resolved
import * as path from 'path';
import { FileSystemProvider } from "./fsProvider";

export interface MementoAsync {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extensionContext already has a memento - globalState or workspaceState depending on your needs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppStorage represents a state at the "application" or "project" level, which may often be sub-workspace-state. (You might have several applications within the workspace folder, say, if using VS Code to open a solution created by VS comprised of several individual ASP.NET projects.) It's intended to be used for things that are specific to the application (e.g. cache settings used to build/run its Docker images/containers) and not dependent on which folder was opened in VS Code (e.g. the solution level or project level folder) to edit that project. This parallels how VS stores such data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I'll have to try out the scenario you described. I just want to make sure we're following VS Code conventions here, which may be different than VS. In general, VS Code has a much bigger reliance on the fact that the project is at the root of the workspace. They also have a concept of 'multi-root' workspaces, which might be how they handle the multi-project scenario you're describing (which is handled appropriately by workspaceState)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to have a meeting to discuss how we expect users to manage these types of projects/solutions in vscode. Do we have actual customer usage data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I setup a time for us to meet and go through Stephen's questions.

@philliphoff
Copy link
Contributor Author

Does that mean anything to you? I admittedly haven't investigated much and plan to look into it further.

I'll have to look into that too. Does that path exist on your machine? What folders are shared in your Docker preferences? (Mine does not explicitly state that /usr/... is shared, but it somehow still works.)

@philliphoff
Copy link
Contributor Author

@EricJizbaMSFT Did you happen to recently install OS X Mojave (10.14)? I'm now seeing that same Docker mount error after doing so. I had to manually add /usr/local/share/dotnet as a shared folder in the Docker preferences; after that, it worked. I'll look into why that's happening and what the extension might be able to do about it.

@ejizba
Copy link
Member

ejizba commented Sep 28, 2018

Uh nope not on Mojave
screen shot 2018-09-28 at 11 55 48 am

@philliphoff
Copy link
Contributor Author

It looks like we ran into this issue with the VS for Mac Docker Tools and submitted a request to allow programmatic additions to the shared folder list. So strange that I hadn't run into this issue until now on my machine. I'll add instructions to the user to add the shared folder in the README.md.

@philliphoff
Copy link
Contributor Author

I've added a "prerequisite check" that the shared folder exists in the user's Docker preferences.

Copy link
Member

@ejizba ejizba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've addressed my biggest blockers so I'll go ahead and approve. (Only thing that's debatably a blocker for me is telemetry - definitely need that before releasing)

I do want to say I'm still not a huge fan of the Provider pattern for the following reasons:

  1. I think it makes the code unnecessarily complicated
  2. I think tests are more effective if they're running against the 'real thing'
  3. I think you might be using patterns that were helpful testing against VS but are less necessary when testing VS Code (which is a lot easier from my experience)

All that being said - I think it's a philosophical discussion that doesn't have a "correct" answer and thus isn't worth blocking a PR.

Thanks for your contribution!

@philliphoff
Copy link
Contributor Author

philliphoff commented Oct 16, 2018

Also still need CTI testcases

Requested access to the OneNote in order to add test cases.

Should do some basic testing on Linux.

Ran through the basic scenario on Ubuntu (and added prerequisite check for user inclusion in docker group).

@philliphoff
Copy link
Contributor Author

Ok, so all comments should now be addressed one way or another.

@StephenWeatherford Let me know if there's anything else I can do.

return this.resolveDockerDebugConfiguration(folder, debugConfiguration);
return callWithTelemetryAndErrorHandling(
'debugCoreClr',
async () => await this.resolveDockerDebugConfiguration(folder, debugConfiguration));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: debugCoreClr doesn't seem to say what it's really doing. How about "resolveDebugConfiguration"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose debugCoreClr as that was effectively what was being done (i.e. an attempt to initiate debugging of .NET Core in Docker) even though resolveDebugConfiguration is what VS Code happens to name the interface function (and PMs looking at the telemetry data typically care more about the intent than the implementation specifics). I'm amenable to other IDs or schemes; are there any conventions/restrictions in the naming (as the codebase uses a couple different styles)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's what it's effectively doing, it's fine, thanks.

@StephenWeatherford
Copy link
Contributor

Other than the suggested rename of telemetry, ship it! Thanks!

@philliphoff philliphoff merged commit ddb600a into microsoft:master Oct 17, 2018
@philliphoff
Copy link
Contributor Author

Many thanks to @EricJizbaMSFT and @StephenWeatherford for their very thorough review!

@philliphoff philliphoff mentioned this pull request Oct 19, 2018
@microsoft microsoft locked and limited conversation to collaborators Oct 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants