microsoft / vscode-remote-release

Visual Studio Code Remote Development: Open any folder in WSL, in a Docker container, or on a remote machine using SSH and take advantage of VS Code's full feature set.
https://aka.ms/vscode-remote
Other
3.6k stars 273 forks source link

Feature request: Allow dynamic "runArgs" for docker #3972

Open dslijepcevic opened 3 years ago

dslijepcevic commented 3 years ago

Could you somehow support a way of building and passing dynamic arguments to the docker run command? We currently only have runArgs array in devcontainer.json that is static in nature and not suitable for any sort of scripting. One use case could be, for example, to mount some extra volumes conditionally while keeping all the nice automatic devcontainer infrastructure that the remote extension has to offer. It'd be enough just to include a shell call somewhere. For example, you could introduce a string version of runArgs that would be included verbatim in the default docker run command and then the entire command would be executed by the shell. Or you could introduce a runCommand override with some pre-defined variables that users would then include in the command they specify. You could also support variables like ${workspaceFolder} in remote.containers.dockerPath so that we can hack our way through :) Thanks

chrmarti commented 3 years ago

There is "initializeCommand", we would then have to reread the devcontainer.json to pick up any changes (we currently don't).

dslijepcevic commented 3 years ago

There is "initializeCommand", we would then have to reread the devcontainer.json to pick up any changes (we currently don't).

If I understood you correctly, you mean to have a self-modifying .devcontainer.json? That would probably not be the best practice since this file is usually stored in a repo, and besides, current initializeCommand already has a major design flaw that requires hacking around. Maybe something like this:

"linux": {
    "runArgsGeneratorCommand": "echo -e '--arg\nvalue'"
}

where the generator would then write run args to stdout, each one on a separate line if you don't want to deal with the quotes. Or even better, you could introduce a similar dynamic envFileGenerator that would produce VAR=VALUE pairs for the container and standard runArgs (and even docker-compose) to consume.

Currently the whole system is pretty much locked down for any non-static content. Even with docker-compose.yml where you can specify multiple compose files, you are not able to generate the dynamic ones in a temporary directory via initializeCommand rather than poluting the source/repo folder because it is not possible to reference /tmp/$TMPDIR/%TEMP%/workspaceHash in a portable way.

chrmarti commented 3 years ago

You could add --env-file to runArgs for a file you generate in initializeCommand. Would that work? (It sounds like you mainly need environment variables.)

dslijepcevic commented 3 years ago

No, that would not work for mounting an extra volume based on user's environment, for example. This information needs to be passed directly to docker run.

Also, currently there is no clean way for initializeCommand to generate a file in platform's temporary directory and then to actually refer to that file path in a portable way in runArgs or docker-compose.yml. VSCode lacks some pre-defined variables like ${hostOS}, ${tmpDir}, ${workspaceHash}, etc. You can only leave a mess in the project directory which is what I'd like to avoid if possible.

chrmarti commented 3 years ago

You could add that file to .gitignore. What is the extra volume being used for?

dslijepcevic commented 3 years ago

One of them is for user's customization of the box itself, we use the same for Vagrant and a dev container. We don't use git or dotfiles repository. Another volume is for some optional app-specific data that the user can mount, if available.

chrmarti commented 3 years ago

You can specify additional mounts with the "mounts" property and there are a few variables (${var}) you can use to make this more dynamic: https://code.visualstudio.com/docs/remote/devcontainerjson-reference#_variables-in-devcontainerjson

dslijepcevic commented 3 years ago

Unfortunately, that's not going to work as we need more complex logic for this than a simple variable replacement. Also, mounts cannot reference any newly calculated dynamic value from the environment and there is no way to specify a "null" volume mount in more complex cases -- docker will report an error. Another issue with this approach is that VSCode cannot provide a default value (e.g. ${localEnv:VARIABLE:-defaultValue}) if user's environment variable is not set! To me this seems like a really useful feature and if you do a google search you'll see than an astounding number of people is asking for the same thing.

chrmarti commented 3 years ago

What if we added a "createCommand" property with which you can implement your own docker run call? I haven't thought it through entirely, but it would likely need to handle the case where the container already exists (and might be stopped) and would have to print the container id to its stdout as the result.

dslijepcevic commented 3 years ago

Hmm, I think that approach would be an overly complicated way to pass a few dynamic args to docker run. Beside making most of the content of .devcontainer.json irrelevant, for automatic start/stop of a container to work seamlessly like before the user would probably need to pass all of these manually:

docker run -a STDOUT -a STDERR -l vsch.quality=stable -l vsch.remote.devPort=0 -l vsch.local.folder=\\wsl$\Ubuntu\proj\test ...

I believe that just making runArgs dynamic in a way similar to what I previously proposed would be simpler. E.g. if runArgs is given as a string, it specifies a shell command to run to get the arguments on stdout, one argument per line. As simple as that. And you add windows, linux and osx overrides for it, like VSCode tasks do so container can be created portably.

dslijepcevic commented 3 years ago

@chrmarti How about something like this?

{
    "initializeCommand": [ "${localWorkspaceFolder}/generateArgsFile.sh" ],
    "linux": { // override
        "initializeCommand": "echo -e '--network\nhost' >${localWorkspaceFolder}/.args",
    }

    // either static or from file
    // "runArgs": [ "--arg", "value" ],
    "runArgsFile": "${localWorkspaceFolder}/.args",
    ...
}

It's using existing initializeCommand (albeit with new platform overloads) and seems more elegant than previous proposals. Input file format can be whatever, e.g. json array, yaml, shell-style quoted args, one arg per line, etc.

dslijepcevic commented 3 years ago

What if we added a "createCommand" property with which you can implement your own docker run call? I haven't thought it through entirely, but it would likely need to handle the case where the container already exists (and might be stopped) and would have to print the container id to its stdout as the result.

@chrmarti Actually, on second thought, I like your idea. However, I'd expect once the container is created for extension to pick it up and run the exact same commands on it like it would normally do. Extension would perform all checks and call this method only if container does not already exist. Also, since Docker does not allow setting of metadata after container creation, there would have to be a way to pass metadata that the extension itself requires for operation to createCommand and, ultimately, to docker run. To keep things simple, if createCommand is defined only a subset of devcontainer.json properties would be available to the user (e.g. no need for build or runArgs, but workspaceFolder or containerUser can be specified, etc.).

chrmarti commented 3 years ago

Related: https://github.com/microsoft/vscode-remote-release/issues/12#issuecomment-644760901

richbai90 commented 2 years ago

Where was this left? I could really use this feature. My specific use case is developing for a USB peripheral in a dev container. Right now my only 2 options are to update the --device option every time I reconnect the device, or to use --privileged. Privileged is not preferred for a variety of well documented reasons. I have a command shell script that can look up a device by its name and returns the absolute path to the device. I only need a way to use it through vscode now.

osemmler commented 2 years ago

+1

Any progress? We would also like to see the ability to dynamically generate runArgs

rubensa commented 1 year ago

Any news on this feature request? Is something been done?

Should It be possible, at least, that VSCode interpolates the environment variables in runArgs before passing the args to docker run?

For example, I want to use:

  "runArgs": [
    // GPU support (Direct Rendering Manager)
    "--device",
    "${DRI_DEVICE:-/dev/null}:/dev/dri"
  ],

that generates:

docker run --device ${DRI_DEVICE:-/dev/null}:/dev/dri ...

that ends with error:

docker: bad mode specified: /dev/dri.

as ${DRI_DEVICE:-/dev/null} is not interpolated (to DRI_DEVICE value or /dev/null if DRI_DEVICE env variable is not defined).

rubensa commented 1 year ago

I also tried with

  "runArgs": [
    // GPU support (Direct Rendering Manager)
    "--device",
    "$(echo ${DRI_DEVICE:-/dev/null}):/dev/dri"
  ],

hopping that it is sent directly to the command line but no luck, the '$(echo ${DRI_DEVICE:-/dev/null})' is not evaluated.

Looks like this is related to the way the runArgs are passed as the generated command works as expected if manually run:

docker run --device $(echo ${DRI_DEVICE:-/dev/null}):/dev/dri ...
chrmarti commented 1 year ago

@rubensa You can use ${localEnv: DRI_DEVICE:/dev/null}.

bhack commented 1 year ago

It is quite annoying to request to devs to comment-uncomment multiple sections every time they need to switch from CPU to GPU:

https://github.com/keras-team/keras-cv/pull/946 https://github.com/airo-ugent/airo-ros/issues/17

joezappie commented 1 year ago

I'm looking to dynamically enable a passthrough serial device if its plugged in. Since docker will fail to run if you specify a device that doesnt exist, I need to check if the device first exists, then add the device to the runsArgs if it does. Sounds like this would be required for something like that.

In our case, for development a serial device is only needed if dealing with hardware. Might not always have a serial USB plugged in or even need one, so to have to have one plugged in just for the container to boot is not preferred.