kamal 1.5.1 → 1.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +27 -22
  3. data/lib/kamal/cli/app/boot.rb +70 -18
  4. data/lib/kamal/cli/app/prepare_assets.rb +1 -1
  5. data/lib/kamal/cli/app.rb +57 -47
  6. data/lib/kamal/cli/base.rb +26 -28
  7. data/lib/kamal/cli/build/clone.rb +61 -0
  8. data/lib/kamal/cli/build.rb +58 -50
  9. data/lib/kamal/cli/env.rb +5 -5
  10. data/lib/kamal/cli/healthcheck/barrier.rb +31 -0
  11. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  12. data/lib/kamal/cli/healthcheck/poller.rb +4 -5
  13. data/lib/kamal/cli/main.rb +36 -43
  14. data/lib/kamal/cli/prune.rb +3 -3
  15. data/lib/kamal/cli/server.rb +39 -15
  16. data/lib/kamal/cli/traefik.rb +8 -8
  17. data/lib/kamal/commander/specifics.rb +1 -1
  18. data/lib/kamal/commander.rb +6 -6
  19. data/lib/kamal/commands/app/containers.rb +8 -0
  20. data/lib/kamal/commands/app/execution.rb +3 -3
  21. data/lib/kamal/commands/app/logging.rb +2 -2
  22. data/lib/kamal/commands/app.rb +6 -5
  23. data/lib/kamal/commands/base.rb +2 -3
  24. data/lib/kamal/commands/builder/base.rb +6 -12
  25. data/lib/kamal/commands/builder/clone.rb +28 -0
  26. data/lib/kamal/commands/builder/multiarch.rb +9 -9
  27. data/lib/kamal/commands/builder/native/cached.rb +6 -7
  28. data/lib/kamal/commands/builder/native/remote.rb +9 -9
  29. data/lib/kamal/commands/builder/native.rb +6 -7
  30. data/lib/kamal/commands/builder.rb +2 -0
  31. data/lib/kamal/configuration/builder.rb +33 -2
  32. data/lib/kamal/configuration/env/tag.rb +12 -0
  33. data/lib/kamal/configuration/env.rb +1 -1
  34. data/lib/kamal/configuration/role.rb +29 -6
  35. data/lib/kamal/configuration.rb +14 -2
  36. data/lib/kamal/git.rb +4 -0
  37. data/lib/kamal/sshkit_with_ext.rb +36 -0
  38. data/lib/kamal/version.rb +1 -1
  39. metadata +18 -10
  40. data/lib/kamal/cli/healthcheck.rb +0 -21
  41. data/lib/kamal/commands/app.rb.orig +0 -127
  42. data/lib/kamal/commands/healthcheck.rb +0 -59
@@ -5,39 +5,50 @@ class Kamal::Cli::Build < Kamal::Cli::Base
5
5
 
6
6
  desc "deliver", "Build app and push app image to registry then pull image on servers"
7
7
  def deliver
8
- mutating do
9
- push
10
- pull
11
- end
8
+ push
9
+ pull
12
10
  end
13
11
 
14
12
  desc "push", "Build and push app image to registry"
15
13
  def push
16
- mutating do
17
- cli = self
14
+ cli = self
15
+
16
+ verify_local_dependencies
17
+ run_hook "pre-build"
18
18
 
19
- verify_local_dependencies
20
- run_hook "pre-build"
19
+ uncommitted_changes = Kamal::Git.uncommitted_changes
21
20
 
22
- if (uncommitted_changes = Kamal::Git.uncommitted_changes).present?
23
- say "The following paths have uncommitted changes:\n #{uncommitted_changes}", :yellow
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
24
  end
25
25
 
26
26
  run_locally do
27
- begin
28
- KAMAL.with_verbosity(:debug) do
29
- execute *KAMAL.builder.push
30
- end
31
- rescue SSHKit::Command::Failed => e
32
- if e.message =~ /(no builder)|(no such file or directory)/
33
- error "Missing compatible builder, so creating a new one first"
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
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
34
+ push = KAMAL.builder.push
35
+
36
+ run_locally do
37
+ begin
38
+ KAMAL.with_verbosity(:debug) do
39
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
40
+ end
41
+ rescue SSHKit::Command::Failed => e
42
+ if e.message =~ /(no builder)|(no such file or directory)/
43
+ warn "Missing compatible builder, so creating a new one first"
34
44
 
35
- if cli.create
36
- KAMAL.with_verbosity(:debug) { execute *KAMAL.builder.push }
45
+ if cli.create
46
+ KAMAL.with_verbosity(:debug) do
47
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
37
48
  end
38
- else
39
- raise
40
49
  end
50
+ else
51
+ raise
41
52
  end
42
53
  end
43
54
  end
@@ -45,34 +56,30 @@ class Kamal::Cli::Build < Kamal::Cli::Base
45
56
 
46
57
  desc "pull", "Pull app image from registry onto servers"
47
58
  def pull
48
- mutating do
49
- on(KAMAL.hosts) do
50
- execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
51
- execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
52
- execute *KAMAL.builder.pull
53
- execute *KAMAL.builder.validate_image
54
- end
59
+ on(KAMAL.hosts) do
60
+ execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
61
+ execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
62
+ execute *KAMAL.builder.pull
63
+ execute *KAMAL.builder.validate_image
55
64
  end
56
65
  end
57
66
 
58
67
  desc "create", "Create a build setup"
59
68
  def create
60
- mutating do
61
- if (remote_host = KAMAL.config.builder.remote_host)
62
- connect_to_remote_host(remote_host)
63
- end
69
+ if (remote_host = KAMAL.config.builder.remote_host)
70
+ connect_to_remote_host(remote_host)
71
+ end
64
72
 
65
- run_locally do
66
- begin
67
- debug "Using builder: #{KAMAL.builder.name}"
68
- execute *KAMAL.builder.create
69
- rescue SSHKit::Command::Failed => e
70
- if e.message =~ /stderr=(.*)/
71
- error "Couldn't create remote builder: #{$1}"
72
- false
73
- else
74
- raise
75
- end
73
+ run_locally do
74
+ begin
75
+ debug "Using builder: #{KAMAL.builder.name}"
76
+ execute *KAMAL.builder.create
77
+ rescue SSHKit::Command::Failed => e
78
+ if e.message =~ /stderr=(.*)/
79
+ error "Couldn't create remote builder: #{$1}"
80
+ false
81
+ else
82
+ raise
76
83
  end
77
84
  end
78
85
  end
@@ -80,11 +87,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
80
87
 
81
88
  desc "remove", "Remove build setup"
82
89
  def remove
83
- mutating do
84
- run_locally do
85
- debug "Using builder: #{KAMAL.builder.name}"
86
- execute *KAMAL.builder.remove
87
- end
90
+ run_locally do
91
+ debug "Using builder: #{KAMAL.builder.name}"
92
+ execute *KAMAL.builder.remove
88
93
  end
89
94
  end
90
95
 
@@ -114,8 +119,11 @@ class Kamal::Cli::Build < Kamal::Cli::Base
114
119
  def connect_to_remote_host(remote_host)
115
120
  remote_uri = URI.parse(remote_host)
116
121
  if remote_uri.scheme == "ssh"
117
- options = { user: remote_uri.user, port: remote_uri.port }.compact
118
- on(remote_uri.host, options) do
122
+ host = SSHKit::Host.new(
123
+ hostname: remote_uri.host,
124
+ ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
125
+ )
126
+ on(host, options) do
119
127
  execute "true"
120
128
  end
121
129
  end
data/lib/kamal/cli/env.rb CHANGED
@@ -3,13 +3,13 @@ require "tempfile"
3
3
  class Kamal::Cli::Env < Kamal::Cli::Base
4
4
  desc "push", "Push the env file to the remote hosts"
5
5
  def push
6
- mutating do
6
+ with_lock do
7
7
  on(KAMAL.hosts) do
8
8
  execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
9
9
 
10
10
  KAMAL.roles_on(host).each do |role|
11
- execute *KAMAL.app(role: role).make_env_directory
12
- upload! role.env.secrets_io, role.env.secrets_file, mode: 400
11
+ execute *KAMAL.app(role: role, host: host).make_env_directory
12
+ upload! role.env(host).secrets_io, role.env(host).secrets_file, mode: 400
13
13
  end
14
14
  end
15
15
 
@@ -30,12 +30,12 @@ class Kamal::Cli::Env < Kamal::Cli::Base
30
30
 
31
31
  desc "delete", "Delete the env file from the remote hosts"
32
32
  def delete
33
- mutating do
33
+ with_lock do
34
34
  on(KAMAL.hosts) do
35
35
  execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
36
36
 
37
37
  KAMAL.roles_on(host).each do |role|
38
- execute *KAMAL.app(role: role).remove_env_file
38
+ execute *KAMAL.app(role: role, host: host).remove_env_file
39
39
  end
40
40
  end
41
41
 
@@ -0,0 +1,31 @@
1
+ class Kamal::Cli::Healthcheck::Barrier
2
+ def initialize
3
+ @ivar = Concurrent::IVar.new
4
+ end
5
+
6
+ def close
7
+ set(false)
8
+ end
9
+
10
+ def open
11
+ set(true)
12
+ end
13
+
14
+ def wait
15
+ unless opened?
16
+ raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
17
+ end
18
+ end
19
+
20
+ private
21
+ def opened?
22
+ @ivar.value
23
+ end
24
+
25
+ def set(value)
26
+ @ivar.set(value)
27
+ true
28
+ rescue Concurrent::MultipleAssignmentError
29
+ false
30
+ end
31
+ end
@@ -0,0 +1,2 @@
1
+ class Kamal::Cli::Healthcheck::Error < StandardError
2
+ end
@@ -3,7 +3,6 @@ module Kamal::Cli::Healthcheck::Poller
3
3
 
4
4
  TRAEFIK_UPDATE_DELAY = 5
5
5
 
6
- class HealthcheckError < StandardError; end
7
6
 
8
7
  def wait_for_healthy(pause_after_ready: false, &block)
9
8
  attempt = 1
@@ -16,9 +15,9 @@ module Kamal::Cli::Healthcheck::Poller
16
15
  when "running" # No health check configured
17
16
  sleep KAMAL.config.readiness_delay if pause_after_ready
18
17
  else
19
- raise HealthcheckError, "container not ready (#{status})"
18
+ raise Kamal::Cli::Healthcheck::Error, "container not ready (#{status})"
20
19
  end
21
- rescue HealthcheckError => e
20
+ rescue Kamal::Cli::Healthcheck::Error => e
22
21
  if attempt <= max_attempts
23
22
  info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
24
23
  sleep attempt
@@ -41,9 +40,9 @@ module Kamal::Cli::Healthcheck::Poller
41
40
  when "unhealthy"
42
41
  sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
43
42
  else
44
- raise HealthcheckError, "container not unhealthy (#{status})"
43
+ raise Kamal::Cli::Healthcheck::Error, "container not unhealthy (#{status})"
45
44
  end
46
- rescue HealthcheckError => e
45
+ rescue Kamal::Cli::Healthcheck::Error => e
47
46
  if attempt <= max_attempts
48
47
  info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
49
48
  sleep attempt
@@ -3,14 +3,14 @@ class Kamal::Cli::Main < Kamal::Cli::Base
3
3
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
4
4
  def setup
5
5
  print_runtime do
6
- mutating do
6
+ with_lock do
7
7
  invoke_options = deploy_options
8
8
 
9
9
  say "Ensure Docker is installed...", :magenta
10
10
  invoke "kamal:cli:server:bootstrap", [], invoke_options
11
11
 
12
- say "Push env files...", :magenta
13
- invoke "kamal:cli:env:push", [], invoke_options
12
+ say "Evaluate and push env files...", :magenta
13
+ invoke "kamal:cli:main:envify", [], invoke_options
14
14
 
15
15
  invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
16
16
  deploy
@@ -22,30 +22,25 @@ class Kamal::Cli::Main < Kamal::Cli::Base
22
22
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
23
23
  def deploy
24
24
  runtime = print_runtime do
25
- mutating do
26
- invoke_options = deploy_options
25
+ invoke_options = deploy_options
27
26
 
28
- say "Log into image registry...", :magenta
29
- invoke "kamal:cli:registry:login", [], invoke_options
27
+ say "Log into image registry...", :magenta
28
+ invoke "kamal:cli:registry:login", [], invoke_options
30
29
 
31
- if options[:skip_push]
32
- say "Pull app image...", :magenta
33
- invoke "kamal:cli:build:pull", [], invoke_options
34
- else
35
- say "Build and push app image...", :magenta
36
- invoke "kamal:cli:build:deliver", [], invoke_options
37
- end
30
+ if options[:skip_push]
31
+ say "Pull app image...", :magenta
32
+ invoke "kamal:cli:build:pull", [], invoke_options
33
+ else
34
+ say "Build and push app image...", :magenta
35
+ invoke "kamal:cli:build:deliver", [], invoke_options
36
+ end
38
37
 
38
+ with_lock do
39
39
  run_hook "pre-deploy"
40
40
 
41
41
  say "Ensure Traefik is running...", :magenta
42
42
  invoke "kamal:cli:traefik:boot", [], invoke_options
43
43
 
44
- if KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
45
- say "Ensure app can pass healthcheck...", :magenta
46
- invoke "kamal:cli:healthcheck:perform", [], invoke_options
47
- end
48
-
49
44
  say "Detect stale containers...", :magenta
50
45
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
51
46
 
@@ -63,22 +58,19 @@ class Kamal::Cli::Main < Kamal::Cli::Base
63
58
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
64
59
  def redeploy
65
60
  runtime = print_runtime do
66
- mutating do
67
- invoke_options = deploy_options
61
+ invoke_options = deploy_options
68
62
 
69
- if options[:skip_push]
70
- say "Pull app image...", :magenta
71
- invoke "kamal:cli:build:pull", [], invoke_options
72
- else
73
- say "Build and push app image...", :magenta
74
- invoke "kamal:cli:build:deliver", [], invoke_options
75
- end
63
+ if options[:skip_push]
64
+ say "Pull app image...", :magenta
65
+ invoke "kamal:cli:build:pull", [], invoke_options
66
+ else
67
+ say "Build and push app image...", :magenta
68
+ invoke "kamal:cli:build:deliver", [], invoke_options
69
+ end
76
70
 
71
+ with_lock do
77
72
  run_hook "pre-deploy"
78
73
 
79
- say "Ensure app can pass healthcheck...", :magenta
80
- invoke "kamal:cli:healthcheck:perform", [], invoke_options
81
-
82
74
  say "Detect stale containers...", :magenta
83
75
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
84
76
 
@@ -93,7 +85,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
93
85
  def rollback(version)
94
86
  rolled_back = false
95
87
  runtime = print_runtime do
96
- mutating do
88
+ with_lock do
97
89
  invoke_options = deploy_options
98
90
 
99
91
  KAMAL.config.version = version
@@ -185,19 +177,23 @@ class Kamal::Cli::Main < Kamal::Cli::Base
185
177
  env_path = ".env"
186
178
  end
187
179
 
188
- File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
180
+ if Pathname.new(File.expand_path(env_template_path)).exist?
181
+ File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
189
182
 
190
- unless options[:skip_push]
191
- reload_envs
192
- invoke "kamal:cli:env:push", options
183
+ unless options[:skip_push]
184
+ reload_envs
185
+ invoke "kamal:cli:env:push", options
186
+ end
187
+ else
188
+ puts "Skipping envify (no #{env_template_path} exist)"
193
189
  end
194
190
  end
195
191
 
196
192
  desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
197
193
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
198
194
  def remove
199
- mutating do
200
- confirming "This will remove all containers and images. Are you sure?" do
195
+ confirming "This will remove all containers and images. Are you sure?" do
196
+ with_lock do
201
197
  invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
202
198
  invoke "kamal:cli:app:remove", [], options.without(:confirmed)
203
199
  invoke "kamal:cli:accessory:remove", [ "all" ], options
@@ -223,9 +219,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
223
219
  desc "env", "Manage environment files"
224
220
  subcommand "env", Kamal::Cli::Env
225
221
 
226
- desc "healthcheck", "Healthcheck application"
227
- subcommand "healthcheck", Kamal::Cli::Healthcheck
228
-
229
222
  desc "lock", "Manage the deploy lock"
230
223
  subcommand "lock", Kamal::Cli::Lock
231
224
 
@@ -246,11 +239,11 @@ class Kamal::Cli::Main < Kamal::Cli::Base
246
239
  begin
247
240
  on(KAMAL.hosts) do
248
241
  KAMAL.roles_on(host).each do |role|
249
- container_id = capture_with_info(*KAMAL.app(role: role).container_id_for_version(version))
242
+ container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
250
243
  raise "Container not found" unless container_id.present?
251
244
  end
252
245
  end
253
- rescue SSHKit::Runner::ExecuteError => e
246
+ rescue SSHKit::Runner::ExecuteError, SSHKit::Runner::MultipleExecuteError => e
254
247
  if e.message =~ /Container not found/
255
248
  say "Error looking for container version #{version}: #{e.message}"
256
249
  return false
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::Prune < Kamal::Cli::Base
2
2
  desc "all", "Prune unused images and stopped containers"
3
3
  def all
4
- mutating do
4
+ with_lock do
5
5
  containers
6
6
  images
7
7
  end
@@ -9,7 +9,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
9
9
 
10
10
  desc "images", "Prune unused images"
11
11
  def images
12
- mutating do
12
+ with_lock do
13
13
  on(KAMAL.hosts) do
14
14
  execute *KAMAL.auditor.record("Pruned images"), verbosity: :debug
15
15
  execute *KAMAL.prune.dangling_images
@@ -24,7 +24,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
24
24
  retain = options.fetch(:retain, KAMAL.config.retain_containers)
25
25
  raise "retain must be at least 1" if retain < 1
26
26
 
27
- mutating do
27
+ with_lock do
28
28
  on(KAMAL.hosts) do
29
29
  execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
30
30
  execute *KAMAL.prune.app_containers(retain: retain)
@@ -1,25 +1,49 @@
1
1
  class Kamal::Cli::Server < Kamal::Cli::Base
2
+ desc "exec", "Run a custom command on the server (use --help to show options)"
3
+ option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
+ def exec(cmd)
5
+ hosts = KAMAL.hosts | KAMAL.accessory_hosts
6
+
7
+ case
8
+ when options[:interactive]
9
+ host = KAMAL.primary_host
10
+
11
+ say "Running '#{cmd}' on #{host} interactively...", :magenta
12
+
13
+ run_locally { exec KAMAL.server.run_over_ssh(cmd, host: host) }
14
+ else
15
+ say "Running '#{cmd}' on #{hosts.join(', ')}...", :magenta
16
+
17
+ on(hosts) do |host|
18
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
19
+ puts_by_host host, capture_with_info(cmd)
20
+ end
21
+ end
22
+ end
23
+
2
24
  desc "bootstrap", "Set up Docker to run Kamal apps"
3
25
  def bootstrap
4
- missing = []
5
-
6
- on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
7
- unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
8
- if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
9
- info "Missing Docker on #{host}. Installing…"
10
- execute *KAMAL.docker.install
11
- else
12
- missing << host
26
+ with_lock do
27
+ missing = []
28
+
29
+ on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
30
+ unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
31
+ if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
32
+ info "Missing Docker on #{host}. Installing…"
33
+ execute *KAMAL.docker.install
34
+ else
35
+ missing << host
36
+ end
13
37
  end
38
+
39
+ execute(*KAMAL.server.ensure_run_directory)
14
40
  end
15
41
 
16
- execute(*KAMAL.server.ensure_run_directory)
17
- end
42
+ if missing.any?
43
+ raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
44
+ end
18
45
 
19
- if missing.any?
20
- raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
46
+ run_hook "docker-setup"
21
47
  end
22
-
23
- run_hook "docker-setup"
24
48
  end
25
49
  end
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::Traefik < Kamal::Cli::Base
2
2
  desc "boot", "Boot Traefik on servers"
3
3
  def boot
4
- mutating do
4
+ with_lock do
5
5
  on(KAMAL.traefik_hosts) do
6
6
  execute *KAMAL.registry.login
7
7
  execute *KAMAL.traefik.start_or_run
@@ -14,7 +14,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
14
14
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
15
15
  def reboot
16
16
  confirming "This will cause a brief outage on each host. Are you sure?" do
17
- mutating do
17
+ with_lock do
18
18
  host_groups = options[:rolling] ? KAMAL.traefik_hosts : [ KAMAL.traefik_hosts ]
19
19
  host_groups.each do |hosts|
20
20
  host_list = Array(hosts).join(",")
@@ -34,7 +34,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
34
34
 
35
35
  desc "start", "Start existing Traefik container on servers"
36
36
  def start
37
- mutating do
37
+ with_lock do
38
38
  on(KAMAL.traefik_hosts) do
39
39
  execute *KAMAL.auditor.record("Started traefik"), verbosity: :debug
40
40
  execute *KAMAL.traefik.start
@@ -44,7 +44,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
44
44
 
45
45
  desc "stop", "Stop existing Traefik container on servers"
46
46
  def stop
47
- mutating do
47
+ with_lock do
48
48
  on(KAMAL.traefik_hosts) do
49
49
  execute *KAMAL.auditor.record("Stopped traefik"), verbosity: :debug
50
50
  execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
@@ -54,7 +54,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
54
54
 
55
55
  desc "restart", "Restart existing Traefik container on servers"
56
56
  def restart
57
- mutating do
57
+ with_lock do
58
58
  stop
59
59
  start
60
60
  end
@@ -91,7 +91,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
91
91
 
92
92
  desc "remove", "Remove Traefik container and image from servers"
93
93
  def remove
94
- mutating do
94
+ with_lock do
95
95
  stop
96
96
  remove_container
97
97
  remove_image
@@ -100,7 +100,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
100
100
 
101
101
  desc "remove_container", "Remove Traefik container from servers", hide: true
102
102
  def remove_container
103
- mutating do
103
+ with_lock do
104
104
  on(KAMAL.traefik_hosts) do
105
105
  execute *KAMAL.auditor.record("Removed traefik container"), verbosity: :debug
106
106
  execute *KAMAL.traefik.remove_container
@@ -110,7 +110,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
110
110
 
111
111
  desc "remove_image", "Remove Traefik image from servers", hide: true
112
112
  def remove_image
113
- mutating do
113
+ with_lock do
114
114
  on(KAMAL.traefik_hosts) do
115
115
  execute *KAMAL.auditor.record("Removed traefik image"), verbosity: :debug
116
116
  execute *KAMAL.traefik.remove_image
@@ -19,7 +19,7 @@ class Kamal::Commander::Specifics
19
19
  end
20
20
 
21
21
  def traefik_hosts
22
- specific_hosts || config.traefik_hosts
22
+ config.traefik_hosts & specified_hosts
23
23
  end
24
24
 
25
25
  def accessory_hosts
@@ -2,13 +2,13 @@ require "active_support/core_ext/enumerable"
2
2
  require "active_support/core_ext/module/delegation"
3
3
 
4
4
  class Kamal::Commander
5
- attr_accessor :verbosity, :holding_lock, :hold_lock_on_error
5
+ attr_accessor :verbosity, :holding_lock, :connected
6
6
  delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :traefik_hosts, :accessory_hosts, to: :specifics
7
7
 
8
8
  def initialize
9
9
  self.verbosity = :info
10
10
  self.holding_lock = false
11
- self.hold_lock_on_error = false
11
+ self.connected = false
12
12
  @specifics = nil
13
13
  end
14
14
 
@@ -65,8 +65,8 @@ class Kamal::Commander
65
65
  end
66
66
 
67
67
 
68
- def app(role: nil)
69
- Kamal::Commands::App.new(config, role: role)
68
+ def app(role: nil, host: nil)
69
+ Kamal::Commands::App.new(config, role: role, host: host)
70
70
  end
71
71
 
72
72
  def accessory(name)
@@ -138,8 +138,8 @@ class Kamal::Commander
138
138
  self.holding_lock
139
139
  end
140
140
 
141
- def hold_lock_on_error?
142
- self.hold_lock_on_error
141
+ def connected?
142
+ self.connected
143
143
  end
144
144
 
145
145
  private
@@ -1,4 +1,6 @@
1
1
  module Kamal::Commands::App::Containers
2
+ DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
3
+
2
4
  def list_containers
3
5
  docker :container, :ls, "--all", *filter_args
4
6
  end
@@ -20,4 +22,10 @@ module Kamal::Commands::App::Containers
20
22
  def remove_containers
21
23
  docker :container, :prune, "--force", *filter_args
22
24
  end
25
+
26
+ def container_health_log(version:)
27
+ pipe \
28
+ container_id_for(container_name: container_name(version)),
29
+ xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
30
+ end
23
31
  end
@@ -11,7 +11,7 @@ module Kamal::Commands::App::Execution
11
11
  docker :run,
12
12
  ("-it" if interactive),
13
13
  "--rm",
14
- *role&.env_args,
14
+ *role&.env_args(host),
15
15
  *argumentize("--env", env),
16
16
  *config.volume_args,
17
17
  *role&.option_args,
@@ -19,11 +19,11 @@ module Kamal::Commands::App::Execution
19
19
  *command
20
20
  end
21
21
 
22
- def execute_in_existing_container_over_ssh(*command, host:, env:)
22
+ def execute_in_existing_container_over_ssh(*command, env:)
23
23
  run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
24
24
  end
25
25
 
26
- def execute_in_new_container_over_ssh(*command, host:, env:)
26
+ def execute_in_new_container_over_ssh(*command, env:)
27
27
  run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
28
28
  end
29
29
  end
@@ -1,7 +1,7 @@
1
1
  module Kamal::Commands::App::Logging
2
- def logs(since: nil, lines: nil, grep: nil)
2
+ def logs(version: nil, since: nil, lines: nil, grep: nil)
3
3
  pipe \
4
- current_running_container_id,
4
+ version ? container_id_for_version(version) : current_running_container_id,
5
5
  "xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
6
6
  ("grep '#{grep}'" if grep)
7
7
  end