nocoffee-kamal 2.3.0.1

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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
@@ -0,0 +1,162 @@
1
+ require "uri"
2
+
3
+ class Kamal::Cli::Build < Kamal::Cli::Base
4
+ class BuildError < StandardError; end
5
+
6
+ desc "deliver", "Build app and push app image to registry then pull image on servers"
7
+ def deliver
8
+ push
9
+ pull
10
+ end
11
+
12
+ desc "push", "Build and push app image to registry"
13
+ def push
14
+ cli = self
15
+
16
+ verify_local_dependencies
17
+ run_hook "pre-build"
18
+
19
+ uncommitted_changes = Kamal::Git.uncommitted_changes
20
+
21
+ if KAMAL.config.builder.git_clone?
22
+ if uncommitted_changes.present?
23
+ say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
24
+ end
25
+
26
+ run_locally do
27
+ Clone.new(self).prepare
28
+ end
29
+ elsif uncommitted_changes.present?
30
+ say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
31
+ end
32
+
33
+ with_env(KAMAL.config.builder.secrets) do
34
+ run_locally do
35
+ begin
36
+ execute *KAMAL.builder.inspect_builder
37
+ rescue SSHKit::Command::Failed => e
38
+ if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
39
+ warn "Missing compatible builder, so creating a new one first"
40
+ begin
41
+ cli.remove
42
+ rescue SSHKit::Command::Failed
43
+ raise unless e.message =~ /(context not found|no builder|does not exist)/
44
+ end
45
+ cli.create
46
+ else
47
+ raise
48
+ end
49
+ end
50
+
51
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
52
+ push = KAMAL.builder.push
53
+
54
+ KAMAL.with_verbosity(:debug) do
55
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ desc "pull", "Pull app image from registry onto servers"
62
+ def pull
63
+ if (first_hosts = mirror_hosts).any?
64
+ #  Pull on a single host per mirror first to seed them
65
+ say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
66
+ pull_on_hosts(first_hosts)
67
+ say "Pulling image on remaining hosts...", :magenta
68
+ pull_on_hosts(KAMAL.hosts - first_hosts)
69
+ else
70
+ pull_on_hosts(KAMAL.hosts)
71
+ end
72
+ end
73
+
74
+ desc "create", "Create a build setup"
75
+ def create
76
+ if (remote_host = KAMAL.config.builder.remote)
77
+ connect_to_remote_host(remote_host)
78
+ end
79
+
80
+ run_locally do
81
+ begin
82
+ debug "Using builder: #{KAMAL.builder.name}"
83
+ execute *KAMAL.builder.create
84
+ rescue SSHKit::Command::Failed => e
85
+ if e.message =~ /stderr=(.*)/
86
+ error "Couldn't create remote builder: #{$1}"
87
+ false
88
+ else
89
+ raise
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ desc "remove", "Remove build setup"
96
+ def remove
97
+ run_locally do
98
+ debug "Using builder: #{KAMAL.builder.name}"
99
+ execute *KAMAL.builder.remove
100
+ end
101
+ end
102
+
103
+ desc "details", "Show build setup"
104
+ def details
105
+ run_locally do
106
+ puts "Builder: #{KAMAL.builder.name}"
107
+ puts capture(*KAMAL.builder.info)
108
+ end
109
+ end
110
+
111
+ private
112
+ def verify_local_dependencies
113
+ run_locally do
114
+ begin
115
+ execute *KAMAL.builder.ensure_local_dependencies_installed
116
+ rescue SSHKit::Command::Failed => e
117
+ build_error = e.message =~ /command not found/ ?
118
+ "Docker is not installed locally" :
119
+ "Docker buildx plugin is not installed locally"
120
+
121
+ raise BuildError, build_error
122
+ end
123
+ end
124
+ end
125
+
126
+ def connect_to_remote_host(remote_host)
127
+ remote_uri = URI.parse(remote_host)
128
+ if remote_uri.scheme == "ssh"
129
+ host = SSHKit::Host.new(
130
+ hostname: remote_uri.host,
131
+ ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
132
+ )
133
+ on(host, options) do
134
+ execute "true"
135
+ end
136
+ end
137
+ end
138
+
139
+ def mirror_hosts
140
+ if KAMAL.hosts.many?
141
+ mirror_hosts = Concurrent::Hash.new
142
+ on(KAMAL.hosts) do |host|
143
+ first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
144
+ mirror_hosts[first_mirror] ||= host.to_s if first_mirror
145
+ rescue SSHKit::Command::Failed => e
146
+ raise unless e.message =~ /error calling index: reflect: slice index out of range/
147
+ end
148
+ mirror_hosts.values
149
+ else
150
+ []
151
+ end
152
+ end
153
+
154
+ def pull_on_hosts(hosts)
155
+ on(hosts) do
156
+ execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
157
+ execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
158
+ execute *KAMAL.builder.pull
159
+ execute *KAMAL.builder.validate_image
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,33 @@
1
+ require "concurrent/ivar"
2
+
3
+ class Kamal::Cli::Healthcheck::Barrier
4
+ def initialize
5
+ @ivar = Concurrent::IVar.new
6
+ end
7
+
8
+ def close
9
+ set(false)
10
+ end
11
+
12
+ def open
13
+ set(true)
14
+ end
15
+
16
+ def wait
17
+ unless opened?
18
+ raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
19
+ end
20
+ end
21
+
22
+ private
23
+ def opened?
24
+ @ivar.value
25
+ end
26
+
27
+ def set(value)
28
+ @ivar.set(value)
29
+ true
30
+ rescue Concurrent::MultipleAssignmentError
31
+ false
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ class Kamal::Cli::Healthcheck::Error < StandardError
2
+ end
@@ -0,0 +1,42 @@
1
+ module Kamal::Cli::Healthcheck::Poller
2
+ extend self
3
+
4
+ def wait_for_healthy(role, &block)
5
+ attempt = 1
6
+ timeout_at = Time.now + KAMAL.config.deploy_timeout
7
+ readiness_delay = KAMAL.config.readiness_delay
8
+
9
+ begin
10
+ status = block.call
11
+
12
+ if status == "running"
13
+ # Wait for the readiness delay and confirm it is still running
14
+ if readiness_delay > 0
15
+ info "Container is running, waiting for readiness delay of #{readiness_delay} seconds"
16
+ sleep readiness_delay
17
+ status = block.call
18
+ end
19
+ end
20
+
21
+ unless %w[ running healthy ].include?(status)
22
+ raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
23
+ end
24
+ rescue Kamal::Cli::Healthcheck::Error => e
25
+ time_left = timeout_at - Time.now
26
+ if time_left > 0
27
+ sleep [ attempt, time_left ].min
28
+ attempt += 1
29
+ retry
30
+ else
31
+ raise
32
+ end
33
+ end
34
+
35
+ info "Container is healthy!"
36
+ end
37
+
38
+ private
39
+ def info(message)
40
+ SSHKit.config.output.info(message)
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ class Kamal::Cli::Lock < Kamal::Cli::Base
2
+ desc "status", "Report lock status"
3
+ def status
4
+ handle_missing_lock do
5
+ on(KAMAL.primary_host) do
6
+ puts capture_with_debug(*KAMAL.lock.status)
7
+ end
8
+ end
9
+ end
10
+
11
+ desc "acquire", "Acquire the deploy lock"
12
+ option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
13
+ def acquire
14
+ message = options[:message]
15
+ ensure_run_directory
16
+
17
+ raise_if_locked do
18
+ on(KAMAL.primary_host) do
19
+ execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
20
+ end
21
+ say "Acquired the deploy lock"
22
+ end
23
+ end
24
+
25
+ desc "release", "Release the deploy lock"
26
+ def release
27
+ handle_missing_lock do
28
+ on(KAMAL.primary_host) do
29
+ execute *KAMAL.lock.release, verbosity: :debug
30
+ end
31
+ say "Released the deploy lock"
32
+ end
33
+ end
34
+
35
+ private
36
+ def handle_missing_lock
37
+ yield
38
+ rescue SSHKit::Runner::ExecuteError => e
39
+ if e.message =~ /No such file or directory/
40
+ say "There is no deploy lock"
41
+ else
42
+ raise
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,279 @@
1
+ class Kamal::Cli::Main < Kamal::Cli::Base
2
+ desc "setup", "Setup all accessories, push the env, and deploy app to servers"
3
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
4
+ def setup
5
+ print_runtime do
6
+ with_lock do
7
+ invoke_options = deploy_options
8
+
9
+ say "Ensure Docker is installed...", :magenta
10
+ invoke "kamal:cli:server:bootstrap", [], invoke_options
11
+
12
+ invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
13
+ deploy
14
+ end
15
+ end
16
+ end
17
+
18
+ desc "deploy", "Deploy app to servers"
19
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
20
+ def deploy
21
+ runtime = print_runtime do
22
+ invoke_options = deploy_options
23
+
24
+ say "Log into image registry...", :magenta
25
+ invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
26
+
27
+ if options[:skip_push]
28
+ say "Pull app image...", :magenta
29
+ invoke "kamal:cli:build:pull", [], invoke_options
30
+ else
31
+ say "Build and push app image...", :magenta
32
+ invoke "kamal:cli:build:deliver", [], invoke_options
33
+ end
34
+
35
+ with_lock do
36
+ run_hook "pre-deploy", secrets: true
37
+
38
+ say "Ensure kamal-proxy is running...", :magenta
39
+ invoke "kamal:cli:proxy:boot", [], invoke_options
40
+
41
+ say "Detect stale containers...", :magenta
42
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
43
+
44
+ invoke "kamal:cli:app:boot", [], invoke_options
45
+
46
+ say "Prune old containers and images...", :magenta
47
+ invoke "kamal:cli:prune:all", [], invoke_options
48
+ end
49
+ end
50
+
51
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
52
+ end
53
+
54
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
55
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
56
+ def redeploy
57
+ runtime = print_runtime do
58
+ invoke_options = deploy_options
59
+
60
+ if options[:skip_push]
61
+ say "Pull app image...", :magenta
62
+ invoke "kamal:cli:build:pull", [], invoke_options
63
+ else
64
+ say "Build and push app image...", :magenta
65
+ invoke "kamal:cli:build:deliver", [], invoke_options
66
+ end
67
+
68
+ with_lock do
69
+ run_hook "pre-deploy", secrets: true
70
+
71
+ say "Detect stale containers...", :magenta
72
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
73
+
74
+ invoke "kamal:cli:app:boot", [], invoke_options
75
+ end
76
+ end
77
+
78
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
79
+ end
80
+
81
+ desc "rollback [VERSION]", "Rollback app to VERSION"
82
+ def rollback(version)
83
+ rolled_back = false
84
+ runtime = print_runtime do
85
+ with_lock do
86
+ invoke_options = deploy_options
87
+
88
+ KAMAL.config.version = version
89
+ old_version = nil
90
+
91
+ if container_available?(version)
92
+ run_hook "pre-deploy", secrets: true
93
+
94
+ invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
95
+ rolled_back = true
96
+ else
97
+ say "The app version '#{version}' is not available as a container (use 'kamal app containers' for available versions)", :red
98
+ end
99
+ end
100
+ end
101
+
102
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s if rolled_back
103
+ end
104
+
105
+ desc "details", "Show details about all containers"
106
+ def details
107
+ invoke "kamal:cli:proxy:details"
108
+ invoke "kamal:cli:app:details"
109
+ invoke "kamal:cli:accessory:details", [ "all" ]
110
+ end
111
+
112
+ desc "audit", "Show audit log from servers"
113
+ def audit
114
+ on(KAMAL.hosts) do |host|
115
+ puts_by_host host, capture_with_info(*KAMAL.auditor.reveal)
116
+ end
117
+ end
118
+
119
+ desc "config", "Show combined config (including secrets!)"
120
+ def config
121
+ run_locally do
122
+ puts Kamal::Utils.redacted(KAMAL.config.to_h).to_yaml
123
+ end
124
+ end
125
+
126
+ desc "docs [SECTION]", "Show Kamal configuration documentation"
127
+ def docs(section = nil)
128
+ case section
129
+ when NilClass
130
+ puts Kamal::Configuration.validation_doc
131
+ else
132
+ puts Kamal::Configuration.const_get(section.titlecase.to_sym).validation_doc
133
+ end
134
+ rescue NameError
135
+ puts "No documentation found for #{section}"
136
+ end
137
+
138
+ desc "init", "Create config stub in config/deploy.yml and secrets stub in .kamal"
139
+ option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
140
+ def init
141
+ require "fileutils"
142
+
143
+ if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
144
+ puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
145
+ else
146
+ FileUtils.mkdir_p deploy_file.dirname
147
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
148
+ puts "Created configuration file in config/deploy.yml"
149
+ end
150
+
151
+ unless (secrets_file = Pathname.new(File.expand_path(".kamal/secrets"))).exist?
152
+ FileUtils.mkdir_p secrets_file.dirname
153
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/secrets", __dir__)), secrets_file
154
+ puts "Created .kamal/secrets file"
155
+ end
156
+
157
+ unless (hooks_dir = Pathname.new(File.expand_path(".kamal/hooks"))).exist?
158
+ hooks_dir.mkpath
159
+ Pathname.new(File.expand_path("templates/sample_hooks", __dir__)).each_child do |sample_hook|
160
+ FileUtils.cp sample_hook, hooks_dir, preserve: true
161
+ end
162
+ puts "Created sample hooks in .kamal/hooks"
163
+ end
164
+
165
+ if options[:bundle]
166
+ if (binstub = Pathname.new(File.expand_path("bin/kamal"))).exist?
167
+ puts "Binstub already exists in bin/kamal (remove first to create a new one)"
168
+ else
169
+ puts "Adding Kamal to Gemfile and bundle..."
170
+ run_locally do
171
+ execute :bundle, :add, :kamal
172
+ execute :bundle, :binstubs, :kamal
173
+ end
174
+ puts "Created binstub file in bin/kamal"
175
+ end
176
+ end
177
+ end
178
+
179
+ desc "remove", "Remove kamal-proxy, app, accessories, and registry session from servers"
180
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
181
+ def remove
182
+ confirming "This will remove all containers and images. Are you sure?" do
183
+ with_lock do
184
+ invoke "kamal:cli:app:remove", [], options.without(:confirmed)
185
+ invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
186
+ invoke "kamal:cli:accessory:remove", [ "all" ], options
187
+ invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
188
+ end
189
+ end
190
+ end
191
+
192
+ desc "upgrade", "Upgrade from Kamal 1.x to 2.0"
193
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
194
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
195
+ def upgrade
196
+ confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
197
+ with_lock do
198
+ if options[:rolling]
199
+ (KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
200
+ KAMAL.with_specific_hosts(host) do
201
+ say "Upgrading #{host}...", :magenta
202
+ if KAMAL.hosts.include?(host)
203
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
204
+ reset_invocation(Kamal::Cli::Proxy)
205
+ end
206
+ if KAMAL.accessory_hosts.include?(host)
207
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
208
+ reset_invocation(Kamal::Cli::Accessory)
209
+ end
210
+ say "Upgraded #{host}", :magenta
211
+ end
212
+ end
213
+ else
214
+ say "Upgrading all hosts...", :magenta
215
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true)
216
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true)
217
+ say "Upgraded all hosts", :magenta
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ desc "version", "Show Kamal version"
224
+ def version
225
+ puts Kamal::VERSION
226
+ end
227
+
228
+ desc "accessory", "Manage accessories (db/redis/search)"
229
+ subcommand "accessory", Kamal::Cli::Accessory
230
+
231
+ desc "app", "Manage application"
232
+ subcommand "app", Kamal::Cli::App
233
+
234
+ desc "build", "Build application image"
235
+ subcommand "build", Kamal::Cli::Build
236
+
237
+ desc "lock", "Manage the deploy lock"
238
+ subcommand "lock", Kamal::Cli::Lock
239
+
240
+ desc "proxy", "Manage kamal-proxy"
241
+ subcommand "proxy", Kamal::Cli::Proxy
242
+
243
+ desc "prune", "Prune old application images and containers"
244
+ subcommand "prune", Kamal::Cli::Prune
245
+
246
+ desc "registry", "Login and -out of the image registry"
247
+ subcommand "registry", Kamal::Cli::Registry
248
+
249
+ desc "secrets", "Helpers for extracting secrets"
250
+ subcommand "secrets", Kamal::Cli::Secrets
251
+
252
+ desc "server", "Bootstrap servers with curl and Docker"
253
+ subcommand "server", Kamal::Cli::Server
254
+
255
+ private
256
+ def container_available?(version)
257
+ begin
258
+ on(KAMAL.hosts) do
259
+ KAMAL.roles_on(host).each do |role|
260
+ container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
261
+ raise "Container not found" unless container_id.present?
262
+ end
263
+ end
264
+ rescue SSHKit::Runner::ExecuteError, SSHKit::Runner::MultipleExecuteError => e
265
+ if e.message =~ /Container not found/
266
+ say "Error looking for container version #{version}: #{e.message}"
267
+ return false
268
+ else
269
+ raise
270
+ end
271
+ end
272
+
273
+ true
274
+ end
275
+
276
+ def deploy_options
277
+ { "version" => KAMAL.config.version }.merge(options.without("skip_push"))
278
+ end
279
+ end