kamal 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +3 -2
  3. data/lib/kamal/cli/app/boot.rb +67 -0
  4. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  5. data/lib/kamal/cli/app.rb +20 -61
  6. data/lib/kamal/cli/base.rb +21 -7
  7. data/lib/kamal/cli/env.rb +3 -3
  8. data/lib/kamal/cli/main.rb +1 -1
  9. data/lib/kamal/cli/templates/deploy.yml +1 -1
  10. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +0 -0
  11. data/lib/kamal/cli/traefik.rb +15 -12
  12. data/lib/kamal/commander/specifics.rb +49 -0
  13. data/lib/kamal/commander.rb +9 -33
  14. data/lib/kamal/commands/accessory.rb +2 -2
  15. data/lib/kamal/commands/app/assets.rb +4 -4
  16. data/lib/kamal/commands/app/cord.rb +2 -2
  17. data/lib/kamal/commands/app/execution.rb +8 -6
  18. data/lib/kamal/commands/app/images.rb +1 -1
  19. data/lib/kamal/commands/app.rb +29 -8
  20. data/lib/kamal/commands/auditor.rb +1 -1
  21. data/lib/kamal/commands/base.rb +5 -1
  22. data/lib/kamal/commands/builder/base.rb +14 -4
  23. data/lib/kamal/commands/builder/multiarch.rb +9 -9
  24. data/lib/kamal/commands/builder/native/cached.rb +7 -6
  25. data/lib/kamal/commands/builder/native/remote.rb +9 -9
  26. data/lib/kamal/commands/builder/native.rb +8 -7
  27. data/lib/kamal/commands/healthcheck.rb +0 -1
  28. data/lib/kamal/commands/hook.rb +1 -1
  29. data/lib/kamal/commands/lock.rb +19 -9
  30. data/lib/kamal/commands/prune.rb +2 -2
  31. data/lib/kamal/commands/server.rb +1 -1
  32. data/lib/kamal/commands/traefik.rb +8 -14
  33. data/lib/kamal/configuration/accessory.rb +9 -19
  34. data/lib/kamal/configuration/boot.rb +1 -1
  35. data/lib/kamal/configuration/builder.rb +7 -3
  36. data/lib/kamal/configuration/env.rb +40 -0
  37. data/lib/kamal/configuration/role.rb +12 -42
  38. data/lib/kamal/configuration.rb +20 -8
  39. data/lib/kamal/env_file.rb +12 -15
  40. data/lib/kamal/utils.rb +7 -3
  41. data/lib/kamal/version.rb +1 -1
  42. data/lib/kamal.rb +1 -1
  43. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df82a171edf966865ca45112056965b93b50a700c4ae190cebe791992c41a825
4
- data.tar.gz: 9c5290566c477597460910a80a6e0694285e6d9779b6897bc6c1c10f5bd23b7b
3
+ metadata.gz: d8429a6060103f16d9aadb517e77fbb32fc6c6d2e6b9b3cf2989c8a9309e0067
4
+ data.tar.gz: e997f3c5765639c4b14aad08a81344c5af182904e3d80f4867792ed55a9fc25b
5
5
  SHA512:
6
- metadata.gz: c2995cb335465be583037f0a23e2a5d3ce128b47e162b69d7666dbc3e313b542c1d10abef9fa2bb542bdb679003dc8993c3037c4738a9e8544fb346755e8ced0
7
- data.tar.gz: a0b7d5500f7ad5408409e0f655274e19a833a8158d2013e79a70e7209d87f5c350b715071d8981f9119f9b3d8c66033bfcd23b5290d3d3880511b3c98c7cff83
6
+ metadata.gz: 575353b6ea9e9d429a6c6e6b89013fd36f0b371b4edbffbf3ab33f707a9637191872d7463fb670dbe45f9a36bc7a1792335fa9c0709e31d2339b1549c4da32ff
7
+ data.tar.gz: 6a71437092b0ce21cce12a6d127098e2aba2a0b18846026b6df1c9bb1d33f60d27da3ac1c3b553490a2f4daa76a7ae2e725b4f2ee7918d4cc808bc29b556e8f3
@@ -177,7 +177,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
177
177
  if name == "all"
178
178
  KAMAL.accessory_names.each { |accessory_name| remove(accessory_name) }
179
179
  else
180
- if options[:confirmed] || ask("This will remove all containers, images and data directories for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
180
+ confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
181
181
  with_accessory(name) do
182
182
  stop(name)
183
183
  remove_container(name)
@@ -226,7 +226,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
226
226
 
227
227
  private
228
228
  def with_accessory(name)
229
- if accessory = KAMAL.accessory(name)
229
+ if KAMAL.config.accessory(name)
230
+ accessory = KAMAL.accessory(name)
230
231
  yield accessory, accessory_hosts(accessory)
231
232
  else
232
233
  error_on_missing_accessory(name)
@@ -0,0 +1,67 @@
1
+ class Kamal::Cli::App::Boot
2
+ attr_reader :host, :role, :version, :sshkit
3
+ delegate :execute, :capture_with_info, :info, to: :sshkit
4
+ delegate :uses_cord?, :assets?, to: :role
5
+
6
+ def initialize(host, role, version, sshkit)
7
+ @host = host
8
+ @role = role
9
+ @version = version
10
+ @sshkit = sshkit
11
+ end
12
+
13
+ def run
14
+ old_version = old_version_renamed_if_clashing
15
+
16
+ start_new_version
17
+
18
+ if old_version
19
+ stop_old_version(old_version)
20
+ end
21
+ end
22
+
23
+ private
24
+ def app
25
+ @app ||= KAMAL.app(role: role)
26
+ end
27
+
28
+ def auditor
29
+ @auditor = KAMAL.auditor(role: role)
30
+ end
31
+
32
+ def audit(message)
33
+ execute *auditor.record(message), verbosity: :debug
34
+ end
35
+
36
+ def old_version_renamed_if_clashing
37
+ if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
38
+ renamed_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
39
+ info "Renaming container #{version} to #{renamed_version} as already deployed on #{host}"
40
+ audit("Renaming container #{version} to #{renamed_version}")
41
+ execute *app.rename_container(version: version, new_version: renamed_version)
42
+ end
43
+
44
+ capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip.presence
45
+ end
46
+
47
+ def start_new_version
48
+ audit "Booted app version #{version}"
49
+ execute *app.tie_cord(role.cord_host_file) if uses_cord?
50
+ execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
51
+ Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
52
+ end
53
+
54
+ def stop_old_version(version)
55
+ if uses_cord?
56
+ cord = capture_with_info(*app.cord(version: version), raise_on_non_zero_exit: false).strip
57
+ if cord.present?
58
+ execute *app.cut_cord(cord)
59
+ Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
60
+ end
61
+ end
62
+
63
+ execute *app.stop(version: version), raise_on_non_zero_exit: false
64
+
65
+ execute *app.clean_up_assets if assets?
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ class Kamal::Cli::App::PrepareAssets
2
+ attr_reader :host, :role, :sshkit
3
+ delegate :execute, :capture_with_info, :info, to: :sshkit
4
+ delegate :assets?, to: :role
5
+
6
+ def initialize(host, role, sshkit)
7
+ @host = host
8
+ @role = role
9
+ @sshkit = sshkit
10
+ end
11
+
12
+ def run
13
+ if assets?
14
+ execute *app.extract_assets
15
+ old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
16
+ execute *app.sync_asset_volumes(old_version: old_version)
17
+ end
18
+ end
19
+
20
+ private
21
+ def app
22
+ @app ||= KAMAL.app(role: role)
23
+ end
24
+ end
data/lib/kamal/cli/app.rb CHANGED
@@ -7,58 +7,23 @@ class Kamal::Cli::App < Kamal::Cli::Base
7
7
  using_version(version_or_latest) do |version|
8
8
  say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
9
9
 
10
+ # Assets are prepared in a separate step to ensure they are on all hosts before booting
10
11
  on(KAMAL.hosts) do
11
- execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
12
- execute *KAMAL.app.tag_current_image_as_latest
13
-
14
12
  KAMAL.roles_on(host).each do |role|
15
- app = KAMAL.app(role: role)
16
-
17
- if role.assets?
18
- execute *app.extract_assets
19
- old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
20
- execute *app.sync_asset_volumes(old_version: old_version)
21
- end
13
+ Kamal::Cli::App::PrepareAssets.new(host, role, self).run
22
14
  end
23
15
  end
24
16
 
25
17
  on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
26
18
  KAMAL.roles_on(host).each do |role|
27
- app = KAMAL.app(role: role)
28
- auditor = KAMAL.auditor(role: role)
29
-
30
- if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
31
- tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
32
- info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
33
- execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
34
- execute *app.rename_container(version: version, new_version: tmp_version)
35
- end
36
-
37
- old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
38
-
39
- execute *app.tie_cord(role.cord_host_file) if role.uses_cord?
40
-
41
- execute *auditor.record("Booted app version #{version}"), verbosity: :debug
42
-
43
- execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
44
-
45
- Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
46
-
47
- if old_version.present?
48
- if role.uses_cord?
49
- cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
50
- if cord.present?
51
- execute *app.cut_cord(cord)
52
- Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: old_version)) }
53
- end
54
- end
55
-
56
- execute *app.stop(version: old_version), raise_on_non_zero_exit: false
57
-
58
- execute *app.clean_up_assets if role.assets?
59
- end
19
+ Kamal::Cli::App::Boot.new(host, role, version, self).run
60
20
  end
61
21
  end
22
+
23
+ on(KAMAL.hosts) do |host|
24
+ execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
25
+ execute *KAMAL.app.tag_latest_image
26
+ end
62
27
  end
63
28
  end
64
29
  end
@@ -107,13 +72,15 @@ class Kamal::Cli::App < Kamal::Cli::Base
107
72
  desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
108
73
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
109
74
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
75
+ option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
110
76
  def exec(cmd)
77
+ env = options[:env]
111
78
  case
112
79
  when options[:interactive] && options[:reuse]
113
80
  say "Get current version of running container...", :magenta unless options[:version]
114
81
  using_version(options[:version] || current_running_version) do |version|
115
82
  say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
116
- run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) }
83
+ run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host, env: env) }
117
84
  end
118
85
 
119
86
  when options[:interactive]
@@ -121,7 +88,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
121
88
  using_version(version_or_latest) do |version|
122
89
  say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
123
90
  run_locally do
124
- exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host)
91
+ exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host, env: env)
125
92
  end
126
93
  end
127
94
 
@@ -135,7 +102,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
135
102
 
136
103
  roles.each do |role|
137
104
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
138
- puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd))
105
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd, env: env))
139
106
  end
140
107
  end
141
108
  end
@@ -149,7 +116,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
149
116
 
150
117
  roles.each do |role|
151
118
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
152
- puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd))
119
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd, env: env))
153
120
  end
154
121
  end
155
122
  end
@@ -173,7 +140,10 @@ class Kamal::Cli::App < Kamal::Cli::Base
173
140
  roles = KAMAL.roles_on(host)
174
141
 
175
142
  roles.each do |role|
176
- cli.send(:stale_versions, host: host, role: role).each do |version|
143
+ versions = capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false).split("\n")
144
+ versions -= [ capture_with_info(*KAMAL.app(role: role).current_running_version, raise_on_non_zero_exit: false).strip ]
145
+
146
+ versions.each do |version|
177
147
  if stop
178
148
  puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
179
149
  execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false
@@ -207,7 +177,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
207
177
  run_locally do
208
178
  info "Following logs on #{KAMAL.primary_host}..."
209
179
 
210
- KAMAL.specific_roles ||= ["web"]
180
+ KAMAL.specific_roles ||= [ "web" ]
211
181
  role = KAMAL.roles_on(KAMAL.primary_host).first
212
182
 
213
183
  info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
@@ -309,18 +279,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
309
279
  version.presence
310
280
  end
311
281
 
312
- def stale_versions(host:, role:)
313
- versions = nil
314
- on(host) do
315
- versions = \
316
- capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false)
317
- .split("\n")
318
- .drop(1)
319
- end
320
- versions
321
- end
322
-
323
282
  def version_or_latest
324
- options[:version] || "latest"
283
+ options[:version] || KAMAL.config.latest_tag
325
284
  end
326
285
  end
@@ -73,7 +73,7 @@ module Kamal::Cli
73
73
  def print_runtime
74
74
  started_at = Time.now
75
75
  yield
76
- return Time.now - started_at
76
+ Time.now - started_at
77
77
  ensure
78
78
  runtime = Time.now - started_at
79
79
  puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
@@ -84,7 +84,7 @@ module Kamal::Cli
84
84
 
85
85
  run_hook "pre-connect"
86
86
 
87
- ensure_run_directory
87
+ ensure_run_and_locks_directory
88
88
 
89
89
  acquire_lock
90
90
 
@@ -103,6 +103,16 @@ module Kamal::Cli
103
103
  release_lock
104
104
  end
105
105
 
106
+ def confirming(question)
107
+ return yield if options[:confirmed]
108
+
109
+ if ask(question, limited_to: %w[ y N ], default: "N") == "y"
110
+ yield
111
+ else
112
+ say "Aborted", :red
113
+ end
114
+ end
115
+
106
116
  def acquire_lock
107
117
  raise_if_locked do
108
118
  say "Acquiring the deploy lock...", :magenta
@@ -147,9 +157,9 @@ module Kamal::Cli
147
157
 
148
158
  say "Running the #{hook} hook...", :magenta
149
159
  run_locally do
150
- KAMAL.with_verbosity(:debug) { execute *KAMAL.hook.run(hook, **details, **extra_details) }
151
- rescue SSHKit::Command::Failed
152
- raise HookError.new("Hook `#{hook}` failed")
160
+ execute *KAMAL.hook.run(hook, **details, **extra_details)
161
+ rescue SSHKit::Command::Failed => e
162
+ raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
153
163
  end
154
164
  end
155
165
  end
@@ -176,10 +186,14 @@ module Kamal::Cli
176
186
  instance_variable_get("@_invocations").first
177
187
  end
178
188
 
179
- def ensure_run_directory
189
+ def ensure_run_and_locks_directory
180
190
  on(KAMAL.hosts) do
181
191
  execute(*KAMAL.server.ensure_run_directory)
182
192
  end
193
+
194
+ on(KAMAL.primary_host) do
195
+ execute(*KAMAL.lock.ensure_locks_directory)
196
+ end
183
197
  end
184
- end
198
+ end
185
199
  end
data/lib/kamal/cli/env.rb CHANGED
@@ -9,20 +9,20 @@ class Kamal::Cli::Env < Kamal::Cli::Base
9
9
 
10
10
  KAMAL.roles_on(host).each do |role|
11
11
  execute *KAMAL.app(role: role).make_env_directory
12
- upload! StringIO.new(role.env_file), role.host_env_file_path, mode: 400
12
+ upload! role.env.secrets_io, role.env.secrets_file, mode: 400
13
13
  end
14
14
  end
15
15
 
16
16
  on(KAMAL.traefik_hosts) do
17
17
  execute *KAMAL.traefik.make_env_directory
18
- upload! StringIO.new(KAMAL.traefik.env_file), KAMAL.traefik.host_env_file_path, mode: 400
18
+ upload! KAMAL.traefik.env.secrets_io, KAMAL.traefik.env.secrets_file, mode: 400
19
19
  end
20
20
 
21
21
  on(KAMAL.accessory_hosts) do
22
22
  KAMAL.accessories_on(host).each do |accessory|
23
23
  accessory_config = KAMAL.config.accessory(accessory)
24
24
  execute *KAMAL.accessory(accessory).make_env_directory
25
- upload! StringIO.new(accessory_config.env_file), accessory_config.host_env_file_path, mode: 400
25
+ upload! accessory_config.env.secrets_io, accessory_config.env.secrets_file, mode: 400
26
26
  end
27
27
  end
28
28
  end
@@ -197,7 +197,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
197
197
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
198
198
  def remove
199
199
  mutating do
200
- if options[:confirmed] || ask("This will remove all containers and images. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
200
+ confirming "This will remove all containers and images. Are you sure?" do
201
201
  invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
202
202
  invoke "kamal:cli:app:remove", [], options.without(:confirmed)
203
203
  invoke "kamal:cli:accessory:remove", [ "all" ], options
@@ -63,7 +63,7 @@ registry:
63
63
  # directories:
64
64
  # - data:/data
65
65
 
66
- # Configure custom arguments for Traefik
66
+ # Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
67
67
  # traefik:
68
68
  # args:
69
69
  # accesslog: true
@@ -11,20 +11,23 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
11
11
 
12
12
  desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
13
13
  option :rolling, type: :boolean, default: false, desc: "Reboot traefik on hosts in sequence, rather than in parallel"
14
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
14
15
  def reboot
15
- mutating do
16
- host_groups = options[:rolling] ? KAMAL.traefik_hosts : [KAMAL.traefik_hosts]
17
- host_groups.each do |hosts|
18
- host_list = Array(hosts).join(",")
19
- run_hook "pre-traefik-reboot", hosts: host_list
20
- on(hosts) do
21
- execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
22
- execute *KAMAL.registry.login
23
- execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
24
- execute *KAMAL.traefik.remove_container
25
- execute *KAMAL.traefik.run
16
+ confirming "This will cause a brief outage on each host. Are you sure?" do
17
+ mutating do
18
+ host_groups = options[:rolling] ? KAMAL.traefik_hosts : [ KAMAL.traefik_hosts ]
19
+ host_groups.each do |hosts|
20
+ host_list = Array(hosts).join(",")
21
+ run_hook "pre-traefik-reboot", hosts: host_list
22
+ on(hosts) do
23
+ execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
24
+ execute *KAMAL.registry.login
25
+ execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
26
+ execute *KAMAL.traefik.remove_container
27
+ execute *KAMAL.traefik.run
28
+ end
29
+ run_hook "post-traefik-reboot", hosts: host_list
26
30
  end
27
- run_hook "post-traefik-reboot", hosts: host_list
28
31
  end
29
32
  end
30
33
  end
@@ -0,0 +1,49 @@
1
+ class Kamal::Commander::Specifics
2
+ attr_reader :primary_host, :primary_role, :hosts, :roles
3
+ delegate :stable_sort!, to: Kamal::Utils
4
+
5
+ def initialize(config, specific_hosts, specific_roles)
6
+ @config, @specific_hosts, @specific_roles = config, specific_hosts, specific_roles
7
+
8
+ @roles, @hosts = specified_roles, specified_hosts
9
+
10
+ @primary_host = specific_hosts&.first || primary_specific_role&.primary_host || config.primary_host
11
+ @primary_role = primary_or_first_role(roles_on(primary_host))
12
+
13
+ stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
14
+ stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
15
+ end
16
+
17
+ def roles_on(host)
18
+ roles.select { |role| role.hosts.include?(host.to_s) }
19
+ end
20
+
21
+ def traefik_hosts
22
+ specific_hosts || config.traefik_hosts
23
+ end
24
+
25
+ def accessory_hosts
26
+ specific_hosts || config.accessories.flat_map(&:hosts)
27
+ end
28
+
29
+ private
30
+ attr_reader :config, :specific_hosts, :specific_roles
31
+
32
+ def primary_specific_role
33
+ primary_or_first_role(specific_roles) if specific_roles.present?
34
+ end
35
+
36
+ def primary_or_first_role(roles)
37
+ roles.detect { |role| role == config.primary_role } || roles.first
38
+ end
39
+
40
+ def specified_roles
41
+ (specific_roles || config.roles) \
42
+ .select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? }
43
+ end
44
+
45
+ def specified_hosts
46
+ (specific_hosts || config.all_hosts) \
47
+ .select { |host| (specific_roles || config.roles).flat_map(&:hosts).include?(host) }
48
+ end
49
+ end
@@ -3,11 +3,13 @@ require "active_support/core_ext/module/delegation"
3
3
 
4
4
  class Kamal::Commander
5
5
  attr_accessor :verbosity, :holding_lock, :hold_lock_on_error
6
+ delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :traefik_hosts, :accessory_hosts, to: :specifics
6
7
 
7
8
  def initialize
8
9
  self.verbosity = :info
9
10
  self.holding_lock = false
10
11
  self.hold_lock_on_error = false
12
+ @specifics = nil
11
13
  end
12
14
 
13
15
  def config
@@ -24,10 +26,12 @@ class Kamal::Commander
24
26
  attr_reader :specific_roles, :specific_hosts
25
27
 
26
28
  def specific_primary!
29
+ @specifics = nil
27
30
  self.specific_hosts = [ config.primary_host ]
28
31
  end
29
32
 
30
33
  def specific_roles=(role_names)
34
+ @specifics = nil
31
35
  if role_names.present?
32
36
  @specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles)
33
37
 
@@ -40,6 +44,7 @@ class Kamal::Commander
40
44
  end
41
45
 
42
46
  def specific_hosts=(hosts)
47
+ @specifics = nil
43
48
  if hosts.present?
44
49
  @specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
45
50
 
@@ -51,39 +56,6 @@ class Kamal::Commander
51
56
  end
52
57
  end
53
58
 
54
- def primary_host
55
- # Given a list of specific roles, make an effort to match up with the primary_role
56
- specific_hosts&.first || specific_roles&.detect { |role| role == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host
57
- end
58
-
59
- def primary_role
60
- roles_on(primary_host).first
61
- end
62
-
63
- def roles
64
- (specific_roles || config.roles).select do |role|
65
- ((specific_hosts || config.all_hosts) & role.hosts).any?
66
- end
67
- end
68
-
69
- def hosts
70
- (specific_hosts || config.all_hosts).select do |host|
71
- (specific_roles || config.roles).flat_map(&:hosts).include?(host)
72
- end
73
- end
74
-
75
- def roles_on(host)
76
- roles.select { |role| role.hosts.include?(host.to_s) }
77
- end
78
-
79
- def traefik_hosts
80
- specific_hosts || config.traefik_hosts
81
- end
82
-
83
- def accessory_hosts
84
- specific_hosts || config.accessories.flat_map(&:hosts)
85
- end
86
-
87
59
  def accessory_names
88
60
  config.accessories&.collect(&:name) || []
89
61
  end
@@ -181,4 +153,8 @@ class Kamal::Commander
181
153
  SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
182
154
  SSHKit.config.output_verbosity = verbosity
183
155
  end
156
+
157
+ def specifics
158
+ @specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles)
159
+ end
184
160
  end
@@ -99,11 +99,11 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
99
99
  end
100
100
 
101
101
  def make_env_directory
102
- make_directory accessory_config.host_env_directory
102
+ make_directory accessory_config.env.secrets_directory
103
103
  end
104
104
 
105
105
  def remove_env_file
106
- [:rm, "-f", accessory_config.host_env_file_path]
106
+ [ :rm, "-f", accessory_config.env.secrets_file ]
107
107
  end
108
108
 
109
109
  private
@@ -4,8 +4,8 @@ module Kamal::Commands::App::Assets
4
4
 
5
5
  combine \
6
6
  make_directory(role.asset_extracted_path),
7
- [*docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true"],
8
- docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep 1000000"),
7
+ [ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ],
8
+ docker(:run, "--name", asset_container, "--detach", "--rm", config.absolute_image, "sleep 1000000"),
9
9
  docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_path),
10
10
  docker(:stop, "-t 1", asset_container),
11
11
  by: "&&"
@@ -17,7 +17,7 @@ module Kamal::Commands::App::Assets
17
17
  old_extracted_path, old_volume_path = role.asset_extracted_path(old_version), role.asset_volume(old_version).host_path
18
18
  end
19
19
 
20
- commands = [make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path)]
20
+ commands = [ make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path) ]
21
21
 
22
22
  if old_version.present?
23
23
  commands << copy_contents(new_extracted_path, old_volume_path, continue_on_error: true)
@@ -46,6 +46,6 @@ module Kamal::Commands::App::Assets
46
46
  end
47
47
 
48
48
  def copy_contents(source, destination, continue_on_error: false)
49
- [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error)]
49
+ [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error) ]
50
50
  end
51
51
  end
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Cord
2
2
  def cord(version:)
3
3
  pipe \
4
4
  docker(:inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", container_name(version)),
5
- [:awk, "'$2 == \"#{role.cord_volume.container_path}\" {print $1}'"]
5
+ [ :awk, "'$2 == \"#{role.cord_volume.container_path}\" {print $1}'" ]
6
6
  end
7
7
 
8
8
  def tie_cord(cord)
@@ -17,6 +17,6 @@ module Kamal::Commands::App::Cord
17
17
  def create_empty_file(file)
18
18
  chain \
19
19
  make_directory_for(file),
20
- [:touch, file]
20
+ [ :touch, file ]
21
21
  end
22
22
  end
@@ -1,27 +1,29 @@
1
1
  module Kamal::Commands::App::Execution
2
- def execute_in_existing_container(*command, interactive: false)
2
+ def execute_in_existing_container(*command, interactive: false, env:)
3
3
  docker :exec,
4
4
  ("-it" if interactive),
5
+ *argumentize("--env", env),
5
6
  container_name,
6
7
  *command
7
8
  end
8
9
 
9
- def execute_in_new_container(*command, interactive: false)
10
+ def execute_in_new_container(*command, interactive: false, env:)
10
11
  docker :run,
11
12
  ("-it" if interactive),
12
13
  "--rm",
13
14
  *role&.env_args,
15
+ *argumentize("--env", env),
14
16
  *config.volume_args,
15
17
  *role&.option_args,
16
18
  config.absolute_image,
17
19
  *command
18
20
  end
19
21
 
20
- def execute_in_existing_container_over_ssh(*command, host:)
21
- run_over_ssh execute_in_existing_container(*command, interactive: true), host: host
22
+ def execute_in_existing_container_over_ssh(*command, host:, env:)
23
+ run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
22
24
  end
23
25
 
24
- def execute_in_new_container_over_ssh(*command, host:)
25
- run_over_ssh execute_in_new_container(*command, interactive: true), host: host
26
+ def execute_in_new_container_over_ssh(*command, host:, env:)
27
+ run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
26
28
  end
27
29
  end
@@ -7,7 +7,7 @@ module Kamal::Commands::App::Images
7
7
  docker :image, :prune, "--all", "--force", *filter_args
8
8
  end
9
9
 
10
- def tag_current_image_as_latest
10
+ def tag_latest_image
11
11
  docker :tag, config.absolute_image, config.latest_image
12
12
  end
13
13
  end