_docker-compose 17.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
#compdef docker-compose

# Description
# -----------
#  zsh completion for docker-compose
#  https://github.com/sdurrheimer/docker-compose-zsh-completion
# -------------------------------------------------------------------------
# Version
# -------
10
#  1.5.0
11
12
13
14
15
16
17
18
19
20
21
# -------------------------------------------------------------------------
# Authors
# -------
#  * Steve Durrheimer <s.durrheimer@gmail.com>
# -------------------------------------------------------------------------
# Inspiration
# -----------
#  * @albers docker-compose bash completion script
#  * @felixr docker zsh completion script : https://github.com/felixr/docker-zsh-completion
# -------------------------------------------------------------------------

22
23
__docker-compose_q() {
    docker-compose 2>/dev/null $compose_options "$@"
24
25
}

26
27
# All services defined in docker-compose.yml
__docker-compose_all_services_in_compose_file() {
28
29
    local already_selected
    local -a services
30
31
32
    already_selected=$(echo $words | tr " " "|")
    __docker-compose_q config --services \
        | grep -Ev "^(${already_selected})$"
33
34
35
36
}

# All services, even those without an existing container
__docker-compose_services_all() {
37
38
39
40
41
42
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    services=$(__docker-compose_all_services_in_compose_file)
    _alternative "args:services:($services)" && ret=0

    return ret
43
44
45
}

# All services that have an entry with the given key in their docker-compose.yml section
46
__docker-compose_services_with_key() {
47
48
    local already_selected
    local -a buildable
49
    already_selected=$(echo $words | tr " " "|")
50
    # flatten sections to one line, then filter lines containing the key and return section name.
51
52
53
54
55
56
57
    __docker-compose_q config \
        | sed -n -e '/^services:/,/^[^ ]/p' \
        | sed -n 's/^  //p' \
        | awk '/^[a-zA-Z0-9]/{printf "\n"};{printf $0;next;}' \
        | grep " \+$1:" \
        | cut -d: -f1 \
        | grep -Ev "^(${already_selected})$"
58
59
60
61
}

# All services that are defined by a Dockerfile reference
__docker-compose_services_from_build() {
62
63
64
65
66
67
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    buildable=$(__docker-compose_services_with_key build)
    _alternative "args:buildable services:($buildable)" && ret=0

   return ret
68
69
70
71
}

# All services that are defined by an image
__docker-compose_services_from_image() {
72
73
74
75
76
77
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    pullable=$(__docker-compose_services_with_key image)
    _alternative "args:pullable services:($pullable)" && ret=0

    return ret
78
79
80
}

__docker-compose_get_services() {
81
82
83
84
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    local kind
    declare -a running paused stopped lines args services
85
86
87
88
89
90
91
92
93

    docker_status=$(docker ps > /dev/null 2>&1)
    if [ $? -ne 0 ]; then
        _message "Error! Docker is not running."
        return 1
    fi

    kind=$1
    shift
94
    [[ $kind =~ (stopped|all) ]] && args=($args -a)
95

96
97
    lines=(${(f)"$(_call_program commands docker $docker_options ps $args)"})
    services=(${(f)"$(_call_program commands docker-compose 2>/dev/null $compose_options ps -q)"})
98
99
100
101

    # Parse header line to find columns
    local i=1 j=1 k header=${lines[1]}
    declare -A begin end
102
103
104
105
106
107
108
    while (( j < ${#header} - 1 )); do
        i=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 1 ))
        j=$(( i + ${${header[$i,-1]}[(i)  ]} - 1 ))
        k=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 2 ))
        begin[${header[$i,$((j-1))]}]=$i
        end[${header[$i,$((j-1))]}]=$k
    done
109
110
111
112
113
114
    lines=(${lines[2,-1]})

    # Container ID
    local line s name
    local -a names
    for line in $lines; do
115
        if [[ ${services[@]} == *"${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"* ]]; then
116
117
118
119
            names=(${(ps:,:)${${line[${begin[NAMES]},-1]}%% *}})
            for name in $names; do
                s="${${name%_*}#*_}:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}"
                s="$s, ${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"
120
                s="$s, ${${${line[${begin[IMAGE]},${end[IMAGE]}]}/:/\\:}%% ##}"
121
122
123
                if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
                    stopped=($stopped $s)
                else
124
125
126
                    if [[  ${line[${begin[STATUS]},${end[STATUS]}]} = *\(Paused\)* ]]; then
                        paused=($paused $s)
                    fi
127
128
129
130
131
132
                    running=($running $s)
                fi
            done
        fi
    done

133
134
135
136
137
138
139
140
141
142
    [[ $kind =~ (running|all) ]] && _describe -t services-running "running services" running "$@" && ret=0
    [[ $kind =~ (paused|all) ]] && _describe -t services-paused "paused services" paused "$@" && ret=0
    [[ $kind =~ (stopped|all) ]] && _describe -t services-stopped "stopped services" stopped "$@" && ret=0

    return ret
}

__docker-compose_pausedservices() {
    [[ $PREFIX = -* ]] && return 1
    __docker-compose_get_services paused "$@"
143
144
145
}

__docker-compose_stoppedservices() {
146
    [[ $PREFIX = -* ]] && return 1
147
148
149
150
    __docker-compose_get_services stopped "$@"
}

__docker-compose_runningservices() {
151
    [[ $PREFIX = -* ]] && return 1
152
153
154
    __docker-compose_get_services running "$@"
}

155
156
__docker-compose_services() {
    [[ $PREFIX = -* ]] && return 1
157
158
159
160
    __docker-compose_get_services all "$@"
}

__docker-compose_caching_policy() {
161
    oldp=( "$1"(Nmh+1) )            # 1 hour
162
163
164
    (( $#oldp ))
}

165
__docker-compose_commands() {
166
167
168
169
170
171
172
173
174
175
176
177
178
    local cache_policy

    zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
    if [[ -z "$cache_policy" ]]; then
        zstyle ":completion:${curcontext}:" cache-policy __docker-compose_caching_policy
    fi

    if ( [[ ${+_docker_compose_subcommands} -eq 0 ]] || _cache_invalid docker_compose_subcommands) \
        && ! _retrieve_cache docker_compose_subcommands;
    then
        local -a lines
        lines=(${(f)"$(_call_program commands docker-compose 2>&1)"})
        _docker_compose_subcommands=(${${${lines[$((${lines[(i)Commands:]} + 1)),${lines[(I)  *]}]}## #}/ ##/:})
179
        (( $#_docker_compose_subcommands > 0 )) && _store_cache docker_compose_subcommands _docker_compose_subcommands
180
181
182
183
    fi
    _describe -t docker-compose-commands "docker-compose command" _docker_compose_subcommands
}

184
185
186
187
188
189
190
191
192
193
194
195
__docker-compose_subcommand() {
    local opts_help opts_force_recreate opts_no_recreate opts_no_build opts_remove_orphans opts_timeout opts_no_color opts_no_deps

    opts_help='(: -)--help[Print usage]'
    opts_force_recreate="(--no-recreate)--force-recreate[Recreate containers even if their configuration and image haven't changed. Incompatible with --no-recreate.]"
    opts_no_recreate="(--force-recreate)--no-recreate[If containers already exist, don't recreate them. Incompatible with --force-recreate.]"
    opts_no_build="(--build)--no-build[Don't build an image, even if it's missing.]"
    opts_remove_orphans="--remove-orphans[Remove containers for services not defined in the Compose file]"
    opts_timeout=('(-t --timeout)'{-t,--timeout}"[Specify a shutdown timeout in seconds. (default: 10)]:seconds: ")
    opts_no_color='--no-color[Produce monochrome output.]'
    opts_no_deps="--no-deps[Don't start linked services.]"

196
    integer ret=1
197

198
199
200
    case "$words[1]" in
        (build)
            _arguments \
201
202
203
204
                $opts_help \
                '--force-rm[Always remove intermediate containers.]' \
                '--no-cache[Do not use cache when building the image.]' \
                '--pull[Always attempt to pull a newer version of the image.]' \
205
206
                '*:services:__docker-compose_services_from_build' && ret=0
            ;;
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
        (bundle)
            _arguments \
                $opts_help \
                '(--output -o)'{--output,-o}'[Path to write the bundle file to. Defaults to "<project name>.dab".]:file:_files' && ret=0
            ;;
        (config)
            _arguments \
                $opts_help \
                '(--quiet -q)'{--quiet,-q}"[Only validate the configuration, don't print anything.]" \
                '--services[Print the service names, one per line.]' && ret=0
            ;;
        (create)
            _arguments \
                $opts_help \
                $opts_force_recreate \
                $opts_no_recreate \
                $opts_no_build \
                "(--no-build)--build[Build images before creating containers.]" \
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (down)
            _arguments \
                $opts_help \
                "--rmi[Remove images. Type must be one of: 'all': Remove all images used by any service. 'local': Remove only images that don't have a custom tag set by the \`image\` field.]:type:(all local)" \
                '(-v --volumes)'{-v,--volumes}"[Remove named volumes declared in the \`volumes\` section of the Compose file and anonymous volumes attached to containers.]" \
                $opts_remove_orphans && ret=0
            ;;
        (events)
            _arguments \
                $opts_help \
                '--json[Output events as a stream of json objects]' \
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (exec)
            _arguments \
                $opts_help \
                '-d[Detached mode: Run command in the background.]' \
                '--privileged[Give extended privileges to the process.]' \
                '--user=[Run the command as this user.]:username:_users' \
                '-T[Disable pseudo-tty allocation. By default `docker-compose exec` allocates a TTY.]' \
                '--index=[Index of the container if there are multiple instances of a service \[default: 1\]]:index: ' \
                '(-):running services:__docker-compose_runningservices' \
                '(-):command: _command_names -e' \
                '*::arguments: _normal' && ret=0
            ;;
252
253
254
255
256
        (help)
            _arguments ':subcommand:__docker-compose_commands' && ret=0
            ;;
        (kill)
            _arguments \
257
                $opts_help \
258
259
260
261
262
                '-s[SIGNAL to send to the container. Default signal is SIGKILL.]:signal:_signals' \
                '*:running services:__docker-compose_runningservices' && ret=0
            ;;
        (logs)
            _arguments \
263
264
265
266
267
                $opts_help \
                '(-f --follow)'{-f,--follow}'[Follow log output]' \
                $opts_no_color \
                '--tail=[Number of lines to show from the end of the logs for each container.]:number of lines: ' \
                '(-t --timestamps)'{-t,--timestamps}'[Show timestamps]' \
268
269
                '*:services:__docker-compose_services_all' && ret=0
            ;;
270
        (pause)
271
            _arguments \
272
273
                $opts_help \
                '*:running services:__docker-compose_runningservices' && ret=0
274
275
276
            ;;
        (port)
            _arguments \
277
278
279
                $opts_help \
                '--protocol=[tcp or udp \[default: tcp\]]:protocol:(tcp udp)' \
                '--index=[index of the container if there are multiple instances of a service \[default: 1\]]:index: ' \
280
281
282
283
284
                '1:running services:__docker-compose_runningservices' \
                '2:port:_ports' && ret=0
            ;;
        (ps)
            _arguments \
285
                $opts_help \
286
287
288
289
290
                '-q[Only display IDs]' \
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (pull)
            _arguments \
291
292
                $opts_help \
                '--ignore-pull-failures[Pull what it can and ignores images with pull failures.]' \
293
294
                '*:services:__docker-compose_services_from_image' && ret=0
            ;;
295
296
297
298
299
300
        (push)
            _arguments \
                $opts_help \
                '--ignore-push-failures[Push what it can and ignores images with push failures.]' \
                '*:services:__docker-compose_services' && ret=0
            ;;
301
302
        (rm)
            _arguments \
303
                $opts_help \
304
                '(-f --force)'{-f,--force}"[Don't ask to confirm removal]" \
305
                '-v[Remove any anonymous volumes attached to containers]' \
306
307
308
309
                '*:stopped services:__docker-compose_stoppedservices' && ret=0
            ;;
        (run)
            _arguments \
310
                $opts_help \
311
312
                '-d[Detached mode: Run container in the background, print new container name.]' \
                '*-e[KEY=VAL Set an environment variable (can be used multiple times)]:environment variable KEY=VAL: ' \
313
314
315
316
                '--entrypoint[Overwrite the entrypoint of the image.]:entry point: ' \
                '--name=[Assign a name to the container]:name: ' \
                $opts_no_deps \
                '(-p --publish)'{-p,--publish=}"[Publish a container's port(s) to the host]" \
317
318
319
                '--rm[Remove container after run. Ignored in detached mode.]' \
                "--service-ports[Run command with the service's ports enabled and mapped to the host.]" \
                '-T[Disable pseudo-tty allocation. By default `docker-compose run` allocates a TTY.]' \
320
321
                '(-u --user)'{-u,--user=}'[Run as specified username or uid]:username or uid:_users' \
                '(-w --workdir)'{-w,--workdir=}'[Working directory inside the container]:workdir: ' \
322
323
324
325
326
                '(-):services:__docker-compose_services' \
                '(-):command: _command_names -e' \
                '*::arguments: _normal' && ret=0
            ;;
        (scale)
327
328
329
330
            _arguments \
                $opts_help \
                $opts_timeout \
                '*:running services:__docker-compose_runningservices' && ret=0
331
332
            ;;
        (start)
333
334
335
            _arguments \
                $opts_help \
                '*:stopped services:__docker-compose_stoppedservices' && ret=0
336
337
338
            ;;
        (stop|restart)
            _arguments \
339
340
                $opts_help \
                $opts_timeout \
341
342
                '*:running services:__docker-compose_runningservices' && ret=0
            ;;
343
344
345
346
347
        (unpause)
            _arguments \
                $opts_help \
                '*:paused services:__docker-compose_pausedservices' && ret=0
            ;;
348
349
        (up)
            _arguments \
350
351
352
353
354
355
356
357
358
359
360
                $opts_help \
                '(--abort-on-container-exit)-d[Detached mode: Run containers in the background, print new container names. Incompatible with --abort-on-container-exit.]' \
                $opts_no_color \
                $opts_no_deps \
                $opts_force_recreate \
                $opts_no_recreate \
                $opts_no_build \
                "(--no-build)--build[Build images before starting containers.]" \
                "(-d)--abort-on-container-exit[Stops all containers if any container was stopped. Incompatible with -d.]" \
                '(-t --timeout)'{-t,--timeout}"[Use this timeout in seconds for container shutdown when attached or when containers are already running. (default: 10)]:seconds: " \
                $opts_remove_orphans \
361
362
363
364
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (version)
            _arguments \
365
                $opts_help \
366
367
368
                "--short[Shows only Compose's version number.]" && ret=0
            ;;
        (*)
369
370
            _message 'Unknown sub command' && ret=1
            ;;
371
372
373
374
375
    esac

    return ret
}

376
_docker-compose() {
377
378
379
380
381
382
383
    # Support for subservices, which allows for `compdef _docker docker-shell=_docker_containers`.
    # Based on /usr/share/zsh/functions/Completion/Unix/_git without support for `ret`.
    if [[ $service != docker-compose ]]; then
        _call_function - _$service
        return
    fi

384
385
    local curcontext="$curcontext" state line
    integer ret=1
386
387
388
389
390
391
    typeset -A opt_args

    _arguments -C \
        '(- :)'{-h,--help}'[Get help]' \
        '(-f --file)'{-f,--file}'[Specify an alternate docker-compose file (default: docker-compose.yml)]:file:_files -g "*.yml"' \
        '(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \
392
393
394
395
396
397
398
399
400
        '--verbose[Show more output]' \
        '(- :)'{-v,--version}'[Print version and exit]' \
        '(-H --host)'{-H,--host}'[Daemon socket to connect to]:host:' \
        '--tls[Use TLS; implied by --tlsverify]' \
        '--tlscacert=[Trust certs signed only by this CA]:ca path:' \
        '--tlscert=[Path to TLS certificate file]:client cert path:' \
        '--tlskey=[Path to TLS key file]:tls key path:' \
        '--tlsverify[Use TLS and verify the remote]' \
        "--skip-hostname-check[Don't check the daemon's hostname against the name specified in the client certificate (for example if your docker host is an IP address)]" \
401
402
403
        '(-): :->command' \
        '(-)*:: :->option-or-argument' && ret=0

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
    local -a relevant_compose_flags relevant_docker_flags compose_options docker_options

    relevant_compose_flags=(
        "--file" "-f"
        "--host" "-H"
        "--project-name" "-p"
        "--tls"
        "--tlscacert"
        "--tlscert"
        "--tlskey"
        "--tlsverify"
        "--skip-hostname-check"
    )

    relevant_docker_flags=(
        "--host" "-H"
        "--tls"
        "--tlscacert"
        "--tlscert"
        "--tlskey"
        "--tlsverify"
    )

    for k in "${(@k)opt_args}"; do
        if [[ -n "${relevant_docker_flags[(r)$k]}" ]]; then
            docker_options+=$k
            if [[ -n "$opt_args[$k]" ]]; then
                docker_options+=$opt_args[$k]
            fi
        fi
        if [[ -n "${relevant_compose_flags[(r)$k]}" ]]; then
            compose_options+=$k
            if [[ -n "$opt_args[$k]" ]]; then
                compose_options+=$opt_args[$k]
            fi
        fi
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
    done

    case $state in
        (command)
            __docker-compose_commands && ret=0
            ;;
        (option-or-argument)
            curcontext=${curcontext%:*:*}:docker-compose-$words[1]:
            __docker-compose_subcommand && ret=0
            ;;
    esac

    return ret
}

_docker-compose "$@"