docker / cli

The Docker CLI
Apache License 2.0
4.89k stars 1.92k forks source link

CLI completion has weird bugs #4932

Open bartekpacia opened 7 months ago

bartekpacia commented 7 months ago

Description

When I type docker into my terminal and click <TAB>, I see some weird stuff:

https://github.com/docker/cli/assets/40357511/20cf40e7-daa7-48df-8028-1e7485619607

Reproduce

(Forgive me for assuming macOS and Homebrew)

  1. brew install --cask docker
  2. Make sure completion setup code is present in ~/.zshrc:

    FPATH="$(brew --prefix)/share/zsh/site-functions:$FPATH"

Expected behavior

When I type docker and click <TAB>, I expect to see only commands:

docker version ```bash Client: Cloud integration: v1.0.35+desktop.11 Version: 25.0.3 API version: 1.44 Go version: go1.21.6 Git commit: 4debf41 Built: Tue Feb 6 21:13:26 2024 OS/Arch: darwin/arm64 Context: desktop-linux Server: Docker Desktop 4.28.0 (139021) Engine: Version: 25.0.3 API version: 1.44 (minimum version 1.24) Go version: go1.21.6 Git commit: f417435 Built: Tue Feb 6 21:14:22 2024 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.6.28 GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb runc: Version: 1.1.12 GitCommit: v1.1.12-0-g51d5e94 docker-init: Version: 0.19.0 GitCommit: de40ad0 ```
docker info ```bash Client: Version: 25.0.3 Context: desktop-linux Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.12.1-desktop.4 Path: /Users/bartek/.docker/cli-plugins/docker-buildx compose: Docker Compose (Docker Inc.) Version: v2.24.6-desktop.1 Path: /Users/bartek/.docker/cli-plugins/docker-compose debug: Get a shell into any image or container. (Docker Inc.) Version: 0.0.24 Path: /Users/bartek/.docker/cli-plugins/docker-debug dev: Docker Dev Environments (Docker Inc.) Version: v0.1.0 Path: /Users/bartek/.docker/cli-plugins/docker-dev extension: Manages Docker extensions (Docker Inc.) Version: v0.2.22 Path: /Users/bartek/.docker/cli-plugins/docker-extension feedback: Provide feedback, right in your terminal! (Docker Inc.) Version: v1.0.4 Path: /Users/bartek/.docker/cli-plugins/docker-feedback init: Creates Docker-related starter files for your project (Docker Inc.) Version: v1.0.1 Path: /Users/bartek/.docker/cli-plugins/docker-init sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.) Version: 0.6.0 Path: /Users/bartek/.docker/cli-plugins/docker-sbom scout: Docker Scout (Docker Inc.) Version: v1.5.0 Path: /Users/bartek/.docker/cli-plugins/docker-scout Server: Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 10 Server Version: 25.0.3 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Using metacopy: false Native Overlay Diff: true userxattr: false Logging Driver: json-file Cgroup Driver: cgroupfs Cgroup Version: 2 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog Swarm: inactive Runtimes: io.containerd.runc.v2 runc Default Runtime: runc Init Binary: docker-init containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb runc version: v1.1.12-0-g51d5e94 init version: de40ad0 Security Options: seccomp Profile: unconfined cgroupns Kernel Version: 6.6.16-linuxkit Operating System: Docker Desktop OSType: linux Architecture: aarch64 CPUs: 8 Total Memory: 7.755GiB Name: docker-desktop ID: 31725210-46cb-4bf3-a925-18c0412459d5 Docker Root Dir: /var/lib/docker Debug Mode: false HTTP Proxy: http.docker.internal:3128 HTTPS Proxy: http.docker.internal:3128 No Proxy: hubproxy.docker.internal Experimental: false Insecure Registries: hubproxy.docker.internal:5555 127.0.0.0/8 Live Restore Enabled: false ```
zzJinux commented 6 months ago

Digging a bit, I discovered that the unusual output arises from the interplay between how global flags are printed and how the zsh completion script (_docker) parses the output.

This is how you see the output:

  1. You invoke docker <tab>.
  2. _docker executes docker to get and parse the help output (I'll explain in more detail below). https://github.com/docker/cli/blob/4debf411d1e6efbd9ce65e4250718e9c529a6525/contrib/completion/zsh/_docker#L2652-L2653
  3. zsh prints the output to the user.

In step 2, docker is executed in an environment where the width of its terminal cannot be retrieved, in which case docker falls back assuming the width is 80.

https://github.com/docker/cli/blob/4debf411d1e6efbd9ce65e4250718e9c529a6525/cli/cobra.go#L344-L350

The wrappedFlagUsages is called for printing options in Global Options section. Every single line is wrapped to fit the width 80. Instead of this:

_call_program commands docker 2>&1 outputs the:

(... commands section ...)
Global Options:
      --config string      Location of client config files (default
                           "/Users/zzjinux/.docker")
  -c, --context string     Name of the context to use to connect to the
                           daemon (overrides DOCKER_HOST env var and
                           default context set with "docker context use")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket to connect to
  -l, --log-level string   Set the logging level ("debug", "info",
                           "warn", "error", "fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default
                           "/Users/zzjinux/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default
                           "/Users/zzjinux/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default
                           "/Users/zzjinux/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

The following line parses the output _assuming each option is confined within one line (it's not), and _docker_commands array stores a broken result:

(... commands section ...)
--config:string      Location of client config files (default
"/Users/zzjinux/.docker")
-c,:--context string     Name of the context to use to connect to the
daemon:(overrides DOCKER_HOST env var and
default:context set with "docker context use")
-D,:--debug              Enable debug mode
-H,:--host list          Daemon socket to connect to
-l,:--log-level string   Set the logging level ("debug", "info",
"warn",:"error", "fatal") (default "info")
--tls:Use TLS; implied by --tlsverify
--tlscacert:string   Trust certs signed only by this CA (default
"/Users/zzjinux/.docker/ca.pem")
--tlscert:string     Path to TLS certificate file (default
"/Users/zzjinux/.docker/cert.pem")
--tlskey:string      Path to TLS key file (default
"/Users/zzjinux/.docker/key.pem")
--tlsverify:Use TLS and verify the remote
-v,:--version            Print version information and quit

"warn",:"error", "fatal") (default "info") now appears. The zsh completion system interprets it as ""warn", is the keyword, and "errors", ... is the description".

I think we have two ways to solve this:

  1. Deceive docker into thinking it's in a wider terminal. _call_program commands docker 2>&1 should be tweaked.
  2. Change the behavior of docker to print help unwrapped when it fails to retrieve the width (By the way, only the Global Options section is affected by the terminal width. I don't know why).

@thaJeztah Is there any option more desirable?

zzJinux commented 6 months ago

@thaJeztah Wow, so fast adding the label!

zzJinux commented 6 months ago

https://github.com/zsh-users/zsh/blob/master/Completion/Base/Utility/_call_program

Respecting the COLUMNS environment variable seems a reasonable option too.