kamal 2.5.3 → 2.6.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +16 -5
  3. data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
  4. data/lib/kamal/cli/app/boot.rb +1 -0
  5. data/lib/kamal/cli/app/error_pages.rb +33 -0
  6. data/lib/kamal/cli/app.rb +66 -22
  7. data/lib/kamal/cli/base.rb +13 -3
  8. data/lib/kamal/cli/build.rb +20 -4
  9. data/lib/kamal/cli/main.rb +4 -7
  10. data/lib/kamal/cli/proxy.rb +57 -10
  11. data/lib/kamal/cli/server.rb +4 -2
  12. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  13. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  14. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
  15. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +6 -5
  16. data/lib/kamal/commander/specifics.rb +4 -0
  17. data/lib/kamal/commander.rb +2 -2
  18. data/lib/kamal/commands/accessory/proxy.rb +1 -1
  19. data/lib/kamal/commands/accessory.rb +2 -3
  20. data/lib/kamal/commands/app/error_pages.rb +9 -0
  21. data/lib/kamal/commands/app/proxy.rb +13 -1
  22. data/lib/kamal/commands/app.rb +1 -1
  23. data/lib/kamal/commands/auditor.rb +11 -5
  24. data/lib/kamal/commands/base.rb +4 -0
  25. data/lib/kamal/commands/builder/base.rb +2 -1
  26. data/lib/kamal/commands/proxy.rb +55 -15
  27. data/lib/kamal/configuration/accessory.rb +24 -2
  28. data/lib/kamal/configuration/docs/accessory.yml +6 -1
  29. data/lib/kamal/configuration/docs/configuration.yml +6 -0
  30. data/lib/kamal/configuration/docs/env.yml +31 -0
  31. data/lib/kamal/configuration/docs/proxy.yml +7 -0
  32. data/lib/kamal/configuration/env.rb +13 -4
  33. data/lib/kamal/configuration/proxy/boot.rb +121 -0
  34. data/lib/kamal/configuration/proxy.rb +18 -1
  35. data/lib/kamal/configuration/servers.rb +8 -1
  36. data/lib/kamal/configuration/validator/accessory.rb +4 -2
  37. data/lib/kamal/configuration/validator/role.rb +1 -0
  38. data/lib/kamal/configuration/validator/servers.rb +1 -1
  39. data/lib/kamal/configuration/validator.rb +6 -0
  40. data/lib/kamal/configuration.rb +36 -74
  41. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +1 -0
  42. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +2 -1
  43. data/lib/kamal/version.rb +1 -1
  44. metadata +9 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a9818fcb16121b1a06431da8a721e5c78133fbb69133b1a91a3d7c924c60815
4
- data.tar.gz: 98bb01ca42ef4cc8b7088527d79dacff099d5cea996c116f5de5a74fa9421606
3
+ metadata.gz: f3b89316447c79f85f3be2a7b1592d32e19350571b81f7438aab1ffd7602da86
4
+ data.tar.gz: '065904d4073e24495c27b0f32d468b384a93ac3bfb0c9303cf9ceb060d7d68cc'
5
5
  SHA512:
6
- metadata.gz: 2514b40a0e2b9729f92d23b170fc03913daaf068f26338c8add9fd27ae05feb52c4d22d1b25585ff77f81783a927beec01f1c92974a85608aa0c86eaca27b465
7
- data.tar.gz: 2fa1045dafb2602ccedcf7b437c2c10e6007ef479a2f5b6f8df18ca94641efb2e776065308ba5292a69f5cfb0d3730e8ec42f1a489a82cc4d64aab595c02d3a0
6
+ metadata.gz: 58e4102f80bb090dbe76f325429b8e74fcf689f92fcf18ffe6ca46b6f67f2acd0ba93adee9411c4790e71f29b5989d7e3a3f7ec26a08bdab7d679e339dbd3cae
7
+ data.tar.gz: 10038a978ad0755cdd25c732bd5886b9c6d59f80292041eaa8449cbaceaa2f8e9f61d1c3775c8eeb9a8a8d772046497f0b84016e0c8f7ab61b7907616182c617
@@ -1,4 +1,5 @@
1
1
  require "active_support/core_ext/array/conversions"
2
+ require "concurrent/array"
2
3
 
3
4
  class Kamal::Cli::Accessory < Kamal::Cli::Base
4
5
  desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
@@ -10,6 +11,16 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
10
11
  prepare(name) if prepare
11
12
 
12
13
  with_accessory(name) do |accessory, hosts|
14
+ booted_hosts = Concurrent::Array.new
15
+ on(hosts) do |host|
16
+ booted_hosts << host.to_s if capture_with_info(*accessory.info(all: true, quiet: true)).strip.presence
17
+ end
18
+
19
+ if booted_hosts.any?
20
+ say "Skipping booting `#{name}` on #{booted_hosts.sort.join(", ")}, a container already exists", :yellow
21
+ hosts -= booted_hosts
22
+ end
23
+
13
24
  directories(name)
14
25
  upload(name)
15
26
 
@@ -130,6 +141,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
130
141
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
131
142
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
132
143
  def exec(name, *cmd)
144
+ pre_connect_if_required
145
+
133
146
  cmd = Kamal::Utils.join_commands(cmd)
134
147
  with_accessory(name) do |accessory, hosts|
135
148
  case
@@ -139,6 +152,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
139
152
 
140
153
  when options[:interactive]
141
154
  say "Launching interactive command via SSH from new container...", :magenta
155
+ on(accessory.hosts.first) { execute *KAMAL.registry.login }
142
156
  run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
143
157
 
144
158
  when options[:reuse]
@@ -151,6 +165,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
151
165
  else
152
166
  say "Launching command from new container...", :magenta
153
167
  on(hosts) do |host|
168
+ execute *KAMAL.registry.login
154
169
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
155
170
  puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
156
171
  end
@@ -275,11 +290,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
275
290
  end
276
291
 
277
292
  def accessory_hosts(accessory)
278
- if KAMAL.specific_hosts&.any?
279
- KAMAL.specific_hosts & accessory.hosts
280
- else
281
- accessory.hosts
282
- end
293
+ KAMAL.accessory_hosts & accessory.hosts
283
294
  end
284
295
 
285
296
  def remove_accessory(name)
@@ -1,4 +1,4 @@
1
- class Kamal::Cli::App::PrepareAssets
1
+ class Kamal::Cli::App::Assets
2
2
  attr_reader :host, :role, :sshkit
3
3
  delegate :execute, :capture_with_info, :info, to: :sshkit
4
4
  delegate :assets?, to: :role
@@ -70,6 +70,7 @@ class Kamal::Cli::App::Boot
70
70
  def stop_old_version(version)
71
71
  execute *app.stop(version: version), raise_on_non_zero_exit: false
72
72
  execute *app.clean_up_assets if assets?
73
+ execute *app.clean_up_error_pages if KAMAL.config.error_pages_path
73
74
  end
74
75
 
75
76
  def release_barrier
@@ -0,0 +1,33 @@
1
+ class Kamal::Cli::App::ErrorPages
2
+ ERROR_PAGES_GLOB = "{4??.html,5??.html}"
3
+
4
+ attr_reader :host, :sshkit
5
+ delegate :upload!, :execute, to: :sshkit
6
+
7
+ def initialize(host, sshkit)
8
+ @host = host
9
+ @sshkit = sshkit
10
+ end
11
+
12
+ def run
13
+ if KAMAL.config.error_pages_path
14
+ with_error_pages_tmpdir do |local_error_pages_dir|
15
+ execute *KAMAL.app.create_error_pages_directory
16
+ upload! local_error_pages_dir, KAMAL.config.proxy_boot.error_pages_directory, mode: "0700", recursive: true
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+ def with_error_pages_tmpdir
23
+ Dir.mktmpdir("kamal-error-pages") do |tmpdir|
24
+ error_pages_dir = File.join(tmpdir, KAMAL.config.version)
25
+ FileUtils.mkdir(error_pages_dir)
26
+
27
+ if (files = Dir[File.join(KAMAL.config.error_pages_path, ERROR_PAGES_GLOB)]).any?
28
+ FileUtils.cp(files, error_pages_dir)
29
+ yield error_pages_dir
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/kamal/cli/app.rb CHANGED
@@ -7,9 +7,11 @@ class Kamal::Cli::App < Kamal::Cli::Base
7
7
  say "Start container with version #{version} (or reboot if already running)...", :magenta
8
8
 
9
9
  # Assets are prepared in a separate step to ensure they are on all hosts before booting
10
- on(KAMAL.hosts) do
10
+ on(KAMAL.app_hosts) do
11
+ Kamal::Cli::App::ErrorPages.new(host, self).run
12
+
11
13
  KAMAL.roles_on(host).each do |role|
12
- Kamal::Cli::App::PrepareAssets.new(host, role, self).run
14
+ Kamal::Cli::App::Assets.new(host, role, self).run
13
15
  end
14
16
  end
15
17
 
@@ -31,7 +33,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
31
33
  end
32
34
 
33
35
  # Tag once the app booted on all hosts
34
- on(KAMAL.hosts) do |host|
36
+ on(KAMAL.app_hosts) do |host|
35
37
  execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
36
38
  execute *KAMAL.app.tag_latest_image
37
39
  end
@@ -42,7 +44,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
42
44
  desc "start", "Start existing app container on servers"
43
45
  def start
44
46
  with_lock do
45
- on(KAMAL.hosts) do |host|
47
+ on(KAMAL.app_hosts) do |host|
46
48
  roles = KAMAL.roles_on(host)
47
49
 
48
50
  roles.each do |role|
@@ -65,7 +67,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
65
67
  desc "stop", "Stop app container on servers"
66
68
  def stop
67
69
  with_lock do
68
- on(KAMAL.hosts) do |host|
70
+ on(KAMAL.app_hosts) do |host|
69
71
  roles = KAMAL.roles_on(host)
70
72
 
71
73
  roles.each do |role|
@@ -89,7 +91,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
89
91
  # FIXME: Drop in favor of just containers?
90
92
  desc "details", "Show details about app containers"
91
93
  def details
92
- on(KAMAL.hosts) do |host|
94
+ on(KAMAL.app_hosts) do |host|
93
95
  roles = KAMAL.roles_on(host)
94
96
 
95
97
  roles.each do |role|
@@ -104,10 +106,16 @@ class Kamal::Cli::App < Kamal::Cli::Base
104
106
  option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
105
107
  option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
106
108
  def exec(*cmd)
109
+ pre_connect_if_required
110
+
107
111
  if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
108
112
  raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
109
113
  end
110
114
 
115
+ if cmd.empty?
116
+ raise ArgumentError, "No command provided. You must specify a command to execute."
117
+ end
118
+
111
119
  cmd = Kamal::Utils.join_commands(cmd)
112
120
  env = options[:env]
113
121
  detach = options[:detach]
@@ -123,6 +131,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
123
131
  say "Get most recent version available as an image...", :magenta unless options[:version]
124
132
  using_version(version_or_latest) do |version|
125
133
  say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
134
+ on(KAMAL.primary_host) { execute *KAMAL.registry.login }
126
135
  run_locally do
127
136
  exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_new_container_over_ssh(cmd, env: env)
128
137
  end
@@ -133,7 +142,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
133
142
  using_version(options[:version] || current_running_version) do |version|
134
143
  say "Launching command with version #{version} from existing container...", :magenta
135
144
 
136
- on(KAMAL.hosts) do |host|
145
+ on(KAMAL.app_hosts) do |host|
137
146
  roles = KAMAL.roles_on(host)
138
147
 
139
148
  roles.each do |role|
@@ -147,7 +156,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
147
156
  say "Get most recent version available as an image...", :magenta unless options[:version]
148
157
  using_version(version_or_latest) do |version|
149
158
  say "Launching command with version #{version} from new container...", :magenta
150
- on(KAMAL.hosts) do |host|
159
+ on(KAMAL.app_hosts) do |host|
160
+ execute *KAMAL.registry.login
161
+
151
162
  roles = KAMAL.roles_on(host)
152
163
 
153
164
  roles.each do |role|
@@ -161,7 +172,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
161
172
 
162
173
  desc "containers", "Show app containers on servers"
163
174
  def containers
164
- on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
175
+ on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
165
176
  end
166
177
 
167
178
  desc "stale_containers", "Detect app stale containers"
@@ -170,7 +181,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
170
181
  stop = options[:stop]
171
182
 
172
183
  with_lock_if_stopping do
173
- on(KAMAL.hosts) do |host|
184
+ on(KAMAL.app_hosts) do |host|
174
185
  roles = KAMAL.roles_on(host)
175
186
 
176
187
  roles.each do |role|
@@ -193,7 +204,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
193
204
 
194
205
  desc "images", "Show app images on servers"
195
206
  def images
196
- on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
207
+ on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
197
208
  end
198
209
 
199
210
  desc "logs", "Show log lines from app on servers (use --help to show options)"
@@ -229,7 +240,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
229
240
  else
230
241
  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
231
242
 
232
- on(KAMAL.hosts) do |host|
243
+ on(KAMAL.app_hosts) do |host|
233
244
  roles = KAMAL.roles_on(host)
234
245
 
235
246
  roles.each do |role|
@@ -249,14 +260,44 @@ class Kamal::Cli::App < Kamal::Cli::Base
249
260
  stop
250
261
  remove_containers
251
262
  remove_images
252
- remove_app_directory
263
+ remove_app_directories
264
+ end
265
+ end
266
+
267
+ desc "live", "Set the app to live mode"
268
+ def live
269
+ with_lock do
270
+ on(KAMAL.proxy_hosts) do |host|
271
+ roles = KAMAL.roles_on(host)
272
+
273
+ roles.each do |role|
274
+ execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ desc "maintenance", "Set the app to maintenance mode"
281
+ option :drain_timeout, type: :numeric, desc: "How long to allow in-flight requests to complete (defaults to drain_timeout from config)"
282
+ option :message, type: :string, desc: "Message to display to clients while stopped"
283
+ def maintenance
284
+ maintenance_options = { drain_timeout: options[:drain_timeout] || KAMAL.config.drain_timeout, message: options[:message] }
285
+
286
+ with_lock do
287
+ on(KAMAL.proxy_hosts) do |host|
288
+ roles = KAMAL.roles_on(host)
289
+
290
+ roles.each do |role|
291
+ execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
292
+ end
293
+ end
253
294
  end
254
295
  end
255
296
 
256
297
  desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
257
298
  def remove_container(version)
258
299
  with_lock do
259
- on(KAMAL.hosts) do |host|
300
+ on(KAMAL.app_hosts) do |host|
260
301
  roles = KAMAL.roles_on(host)
261
302
 
262
303
  roles.each do |role|
@@ -270,7 +311,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
270
311
  desc "remove_containers", "Remove all app containers from servers", hide: true
271
312
  def remove_containers
272
313
  with_lock do
273
- on(KAMAL.hosts) do |host|
314
+ on(KAMAL.app_hosts) do |host|
274
315
  roles = KAMAL.roles_on(host)
275
316
 
276
317
  roles.each do |role|
@@ -284,30 +325,33 @@ class Kamal::Cli::App < Kamal::Cli::Base
284
325
  desc "remove_images", "Remove all app images from servers", hide: true
285
326
  def remove_images
286
327
  with_lock do
287
- on(KAMAL.hosts) do
328
+ on(KAMAL.app_hosts) do
288
329
  execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
289
330
  execute *KAMAL.app.remove_images
290
331
  end
291
332
  end
292
333
  end
293
334
 
294
- desc "remove_app_directory", "Remove the service directory from servers", hide: true
295
- def remove_app_directory
335
+ desc "remove_app_directories", "Remove the app directories from servers", hide: true
336
+ def remove_app_directories
296
337
  with_lock do
297
- on(KAMAL.hosts) do |host|
338
+ on(KAMAL.app_hosts) do |host|
298
339
  roles = KAMAL.roles_on(host)
299
340
 
300
341
  roles.each do |role|
301
- execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory} on all servers", role: role), verbosity: :debug
342
+ execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}", role: role), verbosity: :debug
302
343
  execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
303
344
  end
345
+
346
+ execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}"), verbosity: :debug
347
+ execute *KAMAL.app.remove_proxy_app_directory, raise_on_non_zero_exit: false
304
348
  end
305
349
  end
306
350
  end
307
351
 
308
352
  desc "version", "Show app version currently running on servers"
309
353
  def version
310
- on(KAMAL.hosts) do |host|
354
+ on(KAMAL.app_hosts) do |host|
311
355
  role = KAMAL.roles_on(host).first
312
356
  puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
313
357
  end
@@ -350,6 +394,6 @@ class Kamal::Cli::App < Kamal::Cli::Base
350
394
  end
351
395
 
352
396
  def host_boot_groups
353
- KAMAL.config.boot.limit ? KAMAL.hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.hosts ]
397
+ KAMAL.config.boot.limit ? KAMAL.app_hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.app_hosts ]
354
398
  end
355
399
  end
@@ -133,7 +133,13 @@ module Kamal::Cli
133
133
 
134
134
  def run_hook(hook, **extra_details)
135
135
  if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook)
136
- details = { hosts: KAMAL.hosts.join(","), command: command, subcommand: subcommand }
136
+ details = {
137
+ hosts: KAMAL.hosts.join(","),
138
+ roles: KAMAL.specific_roles&.join(","),
139
+ lock: KAMAL.holding_lock?.to_s,
140
+ command: command,
141
+ subcommand: subcommand
142
+ }.compact
137
143
 
138
144
  say "Running the #{hook} hook...", :magenta
139
145
  with_env KAMAL.hook.env(**details, **extra_details) do
@@ -147,12 +153,16 @@ module Kamal::Cli
147
153
  end
148
154
 
149
155
  def on(*args, &block)
156
+ pre_connect_if_required
157
+
158
+ super
159
+ end
160
+
161
+ def pre_connect_if_required
150
162
  if !KAMAL.connected?
151
163
  run_hook "pre-connect"
152
164
  KAMAL.connected = true
153
165
  end
154
-
155
- super
156
166
  end
157
167
 
158
168
  def command
@@ -15,6 +15,8 @@ class Kamal::Cli::Build < Kamal::Cli::Base
15
15
  cli = self
16
16
 
17
17
  ensure_docker_installed
18
+ login_to_registry_locally
19
+
18
20
  run_hook "pre-build"
19
21
 
20
22
  uncommitted_changes = Kamal::Git.uncommitted_changes
@@ -61,14 +63,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
61
63
 
62
64
  desc "pull", "Pull app image from registry onto servers"
63
65
  def pull
66
+ login_to_registry_remotely
67
+
64
68
  if (first_hosts = mirror_hosts).any?
65
69
  #  Pull on a single host per mirror first to seed them
66
70
  say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
67
71
  pull_on_hosts(first_hosts)
68
72
  say "Pulling image on remaining hosts...", :magenta
69
- pull_on_hosts(KAMAL.hosts - first_hosts)
73
+ pull_on_hosts(KAMAL.app_hosts - first_hosts)
70
74
  else
71
- pull_on_hosts(KAMAL.hosts)
75
+ pull_on_hosts(KAMAL.app_hosts)
72
76
  end
73
77
  end
74
78
 
@@ -159,9 +163,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
159
163
  end
160
164
 
161
165
  def mirror_hosts
162
- if KAMAL.hosts.many?
166
+ if KAMAL.app_hosts.many?
163
167
  mirror_hosts = Concurrent::Hash.new
164
- on(KAMAL.hosts) do |host|
168
+ on(KAMAL.app_hosts) do |host|
165
169
  first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
166
170
  mirror_hosts[first_mirror] ||= host.to_s if first_mirror
167
171
  rescue SSHKit::Command::Failed => e
@@ -181,4 +185,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
181
185
  execute *KAMAL.builder.validate_image
182
186
  end
183
187
  end
188
+
189
+ def login_to_registry_locally
190
+ run_locally do
191
+ execute *KAMAL.registry.login
192
+ end
193
+ end
194
+
195
+ def login_to_registry_remotely
196
+ on(KAMAL.app_hosts) do
197
+ execute *KAMAL.registry.login
198
+ end
199
+ end
184
200
  end
@@ -20,9 +20,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
20
20
  runtime = print_runtime do
21
21
  invoke_options = deploy_options
22
22
 
23
- say "Log into image registry...", :magenta
24
- invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
25
-
26
23
  if options[:skip_push]
27
24
  say "Pull app image...", :magenta
28
25
  invoke "kamal:cli:build:pull", [], invoke_options
@@ -52,7 +49,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
52
49
  run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
53
50
  end
54
51
 
55
- desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
52
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
56
53
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
57
54
  def redeploy
58
55
  runtime = print_runtime do
@@ -197,10 +194,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
197
194
  confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
198
195
  with_lock do
199
196
  if options[:rolling]
200
- (KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
197
+ KAMAL.hosts.each do |host|
201
198
  KAMAL.with_specific_hosts(host) do
202
199
  say "Upgrading #{host}...", :magenta
203
- if KAMAL.hosts.include?(host)
200
+ if KAMAL.app_hosts.include?(host)
204
201
  invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
205
202
  reset_invocation(Kamal::Cli::Proxy)
206
203
  end
@@ -256,7 +253,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
256
253
  private
257
254
  def container_available?(version)
258
255
  begin
259
- on(KAMAL.hosts) do
256
+ on(KAMAL.app_hosts) do
260
257
  KAMAL.roles_on(host).each do |role|
261
258
  container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
262
259
  raise "Container not found" unless container_id.present?
@@ -13,9 +13,10 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
13
13
 
14
14
  version = capture_with_info(*KAMAL.proxy.version).strip.presence
15
15
 
16
- if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
17
- raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
16
+ if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
17
+ raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}"
18
18
  end
19
+ execute *KAMAL.proxy.ensure_apps_config_directory
19
20
  execute *KAMAL.proxy.start_or_run
20
21
  end
21
22
  end
@@ -24,30 +25,75 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
24
25
  desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
25
26
  option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
26
27
  option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
27
- option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host"
28
- option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host"
29
- option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs"
28
+ option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
29
+ option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host"
30
+ option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs"
31
+ option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image"
32
+ option :repository, type: :string, default: nil, desc: "Repository for the proxy image"
33
+ option :image_version, type: :string, default: nil, desc: "Version of the proxy to run"
34
+ option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on"
35
+ option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode"
30
36
  option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
31
37
  def boot_config(subcommand)
38
+ proxy_boot_config = KAMAL.config.proxy_boot
39
+
32
40
  case subcommand
33
41
  when "set"
34
42
  boot_options = [
35
- *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
36
- *(KAMAL.config.proxy_logging_args(options[:log_max_size])),
43
+ *(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
44
+ *(proxy_boot_config.logging_args(options[:log_max_size])),
45
+ *("--expose=#{options[:metrics_port]}" if options[:metrics_port]),
37
46
  *options[:docker_options].map { |option| "--#{option}" }
38
47
  ]
39
48
 
49
+ image = [
50
+ options[:registry].presence,
51
+ options[:repository].presence || proxy_boot_config.repository_name,
52
+ proxy_boot_config.image_name
53
+ ].compact.join("/")
54
+
55
+ image_version = options[:image_version]
56
+
57
+ run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
58
+ run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
59
+
40
60
  on(KAMAL.proxy_hosts) do |host|
41
61
  execute(*KAMAL.proxy.ensure_proxy_directory)
42
- upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file
62
+ if boot_options != proxy_boot_config.default_boot_options
63
+ upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file
64
+ else
65
+ execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
66
+ end
67
+
68
+ if image != proxy_boot_config.image_default
69
+ upload! StringIO.new(image), proxy_boot_config.image_file
70
+ else
71
+ execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
72
+ end
73
+
74
+ if image_version
75
+ upload! StringIO.new(image_version), proxy_boot_config.image_version_file
76
+ else
77
+ execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
78
+ end
79
+
80
+ if run_command
81
+ upload! StringIO.new(run_command), proxy_boot_config.run_command_file
82
+ else
83
+ execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
84
+ end
43
85
  end
44
86
  when "get"
87
+
45
88
  on(KAMAL.proxy_hosts) do |host|
46
- puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.get_boot_options)}"
89
+ puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}"
47
90
  end
48
91
  when "reset"
49
92
  on(KAMAL.proxy_hosts) do |host|
50
- execute *KAMAL.proxy.reset_boot_options
93
+ execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
94
+ execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
95
+ execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
96
+ execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
51
97
  end
52
98
  else
53
99
  raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
@@ -71,6 +117,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
71
117
  "Stopping and removing kamal-proxy on #{host}, if running..."
72
118
  execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
73
119
  execute *KAMAL.proxy.remove_container
120
+ execute *KAMAL.proxy.ensure_apps_config_directory
74
121
 
75
122
  execute *KAMAL.proxy.run
76
123
 
@@ -2,8 +2,10 @@ class Kamal::Cli::Server < Kamal::Cli::Base
2
2
  desc "exec", "Run a custom command on the server (use --help to show options)"
3
3
  option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
4
  def exec(*cmd)
5
+ pre_connect_if_required
6
+
5
7
  cmd = Kamal::Utils.join_commands(cmd)
6
- hosts = KAMAL.hosts | KAMAL.accessory_hosts
8
+ hosts = KAMAL.hosts
7
9
 
8
10
  case
9
11
  when options[:interactive]
@@ -27,7 +29,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
27
29
  with_lock do
28
30
  missing = []
29
31
 
30
- on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
32
+ on(KAMAL.hosts) do |host|
31
33
  unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
32
34
  if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
33
35
  info "Missing Docker on #{host}. Installing…"
@@ -7,7 +7,7 @@
7
7
  # KAMAL_PERFORMER
8
8
  # KAMAL_VERSION
9
9
  # KAMAL_HOSTS
10
- # KAMAL_ROLE (if set)
10
+ # KAMAL_ROLES (if set)
11
11
  # KAMAL_DESTINATION (if set)
12
12
  # KAMAL_RUNTIME
13
13
 
@@ -13,7 +13,7 @@
13
13
  # KAMAL_PERFORMER
14
14
  # KAMAL_VERSION
15
15
  # KAMAL_HOSTS
16
- # KAMAL_ROLE (if set)
16
+ # KAMAL_ROLES (if set)
17
17
  # KAMAL_DESTINATION (if set)
18
18
 
19
19
  if [ -n "$(git status --porcelain)" ]; then
@@ -9,7 +9,7 @@
9
9
  # KAMAL_PERFORMER
10
10
  # KAMAL_VERSION
11
11
  # KAMAL_HOSTS
12
- # KAMAL_ROLE (if set)
12
+ # KAMAL_ROLES (if set)
13
13
  # KAMAL_DESTINATION (if set)
14
14
  # KAMAL_RUNTIME
15
15
 
@@ -13,7 +13,7 @@
13
13
  # KAMAL_HOSTS
14
14
  # KAMAL_COMMAND
15
15
  # KAMAL_SUBCOMMAND
16
- # KAMAL_ROLE (if set)
16
+ # KAMAL_ROLES (if set)
17
17
  # KAMAL_DESTINATION (if set)
18
18
 
19
19
  # Only check the build status for production deployments
@@ -82,11 +82,12 @@ end
82
82
 
83
83
  $stdout.sync = true
84
84
 
85
- puts "Checking build status..."
86
- attempts = 0
87
- checks = GithubStatusChecks.new
88
-
89
85
  begin
86
+ puts "Checking build status..."
87
+
88
+ attempts = 0
89
+ checks = GithubStatusChecks.new
90
+
90
91
  loop do
91
92
  case checks.state
92
93
  when "success"