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

How to use vscode-docker as a dependency of other extensions #1496

Closed
umarcor opened this issue Dec 16, 2019 · 13 comments
Closed

How to use vscode-docker as a dependency of other extensions #1496

umarcor opened this issue Dec 16, 2019 · 13 comments

Comments

@umarcor
Copy link

umarcor commented Dec 16, 2019

I'm writing a TS extension that provides some features on top of vscode-docker and I would like to use some of the types and functions defined here.

Precisely, a command is added to "category": "Docker Images" and to the context menu ("when": "view == dockerImages && viewItem == image", "group": "images_1_run@2"). This is currently handled as follows:

registerCommand('dockerTemplates.runImage',
	(item) => showWarningMessage(`'runImage' not implemented yet [${item}].`));

I would like to define the type of item as ImageTreeItem instead. The type is defined in subdir src/tree/images/ImageTreeItem of this repo. However, it seems not to be possible to add vscode-docker as a dependency through npm or yarn.

Apart from that, when the command is executed directly (not from the context menu), the image (item) is undefined. For other commands, vscode-docker shows a list with all the available images so that users can pick one. It is a multi-level menu that asks for the name first, and then shows the tags. I'd like to import and reuse this "menu to select an image" feature. Is it possible?

@philliphoff
Copy link
Member

@umarcor I'd be interested to know what kind of features you are looking to add.

You can define an extension as a required dependency of your extension via the extensionDependencies of the package.json file. However, while extensions can provide APIs to be consumed or extended by other extensions, vscode-docker does not really do so (yet). VS Code's declarative command definitions also allow arbitrary extensions to "extend" the context menus of other extensions provided you know where to look for views/groups, even if those extensions are not necessarily designed for such extension. In that case, the extender needs to accept that updates to the underlying extension may change those views/groups in ways that break the dependent extension.

I would like to define the type of item as ImageTreeItem instead. The type is defined in subdir src/tree/images/ImageTreeItem of this repo. However, it seems not to be possible to add vscode-docker as a dependency through npm or yarn.

Given that vscode-docker was not specifically designed to be extended in that way, we don't ship the definitions for types used within the extension. You could certainly create your own type definitions to match, however.

Apart from that, when the command is executed directly (not from the context menu), the image (item) is undefined. For other commands, vscode-docker shows a list with all the available images so that users can pick one. It is a multi-level menu that asks for the name first, and then shows the tags. I'd like to import and reuse this "menu to select an image" feature. Is it possible?

Again, as we didn't intend the extension to expose consumable APIs, that logic is not necessarily exposed in a way that can be reused outside of the extension.

It'd be great to have more information about the commands you'd like to add to those context menus. If it makes sense, in that those commands are core to the use of Docker for many developers, an option could be to make a contribution to vscode-docker itself rather than try to "extend" it unnaturally. Another option is for us to consider creating official, supported extension points/APIs for use by extensions like yours. Without more context, it's difficult to say what the best option might be.

@umarcor
Copy link
Author

umarcor commented Dec 16, 2019

@philliphoff, thanks a lot for your very quick reply! Overall, I understand that this is not an expected use case and that sources are not designed to support it. Actually, my main motivation to open this issue was to discuss it.

I'd be interested to know what kind of features you are looking to add.

It'd be great to have more information about the commands you'd like to add to those context menus.

Sure. I use Docker for Windows very frequently in order to try new projects written in Python, JS/TS, Go, Rust, Ruby... Most of the projects have installation instructions but not ready to use Dockerfiles (or images). Projects have different requirements, so I find myself continuously writing commands such as the following, before attaching VSCode to the containers through 'Remotes - Containers':

winpty docker run --rm -it [-v /$(pwd)://src -w //src] [-p 5000] <image_name> [bash]
#or
runx --no-auth -- x11docker --hostdisplay -i [--user=0] -- [-v /$(pwd)://src] [-p 5000] -- <image_name> bash 

As you see, options are:

  • Whether to bind $(pwd).
  • Whether to bind a port.
  • Which user id to use inside the container.
  • Run a regular interactive container, or run an interactive container with X11 support (for GUI apps). Note that runx/x11docker work with either Cygwin/MSYS2 or WSL.

Hence, it is not hard (once you learn them by heart) to write these commands, but it is annoying. Plus, having them written in some file does not help much, because I need to find it, copy, modify and them run. It is almost as much effort as writing it all.

The feature I'd like to implement is to replace current Run and Run interactive commands with a single Run (Template) that would open a QuickPick menu (as suggested in #1311, when a single matching task is not found). The menu would show the new Templates plus docker-run tasks. The difference between a template and a task is that the latter is expected to be defined in a JSON file and to contain all the required arguments. Conversely, Templates are not complete and do not need to be written in a JSON file. Hence, the user is prompted (through QuickPick/QuickInput) to complete the missing fields or to leave them blank. This is a very early example of how the UI might look like:

dockertpls

Of course, current Run and Run interactive commands might be reimplemented to be the two default Templates; which is shown in the screencast.

Such a feature is related to #1001, #1321 and it would hopefully fix/close #1274, #1311.

In that case, the extender needs to accept that updates to the underlying extension may change those views/groups in ways that break the dependent extension.

Yes, I assume that maintaining a separate extension will require it to be kept up to date with changes in vscode-docker; and that UX might be bad when non-compatible versions of the extensions are used.

You could certainly create your own type definitions to match, however.

Importing the type definitions is a kind of most-simple use case to reuse some content from this repo. That is, while internal APIs might depend on some specific objects being allocated/available, importing type definitions might be easier. In practice, if I cannot reuse other resources (say functions), I think that this is not critical.

Again, as we didn't intend the extension to expose consumable APIs, that logic is not necessarily exposed in a way that can be reused outside of the extension.

Another option is for us to consider creating official, supported extension points/APIs for use by extensions like yours.

I guess that this was/is the implicit question in this issue: what do you think about enhancing this extension to expose consumable APIs? There are at least two use cases where it'd be so helpful:

  • Extensions that want to interact with the Docker daemon, without reimplementing all the detection logic/settings. Hence, instead of calling docker run, extensions would use ${vscode-docker.cli} run or vscode-docker.cli.run(...).
  • Extensions that want to retrieve/select lists of images, containers, volumes, networks, etc.

It is possible for third-party extensions to import docker-modem, but I believe that vscode-docker contains features specific to VSCode that would be a better fit for extensions.

If it makes sense, in that those commands are core to the use of Docker for many developers, an option could be to make a contribution to vscode-docker itself rather than try to "extend" it unnaturally.

Without more context, it's difficult to say what the best option might be.

I agree that this feature might be better contributed to vscode-docker itself, rather than trying to extend it unnaturally. On the one hand, this is because I'm lacking some knowledge about the canonical location of settings and common resources in different contexts: single folder, workspace, user settings, host settings, etc. On the other hand, I think that the use cases that I would like to address are not well supported yet.

Unfortunately, I could not build this extension... I tried with master, 0.9.0 and 0.8.2. I get:

> [email protected] build /wrk
> tsc -p ./

src/tasks/DockerPseudoterminal.ts:6:75 - error TS2305: Module '"vscode"' has no exported member 'Pseudoterminal'.

6 import { CancellationToken, CancellationTokenSource, Event, EventEmitter, Pseudoterminal, TaskScope, TerminalDimensions, workspace, WorkspaceFolder } from 'vscode';
                                                                            ~~~~~~~~~~~~~~

src/tasks/DockerPseudoterminal.ts:6:102 - error TS2305: Module '"vscode"' has no exported member 'TerminalDimensions'.

6 import { CancellationToken, CancellationTokenSource, Event, EventEmitter, Pseudoterminal, TaskScope, TerminalDimensions, workspace, WorkspaceFolder } from 'vscode';
                                                                                                       ~~~~~~~~~~~~~~~~~~

src/tasks/DockerTaskProvider.ts:6:29 - error TS2305: Module '"vscode"' has no exported member 'CustomExecution'.

6 import { CancellationToken, CustomExecution, ProviderResult, Task, TaskProvider } from 'vscode';
                              ~~~~~~~~~~~~~~~

src/utils/getVSCodeRemoteInfo.ts:6:15 - error TS2305: Module '"vscode"' has no exported member 'ExtensionKind'.

6 import { env, ExtensionKind, extensions } from 'vscode';
                ~~~~~~~~~~~~~

src/utils/getVSCodeRemoteInfo.ts:43:48 - error TS2339: Property 'remoteName' does not exist on type 'typeof env'.

43     const remoteName: string | undefined = env.remoteName;
                                                  ~~~~~~~~~~

src/utils/getVSCodeRemoteInfo.ts:62:23 - error TS2339: Property 'extensionKind' does not exist on type 'Extension<any>'.

62         if (extension.extensionKind === ExtensionKind.UI) {
                         ~~~~~~~~~~~~~


Found 6 errors.

Apart from that, considering how #1001 was handled, I feel that there is no much interest on this feature, as the suggested approach seems to be using either compose files or tasks. Nonetheless, I'd be glad to upstream it.

Note that I also considered implementing this as a Task Provider. However, I am not familiar with class vscode.Task. Anyway, this is independent from the menus to complete missing fields in Templates.

@philliphoff
Copy link
Member

@umarcor Based on a lot of similar user feedback, we now have a proposal to provide customization of many of the Docker commands issued by the extension. The proposal includes the ability to have multiple customizations, some of which can be automatically matched to specific images/containers and others which can be selected via a prompt.

It'd be great to get feedback from you as to how the proposal would meet your needs (or not) that you brought up in this issue (that prompted your original desire to extend our extension).

@bwateratmsft
Copy link
Collaborator

@umarcor Have you been able to try out the command customization in 1.0.0?

@umarcor
Copy link
Author

umarcor commented Mar 31, 2020

@bwateratmsft, unfortunately, I could not. It is in first place of my non-compulsory tasks because I'm using VSCode and Docker every day, and every day I remember it. But I had a trip in the end Feb, then the crisis came and I'm having a hard time to keep up with this. I will let you know as soon as I can.

@bwateratmsft
Copy link
Collaborator

No worries!

@umarcor
Copy link
Author

umarcor commented Apr 14, 2020

@bwateratmsft I wanted to try the new features, but I don't know where to start. I tried adding "docker.commands.runInteractive": "docker run --rm -it ghdl/ghdl:buster-mcode" to my settings.json, in the hope that it would replace the default "Docker Images: Run Interactive" command, not to prompt for any parameter. However, it seems to have no effect at all. Where do I need to set the templates explained in #1596?

EDIT

Ok. Got it:

    "docker.commands.runInteractive":
    [
        {
            "label": "mine",
            "template": "docker run --rm -it ghdl/ghdl:buster-mcode"
        },
        {
            "label": "yours",
            "template": "docker run --rm -it ${tag}"
        }
    ]

@umarcor
Copy link
Author

umarcor commented Apr 14, 2020

First off, I'd like to say that this new feature is awesome! It is still quite verbose, but it will definitely save lots of typing! It's a great step in the good direction.

The following questions are to wrap my head around the current limits and to know about your plans for future releases.

  • What's the difference between 'Run' and 'Run Interactive'? Shouldn't those be two default templates for 'Run'?
    "docker.commands.run":
    [
        {
            "label": "interactive",
            "template": "docker run --rm -it ${exposedPorts} ${tag}"
        },
        {
            "label": "daemon",
            "template": "docker run --rm -d ${exposedPorts} ${tag}"
        }
    ]

I thought that 'Run' might not open a new terminal, while 'Run Interactive' would. That would explain why it is required to tell them apart. However, I found it not to be the case. Both of them do open a new terminal.

  • Related to the previous point, would it be possible to define in the template whether a new terminal should be opened or the currently opened one should be used instead? I guess that this might well be a completeley independent feature which I just happened to care about now...

  • Command "Docker Images: Run Interactive" seems not to be affected by my settings. When I try to run an image by right-clicking on it, template selection is shown as expected. However, if I do F1 -> "Docker Images: Run Interactive", no template selection is available.

  • How can I define one or multiple custom parameters which the user can set in a dropdown menu?

        {
            "label": "customRun",
            "template": "docker run --rm -it ${myCustomParam} ${tag}"
        }
  • Is it possible to define "partial configurations"?
    • When no configurations exist, the behaviour is the default.
    • When configurations exist, the user can choose any of them or the default (the raw template).
      • Configuration selection can be shown after a template is selected, or configurations can be expanded in the initial template list.
    • When the command is executed from the GUI (right-clicking on an image), conflicts need to be resolved. In the example above, tag would be a conflict. However, other parameters might not conflict. I guess the most simple approach is not to show configurations/templates that conflict.
    {
        "label": "ghdl",
        "template": "docker run --rm -it ${tag}",
        "configurations": {
            "gmcode": {
                "tag": "ghdl/ghdl:buster-mcode"
            },
            "gllvm": {
                "tag": "ghdl/ghdl:buster-llvm-7",
            }
        }
    }

In fact, my main use case of these configurations would be with the custom params as asked in the previous point. For example:

    {
        "label": "mount",
        "template": "docker run --rm -it ${volume} -w /src ${tag}",
        "configurations": {
            "src": {
                "volume": "$(pwd):/src"
            },
            "wrk": {
                "volume": "$(pwd):/wrk"
            },
            "abs": {
                "volume": "${host}:${container}"
            }
        }
    }

Note that the last configuration would be an edge case because it would always prompt the user for host and container. This is related to the conversation we had in #1596 about "remembering" latest used configurations. Hence, if the user used template "mount" with configuration "abs" and "/c/Users/username/workdir" and "/wrk" were introduced as parameters, a new configuration would be saved automatically:

"randomID": {
    "volume": "/c/Users/username/workdir:/wrk"
}

@bwateratmsft
Copy link
Collaborator

Thanks for your interest! I'll try to cover your questions:

What's the difference between 'Run' and 'Run Interactive'? Shouldn't those be two default templates for 'Run'?

"Run" is meant for background container starts (using -d flag), and "Run Interactive" for launching with a shell into the container (using -it flag).

Related to the previous point, would it be possible to define in the template whether a new terminal should be opened or the currently opened one should be used instead? I guess that this might well be a completeley independent feature which I just happened to care about now...

No, but we have another bug for that planned for 1.2 (#251). The behavior will be how VSCode treats tasks--if terminal 1 is busy running a different task, then a new terminal will be created, otherwise it will be reused--and so on.

Command "Docker Images: Run Interactive" seems not to be affected by my settings. When I try to run an image by right-clicking on it, template selection is shown as expected. However, if I do F1 -> "Docker Images: Run Interactive", no template selection is available.

When you are right-clicking, are you doing "Run" or "Run Interactive"? If you set up exactly one "Run Interactive" template, there will never be a prompt for which (more on that below).

How can I define one or multiple custom parameters which the user can set in a dropdown menu?

We don't have a quickpick/dropdown option for parameters, but we do support arbitrary config values. For example, in settings.json, I could put "some.param": "someValue", and in the command config, "template": "... ${config:some.param}". The setting name you define does not need to belong to any existing extension or to VSCode itself--it can be anything you want.

Is it possible to define "partial configurations"?

I guess it depends what you mean. We have the ability to select through multiple available commands using some contextual hints, with the match field on the command definition:

"docker.commands.runInteractive": {
    "label": "My Alpine starter",
    "template": "docker run -it ${exposedPorts} ${tag}",
    "match": "alpine"
}

In this example, match is a regular expression (case-insensitive), and we will look at a few contextual hints to match it against. For "Run Interactive", and "Run", we look at the full tag of the image. So, if I do "Run Interactive" on any image with "alpine" in the full tag, it will run this command instead of the default.

Our selection behavior is basically this--if there are matching templates, we ask you to choose which (the choice is automatic if there's exactly 1 matching template--no prompt). If none match, we ask you to choose between all "universal" templates, i.e. those without match parameter (again, automatic if there's exactly 1). Finally, if there's none matching and none universal, we use the default.

If you want to get really advanced for a particular image--for example, a database container--and use volumes, specific ports, environment variables, etc.--we'd recommend using the docker-run task. The command customization is meant to be more generic. The task offers relatively easy customization of nearly everything you can pass to docker run.

@umarcor
Copy link
Author

umarcor commented Apr 17, 2020

"Run" is meant for background container starts (using -d flag), and "Run Interactive" for launching with a shell into the container (using -it flag).

That's the default template for each of them. However, now, both of them can be defined as templates of the same command. For example:

    "docker.commands.run":
    [
        {
            "label": "interactive",
            "template": "docker run --rm -it ${exposedPorts} ${tag}"
        },
        {
            "label": "daemon",
            "template": "docker run --rm -d ${exposedPorts} ${tag}"
        }
    ]

Then, 'Run Interactive' feels redundant, because it is already accesible as 'Run'.

When you are right-clicking, are you doing "Run" or "Run Interactive"? If you set up exactly one "Run Interactive" template, there will never be a prompt for which (more on that below).

When I right-click, it works as expected. However, when I do F1 -> "Docker Images: Run Interactive" instead, the default command is executed. I.e., even if I have three templates, I am not prompted to choose one.

I guess it depends what you mean. We have the ability to select through multiple available commands using some contextual hints, with the match field on the command definition

I mean to reduce the verbosity of setting the templates, so that it is possible to have granularity without copying almost identical templates again and again. Conversely, match is about reducing the verbosity/effort when choosing which to execute. I'm ok with the current status in this regard. I don't want the tool to make a clever guess for me, I want it to let me decide fast.

For example, being able to write this:

    {
        "label": "mount",
        "template": "docker run --rm -it ${volume} -w /src ${tag}",
        "configurations": {
            "src": {"volume": "$(pwd):/src"},
            "wrk": {"volume": "$(pwd):/wrk"},
            "abs": {"volume": "${host}:${container}"}
        }
    }

instead of this:

    {
        "label": "src",
        "template": "docker run --rm -it $(pwd):/src -w /src ${tag}",
    },
    {
        "label": "wrk",
        "template": "docker run --rm -it $(pwd):/wrk -w /src ${tag}",
    },
    {
        "label": "wrk",
        "template": "docker run --rm -it ${host}:${container} -w /src ${tag}",
    }

If you want to get really advanced for a particular image--for example, a database container--and use volumes, specific ports, environment variables, etc.--we'd recommend using the docker-run task. The command customization is meant to be more generic. The task offers relatively easy customization of nearly everything you can pass to docker run.

I'm afraid that docker-run task won't work because it does not allow to customize docker run. Only arguments/options can be modified. In fact, using tasks was my first investigation before opening this issue and trying to write an extension myself. Now, at least, it's much easier to just have the settings open in a tab and customize the commands before running a container.

@bwateratmsft
Copy link
Collaborator

However, now, both of them can be defined as templates of the same command.

They can, and that is intentional. You can also make "Run Interactive" do something totally weird like rm -rf /. The two of them need to stay as separate commands (and separate settings) because they correspond to separate actions in the UI. That said, you could define an interactive version of the "Run" command.

However, when I do F1 -> "Docker Images: Run Interactive" instead, the default command is executed. I.e., even if I have three templates, I am not prompted to choose one.

That sounds like it might be a bug, I'm going to take a look.

I'm afraid that docker-run task won't work because it does not allow to customize docker run.

What is the task missing that you need? We're certainly open to expanding it if it's lacking.

@bwateratmsft
Copy link
Collaborator

However, when I do F1 -> "Docker Images: Run Interactive" instead, the default command is executed. I.e., even if I have three templates, I am not prompted to choose one.

That sounds like it might be a bug, I'm going to take a look.

I tried it out from both command palette and the explorer view, and got the prompt:
image

Here's what I have in settings.json:

    "docker.commands.runInteractive": [
        {
            "label": "Regular",
            "template": "docker run --rm -it ${exposedPorts} ${tag}"
        },
        {
            "label": "No RM",
            "template": "docker run -it ${exposedPorts} ${tag}"
        }
    ]

Can you share what setting you have that isn't resulting in a prompt?

@vscodebot vscodebot bot closed this as completed May 8, 2020
@vscodebot
Copy link

vscodebot bot commented May 8, 2020

This issue has been closed automatically because it needs more information and has not had recent activity. See also our issue reporting guidelines.

Happy Coding!

@bwateratmsft bwateratmsft removed this from the Future milestone May 12, 2020
@vscodebot vscodebot bot locked and limited conversation to collaborators Jun 22, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants