kamal 1.8.3 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +44 -20
  4. data/lib/kamal/cli/alias/command.rb +9 -0
  5. data/lib/kamal/cli/app/boot.rb +22 -16
  6. data/lib/kamal/cli/app.rb +40 -5
  7. data/lib/kamal/cli/base.rb +19 -51
  8. data/lib/kamal/cli/build.rb +12 -13
  9. data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
  10. data/lib/kamal/cli/healthcheck/poller.rb +18 -39
  11. data/lib/kamal/cli/lock.rb +2 -3
  12. data/lib/kamal/cli/main.rb +54 -51
  13. data/lib/kamal/cli/proxy.rb +224 -0
  14. data/lib/kamal/cli/prune.rb +0 -1
  15. data/lib/kamal/cli/secrets.rb +36 -0
  16. data/lib/kamal/cli/server.rb +2 -3
  17. data/lib/kamal/cli/templates/deploy.yml +4 -21
  18. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  19. data/lib/kamal/cli/templates/secrets +16 -0
  20. data/lib/kamal/cli.rb +1 -0
  21. data/lib/kamal/commander/specifics.rb +3 -3
  22. data/lib/kamal/commander.rb +24 -8
  23. data/lib/kamal/commands/accessory.rb +7 -7
  24. data/lib/kamal/commands/app/assets.rb +8 -8
  25. data/lib/kamal/commands/app/proxy.rb +16 -0
  26. data/lib/kamal/commands/app.rb +7 -15
  27. data/lib/kamal/commands/auditor.rb +6 -3
  28. data/lib/kamal/commands/base.rb +8 -0
  29. data/lib/kamal/commands/builder/base.rb +26 -13
  30. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  31. data/lib/kamal/commands/builder/local.rb +14 -0
  32. data/lib/kamal/commands/builder/remote.rb +63 -0
  33. data/lib/kamal/commands/builder.rb +13 -29
  34. data/lib/kamal/commands/docker.rb +4 -0
  35. data/lib/kamal/commands/hook.rb +5 -2
  36. data/lib/kamal/commands/lock.rb +2 -6
  37. data/lib/kamal/commands/proxy.rb +77 -0
  38. data/lib/kamal/commands/prune.rb +1 -9
  39. data/lib/kamal/commands/server.rb +11 -1
  40. data/lib/kamal/configuration/accessory.rb +14 -2
  41. data/lib/kamal/configuration/alias.rb +15 -0
  42. data/lib/kamal/configuration/builder.rb +52 -18
  43. data/lib/kamal/configuration/docs/alias.yml +26 -0
  44. data/lib/kamal/configuration/docs/builder.yml +26 -23
  45. data/lib/kamal/configuration/docs/configuration.yml +22 -16
  46. data/lib/kamal/configuration/docs/env.yml +10 -11
  47. data/lib/kamal/configuration/docs/proxy.yml +100 -0
  48. data/lib/kamal/configuration/docs/registry.yml +4 -2
  49. data/lib/kamal/configuration/docs/role.yml +3 -5
  50. data/lib/kamal/configuration/env/tag.rb +4 -3
  51. data/lib/kamal/configuration/env.rb +10 -17
  52. data/lib/kamal/configuration/proxy.rb +66 -0
  53. data/lib/kamal/configuration/registry.rb +3 -2
  54. data/lib/kamal/configuration/role.rb +63 -94
  55. data/lib/kamal/configuration/validator/alias.rb +15 -0
  56. data/lib/kamal/configuration/validator/builder.rb +4 -0
  57. data/lib/kamal/configuration/validator/proxy.rb +11 -0
  58. data/lib/kamal/configuration/validator.rb +42 -24
  59. data/lib/kamal/configuration.rb +91 -33
  60. data/lib/kamal/env_file.rb +4 -0
  61. data/lib/kamal/secrets/adapters/base.rb +18 -0
  62. data/lib/kamal/secrets/adapters/bitwarden.rb +64 -0
  63. data/lib/kamal/secrets/adapters/last_pass.rb +30 -0
  64. data/lib/kamal/secrets/adapters/one_password.rb +61 -0
  65. data/lib/kamal/secrets/adapters/test.rb +10 -0
  66. data/lib/kamal/secrets/adapters.rb +14 -0
  67. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  68. data/lib/kamal/secrets.rb +37 -0
  69. data/lib/kamal/sshkit_with_ext.rb +1 -0
  70. data/lib/kamal/utils.rb +28 -0
  71. data/lib/kamal/version.rb +1 -1
  72. data/lib/kamal.rb +3 -1
  73. metadata +32 -23
  74. data/lib/kamal/cli/env.rb +0 -54
  75. data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
  76. data/lib/kamal/cli/templates/template.env +0 -2
  77. data/lib/kamal/cli/traefik.rb +0 -122
  78. data/lib/kamal/commands/app/cord.rb +0 -22
  79. data/lib/kamal/commands/builder/multiarch/remote.rb +0 -65
  80. data/lib/kamal/commands/builder/multiarch.rb +0 -41
  81. data/lib/kamal/commands/builder/native/cached.rb +0 -25
  82. data/lib/kamal/commands/builder/native/remote.rb +0 -67
  83. data/lib/kamal/commands/builder/native.rb +0 -20
  84. data/lib/kamal/commands/traefik.rb +0 -85
  85. data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
  86. data/lib/kamal/configuration/docs/traefik.yml +0 -62
  87. data/lib/kamal/configuration/healthcheck.rb +0 -63
  88. data/lib/kamal/configuration/traefik.rb +0 -60
  89. /data/lib/kamal/cli/templates/sample_hooks/{pre-traefik-reboot.sample → pre-proxy-reboot.sample} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67aa8d9022e0dd44e1be0a0ab8c26f20dd458fb469cf5e9d461dd58871712d8d
4
- data.tar.gz: 286d81788711c43f783e7a4bebc861bf89eeb948f7bebb622686e9947151296d
3
+ metadata.gz: c08e90e760f2392afcd73847fa1d9f7e987c684d687f49f227b808fda6cc28d5
4
+ data.tar.gz: c0945022760eeca770901d2c163c58a4a46a11f917e29f2d516d95d4af51ef3c
5
5
  SHA512:
6
- metadata.gz: dad7512c1f60ab760bb4cbe5183f8af92b8388697d7778fef2def69bed50be1e90befa09e765e30ad60af58548db69e8184df82053c6f9c1c28e3fa1549373de
7
- data.tar.gz: 75b5950ba4d744e9c7040c0531efcc2c781d13b1910d14226f6241424a6aba87aa0b24dcc249e13248f9c6da64fe2248fadffffbda630033a71ba78a2b3dd4c6
6
+ metadata.gz: 80ee86cd4b2ee5f5369cb6b1a7b81165a313cdedfd11352eb9215c5b6bf792be627927fad72c3bf25f69d12bcbf02a1fd088771ac495b45c0272145f16536f3f
7
+ data.tar.gz: df01ab908c65534003c0f089727c5ec6165be5ad72e72fab0f71c73c181ba0c8d7230a1887a2a3d7650c1f0ecfe2db349ebc2367ed64fbf052863d3bf37e4b57
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kamal: Deploy web apps anywhere
2
2
 
3
- From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal has the dynamic reverse-proxy Traefik hold requests while a new app container is started and the old one is stopped. Works seamlessly across multiple hosts, using SSHKit to execute commands. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized with Docker.
3
+ From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal uses [kamal-proxy](https://github.com/basecamp/kamal-proxy) to seamlessly switch requests between containers. Works seamlessly across multiple servers, using SSHKit to execute commands. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized with Docker.
4
4
 
5
5
  ➡️ See [kamal-deploy.org](https://kamal-deploy.org) for documentation on [installation](https://kamal-deploy.org/docs/installation), [configuration](https://kamal-deploy.org/docs/configuration), and [commands](https://kamal-deploy.org/docs/commands).
6
6
 
@@ -1,17 +1,20 @@
1
1
  class Kamal::Cli::Accessory < Kamal::Cli::Base
2
2
  desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
3
- def boot(name, login: true)
3
+ def boot(name, prepare: true)
4
4
  with_lock do
5
5
  if name == "all"
6
6
  KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
7
7
  else
8
+ prepare(name) if prepare
9
+
8
10
  with_accessory(name) do |accessory, hosts|
9
11
  directories(name)
10
12
  upload(name)
11
13
 
12
14
  on(hosts) do
13
- execute *KAMAL.registry.login if login
14
15
  execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
16
+ execute *accessory.ensure_env_directory
17
+ upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
15
18
  execute *accessory.run
16
19
  end
17
20
  end
@@ -55,15 +58,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
55
58
  if name == "all"
56
59
  KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
57
60
  else
58
- with_accessory(name) do |accessory, hosts|
59
- on(hosts) do
60
- execute *KAMAL.registry.login
61
- end
62
-
63
- stop(name)
64
- remove_container(name)
65
- boot(name, login: false)
66
- end
61
+ prepare(name)
62
+ stop(name)
63
+ remove_container(name)
64
+ boot(name, prepare: false)
67
65
  end
68
66
  end
69
67
  end
@@ -95,10 +93,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
95
93
  desc "restart [NAME]", "Restart existing accessory container on host"
96
94
  def restart(name)
97
95
  with_lock do
98
- with_accessory(name) do
99
- stop(name)
100
- start(name)
101
- end
96
+ stop(name)
97
+ start(name)
102
98
  end
103
99
  end
104
100
 
@@ -222,6 +218,25 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
222
218
  end
223
219
  end
224
220
 
221
+ desc "upgrade", "Upgrade accessories from Kamal 1.x to 2.0 (restart them in 'kamal' network)"
222
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
223
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
224
+ def upgrade(name)
225
+ confirming "This will restart all accessories" do
226
+ with_lock do
227
+ host_groups = options[:rolling] ? KAMAL.accessory_hosts : [ KAMAL.accessory_hosts ]
228
+ host_groups.each do |hosts|
229
+ host_list = Array(hosts).join(",")
230
+ KAMAL.with_specific_hosts(hosts) do
231
+ say "Upgrading #{name} accessories on #{host_list}...", :magenta
232
+ reboot name
233
+ say "Upgraded #{name} accessories on #{host_list}...", :magenta
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+
225
240
  private
226
241
  def with_accessory(name)
227
242
  if KAMAL.config.accessory(name)
@@ -249,11 +264,20 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
249
264
  end
250
265
 
251
266
  def remove_accessory(name)
252
- with_accessory(name) do
253
- stop(name)
254
- remove_container(name)
255
- remove_image(name)
256
- remove_service_directory(name)
267
+ stop(name)
268
+ remove_container(name)
269
+ remove_image(name)
270
+ remove_service_directory(name)
271
+ end
272
+
273
+ def prepare(name)
274
+ with_accessory(name) do |accessory, hosts|
275
+ on(hosts) do
276
+ execute *KAMAL.registry.login
277
+ execute *KAMAL.docker.create_network
278
+ rescue SSHKit::Command::Failed => e
279
+ raise unless e.message.include?("already exists")
280
+ end
257
281
  end
258
282
  end
259
283
  end
@@ -0,0 +1,9 @@
1
+ class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
+ def run(instance, args = [])
3
+ if (_alias = KAMAL.config.aliases[name])
4
+ Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
+ else
6
+ super
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::App::Boot
2
2
  attr_reader :host, :role, :version, :barrier, :sshkit
3
- delegate :execute, :capture_with_info, :capture_with_pretty_json, :info, :error, to: :sshkit
4
- delegate :uses_cord?, :assets?, :running_traefik?, to: :role
3
+ delegate :execute, :capture_with_info, :capture_with_pretty_json, :info, :error, :upload!, to: :sshkit
4
+ delegate :assets?, :running_proxy?, to: :role
5
5
 
6
6
  def initialize(host, role, sshkit, version, barrier)
7
7
  @host = host
@@ -45,11 +45,22 @@ class Kamal::Cli::App::Boot
45
45
 
46
46
  def start_new_version
47
47
  audit "Booted app version #{version}"
48
-
49
- execute *app.tie_cord(role.cord_host_file) if uses_cord?
50
48
  hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}"
49
+
50
+ execute *app.ensure_env_directory
51
+ upload! role.secrets_io(host), role.secrets_path, mode: "0600"
52
+
51
53
  execute *app.run(hostname: hostname)
52
- Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
54
+ if running_proxy?
55
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
56
+ raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
57
+ execute *app.deploy(target: endpoint)
58
+ else
59
+ Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
60
+ end
61
+ rescue => e
62
+ error "Failed to boot #{role} on #{host}"
63
+ raise e
53
64
  end
54
65
 
55
66
  def stop_new_version
@@ -57,16 +68,7 @@ class Kamal::Cli::App::Boot
57
68
  end
58
69
 
59
70
  def stop_old_version(version)
60
- if uses_cord?
61
- cord = capture_with_info(*app.cord(version: version), raise_on_non_zero_exit: false).strip
62
- if cord.present?
63
- execute *app.cut_cord(cord)
64
- Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
65
- end
66
- end
67
-
68
71
  execute *app.stop(version: version), raise_on_non_zero_exit: false
69
-
70
72
  execute *app.clean_up_assets if assets?
71
73
  end
72
74
 
@@ -88,8 +90,12 @@ class Kamal::Cli::App::Boot
88
90
  def close_barrier
89
91
  if barrier.close
90
92
  info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
91
- error capture_with_info(*app.logs(version: version))
92
- error capture_with_info(*app.container_health_log(version: version))
93
+ begin
94
+ error capture_with_info(*app.logs(version: version))
95
+ error capture_with_info(*app.container_health_log(version: version))
96
+ rescue SSHKit::Command::Failed
97
+ error "Could not fetch logs for #{version}"
98
+ end
93
99
  end
94
100
  end
95
101
 
data/lib/kamal/cli/app.rb CHANGED
@@ -4,7 +4,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
4
4
  with_lock do
5
5
  say "Get most recent version available as an image...", :magenta unless options[:version]
6
6
  using_version(version_or_latest) do |version|
7
- say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
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
10
  on(KAMAL.hosts) do
@@ -38,8 +38,17 @@ class Kamal::Cli::App < Kamal::Cli::Base
38
38
  roles = KAMAL.roles_on(host)
39
39
 
40
40
  roles.each do |role|
41
+ app = KAMAL.app(role: role, host: host)
41
42
  execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
42
- execute *KAMAL.app(role: role, host: host).start, raise_on_non_zero_exit: false
43
+ execute *app.start, raise_on_non_zero_exit: false
44
+
45
+ if role.running_proxy?
46
+ version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
47
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
48
+ raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
49
+
50
+ execute *app.deploy(target: endpoint)
51
+ end
43
52
  end
44
53
  end
45
54
  end
@@ -52,8 +61,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
52
61
  roles = KAMAL.roles_on(host)
53
62
 
54
63
  roles.each do |role|
64
+ app = KAMAL.app(role: role, host: host)
55
65
  execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
56
- execute *KAMAL.app(role: role, host: host).stop, raise_on_non_zero_exit: false
66
+
67
+ if role.running_proxy?
68
+ version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
69
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
70
+ if endpoint.present?
71
+ execute *app.remove(target: endpoint), raise_on_non_zero_exit: false
72
+ end
73
+ end
74
+
75
+ execute *app.stop, raise_on_non_zero_exit: false
57
76
  end
58
77
  end
59
78
  end
@@ -71,11 +90,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
71
90
  end
72
91
  end
73
92
 
74
- desc "exec [CMD]", "Execute a custom command on servers within the app container (use --help to show options)"
93
+ desc "exec [CMD...]", "Execute a custom command on servers within the app container (use --help to show options)"
75
94
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
76
95
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
77
96
  option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
78
- def exec(cmd)
97
+ def exec(*cmd)
98
+ cmd = Kamal::Utils.join_commands(cmd)
79
99
  env = options[:env]
80
100
  case
81
101
  when options[:interactive] && options[:reuse]
@@ -211,6 +231,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
211
231
  stop
212
232
  remove_containers
213
233
  remove_images
234
+ remove_app_directory
214
235
  end
215
236
  end
216
237
 
@@ -252,6 +273,20 @@ class Kamal::Cli::App < Kamal::Cli::Base
252
273
  end
253
274
  end
254
275
 
276
+ desc "remove_app_directory", "Remove the service directory from servers", hide: true
277
+ def remove_app_directory
278
+ with_lock do
279
+ on(KAMAL.hosts) do |host|
280
+ roles = KAMAL.roles_on(host)
281
+
282
+ roles.each do |role|
283
+ execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory} on all servers", role: role), verbosity: :debug
284
+ execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
285
+ end
286
+ end
287
+ end
288
+ end
289
+
255
290
  desc "version", "Show app version currently running on servers"
256
291
  def version
257
292
  on(KAMAL.hosts) do |host|
@@ -1,12 +1,12 @@
1
1
  require "thor"
2
- require "dotenv"
3
2
  require "kamal/sshkit_with_ext"
4
3
 
5
4
  module Kamal::Cli
6
5
  class Base < Thor
7
6
  include SSHKit::DSL
8
7
 
9
- def self.exit_on_failure?() true end
8
+ def self.exit_on_failure?() false end
9
+ def self.dynamic_command_class() Kamal::Cli::Alias::Command end
10
10
 
11
11
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
12
12
  class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
@@ -22,55 +22,23 @@ module Kamal::Cli
22
22
 
23
23
  class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
24
24
 
25
- def initialize(*)
26
- super
27
- @original_env = ENV.to_h.dup
28
- load_env
29
- initialize_commander(options_with_subcommand_class_options)
25
+ def initialize(args = [], local_options = {}, config = {})
26
+ if config[:current_command].is_a?(Kamal::Cli::Alias::Command)
27
+ # When Thor generates a dynamic command, it doesn't attempt to parse the arguments.
28
+ # For our purposes, it means the arguments are passed in args rather than local_options.
29
+ super([], args, config)
30
+ else
31
+ super
32
+ end
33
+ initialize_commander unless KAMAL.configured?
30
34
  end
31
35
 
32
36
  private
33
- def reload_env
34
- reset_env
35
- load_env
36
- end
37
-
38
- def load_env
39
- if destination = options[:destination]
40
- Dotenv.load(".env.#{destination}", ".env")
41
- else
42
- Dotenv.load(".env")
43
- end
44
- end
45
-
46
- def reset_env
47
- replace_env @original_env
48
- end
49
-
50
- def replace_env(env)
51
- ENV.clear
52
- ENV.update(env)
53
- end
54
-
55
- def with_original_env
56
- keeping_current_env do
57
- reset_env
58
- yield
59
- end
60
- end
61
-
62
- def keeping_current_env
63
- current_env = ENV.to_h.dup
64
- yield
65
- ensure
66
- replace_env(current_env)
67
- end
68
-
69
37
  def options_with_subcommand_class_options
70
38
  options.merge(@_initializer.last[:class_options] || {})
71
39
  end
72
40
 
73
- def initialize_commander(options)
41
+ def initialize_commander
74
42
  KAMAL.tap do |commander|
75
43
  if options[:verbose]
76
44
  ENV["VERBOSE"] = "1" # For backtraces via cli/start
@@ -105,8 +73,6 @@ module Kamal::Cli
105
73
  if KAMAL.holding_lock?
106
74
  yield
107
75
  else
108
- ensure_run_and_locks_directory
109
-
110
76
  acquire_lock
111
77
 
112
78
  begin
@@ -135,6 +101,8 @@ module Kamal::Cli
135
101
  end
136
102
 
137
103
  def acquire_lock
104
+ ensure_run_directory
105
+
138
106
  raise_if_locked do
139
107
  say "Acquiring the deploy lock...", :magenta
140
108
  on(KAMAL.primary_host) { execute *KAMAL.lock.acquire("Automatic deploy lock", KAMAL.config.version), verbosity: :debug }
@@ -206,14 +174,14 @@ module Kamal::Cli
206
174
  instance_variable_get("@_invocations").first
207
175
  end
208
176
 
209
- def ensure_run_and_locks_directory
177
+ def reset_invocation(cli_class)
178
+ instance_variable_get("@_invocations")[cli_class].pop
179
+ end
180
+
181
+ def ensure_run_directory
210
182
  on(KAMAL.hosts) do
211
183
  execute(*KAMAL.server.ensure_run_directory)
212
184
  end
213
-
214
- on(KAMAL.primary_host) do
215
- execute(*KAMAL.lock.ensure_locks_directory)
216
- end
217
185
  end
218
186
  end
219
187
  end
@@ -30,29 +30,28 @@ class Kamal::Cli::Build < Kamal::Cli::Base
30
30
  say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
31
31
  end
32
32
 
33
- # Get the command here to ensure the Dir.chdir doesn't interfere with it
34
- push = KAMAL.builder.push
35
-
36
33
  run_locally do
37
34
  begin
38
- context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
39
-
40
- if context_hosts != KAMAL.builder.config_context_hosts
41
- warn "Context hosts have changed, so re-creating builder, was: #{context_hosts.join(", ")}], now: #{KAMAL.builder.config_context_hosts.join(", ")}"
42
- cli.remove
43
- cli.create
44
- end
35
+ execute *KAMAL.builder.inspect_builder
45
36
  rescue SSHKit::Command::Failed => e
46
- if e.message =~ /(context not found|no builder|does not exist)/
37
+ if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
47
38
  warn "Missing compatible builder, so creating a new one first"
39
+ begin
40
+ cli.remove
41
+ rescue SSHKit::Command::Failed
42
+ raise unless e.message =~ /(context not found|no builder|does not exist)/
43
+ end
48
44
  cli.create
49
45
  else
50
46
  raise
51
47
  end
52
48
  end
53
49
 
50
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
51
+ push = KAMAL.builder.push
52
+
54
53
  KAMAL.with_verbosity(:debug) do
55
- Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
54
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.config.builder.secrets }
56
55
  end
57
56
  end
58
57
  end
@@ -72,7 +71,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
72
71
 
73
72
  desc "create", "Create a build setup"
74
73
  def create
75
- if (remote_host = KAMAL.config.builder.remote_host)
74
+ if (remote_host = KAMAL.config.builder.remote)
76
75
  connect_to_remote_host(remote_host)
77
76
  end
78
77
 
@@ -1,3 +1,5 @@
1
+ require "concurrent/ivar"
2
+
1
3
  class Kamal::Cli::Healthcheck::Barrier
2
4
  def initialize
3
5
  @ivar = Concurrent::IVar.new
@@ -1,51 +1,30 @@
1
1
  module Kamal::Cli::Healthcheck::Poller
2
2
  extend self
3
3
 
4
- TRAEFIK_UPDATE_DELAY = 5
5
-
6
-
7
- def wait_for_healthy(pause_after_ready: false, &block)
4
+ def wait_for_healthy(role, &block)
8
5
  attempt = 1
9
- max_attempts = KAMAL.config.healthcheck.max_attempts
6
+ timeout_at = Time.now + KAMAL.config.deploy_timeout
7
+ readiness_delay = KAMAL.config.readiness_delay
10
8
 
11
9
  begin
12
- case status = block.call
13
- when "healthy"
14
- sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
15
- when "running" # No health check configured
16
- sleep KAMAL.config.readiness_delay if pause_after_ready
17
- else
18
- raise Kamal::Cli::Healthcheck::Error, "container not ready (#{status})"
19
- end
20
- rescue Kamal::Cli::Healthcheck::Error => e
21
- if attempt <= max_attempts
22
- info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
23
- sleep attempt
24
- attempt += 1
25
- retry
26
- else
27
- raise
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
28
19
  end
29
- end
30
20
 
31
- info "Container is healthy!"
32
- end
33
-
34
- def wait_for_unhealthy(pause_after_ready: false, &block)
35
- attempt = 1
36
- max_attempts = KAMAL.config.healthcheck.max_attempts
37
-
38
- begin
39
- case status = block.call
40
- when "unhealthy"
41
- sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
42
- else
43
- raise Kamal::Cli::Healthcheck::Error, "container not unhealthy (#{status})"
21
+ unless %w[ running healthy ].include?(status)
22
+ raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
44
23
  end
45
24
  rescue Kamal::Cli::Healthcheck::Error => e
46
- if attempt <= max_attempts
47
- info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
48
- sleep attempt
25
+ time_left = timeout_at - Time.now
26
+ if time_left > 0
27
+ sleep [ attempt, time_left ].min
49
28
  attempt += 1
50
29
  retry
51
30
  else
@@ -53,7 +32,7 @@ module Kamal::Cli::Healthcheck::Poller
53
32
  end
54
33
  end
55
34
 
56
- info "Container is unhealthy!"
35
+ info "Container is healthy!"
57
36
  end
58
37
 
59
38
  private
@@ -3,7 +3,6 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
3
3
  def status
4
4
  handle_missing_lock do
5
5
  on(KAMAL.primary_host) do
6
- execute *KAMAL.server.ensure_run_directory
7
6
  puts capture_with_debug(*KAMAL.lock.status)
8
7
  end
9
8
  end
@@ -13,9 +12,10 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
13
12
  option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
14
13
  def acquire
15
14
  message = options[:message]
15
+ ensure_run_directory
16
+
16
17
  raise_if_locked do
17
18
  on(KAMAL.primary_host) do
18
- execute *KAMAL.server.ensure_run_directory
19
19
  execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
20
20
  end
21
21
  say "Acquired the deploy lock"
@@ -26,7 +26,6 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
26
26
  def release
27
27
  handle_missing_lock do
28
28
  on(KAMAL.primary_host) do
29
- execute *KAMAL.server.ensure_run_directory
30
29
  execute *KAMAL.lock.release, verbosity: :debug
31
30
  end
32
31
  say "Released the deploy lock"