Commit Graph

84 Commits

Author SHA1 Message Date
Sebastiaan van Stijn 54ba28f402
docker info: list CLI plugins alphabetically
Before this change, plugins were listed in a random order:

    Client:
     Debug Mode: false
     Plugins:
      doodle: Docker Doodles  all around! 🐳 🎃 (thaJeztah, v0.0.1)
      shell: Open a browser shell on the Docker Host. (thaJeztah, v0.0.1)
      app: Docker Application (Docker Inc., v0.8.0)
      buildx: Build with BuildKit (Docker Inc., v0.3.1-tp-docker)

With this change, plugins are listed alphabetically:

    Client:
     Debug Mode: false
     Plugins:
      app: Docker Application (Docker Inc., v0.8.0)
      buildx: Build with BuildKit (Docker Inc., v0.3.1-tp-docker)
      doodle: Docker Doodles  all around! 🐳 🎃 (thaJeztah, v0.0.1)
      shell: Open a browser shell on the Docker Host. (thaJeztah, v0.0.1)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-12-31 14:33:07 +01:00
Tibor Vass bb8e89bb2e cli-plugins: add test names for easier debugging
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-05-22 15:35:34 +00:00
Tibor Vass 6ca8783730 cli-plugins: add concept of experimental plugin, only enabled in experimental mode
To test, add $(pwd)/build/plugins-linux-amd64 to "cliPluginsExtraDirs" config and run:
make plugins
make binary
HELLO_EXPERIMENTAL=1 docker helloworld

To show it enabled:
HELLO_EXPERIMENTAL=1 DOCKER_CLI_EXPERIMENTAL=enabled docker helloworld

Signed-off-by: Tibor Vass <tibor@docker.com>
2019-05-22 15:35:34 +00:00
Ian Campbell af200f14ed cli-plugins: fix when plugin does not use PersistentPreRun* hooks
This regressed in 3af168c7df ("Ensure plugins can use `PersistentPreRunE`
again.") but this wasn't noticed because the helloworld test plugin has it's
own `PersistentPreRunE` which has the effect of deferring the resolution of the
global variable. In the case where the hook isn't used the variable is resolved
during `newPluginCommand` which is before the global variable was set.

Initialize the plugin command with a stub function wrapping the call to the
(global) hook, this defers resolving the variable until after it has been set,
otherwise the initial value (`nil`) is used in the struct.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-04-30 10:45:29 +01:00
Tibor Vass 1ed02c40fe cli-plugins: alias an existing allowed command (only builder for now)
With this patch it is possible to alias an existing allowed command.
At the moment only builder allows an alias.

This also properly puts the build command under builder, instead of image
where it was for historical reasons.

Signed-off-by: Tibor Vass <tibor@docker.com>
2019-04-19 01:26:45 +00:00
Ian Campbell 1425aeba4a cli-plugins: only parse global arguments once.
This fixes `TestGlobalArgsOnlyParsedOnce/plugin` in the cli-plugins e2e tests.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-04-03 15:07:38 +01:00
Stefan Scherer 4d3a76d71e
Search Windows CLI plugins also in ProgramFiles
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2019-03-20 11:45:03 -07:00
Ian Campbell ff2ed6efa8 cli-plugins: disable use of dial-stdio
Since #1654 so far we've had problems with it not working on Windows (npipe
lacked the `CloseRead` method) and problems with using tcp with tls (the tls
connection also lacks `CloseRead`). Both of these were workedaround in #1718
which added a nop `CloseRead` method.

However I am now seeing hangs (on Windows) where the `system dial-stdio`
subprocess is not exiting (I'm unsure why so far).

I think the 3rd problem found with this is an indication that `dial-stdio` is
not quite ready for wider use outside of its initial usecase (support for
`ssh://` URLs to connect to remote daemons).

This change simply disables the `dial-stdio` path for all plugins. However
rather than completely reverting 891b3d953e ("cli-plugins: use `docker system
dial-stdio` to call the daemon") I've just disabled the functionality at the
point of use and left in a trap door environment variable so that those who
want to experiment with this mode (and perhaps fully debug it) have an easier
path do doing so.

The e2e test for this case is disabled unless the trap door envvar is set. I
also renamed the test to clarify that it is about cli plugins.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-18 10:58:51 +00:00
Ian Campbell 3af168c7df Ensure plugins can use PersistentPreRunE again.
I got a bit carried away in d4ced2ef77 ("allow plugins to have argument
which match a top-level flag.") and broke the ability of a plugin to use the
`PersistentPreRun(E)` hook on its top-level command (by unconditionally
overwriting it) and also broke the plugin framework if a plugin's subcommand
used those hooks (because they would shadow the root one). This could result in
either `dockerCli.Client()` returning `nil` or whatever initialisation the
plugin hoped to do not occuring.

This change revert the relevant bits and reinstates the requirement that a
plugin calls `plugin.PersistentPreRunE` if it uses that hook itself.

It is at least a bit nicer now since we avoid the need for the global struct
since the interesting state is now encapsulated in `tcmd` (and the closure).

In principal this could be done even more simply (by calling `tcmd.Initialize`
statically between `tcmd.HandleGlobalFlags` and `cmd.Execute`) however this has
the downside of _always_ initialising the cli (and therefore dialing the
daemon) even for the `docker-cli-plugin-metadata` command but also for the
`help foo` and `foo --help` commands (Cobra short-circuits the hooks in this
case).

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-14 14:29:09 +00:00
Ian Campbell 0b794e0620 e2e/cli-plugins: explicitly check that PersistentPreRunE works
I regressed this in d4ced2ef77 ("allow plugins to have argument which match a
top-level flag.") by unconditionally overwriting any `PersistentRunE` that the
user may have supplied.

We need to ensure two things:

1. That the user can use `PersistentRunE` (or `PersistentRun`) for their own
   purposes.
2. That our initialisation always runs, even if the user has used
   `PersistentRun*`, since that will shadow the root.

To do this add a `PersistentRunE` to the helloworld plugin which logs (covers 1
above) and then use it when calling the `apiversion` subcommand (which covers 2
since that uses the client)

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-14 14:20:19 +00:00
Ian Campbell d4ced2ef77 allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.

Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.

In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.

We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.

Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.

One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.

The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.

Fixes #1699, and also, as a side-effect, the first item in #1661.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-13 11:28:17 +00:00
Ian Campbell 8289ae03f8 Add e2e tests for plugin vs global argument issues
These won't pass right now due to https://github.com/docker/cli/issues/1699
("Plugins can't re-use the same flags as cli global flags") and the change in
935d47bbe9 ("Ignore unknown arguments on the top-level command."), but the
intention is to fix them now.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-13 10:54:31 +00:00
Ian Campbell 0449ad8d06 Revert "Disable `docker system dial-stdio` on Windows"
This reverts commit c41c23813c.

This case is now handled due to the previous commit.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-12 14:57:52 +00:00
Silvin Lubecki b86bff84b6
Merge pull request #1710 from ijc/no-dial-stdio-on-windows
Disable `docker system dial-stdio` on Windows
2019-03-11 15:14:23 +01:00
Nick Adcock ff51b0d77d harden config.Path() to disallow directory traversal
Signed-off-by: Nick Adcock <nick.adcock@docker.com>
2019-03-07 14:40:53 +00:00
Ian Campbell c41c23813c Disable `docker system dial-stdio` on Windows
The `conn` here is `*winio.win32MessageBytePipe` which does not have a
`CloseRead` method (it does have `CloseWrite`) resulting in:

    docker@WIN-NUC0 C:\Users\docker>.\docker-windows-amd64.exe system dial-stdio
    the raw stream connection does not implement halfCloser

Also disable the path which uses this for cli-plugins on Windows.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-04 17:36:21 +00:00
Corey Quon d871451049
Fix issue where plugin command error exit code is printed out
Signed-off-by: Corey Quon <corey.quon@docker.com>
2019-02-26 09:39:07 -08:00
Silvin Lubecki cdba45bd8b
Merge pull request #1652 from ijc/plugins-config
Add a field to the config file for plugin use.
2019-02-25 12:01:41 +01:00
Silvin Lubecki 11985c6250
Merge pull request #1675 from ulyssessouza/format-plugin-vendor-version-help
Reformat plugin's vendor position and add version on --help
2019-02-25 11:47:09 +01:00
Ian Campbell 20439aa662 Simplify cli plugin config file entry
Make it a simple `map[string]string` for now.

Added a unit test for it.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-02-25 10:38:48 +00:00
Ulysses Souza 92013600f9 Refactor plugins' vendor location on --help
- The placement of the vendor is now in the end of the line.
- A '*' is now added as suffix of plugins' top level commands.

Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2019-02-21 17:54:11 +01:00
Ian Campbell 891b3d953e cli-plugins: use `docker system dial-stdio` to call the daemon
This means that plugins can use whatever methods the monolithic CLI supports,
which is good for consistency.

This relies on `os.Args[0]` being something which can be executed again to
reach the same binary, since it is propagated (via an envvar) to the plugin for
this purpose. This essentially requires that the current working directory and
path are not modified by the monolithic CLI before it launches the plugin nor
by the plugin before it initializes the client. This should be the case.

Previously the fake apiclient used by `TestExperimentalCLI` was not being used,
since `cli.Initialize` was unconditionally overwriting it with a real one
(talking to a real daemon during unit testing, it seems). This wasn't expected
nor desirable and no longer happens with the new arrangements, exposing the
fact that no `pingFunc` is provided, leading to a panic. Add a `pingFunc` to
the fake client to avoid this.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-02-18 11:53:37 +00:00
Ian Campbell 8fa7c572d4 cli-plugins: use only the first word of `Use` as the name
This can be obtained with the `.Name()` method.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-02-01 10:41:10 +00:00
Ian Campbell baabf6e8ad Ensure that plugins are only listed once in help outputs.
They were listed twice in `docker --help` (but not `docker help`), since the
stubs were added in both `tryRunPluginHelp` and the `setHelpFunc` closure.

Calling `AddPluginStubCommands` earlier in `setHelpFunc` before the call to
`tryRunPluginHelp` is sufficient. Also it is no longer necessary to add just
valid plugins (`tryRunPluginHelp` handles invalid plugins correctly) so remove
that logic (which was in any case broken for e.g. `docker --help`).

Update the e2e test to check for duplicate entries and also to test `docker
--help` which was previously missed.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:55:42 +00:00
Ian Campbell 0a89eb554b Ensure plugins default search path obeys `--config`
A static global initialiser happens before the arguments are parsed, so we need
to calculate the path later.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell 935d47bbe9 Ignore unknown arguments on the top-level command.
This allows passing argument to plugins, otherwise they are caught by the parse
loop, since cobra does not know about each plugin at this stage (to avoid
having to always scan for all plugins) this means that e.g. `docker plugin
--foo` would accumulate `plugin` as an arg to the `docker` command, then choke
on the unknown `--foo`.

This allows unknown global args only, unknown arguments on subcommands (e.g.
`docker ps --foo`) are still correctly caught.

Add an e2e test covering this case.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell 1337895751 Check for `.exe` case insensitively
On Windows `foo.exe`, `foo.eXe` and `foo.EXE` are equally executable.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell 63f3ad181b Refactor code which deals with Windows' `.exe` suffix
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell 609dcb9152 Documentation on writing a plugin
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell 1c576e9043 Integrate CLI plugins into `docker info`
Fairly straight forward. It became necessary to wrap `Plugin.Err` with a type
which implements `encoding.MarshalText` in order to have that field rendered
properly in the `docker info -f '{{json}}'` output.

Since I changed the type somewhat I also added a unit test for `formatInfo`.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell e5e578abc7 Allow plugins to make use of the cobra `PersistentPreRun` hooks.
Previously a plugin which used these hooks would overwrite the top-level plugin
command's use of this hook, resulting in the dockerCli object not being fully
initialised.

Provide a function which plugins can use to chain to the required behaviour.
This required some fairly ugly arrangements to preserve state (which was
previously in-scope in `newPluginCOmmand`) to be used by the new function.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:45:26 +00:00
Ian Campbell f912b55bd1 Integrate CLI plugins into `docker help` output.
To do this we add a stub `cobra.Command` for each installed plugin (only when
invoking `help`, not for normal running).

This requires a function to list all available plugins so that is added here.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:44:06 +00:00
Ian Campbell f1f31abbe5 Add support for running a CLI plugin
Also includes the  scaffolding for finding a validating plugin candidates.

Argument validation is moved to RunE to support this, so `noArgs` is removed.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-30 13:44:04 +00:00
Ian Campbell e96240427f Add basic framework for writing a CLI plugin
That is, the helper to be used from the plugin's `main`.

Also add a `helloworld` plugin example and build integration.

Signed-off-by: Ian Campbell <ijc@docker.com>
2019-01-29 11:26:40 +00:00