Parent directory

_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 "$@"