_docker-compose 18.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#compdef docker-compose

# Description
# -----------
#  zsh completion for docker-compose
# -------------------------------------------------------------------------
# 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
# -------------------------------------------------------------------------

17
18
__docker-compose_q() {
    docker-compose 2>/dev/null $compose_options "$@"
19
20
}

21
22
# All services defined in docker-compose.yml
__docker-compose_all_services_in_compose_file() {
23
24
    local already_selected
    local -a services
25
26
27
    already_selected=$(echo $words | tr " " "|")
    __docker-compose_q config --services \
        | grep -Ev "^(${already_selected})$"
28
29
30
31
}

# All services, even those without an existing container
__docker-compose_services_all() {
32
33
34
35
36
37
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    services=$(__docker-compose_all_services_in_compose_file)
    _alternative "args:services:($services)" && ret=0

    return ret
38
39
40
}

# All services that have an entry with the given key in their docker-compose.yml section
41
__docker-compose_services_with_key() {
42
43
    local already_selected
    local -a buildable
44
    already_selected=$(echo $words | tr " " "|")
45
    # flatten sections to one line, then filter lines containing the key and return section name.
46
47
48
49
50
51
52
    __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})$"
53
54
55
56
}

# All services that are defined by a Dockerfile reference
__docker-compose_services_from_build() {
57
58
59
60
61
62
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    buildable=$(__docker-compose_services_with_key build)
    _alternative "args:buildable services:($buildable)" && ret=0

   return ret
63
64
65
66
}

# All services that are defined by an image
__docker-compose_services_from_image() {
67
68
69
70
71
72
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    pullable=$(__docker-compose_services_with_key image)
    _alternative "args:pullable services:($pullable)" && ret=0

    return ret
73
74
75
}

__docker-compose_get_services() {
76
77
78
79
    [[ $PREFIX = -* ]] && return 1
    integer ret=1
    local kind
    declare -a running paused stopped lines args services
80
81
82
83
84
85
86
87
88

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

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

91
92
    lines=(${(f)"$(_call_program commands docker $docker_options ps $args)"})
    services=(${(f)"$(_call_program commands docker-compose 2>/dev/null $compose_options ps -q)"})
93
94
95
96

    # Parse header line to find columns
    local i=1 j=1 k header=${lines[1]}
    declare -A begin end
97
98
99
100
101
102
103
    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
104
105
106
107
108
109
    lines=(${lines[2,-1]})

    # Container ID
    local line s name
    local -a names
    for line in $lines; do
110
        if [[ ${services[@]} == *"${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"* ]]; then
111
112
113
114
            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]}]%% ##}"
115
                s="$s, ${${${line[${begin[IMAGE]},${end[IMAGE]}]}/:/\\:}%% ##}"
116
117
118
                if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
                    stopped=($stopped $s)
                else
119
120
121
                    if [[  ${line[${begin[STATUS]},${end[STATUS]}]} = *\(Paused\)* ]]; then
                        paused=($paused $s)
                    fi
122
123
124
125
126
127
                    running=($running $s)
                fi
            done
        fi
    done

128
129
130
131
132
133
134
135
136
137
    [[ $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 "$@"
138
139
140
}

__docker-compose_stoppedservices() {
141
    [[ $PREFIX = -* ]] && return 1
142
143
144
145
    __docker-compose_get_services stopped "$@"
}

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

150
151
__docker-compose_services() {
    [[ $PREFIX = -* ]] && return 1
152
153
154
155
    __docker-compose_get_services all "$@"
}

__docker-compose_caching_policy() {
156
    oldp=( "$1"(Nmh+1) )            # 1 hour
157
158
159
    (( $#oldp ))
}

160
__docker-compose_commands() {
161
162
163
164
165
166
167
168
169
170
171
172
173
    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)  *]}]}## #}/ ##/:})
174
        (( $#_docker_compose_subcommands > 0 )) && _store_cache docker_compose_subcommands _docker_compose_subcommands
175
176
177
178
    fi
    _describe -t docker-compose-commands "docker-compose command" _docker_compose_subcommands
}

179
180
181
182
183
184
185
186
187
188
189
190
__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.]"

191
    integer ret=1
192

193
194
195
    case "$words[1]" in
        (build)
            _arguments \
196
                $opts_help \
197
                "*--build-arg=[Set build-time variables for one service.]:<varname>=<value>: " \
198
                '--force-rm[Always remove intermediate containers.]' \
199
                '--memory[Memory limit for the build container.]' \
200
201
                '--no-cache[Do not use cache when building the image.]' \
                '--pull[Always attempt to pull a newer version of the image.]' \
202
203
                '*:services:__docker-compose_services_from_build' && ret=0
            ;;
204
205
206
        (bundle)
            _arguments \
                $opts_help \
207
                '--push-images[Automatically push images for any services which have a `build` option specified.]' \
208
209
210
211
212
213
                '(--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.]" \
214
215
216
                '--resolve-image-digests[Pin image tags to digests.]' \
                '--services[Print the service names, one per line.]' \
                '--volumes[Print the volume names, one per line.]' && ret=0
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
            ;;
        (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.]' \
245
		'(-u --user)'{-u,--user=}'[Run the command as this user.]:username:_users' \
246
247
248
249
250
251
                '-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
        (help)
            _arguments ':subcommand:__docker-compose_commands' && ret=0
            ;;
255
256
257
258
259
260
	(images)
	    _arguments \
		$opts_help \
		'-q[Only display IDs]' \
		'*:services:__docker-compose_services_all' && ret=0
	    ;;
261
262
        (kill)
            _arguments \
263
                $opts_help \
264
265
266
267
268
                '-s[SIGNAL to send to the container. Default signal is SIGKILL.]:signal:_signals' \
                '*:running services:__docker-compose_runningservices' && ret=0
            ;;
        (logs)
            _arguments \
269
270
271
272
273
                $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]' \
274
275
                '*:services:__docker-compose_services_all' && ret=0
            ;;
276
        (pause)
277
            _arguments \
278
279
                $opts_help \
                '*:running services:__docker-compose_runningservices' && ret=0
280
281
282
            ;;
        (port)
            _arguments \
283
284
285
                $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: ' \
286
287
288
289
290
                '1:running services:__docker-compose_runningservices' \
                '2:port:_ports' && ret=0
            ;;
        (ps)
            _arguments \
291
                $opts_help \
292
293
294
295
296
                '-q[Only display IDs]' \
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (pull)
            _arguments \
297
298
                $opts_help \
                '--ignore-pull-failures[Pull what it can and ignores images with pull failures.]' \
299
300
                '*:services:__docker-compose_services_from_image' && ret=0
            ;;
301
302
303
304
305
306
        (push)
            _arguments \
                $opts_help \
                '--ignore-push-failures[Push what it can and ignores images with push failures.]' \
                '*:services:__docker-compose_services' && ret=0
            ;;
307
308
        (rm)
            _arguments \
309
                $opts_help \
310
                '(-f --force)'{-f,--force}"[Don't ask to confirm removal]" \
311
                '-v[Remove any anonymous volumes attached to containers]' \
312
313
314
315
                '*:stopped services:__docker-compose_stoppedservices' && ret=0
            ;;
        (run)
            _arguments \
316
                $opts_help \
317
                $opts_no_deps \
318
319
                '-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: ' \
320
321
322
                '--entrypoint[Overwrite the entrypoint of the image.]:entry point: ' \
                '--name=[Assign a name to the container]:name: ' \
                '(-p --publish)'{-p,--publish=}"[Publish a container's port(s) to the host]" \
323
324
325
                '--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.]' \
326
                '(-u --user)'{-u,--user=}'[Run as specified username or uid]:username or uid:_users' \
327
                '(-v --volume)*'{-v,--volume=}'[Bind mount a volume]:volume: ' \
328
                '(-w --workdir)'{-w,--workdir=}'[Working directory inside the container]:workdir: ' \
329
330
331
332
333
                '(-):services:__docker-compose_services' \
                '(-):command: _command_names -e' \
                '*::arguments: _normal' && ret=0
            ;;
        (scale)
334
335
336
337
            _arguments \
                $opts_help \
                $opts_timeout \
                '*:running services:__docker-compose_runningservices' && ret=0
338
339
            ;;
        (start)
340
341
342
            _arguments \
                $opts_help \
                '*:stopped services:__docker-compose_stoppedservices' && ret=0
343
344
345
            ;;
        (stop|restart)
            _arguments \
346
347
                $opts_help \
                $opts_timeout \
348
349
                '*:running services:__docker-compose_runningservices' && ret=0
            ;;
350
351
352
353
354
        (top)
            _arguments \
                $opts_help \
                '*:running services:__docker-compose_runningservices' && ret=0
            ;;
355
356
357
358
359
        (unpause)
            _arguments \
                $opts_help \
                '*:paused services:__docker-compose_pausedservices' && ret=0
            ;;
360
361
        (up)
            _arguments \
362
363
364
365
366
367
368
369
370
371
372
                $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 \
373
374
375
376
                '*:services:__docker-compose_services_all' && ret=0
            ;;
        (version)
            _arguments \
377
                $opts_help \
378
379
380
                "--short[Shows only Compose's version number.]" && ret=0
            ;;
        (*)
381
382
            _message 'Unknown sub command' && ret=1
            ;;
383
384
385
386
387
    esac

    return ret
}

388
_docker-compose() {
389
390
391
392
393
394
395
    # 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

396
397
    local curcontext="$curcontext" state line
    integer ret=1
398
399
    typeset -A opt_args

400
401
402
403
404
405
406
407
    local file_description

    if [[ -n ${words[(r)-f]} || -n ${words[(r)--file]} ]] ; then
        file_description="Specify an override docker-compose file (default: docker-compose.override.yml)"
    else
        file_description="Specify an alternate docker-compose file (default: docker-compose.yml)"
    fi

408
409
    _arguments -C \
        '(- :)'{-h,--help}'[Get help]' \
410
        '*'{-f,--file}"[${file_description}]:file:_files -g '*.yml'" \
411
        '(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \
412
413
414
415
416
417
418
419
420
        '--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)]" \
421
422
423
        '(-): :->command' \
        '(-)*:: :->option-or-argument' && ret=0

424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    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
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
    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 "$@"