feat(zsh): support for gain-privileges (automatic sudo)

Currently, this is not possible to make completion work when using sudo.
Zsh supports the "gain-priveleges" feature for this exact use case. By
using the `-p` argument of `_call_program`, if correctly set with
zstyles, Zsh automatically tries to invoke sudo or doas if the command
itself is invoked with sudo or doas. This makes the completion works in
this context:

    sudo docker rm a<tab>

It seems to be a good practice to distinguish between the various cases
where sudo can be invoked by customizing the tag provided to
`_call_program`. Instead of `commands`, we therefore use `docker-X`
where X is the subcommand invoked.

To optin this feature, a user needs something like this in its
configuration file:

    zstyle ':completion:*:docker/*' gain-privileges yes
    zstyle ':completion:*:docker-*/*' gain-privileges yes

Signed-off-by: Vincent Bernat <vincent@bernat.ch>
This commit is contained in:
Vincent Bernat 2023-01-25 14:56:02 +01:00
parent 645395cc77
commit ca97912689
1 changed files with 22 additions and 18 deletions

View File

@ -42,6 +42,10 @@
# Short-option stacking can be enabled with: # Short-option stacking can be enabled with:
# zstyle ':completion:*:*:docker:*' option-stacking yes # zstyle ':completion:*:*:docker:*' option-stacking yes
# zstyle ':completion:*:*:docker-*:*' option-stacking yes # zstyle ':completion:*:*:docker-*:*' option-stacking yes
# Automatic sudo for completion can be enabled with:
# zstyle ':completion:*:docker/*' gain-privileges yes
# zstyle ':completion:*:docker-*/*' gain-privileges yes
__docker_arguments() { __docker_arguments() {
if zstyle -t ":completion:${curcontext}:" option-stacking; then if zstyle -t ":completion:${curcontext}:" option-stacking; then
print -- -s print -- -s
@ -58,7 +62,7 @@ __docker_get_containers() {
type=$1; shift type=$1; shift
[[ $kind = (stopped|all) ]] && args=($args -a) [[ $kind = (stopped|all) ]] && args=($args -a)
lines=(${(f)${:-"$(_call_program commands docker $docker_options ps --format 'table' --no-trunc $args)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-ps docker $docker_options ps --format 'table' --no-trunc $args)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -145,7 +149,7 @@ __docker_complete_info_plugins() {
emulate -L zsh emulate -L zsh
setopt extendedglob setopt extendedglob
local -a plugins local -a plugins
plugins=(${(ps: :)${(M)${(f)${${"$(_call_program commands docker $docker_options info)"##*$'\n'Plugins:}%%$'\n'^ *}}:# $1: *}## $1: }) plugins=(${(ps: :)${(M)${(f)${${"$(_call_program -p docker-info docker $docker_options info)"##*$'\n'Plugins:}%%$'\n'^ *}}:# $1: *}## $1: })
_describe -t plugins "$1 plugins" plugins && ret=0 _describe -t plugins "$1 plugins" plugins && ret=0
return ret return ret
} }
@ -154,7 +158,7 @@ __docker_complete_images() {
[[ $PREFIX = -* ]] && return 1 [[ $PREFIX = -* ]] && return 1
integer ret=1 integer ret=1
declare -a images declare -a images
images=(${${${(f)${:-"$(_call_program commands docker $docker_options images)"$'\n'}}[2,-1]}/(#b)([^ ]##) ##([^ ]##) ##([^ ]##)*/${match[3]}:${(r:15:: :::)match[2]} in ${match[1]}}) images=(${${${(f)${:-"$(_call_program -p docker-image $docker_options images)"$'\n'}}[2,-1]}/(#b)([^ ]##) ##([^ ]##) ##([^ ]##)*/${match[3]}:${(r:15:: :::)match[2]} in ${match[1]}})
_describe -t docker-images "images" images && ret=0 _describe -t docker-images "images" images && ret=0
__docker_complete_repositories_with_tags && ret=0 __docker_complete_repositories_with_tags && ret=0
return ret return ret
@ -164,7 +168,7 @@ __docker_complete_repositories() {
[[ $PREFIX = -* ]] && return 1 [[ $PREFIX = -* ]] && return 1
integer ret=1 integer ret=1
declare -a repos declare -a repos
repos=(${${${(f)${:-"$(_call_program commands docker $docker_options images)"$'\n'}}%% *}[2,-1]}) repos=(${${${(f)${:-"$(_call_program -p docker-image $docker_options images)"$'\n'}}%% *}[2,-1]})
repos=(${repos#<none>}) repos=(${repos#<none>})
_describe -t docker-repos "repositories" repos && ret=0 _describe -t docker-repos "repositories" repos && ret=0
return ret return ret
@ -175,7 +179,7 @@ __docker_complete_repositories_with_tags() {
integer ret=1 integer ret=1
declare -a repos onlyrepos matched declare -a repos onlyrepos matched
declare m declare m
repos=(${${${${(f)${:-"$(_call_program commands docker $docker_options images)"$'\n'}}[2,-1]}/ ##/:::}%% *}) repos=(${${${${(f)${:-"$(_call_program -p docker-image docker $docker_options images)"$'\n'}}[2,-1]}/ ##/:::}%% *})
repos=(${${repos%:::<none>}#<none>}) repos=(${${repos%:::<none>}#<none>})
# Check if we have a prefix-match for the current prefix. # Check if we have a prefix-match for the current prefix.
onlyrepos=(${repos%::*}) onlyrepos=(${repos%::*})
@ -211,7 +215,7 @@ __docker_search() {
if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \ if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \
&& ! _retrieve_cache ${cachename#_}; then && ! _retrieve_cache ${cachename#_}; then
_message "Searching for ${searchterm}..." _message "Searching for ${searchterm}..."
result=(${${${(f)${:-"$(_call_program commands docker $docker_options search $searchterm)"$'\n'}}%% *}[2,-1]}) result=(${${${(f)${:-"$(_call_program -p docker-search docker $docker_options search $searchterm)"$'\n'}}%% *}[2,-1]})
_store_cache ${cachename#_} result _store_cache ${cachename#_} result
fi fi
_wanted dockersearch expl 'available images' compadd -a result _wanted dockersearch expl 'available images' compadd -a result
@ -325,7 +329,7 @@ __docker_complete_runtimes() {
emulate -L zsh emulate -L zsh
setopt extendedglob setopt extendedglob
local -a runtimes_opts local -a runtimes_opts
runtimes_opts=(${(ps: :)${(f)${${"$(_call_program commands docker $docker_options info)"##*$'\n'Runtimes: }%%$'\n'^ *}}}) runtimes_opts=(${(ps: :)${(f)${${"$(_call_program -p docker-info docker $docker_options info)"##*$'\n'Runtimes: }%%$'\n'^ *}}})
_describe -t runtimes-opts "runtimes options" runtimes_opts && ret=0 _describe -t runtimes-opts "runtimes options" runtimes_opts && ret=0
} }
@ -444,8 +448,8 @@ __docker_complete_events_filter() {
setopt extendedglob setopt extendedglob
local -a daemon_opts local -a daemon_opts
daemon_opts=( daemon_opts=(
${(f)${${"$(_call_program commands docker $docker_options info)"##*$'\n'Name: }%%$'\n'^ *}} ${(f)${${"$(_call_program -p docker-info docker $docker_options info)"##*$'\n'Name: }%%$'\n'^ *}}
${${(f)${${"$(_call_program commands docker $docker_options info)"##*$'\n'ID: }%%$'\n'^ *}}//:/\\:} ${${(f)${${"$(_call_program -p docker-info docker $docker_options info)"##*$'\n'ID: }%%$'\n'^ *}}//:/\\:}
) )
_describe -t daemon-filter-opts "daemon filter options" daemon_opts && ret=0 _describe -t daemon-filter-opts "daemon filter options" daemon_opts && ret=0
;; ;;
@ -1161,7 +1165,7 @@ __docker_get_networks() {
type=$1; shift type=$1; shift
lines=(${(f)${:-"$(_call_program commands docker $docker_options network ls)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-network docker $docker_options network ls)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -1383,7 +1387,7 @@ __docker_nodes() {
filter=$1; shift filter=$1; shift
[[ $filter != "none" ]] && args=("-f $filter") [[ $filter != "none" ]] && args=("-f $filter")
lines=(${(f)${:-"$(_call_program commands docker $docker_options node ls $args)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-node docker $docker_options node ls $args)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
declare -A begin end declare -A begin end
@ -1565,7 +1569,7 @@ __docker_plugins() {
filter=$1; shift filter=$1; shift
[[ $filter != "none" ]] && args=("-f $filter") [[ $filter != "none" ]] && args=("-f $filter")
lines=(${(f)${:-"$(_call_program commands docker $docker_options plugin ls $args)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-plugin docker $docker_options plugin ls $args)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -1713,7 +1717,7 @@ __docker_secrets() {
type=$1; shift type=$1; shift
lines=(${(f)${:-"$(_call_program commands docker $docker_options secret ls)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-secret docker $docker_options secret ls)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -1887,7 +1891,7 @@ __docker_services() {
type=$1; shift type=$1; shift
lines=(${(f)${:-"$(_call_program commands docker $docker_options service ls)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-service docker $docker_options service ls)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -2173,7 +2177,7 @@ __docker_stacks() {
local line s local line s
declare -a lines stacks declare -a lines stacks
lines=(${(f)${:-"$(_call_program commands docker $docker_options stack ls)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-stack docker $docker_options stack ls)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -2456,7 +2460,7 @@ __docker_complete_volumes() {
integer ret=1 integer ret=1
declare -a lines volumes declare -a lines volumes
lines=(${(f)${:-"$(_call_program commands docker $docker_options volume ls)"$'\n'}}) lines=(${(f)${:-"$(_call_program -p docker-volume docker $docker_options volume ls)"$'\n'}})
# Parse header line to find columns # Parse header line to find columns
local i=1 j=1 k header=${lines[1]} local i=1 j=1 k header=${lines[1]}
@ -2552,7 +2556,7 @@ __docker_complete_contexts() {
integer ret=1 integer ret=1
declare -a contexts declare -a contexts
contexts=(${(f)${:-"$(_call_program commands docker $docker_options context ls -q)"$'\n'}}) contexts=(${(f)${:-"$(_call_program -p docker-context docker $docker_options context ls -q)"$'\n'}})
_describe -t context-list "context" contexts && ret=0 _describe -t context-list "context" contexts && ret=0
return ret return ret
@ -2648,7 +2652,7 @@ __docker_commands() {
&& ! _retrieve_cache docker_subcommands || [[ ${force_invalidation} -eq 1 ]]; && ! _retrieve_cache docker_subcommands || [[ ${force_invalidation} -eq 1 ]];
then then
local -a lines local -a lines
lines=(${(f)"$(_call_program commands docker 2>&1)"}) lines=(${(f)"$(_call_program docker docker 2>&1)"})
_docker_subcommands=(${${${(M)${lines[$((${lines[(i)*Commands:]} + 1)),-1]}:# *}## #}/\*# ##/:}) _docker_subcommands=(${${${(M)${lines[$((${lines[(i)*Commands:]} + 1)),-1]}:# *}## #}/\*# ##/:})
_docker_subcommands=($_docker_subcommands 'daemon:Enable daemon mode' 'help:Show help for a command') _docker_subcommands=($_docker_subcommands 'daemon:Enable daemon mode' 'help:Show help for a command')
(( $#_docker_subcommands > 2 )) && _store_cache docker_subcommands _docker_subcommands (( $#_docker_subcommands > 2 )) && _store_cache docker_subcommands _docker_subcommands