kamal 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/MIT-LICENSE +20 -0
 - data/README.md +1021 -0
 - data/bin/kamal +18 -0
 - data/lib/kamal/cli/accessory.rb +239 -0
 - data/lib/kamal/cli/app.rb +296 -0
 - data/lib/kamal/cli/base.rb +171 -0
 - data/lib/kamal/cli/build.rb +106 -0
 - data/lib/kamal/cli/healthcheck.rb +20 -0
 - data/lib/kamal/cli/lock.rb +37 -0
 - data/lib/kamal/cli/main.rb +249 -0
 - data/lib/kamal/cli/prune.rb +30 -0
 - data/lib/kamal/cli/registry.rb +18 -0
 - data/lib/kamal/cli/server.rb +21 -0
 - data/lib/kamal/cli/templates/deploy.yml +74 -0
 - data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
 - data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
 - data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
 - data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
 - data/lib/kamal/cli/templates/template.env +2 -0
 - data/lib/kamal/cli/traefik.rb +111 -0
 - data/lib/kamal/cli.rb +7 -0
 - data/lib/kamal/commander.rb +154 -0
 - data/lib/kamal/commands/accessory.rb +113 -0
 - data/lib/kamal/commands/app.rb +175 -0
 - data/lib/kamal/commands/auditor.rb +28 -0
 - data/lib/kamal/commands/base.rb +65 -0
 - data/lib/kamal/commands/builder/base.rb +60 -0
 - data/lib/kamal/commands/builder/multiarch/remote.rb +51 -0
 - data/lib/kamal/commands/builder/multiarch.rb +29 -0
 - data/lib/kamal/commands/builder/native/cached.rb +16 -0
 - data/lib/kamal/commands/builder/native/remote.rb +59 -0
 - data/lib/kamal/commands/builder/native.rb +20 -0
 - data/lib/kamal/commands/builder.rb +62 -0
 - data/lib/kamal/commands/docker.rb +21 -0
 - data/lib/kamal/commands/healthcheck.rb +57 -0
 - data/lib/kamal/commands/hook.rb +14 -0
 - data/lib/kamal/commands/lock.rb +63 -0
 - data/lib/kamal/commands/prune.rb +38 -0
 - data/lib/kamal/commands/registry.rb +20 -0
 - data/lib/kamal/commands/traefik.rb +104 -0
 - data/lib/kamal/commands.rb +2 -0
 - data/lib/kamal/configuration/accessory.rb +169 -0
 - data/lib/kamal/configuration/boot.rb +20 -0
 - data/lib/kamal/configuration/builder.rb +114 -0
 - data/lib/kamal/configuration/role.rb +155 -0
 - data/lib/kamal/configuration/ssh.rb +38 -0
 - data/lib/kamal/configuration/sshkit.rb +20 -0
 - data/lib/kamal/configuration.rb +251 -0
 - data/lib/kamal/sshkit_with_ext.rb +104 -0
 - data/lib/kamal/tags.rb +39 -0
 - data/lib/kamal/utils/healthcheck_poller.rb +39 -0
 - data/lib/kamal/utils/sensitive.rb +19 -0
 - data/lib/kamal/utils.rb +100 -0
 - data/lib/kamal/version.rb +3 -0
 - data/lib/kamal.rb +10 -0
 - metadata +266 -0
 
    
        data/bin/kamal
    ADDED
    
    | 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Prevent failures from being reported twice.
         
     | 
| 
      
 4 
     | 
    
         
            +
            Thread.report_on_exception = false
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            require "kamal"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            begin
         
     | 
| 
      
 9 
     | 
    
         
            +
              Kamal::Cli::Main.start(ARGV)
         
     | 
| 
      
 10 
     | 
    
         
            +
            rescue SSHKit::Runner::ExecuteError => e
         
     | 
| 
      
 11 
     | 
    
         
            +
              puts "  \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m"
         
     | 
| 
      
 12 
     | 
    
         
            +
              puts e.cause.backtrace if ENV["VERBOSE"]
         
     | 
| 
      
 13 
     | 
    
         
            +
              exit 1
         
     | 
| 
      
 14 
     | 
    
         
            +
            rescue => e
         
     | 
| 
      
 15 
     | 
    
         
            +
              puts "  \e[31mERROR (#{e.class}): #{e.message}\e[0m"
         
     | 
| 
      
 16 
     | 
    
         
            +
              puts e.backtrace if ENV["VERBOSE"]
         
     | 
| 
      
 17 
     | 
    
         
            +
              exit 1
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,239 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Kamal::Cli::Accessory < Kamal::Cli::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
         
     | 
| 
      
 3 
     | 
    
         
            +
              def boot(name, login: true)
         
     | 
| 
      
 4 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 5 
     | 
    
         
            +
                  if name == "all"
         
     | 
| 
      
 6 
     | 
    
         
            +
                    KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
         
     | 
| 
      
 7 
     | 
    
         
            +
                  else
         
     | 
| 
      
 8 
     | 
    
         
            +
                    with_accessory(name) do |accessory|
         
     | 
| 
      
 9 
     | 
    
         
            +
                      directories(name)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      upload(name)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                      on(accessory.hosts) do
         
     | 
| 
      
 13 
     | 
    
         
            +
                        execute *KAMAL.registry.login if login
         
     | 
| 
      
 14 
     | 
    
         
            +
                        execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
         
     | 
| 
      
 15 
     | 
    
         
            +
                        execute *accessory.run
         
     | 
| 
      
 16 
     | 
    
         
            +
                      end
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              desc "upload [NAME]", "Upload accessory files to host", hide: true
         
     | 
| 
      
 23 
     | 
    
         
            +
              def upload(name)
         
     | 
| 
      
 24 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 25 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 27 
     | 
    
         
            +
                      accessory.files.each do |(local, remote)|
         
     | 
| 
      
 28 
     | 
    
         
            +
                        accessory.ensure_local_file_present(local)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                        execute *accessory.make_directory_for(remote)
         
     | 
| 
      
 31 
     | 
    
         
            +
                        upload! local, remote
         
     | 
| 
      
 32 
     | 
    
         
            +
                        execute :chmod, "755", remote
         
     | 
| 
      
 33 
     | 
    
         
            +
                      end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              desc "directories [NAME]", "Create accessory directories on host", hide: true
         
     | 
| 
      
 40 
     | 
    
         
            +
              def directories(name)
         
     | 
| 
      
 41 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 42 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 43 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      accessory.directories.keys.each do |host_path|
         
     | 
| 
      
 45 
     | 
    
         
            +
                        execute *accessory.make_directory(host_path)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
         
     | 
| 
      
 53 
     | 
    
         
            +
              def reboot(name)
         
     | 
| 
      
 54 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 55 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 56 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 57 
     | 
    
         
            +
                      execute *KAMAL.registry.login
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    stop(name)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    remove_container(name)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    boot(name, login: false)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              desc "start [NAME]", "Start existing accessory container on host"
         
     | 
| 
      
 68 
     | 
    
         
            +
              def start(name)
         
     | 
| 
      
 69 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 70 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 72 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
         
     | 
| 
      
 73 
     | 
    
         
            +
                      execute *accessory.start
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              desc "stop [NAME]", "Stop existing accessory container on host"
         
     | 
| 
      
 80 
     | 
    
         
            +
              def stop(name)
         
     | 
| 
      
 81 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 82 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 83 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 84 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
         
     | 
| 
      
 85 
     | 
    
         
            +
                      execute *accessory.stop, raise_on_non_zero_exit: false
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
              desc "restart [NAME]", "Restart existing accessory container on host"
         
     | 
| 
      
 92 
     | 
    
         
            +
              def restart(name)
         
     | 
| 
      
 93 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 94 
     | 
    
         
            +
                  with_accessory(name) do
         
     | 
| 
      
 95 
     | 
    
         
            +
                    stop(name)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    start(name)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
         
     | 
| 
      
 102 
     | 
    
         
            +
              def details(name)
         
     | 
| 
      
 103 
     | 
    
         
            +
                if name == "all"
         
     | 
| 
      
 104 
     | 
    
         
            +
                  KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
         
     | 
| 
      
 105 
     | 
    
         
            +
                else
         
     | 
| 
      
 106 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 107 
     | 
    
         
            +
                    on(accessory.hosts) { puts capture_with_info(*accessory.info) }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
              desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
         
     | 
| 
      
 113 
     | 
    
         
            +
              option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
         
     | 
| 
      
 114 
     | 
    
         
            +
              option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
         
     | 
| 
      
 115 
     | 
    
         
            +
              def exec(name, cmd)
         
     | 
| 
      
 116 
     | 
    
         
            +
                with_accessory(name) do |accessory|
         
     | 
| 
      
 117 
     | 
    
         
            +
                  case
         
     | 
| 
      
 118 
     | 
    
         
            +
                  when options[:interactive] && options[:reuse]
         
     | 
| 
      
 119 
     | 
    
         
            +
                    say "Launching interactive command with via SSH from existing container...", :magenta
         
     | 
| 
      
 120 
     | 
    
         
            +
                    run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  when options[:interactive]
         
     | 
| 
      
 123 
     | 
    
         
            +
                    say "Launching interactive command via SSH from new container...", :magenta
         
     | 
| 
      
 124 
     | 
    
         
            +
                    run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                  when options[:reuse]
         
     | 
| 
      
 127 
     | 
    
         
            +
                    say "Launching command from existing container...", :magenta
         
     | 
| 
      
 128 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 129 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
         
     | 
| 
      
 130 
     | 
    
         
            +
                      capture_with_info(*accessory.execute_in_existing_container(cmd))
         
     | 
| 
      
 131 
     | 
    
         
            +
                    end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                  else
         
     | 
| 
      
 134 
     | 
    
         
            +
                    say "Launching command from new container...", :magenta
         
     | 
| 
      
 135 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 136 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
         
     | 
| 
      
 137 
     | 
    
         
            +
                      capture_with_info(*accessory.execute_in_new_container(cmd))
         
     | 
| 
      
 138 
     | 
    
         
            +
                    end
         
     | 
| 
      
 139 
     | 
    
         
            +
                  end
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
              end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
              desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
         
     | 
| 
      
 144 
     | 
    
         
            +
              option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
         
     | 
| 
      
 145 
     | 
    
         
            +
              option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
         
     | 
| 
      
 146 
     | 
    
         
            +
              option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
         
     | 
| 
      
 147 
     | 
    
         
            +
              option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
         
     | 
| 
      
 148 
     | 
    
         
            +
              def logs(name)
         
     | 
| 
      
 149 
     | 
    
         
            +
                with_accessory(name) do |accessory|
         
     | 
| 
      
 150 
     | 
    
         
            +
                  grep = options[:grep]
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  if options[:follow]
         
     | 
| 
      
 153 
     | 
    
         
            +
                    run_locally do
         
     | 
| 
      
 154 
     | 
    
         
            +
                      info "Following logs on #{accessory.hosts}..."
         
     | 
| 
      
 155 
     | 
    
         
            +
                      info accessory.follow_logs(grep: grep)
         
     | 
| 
      
 156 
     | 
    
         
            +
                      exec accessory.follow_logs(grep: grep)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                  else
         
     | 
| 
      
 159 
     | 
    
         
            +
                    since = options[:since]
         
     | 
| 
      
 160 
     | 
    
         
            +
                    lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 163 
     | 
    
         
            +
                      puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  end
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
              end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
              desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
         
     | 
| 
      
 170 
     | 
    
         
            +
              option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
         
     | 
| 
      
 171 
     | 
    
         
            +
              def remove(name)
         
     | 
| 
      
 172 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 173 
     | 
    
         
            +
                  if name == "all"
         
     | 
| 
      
 174 
     | 
    
         
            +
                    KAMAL.accessory_names.each { |accessory_name| remove(accessory_name) }
         
     | 
| 
      
 175 
     | 
    
         
            +
                  else
         
     | 
| 
      
 176 
     | 
    
         
            +
                    if options[:confirmed] || ask("This will remove all containers, images and data directories for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
         
     | 
| 
      
 177 
     | 
    
         
            +
                      with_accessory(name) do
         
     | 
| 
      
 178 
     | 
    
         
            +
                        stop(name)
         
     | 
| 
      
 179 
     | 
    
         
            +
                        remove_container(name)
         
     | 
| 
      
 180 
     | 
    
         
            +
                        remove_image(name)
         
     | 
| 
      
 181 
     | 
    
         
            +
                        remove_service_directory(name)
         
     | 
| 
      
 182 
     | 
    
         
            +
                      end
         
     | 
| 
      
 183 
     | 
    
         
            +
                    end
         
     | 
| 
      
 184 
     | 
    
         
            +
                  end
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
              end
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
              desc "remove_container [NAME]", "Remove accessory container from host", hide: true
         
     | 
| 
      
 189 
     | 
    
         
            +
              def remove_container(name)
         
     | 
| 
      
 190 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 191 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 192 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 193 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
         
     | 
| 
      
 194 
     | 
    
         
            +
                      execute *accessory.remove_container
         
     | 
| 
      
 195 
     | 
    
         
            +
                    end
         
     | 
| 
      
 196 
     | 
    
         
            +
                  end
         
     | 
| 
      
 197 
     | 
    
         
            +
                end
         
     | 
| 
      
 198 
     | 
    
         
            +
              end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
              desc "remove_image [NAME]", "Remove accessory image from host", hide: true
         
     | 
| 
      
 201 
     | 
    
         
            +
              def remove_image(name)
         
     | 
| 
      
 202 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 203 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 204 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 205 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
         
     | 
| 
      
 206 
     | 
    
         
            +
                      execute *accessory.remove_image
         
     | 
| 
      
 207 
     | 
    
         
            +
                    end
         
     | 
| 
      
 208 
     | 
    
         
            +
                  end
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
      
 210 
     | 
    
         
            +
              end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
              desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
         
     | 
| 
      
 213 
     | 
    
         
            +
              def remove_service_directory(name)
         
     | 
| 
      
 214 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 215 
     | 
    
         
            +
                  with_accessory(name) do |accessory|
         
     | 
| 
      
 216 
     | 
    
         
            +
                    on(accessory.hosts) do
         
     | 
| 
      
 217 
     | 
    
         
            +
                      execute *accessory.remove_service_directory
         
     | 
| 
      
 218 
     | 
    
         
            +
                    end
         
     | 
| 
      
 219 
     | 
    
         
            +
                  end
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
              end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
              private
         
     | 
| 
      
 224 
     | 
    
         
            +
                def with_accessory(name)
         
     | 
| 
      
 225 
     | 
    
         
            +
                  if accessory = KAMAL.accessory(name)
         
     | 
| 
      
 226 
     | 
    
         
            +
                    yield accessory
         
     | 
| 
      
 227 
     | 
    
         
            +
                  else
         
     | 
| 
      
 228 
     | 
    
         
            +
                    error_on_missing_accessory(name)
         
     | 
| 
      
 229 
     | 
    
         
            +
                  end
         
     | 
| 
      
 230 
     | 
    
         
            +
                end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                def error_on_missing_accessory(name)
         
     | 
| 
      
 233 
     | 
    
         
            +
                  options = KAMAL.accessory_names.presence
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                  error \
         
     | 
| 
      
 236 
     | 
    
         
            +
                    "No accessory by the name of '#{name}'" +
         
     | 
| 
      
 237 
     | 
    
         
            +
                    (options ? " (options: #{options.to_sentence})" : "")
         
     | 
| 
      
 238 
     | 
    
         
            +
                end
         
     | 
| 
      
 239 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,296 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Kamal::Cli::App < Kamal::Cli::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              desc "boot", "Boot app on servers (or reboot app if already running)"
         
     | 
| 
      
 3 
     | 
    
         
            +
              def boot
         
     | 
| 
      
 4 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 5 
     | 
    
         
            +
                  hold_lock_on_error do
         
     | 
| 
      
 6 
     | 
    
         
            +
                    say "Get most recent version available as an image...", :magenta unless options[:version]
         
     | 
| 
      
 7 
     | 
    
         
            +
                    using_version(version_or_latest) do |version|
         
     | 
| 
      
 8 
     | 
    
         
            +
                      say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                      on(KAMAL.hosts) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                        execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
         
     | 
| 
      
 12 
     | 
    
         
            +
                        execute *KAMAL.app.tag_current_as_latest
         
     | 
| 
      
 13 
     | 
    
         
            +
                      end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                      on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
         
     | 
| 
      
 16 
     | 
    
         
            +
                        roles = KAMAL.roles_on(host)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                        roles.each do |role|
         
     | 
| 
      
 19 
     | 
    
         
            +
                          app = KAMAL.app(role: role)
         
     | 
| 
      
 20 
     | 
    
         
            +
                          auditor = KAMAL.auditor(role: role)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                          if capture_with_info(*app.container_id_for_version(version, only_running: true), raise_on_non_zero_exit: false).present?
         
     | 
| 
      
 23 
     | 
    
         
            +
                            tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                            info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
         
     | 
| 
      
 25 
     | 
    
         
            +
                            execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
         
     | 
| 
      
 26 
     | 
    
         
            +
                            execute *app.rename_container(version: version, new_version: tmp_version)
         
     | 
| 
      
 27 
     | 
    
         
            +
                          end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                          execute *auditor.record("Booted app version #{version}"), verbosity: :debug
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                          old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
         
     | 
| 
      
 32 
     | 
    
         
            +
                          execute *app.start_or_run(hostname: "#{host}-#{SecureRandom.hex(6)}")
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                          Kamal::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                          execute *app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
         
     | 
| 
      
 37 
     | 
    
         
            +
                        end
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              desc "start", "Start existing app container on servers"
         
     | 
| 
      
 45 
     | 
    
         
            +
              def start
         
     | 
| 
      
 46 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 51 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
         
     | 
| 
      
 52 
     | 
    
         
            +
                      execute *KAMAL.app(role: role).start, raise_on_non_zero_exit: false
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              desc "stop", "Stop app container on servers"
         
     | 
| 
      
 59 
     | 
    
         
            +
              def stop
         
     | 
| 
      
 60 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 61 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 62 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 65 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
         
     | 
| 
      
 66 
     | 
    
         
            +
                      execute *KAMAL.app(role: role).stop, raise_on_non_zero_exit: false
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              # FIXME: Drop in favor of just containers?
         
     | 
| 
      
 73 
     | 
    
         
            +
              desc "details", "Show details about app containers"
         
     | 
| 
      
 74 
     | 
    
         
            +
              def details
         
     | 
| 
      
 75 
     | 
    
         
            +
                on(KAMAL.hosts) do |host|
         
     | 
| 
      
 76 
     | 
    
         
            +
                  roles = KAMAL.roles_on(host)
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  roles.each do |role|
         
     | 
| 
      
 79 
     | 
    
         
            +
                    puts_by_host host, capture_with_info(*KAMAL.app(role: role).info)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
         
     | 
| 
      
 85 
     | 
    
         
            +
              option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
         
     | 
| 
      
 86 
     | 
    
         
            +
              option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
         
     | 
| 
      
 87 
     | 
    
         
            +
              def exec(cmd)
         
     | 
| 
      
 88 
     | 
    
         
            +
                case
         
     | 
| 
      
 89 
     | 
    
         
            +
                when options[:interactive] && options[:reuse]
         
     | 
| 
      
 90 
     | 
    
         
            +
                  say "Get current version of running container...", :magenta unless options[:version]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  using_version(options[:version] || current_running_version) do |version|
         
     | 
| 
      
 92 
     | 
    
         
            +
                    say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
         
     | 
| 
      
 93 
     | 
    
         
            +
                    run_locally { exec KAMAL.app(role: "web").execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) }
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                when options[:interactive]
         
     | 
| 
      
 97 
     | 
    
         
            +
                  say "Get most recent version available as an image...", :magenta unless options[:version]
         
     | 
| 
      
 98 
     | 
    
         
            +
                  using_version(version_or_latest) do |version|
         
     | 
| 
      
 99 
     | 
    
         
            +
                    say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
         
     | 
| 
      
 100 
     | 
    
         
            +
                    run_locally { exec KAMAL.app(role: KAMAL.primary_host.roles.first).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                when options[:reuse]
         
     | 
| 
      
 104 
     | 
    
         
            +
                  say "Get current version of running container...", :magenta unless options[:version]
         
     | 
| 
      
 105 
     | 
    
         
            +
                  using_version(options[:version] || current_running_version) do |version|
         
     | 
| 
      
 106 
     | 
    
         
            +
                    say "Launching command with version #{version} from existing container...", :magenta
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    on(KAMAL.hosts) do |host|
         
     | 
| 
      
 109 
     | 
    
         
            +
                      roles = KAMAL.roles_on(host)
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                      roles.each do |role|
         
     | 
| 
      
 112 
     | 
    
         
            +
                        execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
         
     | 
| 
      
 113 
     | 
    
         
            +
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd))
         
     | 
| 
      
 114 
     | 
    
         
            +
                      end
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                else
         
     | 
| 
      
 119 
     | 
    
         
            +
                  say "Get most recent version available as an image...", :magenta unless options[:version]
         
     | 
| 
      
 120 
     | 
    
         
            +
                  using_version(version_or_latest) do |version|
         
     | 
| 
      
 121 
     | 
    
         
            +
                    say "Launching command with version #{version} from new container...", :magenta
         
     | 
| 
      
 122 
     | 
    
         
            +
                    on(KAMAL.hosts) do |host|
         
     | 
| 
      
 123 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
         
     | 
| 
      
 124 
     | 
    
         
            +
                      puts_by_host host, capture_with_info(*KAMAL.app.execute_in_new_container(cmd))
         
     | 
| 
      
 125 
     | 
    
         
            +
                    end
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              desc "containers", "Show app containers on servers"
         
     | 
| 
      
 131 
     | 
    
         
            +
              def containers
         
     | 
| 
      
 132 
     | 
    
         
            +
                on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
         
     | 
| 
      
 133 
     | 
    
         
            +
              end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
              desc "stale_containers", "Detect app stale containers"
         
     | 
| 
      
 136 
     | 
    
         
            +
              option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
         
     | 
| 
      
 137 
     | 
    
         
            +
              def stale_containers
         
     | 
| 
      
 138 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 139 
     | 
    
         
            +
                  stop = options[:stop]
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  cli = self
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 144 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 147 
     | 
    
         
            +
                      cli.send(:stale_versions, host: host, role: role).each do |version|
         
     | 
| 
      
 148 
     | 
    
         
            +
                        if stop
         
     | 
| 
      
 149 
     | 
    
         
            +
                          puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
         
     | 
| 
      
 150 
     | 
    
         
            +
                          execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false
         
     | 
| 
      
 151 
     | 
    
         
            +
                        else
         
     | 
| 
      
 152 
     | 
    
         
            +
                          puts_by_host host,  "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)"
         
     | 
| 
      
 153 
     | 
    
         
            +
                        end
         
     | 
| 
      
 154 
     | 
    
         
            +
                      end
         
     | 
| 
      
 155 
     | 
    
         
            +
                    end
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
              end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
              desc "images", "Show app images on servers"
         
     | 
| 
      
 161 
     | 
    
         
            +
              def images
         
     | 
| 
      
 162 
     | 
    
         
            +
                on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
         
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
              desc "logs", "Show log lines from app on servers (use --help to show options)"
         
     | 
| 
      
 166 
     | 
    
         
            +
              option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
         
     | 
| 
      
 167 
     | 
    
         
            +
              option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
         
     | 
| 
      
 168 
     | 
    
         
            +
              option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
         
     | 
| 
      
 169 
     | 
    
         
            +
              option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
         
     | 
| 
      
 170 
     | 
    
         
            +
              def logs
         
     | 
| 
      
 171 
     | 
    
         
            +
                # FIXME: Catch when app containers aren't running
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                grep = options[:grep]
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                if options[:follow]
         
     | 
| 
      
 176 
     | 
    
         
            +
                  run_locally do
         
     | 
| 
      
 177 
     | 
    
         
            +
                    info "Following logs on #{KAMAL.primary_host}..."
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                    KAMAL.specific_roles ||= ["web"]
         
     | 
| 
      
 180 
     | 
    
         
            +
                    role = KAMAL.roles_on(KAMAL.primary_host).first
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                    info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
         
     | 
| 
      
 183 
     | 
    
         
            +
                    exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
         
     | 
| 
      
 184 
     | 
    
         
            +
                  end
         
     | 
| 
      
 185 
     | 
    
         
            +
                else
         
     | 
| 
      
 186 
     | 
    
         
            +
                  since = options[:since]
         
     | 
| 
      
 187 
     | 
    
         
            +
                  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 190 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 193 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 194 
     | 
    
         
            +
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).logs(since: since, lines: lines, grep: grep))
         
     | 
| 
      
 195 
     | 
    
         
            +
                      rescue SSHKit::Command::Failed
         
     | 
| 
      
 196 
     | 
    
         
            +
                        puts_by_host host, "Nothing found"
         
     | 
| 
      
 197 
     | 
    
         
            +
                      end
         
     | 
| 
      
 198 
     | 
    
         
            +
                    end
         
     | 
| 
      
 199 
     | 
    
         
            +
                  end
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
              end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
              desc "remove", "Remove app containers and images from servers"
         
     | 
| 
      
 204 
     | 
    
         
            +
              def remove
         
     | 
| 
      
 205 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 206 
     | 
    
         
            +
                  stop
         
     | 
| 
      
 207 
     | 
    
         
            +
                  remove_containers
         
     | 
| 
      
 208 
     | 
    
         
            +
                  remove_images
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
      
 210 
     | 
    
         
            +
              end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
              desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
         
     | 
| 
      
 213 
     | 
    
         
            +
              def remove_container(version)
         
     | 
| 
      
 214 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 215 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 216 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 219 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
         
     | 
| 
      
 220 
     | 
    
         
            +
                      execute *KAMAL.app(role: role).remove_container(version: version)
         
     | 
| 
      
 221 
     | 
    
         
            +
                    end
         
     | 
| 
      
 222 
     | 
    
         
            +
                  end
         
     | 
| 
      
 223 
     | 
    
         
            +
                end
         
     | 
| 
      
 224 
     | 
    
         
            +
              end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
              desc "remove_containers", "Remove all app containers from servers", hide: true
         
     | 
| 
      
 227 
     | 
    
         
            +
              def remove_containers
         
     | 
| 
      
 228 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 229 
     | 
    
         
            +
                  on(KAMAL.hosts) do |host|
         
     | 
| 
      
 230 
     | 
    
         
            +
                    roles = KAMAL.roles_on(host)
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                    roles.each do |role|
         
     | 
| 
      
 233 
     | 
    
         
            +
                      execute *KAMAL.auditor.record("Removed all app containers", role: role), verbosity: :debug
         
     | 
| 
      
 234 
     | 
    
         
            +
                      execute *KAMAL.app(role: role).remove_containers
         
     | 
| 
      
 235 
     | 
    
         
            +
                    end
         
     | 
| 
      
 236 
     | 
    
         
            +
                  end
         
     | 
| 
      
 237 
     | 
    
         
            +
                end
         
     | 
| 
      
 238 
     | 
    
         
            +
              end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
              desc "remove_images", "Remove all app images from servers", hide: true
         
     | 
| 
      
 241 
     | 
    
         
            +
              def remove_images
         
     | 
| 
      
 242 
     | 
    
         
            +
                mutating do
         
     | 
| 
      
 243 
     | 
    
         
            +
                  on(KAMAL.hosts) do
         
     | 
| 
      
 244 
     | 
    
         
            +
                    execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
         
     | 
| 
      
 245 
     | 
    
         
            +
                    execute *KAMAL.app.remove_images
         
     | 
| 
      
 246 
     | 
    
         
            +
                  end
         
     | 
| 
      
 247 
     | 
    
         
            +
                end
         
     | 
| 
      
 248 
     | 
    
         
            +
              end
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
              desc "version", "Show app version currently running on servers"
         
     | 
| 
      
 251 
     | 
    
         
            +
              def version
         
     | 
| 
      
 252 
     | 
    
         
            +
                on(KAMAL.hosts) do |host|
         
     | 
| 
      
 253 
     | 
    
         
            +
                  role = KAMAL.roles_on(host).first
         
     | 
| 
      
 254 
     | 
    
         
            +
                  puts_by_host host, capture_with_info(*KAMAL.app(role: role).current_running_version).strip
         
     | 
| 
      
 255 
     | 
    
         
            +
                end
         
     | 
| 
      
 256 
     | 
    
         
            +
              end
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
              private
         
     | 
| 
      
 259 
     | 
    
         
            +
                def using_version(new_version)
         
     | 
| 
      
 260 
     | 
    
         
            +
                  if new_version
         
     | 
| 
      
 261 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 262 
     | 
    
         
            +
                      old_version = KAMAL.config.version
         
     | 
| 
      
 263 
     | 
    
         
            +
                      KAMAL.config.version = new_version
         
     | 
| 
      
 264 
     | 
    
         
            +
                      yield new_version
         
     | 
| 
      
 265 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 266 
     | 
    
         
            +
                      KAMAL.config.version = old_version
         
     | 
| 
      
 267 
     | 
    
         
            +
                    end
         
     | 
| 
      
 268 
     | 
    
         
            +
                  else
         
     | 
| 
      
 269 
     | 
    
         
            +
                    yield KAMAL.config.version
         
     | 
| 
      
 270 
     | 
    
         
            +
                  end
         
     | 
| 
      
 271 
     | 
    
         
            +
                end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                def current_running_version(host: KAMAL.primary_host)
         
     | 
| 
      
 274 
     | 
    
         
            +
                  version = nil
         
     | 
| 
      
 275 
     | 
    
         
            +
                  on(host) do
         
     | 
| 
      
 276 
     | 
    
         
            +
                    role = KAMAL.roles_on(host).first
         
     | 
| 
      
 277 
     | 
    
         
            +
                    version = capture_with_info(*KAMAL.app(role: role).current_running_version).strip
         
     | 
| 
      
 278 
     | 
    
         
            +
                  end
         
     | 
| 
      
 279 
     | 
    
         
            +
                  version.presence
         
     | 
| 
      
 280 
     | 
    
         
            +
                end
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
                def stale_versions(host:, role:)
         
     | 
| 
      
 283 
     | 
    
         
            +
                  versions = nil
         
     | 
| 
      
 284 
     | 
    
         
            +
                  on(host) do
         
     | 
| 
      
 285 
     | 
    
         
            +
                    versions = \
         
     | 
| 
      
 286 
     | 
    
         
            +
                      capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false)
         
     | 
| 
      
 287 
     | 
    
         
            +
                      .split("\n")
         
     | 
| 
      
 288 
     | 
    
         
            +
                      .drop(1)
         
     | 
| 
      
 289 
     | 
    
         
            +
                  end
         
     | 
| 
      
 290 
     | 
    
         
            +
                  versions
         
     | 
| 
      
 291 
     | 
    
         
            +
                end
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
      
 293 
     | 
    
         
            +
                def version_or_latest
         
     | 
| 
      
 294 
     | 
    
         
            +
                  options[:version] || "latest"
         
     | 
| 
      
 295 
     | 
    
         
            +
                end
         
     | 
| 
      
 296 
     | 
    
         
            +
            end
         
     |