kamal 1.4.0 → 1.5.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 (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