_kamal
20943 bytes
1#compdef kamal
2
3# Description
4# -----------
5# zsh completion for Kamal (https://kamal-deploy.org/)
6# -------------------------------------------------------------------------
7# Authors
8# -------
9# * Igor Aleksandrov <igor.alexandrov@gmail.com>
10# -------------------------------------------------------------------------
11# Inspiration
12# -----------
13# * docker-compose ohmyzsh completion script by @sdurrheimer Steve Durrheimer
14# -------------------------------------------------------------------------
15
16# _kamal_commands() {
17# # Only initialize if empty
18# if (( ${#kamal_commands} == 0 )); then
19# kamal_commands=(
20# accessory
21# app
22# audit
23# build
24# config
25# deploy
26# details
27# docs
28# help
29# init
30# lock
31# proxy
32# prune
33# redeploy
34# registry
35# remove
36# rollback
37# secrets
38# server
39# setup
40# upgrade
41# version
42# )
43# fi
44
45# _values 'Kamal commands' $kamal_commands
46# }
47
48typeset -gr _kamal_commands=(
49 'accessory:Control accessory services'
50 'app:Control application deployment'
51 'audit:Audit security of deployment'
52 'build:Build and manage app images'
53 'config:Show effective configuration'
54 'deploy:Deploy app to servers'
55 'details:Show details about deployment'
56 'docs:Open documentation in browser'
57 'help:Show command help'
58 'init:Initialize new Kamal project'
59 'lock:Manage deployment locks'
60 'proxy:Control reverse proxy'
61 'prune:Clean up containers and images'
62 'redeploy:Redeploy current version'
63 'registry:Manage Docker registry access'
64 'remove:Remove app from servers'
65 'rollback:Rollback to a previous version'
66 'secrets:Manage deployment secrets'
67 'server:Control server configuration'
68 'setup:Setup initial deployment'
69 'upgrade:Upgrade deployment'
70 'version:Show Kamal version'
71)
72
73# Helper function to display messages
74_kamal_message() {
75 local msg="$1"
76 _kamal_message "==> $msg"
77}
78
79# Helper function to extract destination names from ./config/deploy.*.yml
80_kamal_destinations() {
81 local -a dests
82 local file
83
84 # Check if config directory exists
85 if [[ ! -d "config" ]]; then
86 _kamal_message "Cannot find Kamal configuration directory at ./config" && return 1
87 fi
88
89 for file in config/deploy.*.yml(N); do
90 [[ $file =~ config/deploy\.(.+)\.yml ]] && dests+=("${match[1]}")
91 done
92
93 _values 'Destination' $dests
94}
95
96# Define global _kamal_flags array
97typeset -ga _kamal_flags
98_kamal_flags=(
99 '(-v --verbose )'{-v,--verbose}'[Detailed logging]'
100 '(--no-verbose --skip-verbose)'{--no-verbose,--skip-verbose}'[No detailed logging]'
101 '(-q --quiet --no-quiet --skip-quiet)'{-q,--quiet}'[Minimal logging]'
102 '(-q --quiet --no-quiet --skip-quiet)'{--no-quiet,--skip-quiet}'[No minimal logging]'
103 '--version=[Run commands against a specific app version]:version'
104 '(-p --primary --no-primary --skip-primary)'{-p,--primary}'[Run commands only on primary host instead of all]'
105 '(-p --primary --no-primary --skip-primary)'{--no-primary,--skip-primary}'[Do not run commands only on primary host]'
106 '(-h --hosts)'{-h,--hosts=}'[Run commands on these hosts instead of all]:hosts'
107 '(-r --roles)'{-r,--roles=}'[Run commands on these roles instead of all]:roles'
108 '(-c --config-file)'{-c,--config-file=}'[Path to config file]:config file:_files'
109 '(-d --destination)'{-d,--destination=}'[Specify destination to be used for config file]:destination:_kamal_destinations'
110 '(-H --skip-hooks)'{-H,--skip-hooks}'[Do not run hooks]'
111)
112
113_kamal() {
114 local context state state_descr line curcontext="$curcontext"
115 typeset -A opt_args
116
117 local ret=1
118
119 _arguments -C \
120 $_kamal_flags \
121 '1: :->command' \
122 '*:: :->args' && ret=0
123
124 case $state in
125 (command)
126 # First argument - show available commands
127 _describe -t kamal-commands "Kamal commands" _kamal_commands && ret=0
128 ;;
129 (args)
130 # Subsequent arguments - handle based on the command
131 case $words[1] in
132 (accessory)
133 _kamal_accessory && ret=0
134 ;;
135 (app)
136 _kamal_app && ret=0
137 ;;
138 (audit)
139 _arguments $_kamal_flags && ret=0
140 ;;
141 (build)
142 _kamal_build && ret=0
143 ;;
144 (config)
145 _arguments $_kamal_flags && ret=0
146 ;;
147 (deploy)
148 _arguments $_kamal_flags && ret=0
149 ;;
150 (details)
151 _arguments $_kamal_flags && ret=0
152 ;;
153 (docs)
154 _arguments $_kamal_flags && ret=0
155 ;;
156 (help)
157 _kamal_help && ret=0
158 ;;
159 (init)
160 local -a init_flags
161 init_flags=(
162 $_kamal_flags
163 '(--bundle --no-bundle --skip-bundle)--bundle[Add Kamal to the Gemfile and create a bin/kamal binstub]'
164 '(--bundle --no-bundle --skip-bundle)--no-bundle[Do not add Kamal to the Gemfile and create a bin/kamal binstub]'
165 '(--bundle --no-bundle --skip-bundle)--skip-bundle[Skip add Kamal to the Gemfile and create a bin/kamal binstub]'
166 )
167 _arguments $init_flags && ret=0
168 ;;
169 (lock)
170 _kamal_lock && ret=0
171 ;;
172 (proxy)
173 _kamal_proxy && ret=0
174 ;;
175 (prune)
176 _kamal_prune && ret=0
177 ;;
178 (redeploy)
179 _arguments $_kamal_flags && ret=0
180 ;;
181 (registry)
182 _kamal_registry && ret=0
183 ;;
184 (remove)
185 local -a remove_flags
186 remove_flags=(
187 $_kamal_flags
188 '(-y --confirmed --no-confirmed --skip-confirmed)'{-y,--confirmed}'[Proceed without confirmation question]'
189 '(-y --confirmed --no-confirmed --skip-confirmed)--no-confirmed[Proceed without confirmation question]'
190 '(-y --confirmed --no-confirmed --skip-confirmed)--skip-confirmed[Proceed without confirmation question]'
191 )
192 _arguments $remove_flags && ret=0
193 ;;
194 (rollback)
195 if (( CURRENT == 2 )); then
196 _kamal_message "Enter the version to rollback to" && ret=0
197 else
198 _values $_kamal_flags && ret=0
199 fi
200 ;;
201 (secrets)
202 _kamal_secrets && ret=0
203 ;;
204 (server)
205 _kamal_server && ret=0
206 ;;
207 (setup)
208 local -a setup_flags
209 setup_flags=(
210 $_kamal_flags
211 '(-P --skip-push)'{-P,--skip-push}'[Skip image build and push]'
212 )
213 _arguments $setup_flags && ret=0
214 ;;
215 (upgrade)
216 local -a upgrade_flags
217 upgrade_flags=(
218 $_kamal_flags
219 '(-y --confirmed --no-confirmed --skip-confirmed)'{-y,--confirmed}'[Proceed without confirmation question]'
220 '(-y --confirmed --no-confirmed --skip-confirmed)--no-confirmed[Do not proceed without confirmation question]'
221 '(-y --confirmed --no-confirmed --skip-confirmed)--skip-confirmed[Skip confirmation question]'
222 '(--rolling --no-rolling --skip-rolling)--rolling[Upgrade one host at a time]'
223 '(--rolling --no-rolling --skip-rolling)--no-rolling[Do not upgrade one host at a time]'
224 '(--rolling --no-rolling --skip-rolling)--skip-rolling[Skip rolling upgrade]'
225 )
226 _arguments $upgrade_flags && ret=0
227 ;;
228 (version)
229 _arguments $_kamal_flags && ret=0
230 esac
231 ;;
232 esac
233
234 return ret
235}
236
237_kamal_accessory() {
238 local context state line
239 typeset -A opt_args
240 local ret=1
241
242 # Define accessory subcommands
243 local -a accessory_subcommands
244 accessory_subcommands=(
245 "boot:Boot new accessory service on host (use NAME=all to boot all accessories)"
246 "details:Show details about accessory on host (use NAME=all to show all accessories)"
247 "exec:Execute a custom command on servers within the accessory container (use --help to show options)"
248 "help:Describe subcommands or one specific subcommand"
249 "logs:Show log lines from accessory on host (use --help to show options)"
250 "reboot:Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
251 "remove:Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
252 "restart:Restart existing accessory container on host"
253 "start:Start existing accessory container on host"
254 "stop:Stop existing accessory container on host"
255 "upgrade:Upgrade accessories from Kamal 1.x to 2.0 (restart them in 'kamal' network)"
256 )
257
258 _arguments -C \
259 '1: :->subcmd' \
260 '*:: :->args' && ret=0
261
262 case $state in
263 (subcmd)
264 _describe -t accessory-commands "Kamal accessory commands" accessory_subcommands && ret=0
265 ;;
266 (args)
267 case $words[1] in
268 (boot|details|exec|logs|reboot|remove|restart|start|stop)
269 # These commands require a NAME parameter
270 if (( CURRENT == 2 )); then
271 # At the NAME position - we could add accessory name completion here
272 # if we had a way to list available accessories
273 _kamal_message "Specify an accessory name (or 'all' for all accessories)" && ret=0
274 elif [[ "$words[1]" == "exec" && CURRENT == 3 ]]; then
275 # For exec, the 3rd argument is a command
276 _kamal_message "Enter a command to execute" && ret=0
277 elif (( CURRENT > 2 )) && [[ "$words[1]" != "exec" || CURRENT > 3 ]]; then
278 _values $_kamal_flags && ret=0
279 fi
280 ;;
281 (help)
282 # Remove help itself from the list of commands
283 accessory_subcommands=("${(@)accessory_subcommands:#help*}")
284 _describe -t accessory-help-commands "Kamal accessory help commands" accessory_subcommands
285 ;;
286 (upgrade)
287 # For upgrade, show flags immediately
288 _arguments $_kamal_flags && ret=0
289 ;;
290 esac
291 ;;
292 esac
293
294 return ret
295}
296
297_kamal_app() {
298 local context state line
299 typeset -A opt_args
300 local ret=1
301
302 local -a app_subcommands
303 app_subcommands=(
304 "boot:Boot app on servers (or reboot app if already running)"
305 "containers:Show app containers on servers"
306 "details:Show details about app containers"
307 "exec:Execute a custom command on servers within the app container (use --help to show options)"
308 "help:Describe subcommands or one specific subcommand"
309 "images:Show app images on servers"
310 "logs:Show log lines from app on servers (use --help to show options)"
311 "remove:Remove app containers and images from servers"
312 "stale_containers:Detect app stale containers"
313 "start:Start existing app container on servers"
314 "stop:Stop app container on servers"
315 "version:Show app version currently running on servers"
316 )
317
318 _arguments -C \
319 '1: :->subcmd' \
320 '*:: :->args' && ret=0
321
322 case $state in
323 (subcmd)
324 _describe -t app-commands "Kamal app commands" app_subcommands && ret=0
325 ;;
326 (args)
327 case $words[1] in
328 (boot|containers|details|images|logs|remove|stale_containers|start|stop)
329 _arguments $_kamal_flags && ret=0
330 ;;
331 (exec)
332 # For exec, the next argument is a command
333 if (( CURRENT == 2 )); then
334 _kamal_message "Enter a command to execute" && ret=0
335 else
336 _values $_kamal_flags && ret=0
337 fi
338 ;;
339 (help)
340 # Remove help itself from the list of commands
341 app_subcommands=("${(@)app_subcommands:#help*}")
342 _describe -t app-help-commands "Kamal app help commands" app_subcommands
343 ;;
344 esac
345 ;;
346 esac
347
348 return ret
349}
350
351_kamal_build() {
352 local context state line
353 typeset -A opt_args
354 local ret=1
355
356 local -a build_subcommands
357 build_subcommands=(
358 "create:Create a build setup"
359 "deliver:Build app and push app image to registry then pull image on servers"
360 "details:Show build setup"
361 "dev:Build using the working directory, tag it as dirty, and push to local image store."
362 "help:Describe subcommands or one specific subcommand"
363 "pull:Pull app image from registry onto servers"
364 "push:Build and push app image to registry"
365 "remove:Remove build setup"
366 )
367
368 _arguments -C \
369 '1: :->subcmd' \
370 '*:: :->args' && ret=0
371
372 case $state in
373 (subcmd)
374 _describe -t build-commands "Kamal build commands" build_subcommands && ret=0
375 ;;
376 (args)
377 case $words[1] in
378 (create|deliver|details|dev|pull|push|remove)
379 _arguments $_kamal_flags && ret=0
380 ;;
381 (help)
382 # Remove help itself from the list of commands
383 build_subcommands=("${(@)build_subcommands:#help*}")
384 _describe -t build-help-commands "Kamal build help commands" build_subcommands
385 ;;
386 esac
387 ;;
388 esac
389
390 return ret
391}
392
393_kamal_help() {
394 local ret=1
395
396 # Make sure kamal_commands is initialized properly
397 # if (( ${#kamal_commands} == 0 )); then
398 # _kamal_commands >/dev/null
399 # fi
400
401 # If we already have a command after "help", return without suggestions
402 if (( CURRENT > 2 )); then
403 ret=0
404 else
405 local -a help_commands
406 # Filter out help from the list of commands
407 help_commands=("${(@)_kamal_commands:#help}")
408
409 _values 'Kamal help' $help_commands && ret=0
410 fi
411
412 return ret
413}
414
415_kamal_lock() {
416 local context state line
417 typeset -A opt_args
418 local ret=1
419
420 local -a lock_subcommands
421 lock_subcommands=(
422 "acquire:Acquire the deploy lock"
423 "help:Describe subcommands or one specific subcommand"
424 "release:Release the deploy lock"
425 "status:Report lock status"
426 )
427
428 _arguments -C \
429 '1: :->subcmd' \
430 '*:: :->args' && ret=0
431
432 case $state in
433 (subcmd)
434 _describe -t lock-commands "Kamal lock commands" lock_subcommands && ret=0
435 ;;
436 (args)
437 case $words[1] in
438 (acquire)
439 local -a acquire_flags
440 acquire_flags=(
441 $_kamal_flags
442 '(-m --message)'{-m,--message=}'[A lock message]:message:' # Required flag
443 )
444 _arguments $acquire_flags && ret=0
445 ;;
446 (release|status)
447 _arguments $_kamal_flags && ret=0
448 ;;
449 (help)
450 # Remove help itself from the list of commands
451 lock_subcommands=("${(@)lock_subcommands:#help*}")
452 _describe -t lock-help-commands "Kamal lock help commands" lock_subcommands
453 ;;
454 esac
455 ;;
456 esac
457
458 return ret
459}
460
461_kamal_proxy() {
462 local context state line
463 typeset -A opt_args
464 local ret=1
465
466 local -a proxy_subcommands
467 proxy_subcommands=(
468 "boot:Boot proxy on servers"
469 "boot_config:Manage kamal-proxy boot configuration"
470 "details:Show details about proxy container from servers"
471 "help:Describe subcommands or one specific subcommand"
472 "logs:Show log lines from proxy on servers"
473 "reboot:Reboot proxy on servers (stop container, remove container, start new container)"
474 "remove:Remove proxy container and image from servers"
475 "restart:Restart existing proxy container on servers"
476 "start:Start existing proxy container on servers"
477 "stop:Stop existing proxy container on servers"
478 )
479
480 _arguments -C \
481 '1: :->subcmd' \
482 '*:: :->args' && ret=0
483
484 case $state in
485 (subcmd)
486 _describe -t proxy-commands "Kamal proxy commands" proxy_subcommands && ret=0
487 ;;
488 (args)
489 case $words[1] in
490 (boot|details|logs|reboot|remove|restart|start|stop)
491 _arguments $_kamal_flags && ret=0
492 ;;
493 (boot_config)
494 if (( CURRENT == 2 )); then
495 local -a boot_config_commands=(
496 "set:Set boot configuration"
497 "get:Get boot configuration"
498 "reset:Reset boot configuration"
499 )
500 _describe -t boot-config-commands "Boot config commands" boot_config_commands && ret=0
501 else
502 _values $_kamal_flags && ret=0
503 fi
504 ;;
505 (help)
506 # Remove help itself from the list of commands
507 proxy_subcommands=("${(@)proxy_subcommands:#help*}")
508 _describe -t proxy-help-commands "Kamal proxy help commands" proxy_subcommands
509 ;;
510 esac
511 ;;
512 esac
513
514 return ret
515}
516
517_kamal_prune() {
518 local context state line
519 typeset -A opt_args
520 local ret=1
521
522 local -a prune_subcommands
523 prune_subcommands=(
524 "all:Prune unused images and stopped containers"
525 "containers:Prune all stopped containers, except the last n (default 5)"
526 "help:Describe subcommands or one specific subcommand"
527 "images:Prune unused images"
528 )
529
530 _arguments -C \
531 '1: :->subcmd' \
532 '*:: :->args' && ret=0
533
534 case $state in
535 (subcmd)
536 _describe -t prune-commands "Kamal prune commands" prune_subcommands && ret=0
537 ;;
538 (args)
539 case $words[1] in
540 (all|containers|images)
541 _arguments $_kamal_flags && ret=0
542 ;;
543 (help)
544 # Remove help itself from the list of commands
545 prune_subcommands=("${(@)prune_subcommands:#help*}")
546 _describe -t prune-help-commands "Kamal prune help commands" prune_subcommands
547 ;;
548 esac
549 ;;
550 esac
551
552 return ret
553}
554
555_kamal_registry() {
556 local context state line
557 typeset -A opt_args
558 local ret=1
559
560 local -a registry_subcommands
561 registry_subcommands=(
562 "help:Describe subcommands or one specific subcommand"
563 "login:Log in to registry locally and remotely"
564 "logout:Log out of registry locally and remotely"
565 )
566
567 _arguments -C \
568 '1: :->subcmd' \
569 '*:: :->args' && ret=0
570
571 case $state in
572 (subcmd)
573 _describe -t registry-commands "Kamal registry commands" registry_subcommands && ret=0
574 ;;
575 (args)
576 case $words[1] in
577 (help)
578 # Remove help itself from the list of commands
579 registry_subcommands=("${(@)registry_subcommands:#help*}")
580 _describe -t registry-help-commands "Kamal registry help commands" registry_subcommands
581 ;;
582 (login|logout)
583 _arguments $_kamal_flags && ret=0
584 ;;
585 esac
586 ;;
587 esac
588
589 return ret
590}
591
592_kamal_secrets() {
593 local context state line
594 typeset -A opt_args
595 local ret=1
596
597 local -a secrets_subcommands
598 secrets_subcommands=(
599 "extract:Extract a single secret from the results of a fetch call"
600 "fetch:Fetch secrets from a vault"
601 "help:Describe subcommands or one specific subcommand"
602 "print:Print the secrets (for debugging)"
603 )
604
605 _arguments -C \
606 '1: :->subcmd' \
607 '*:: :->args' && ret=0
608
609 case $state in
610 (subcmd)
611 _describe -t secrets-commands "Kamal secrets commands" secrets_subcommands && ret=0
612 ;;
613 (args)
614 case $words[1] in
615 (fetch)
616 local -a fetch_flags
617 fetch_flags=(
618 $_kamal_flags
619 '(-a --adapter)'{-a,--adapter=}'[Secret storage adapter]:adapter:(aws-parameter-store)'
620 )
621 _arguments $fetch_flags && ret=0
622 ;;
623 (extract|print)
624 _arguments $_kamal_flags && ret=0
625 ;;
626 (help)
627 # Remove help itself from the list of commands
628 secrets_subcommands=("${(@)secrets_subcommands:#help*}")
629 _describe -t secrets-help-commands "Kamal secrets help commands" secrets_subcommands
630 ;;
631 esac
632 ;;
633 esac
634
635 return ret
636}
637
638_kamal_server() {
639 local context state line
640 typeset -A opt_args
641 local ret=1
642
643 local -a server_subcommands
644 server_subcommands=(
645 "bootstrap:Set up Docker to run Kamal apps"
646 "exec:Run a custom command on the server (use --help to show options)"
647 "help:Describe subcommands or one specific subcommand"
648 )
649
650 local -a server_flags
651 server_flags=(
652 $_kamal_flags
653 '(-i --interactive --no-interactive --skip-interactive)'{-i,--interactive}'[Run the command interactively]'
654 '(-i --interactive --no-interactive --skip-interactive)--no-interactive[Do not run the command interactively]'
655 '(-i --interactive --no-interactive --skip-interactive)--skip-interactive[Skip interactive mode]'
656 )
657
658 _arguments -C \
659 '1: :->subcmd' \
660 '*:: :->args' && ret=0
661
662 case $state in
663 (subcmd)
664 _describe -t server-commands "Kamal server commands" server_subcommands && ret=0
665 ;;
666 (args)
667 case $words[1] in
668 (bootstrap)
669 _arguments $server_flags && ret=0
670 ;;
671 (exec)
672 if (( CURRENT == 2 )); then
673 # For exec, the next argument is a command
674 _kamal_message "Enter a command to execute" && ret=0
675 else
676 _values $server_flags && ret=0
677 fi
678 ;;
679 (help)
680 # Remove help itself from the list of commands
681 server_subcommands=("${(@)server_subcommands:#help*}")
682 _describe -t server-help-commands "Kamal server help commands" server_subcommands
683 ;;
684 esac
685 ;;
686 esac
687
688 return ret
689}
690
691_kamal "$@"