kamal 2.7.0 → 2.11.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +27 -7
  4. data/lib/kamal/cli/alias/command.rb +2 -2
  5. data/lib/kamal/cli/app/boot.rb +1 -1
  6. data/lib/kamal/cli/app.rb +74 -115
  7. data/lib/kamal/cli/base.rb +19 -6
  8. data/lib/kamal/cli/build/clone.rb +0 -2
  9. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  10. data/lib/kamal/cli/build.rb +70 -35
  11. data/lib/kamal/cli/healthcheck/poller.rb +1 -1
  12. data/lib/kamal/cli/main.rb +9 -3
  13. data/lib/kamal/cli/proxy.rb +42 -35
  14. data/lib/kamal/cli/registry.rb +37 -7
  15. data/lib/kamal/cli/secrets.rb +2 -1
  16. data/lib/kamal/cli/server.rb +12 -1
  17. data/lib/kamal/cli/templates/deploy.yml +4 -3
  18. data/lib/kamal/cli/templates/secrets +2 -1
  19. data/lib/kamal/commander.rb +21 -19
  20. data/lib/kamal/commands/accessory.rb +5 -0
  21. data/lib/kamal/commands/app/execution.rb +7 -1
  22. data/lib/kamal/commands/app.rb +1 -0
  23. data/lib/kamal/commands/base.rb +15 -2
  24. data/lib/kamal/commands/builder/base.rb +20 -1
  25. data/lib/kamal/commands/builder/hybrid.rb +3 -3
  26. data/lib/kamal/commands/builder/local.rb +8 -2
  27. data/lib/kamal/commands/builder/pack.rb +5 -5
  28. data/lib/kamal/commands/builder/remote.rb +15 -3
  29. data/lib/kamal/commands/builder.rb +8 -2
  30. data/lib/kamal/commands/docker.rb +17 -1
  31. data/lib/kamal/commands/proxy.rb +22 -3
  32. data/lib/kamal/commands/registry.rb +22 -0
  33. data/lib/kamal/configuration/accessory.rb +56 -25
  34. data/lib/kamal/configuration/boot.rb +4 -0
  35. data/lib/kamal/configuration/builder.rb +10 -3
  36. data/lib/kamal/configuration/docs/accessory.yml +37 -5
  37. data/lib/kamal/configuration/docs/alias.yml +3 -0
  38. data/lib/kamal/configuration/docs/boot.yml +12 -10
  39. data/lib/kamal/configuration/docs/configuration.yml +30 -1
  40. data/lib/kamal/configuration/docs/proxy.yml +48 -16
  41. data/lib/kamal/configuration/docs/registry.yml +12 -4
  42. data/lib/kamal/configuration/docs/ssh.yml +7 -4
  43. data/lib/kamal/configuration/docs/sshkit.yml +8 -0
  44. data/lib/kamal/configuration/env.rb +7 -3
  45. data/lib/kamal/configuration/proxy/boot.rb +4 -9
  46. data/lib/kamal/configuration/proxy/run.rb +143 -0
  47. data/lib/kamal/configuration/proxy.rb +7 -3
  48. data/lib/kamal/configuration/registry.rb +8 -0
  49. data/lib/kamal/configuration/role.rb +15 -3
  50. data/lib/kamal/configuration/ssh.rb +18 -3
  51. data/lib/kamal/configuration/sshkit.rb +4 -0
  52. data/lib/kamal/configuration/validator/proxy.rb +20 -0
  53. data/lib/kamal/configuration/validator/registry.rb +5 -3
  54. data/lib/kamal/configuration/validator.rb +52 -4
  55. data/lib/kamal/configuration/volume.rb +11 -4
  56. data/lib/kamal/configuration.rb +89 -5
  57. data/lib/kamal/secrets/adapters/one_password.rb +1 -1
  58. data/lib/kamal/secrets/adapters/passbolt.rb +1 -2
  59. data/lib/kamal/secrets/adapters/test.rb +3 -1
  60. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +15 -1
  61. data/lib/kamal/secrets.rb +17 -6
  62. data/lib/kamal/sshkit_with_ext.rb +135 -10
  63. data/lib/kamal/utils.rb +3 -3
  64. data/lib/kamal/version.rb +1 -1
  65. data/lib/kamal.rb +1 -0
  66. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e1cf57d731a8b129a8ccbb86faddd3e813bc4d17895e6e538fe904f5bb65d27
4
- data.tar.gz: 2d7d81b3a34f42fb427bfed18f9cf0ed1955d38e4b4783c1407bc1db6de5cef6
3
+ metadata.gz: c8f7525b3ed804be2810d0af353c254b8c7ebf4d5cf5916dbfa97d5bf4615d54
4
+ data.tar.gz: adfac172b3e4b45bc00092317bde87395fcedecf107b30c8500ab58097ef22d8
5
5
  SHA512:
6
- metadata.gz: f3144c40082cfa24c78e2a1ebf2f433e491be3f5966e45cfc5488c3a11f5a3f513f097f7818cbc9e34d20b79986e64212055a87030fd4aa386a1d82bbb7d0efe
7
- data.tar.gz: c6b796497a6f7a68815d340664b34fb9943f55ea7121122994aa3fdadaa5b12a18fb4078ff194cffb6060917a74611fdf31017afa75f2515d94ec259e0809251
6
+ metadata.gz: 05150f9253685ce18dd910cf809a99e51449b7452536e6c3f39c8e37d5ce3db4806bd8e198ed3e29aa872523eb8937db5b58faee036619f8e5b43d1084eebffc
7
+ data.tar.gz: 47e77a8d347c44aff4326f9927a0933c0240b5bfdb90f1b623e85ecdad8ba48c0e3443d43cd062b265aa29e2dbd47d778499be39a079d3cb93d8856cb0388108
data/README.md CHANGED
@@ -6,7 +6,7 @@ From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal
6
6
 
7
7
  ## Contributing to the documentation
8
8
 
9
- Please help us improve Kamal's documentation on the [the basecamp/kamal-site repository](https://github.com/basecamp/kamal-site).
9
+ Please help us improve Kamal's documentation on [the basecamp/kamal-site repository](https://github.com/basecamp/kamal-site).
10
10
 
11
11
  ## License
12
12
 
@@ -45,12 +45,14 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
45
45
  with_lock do
46
46
  with_accessory(name) do |accessory, hosts|
47
47
  on(hosts) do
48
- accessory.files.each do |(local, remote)|
48
+ accessory.files.each do |(local, config)|
49
+ remote = config[:host_path]
49
50
  accessory.ensure_local_file_present(local)
50
51
 
51
52
  execute *accessory.make_directory_for(remote)
52
53
  upload! local, remote
53
- execute :chmod, "755", remote
54
+ execute :chmod, config[:mode], remote
55
+ execute :chown, config[:owner], remote if config[:owner]
54
56
  end
55
57
  end
56
58
  end
@@ -62,8 +64,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
62
64
  with_lock do
63
65
  with_accessory(name) do |accessory, hosts|
64
66
  on(hosts) do
65
- accessory.directories.keys.each do |host_path|
66
- execute *accessory.make_directory(host_path)
67
+ accessory.directories.each do |(local, config)|
68
+ execute *accessory.make_directory(local)
69
+ execute :chmod, config[:mode], local if config[:mode]
70
+ execute :chown, config[:owner], local if config[:owner]
67
71
  end
68
72
  end
69
73
  end
@@ -77,6 +81,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
77
81
  KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
78
82
  else
79
83
  prepare(name)
84
+ pull_image(name)
80
85
  stop(name)
81
86
  remove_container(name)
82
87
  boot(name, prepare: false)
@@ -127,12 +132,13 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
127
132
 
128
133
  desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
129
134
  def details(name)
135
+ quiet = options[:quiet]
130
136
  if name == "all"
131
137
  KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
132
138
  else
133
139
  type = "Accessory #{name}"
134
140
  with_accessory(name) do |accessory, hosts|
135
- on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type }
141
+ on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type, quiet: quiet }
136
142
  end
137
143
  end
138
144
  end
@@ -144,6 +150,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
144
150
  pre_connect_if_required
145
151
 
146
152
  cmd = Kamal::Utils.join_commands(cmd)
153
+ quiet = options[:quiet]
154
+
147
155
  with_accessory(name) do |accessory, hosts|
148
156
  case
149
157
  when options[:interactive] && options[:reuse]
@@ -159,7 +167,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
159
167
  say "Launching command from existing container...", :magenta
160
168
  on(hosts) do |host|
161
169
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
162
- puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
170
+ puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd)), quiet: quiet
163
171
  end
164
172
 
165
173
  else
@@ -167,7 +175,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
167
175
  on(hosts) do |host|
168
176
  execute *KAMAL.registry.login
169
177
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
170
- puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
178
+ puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd)), quiet: quiet
171
179
  end
172
180
  end
173
181
  end
@@ -203,6 +211,18 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
203
211
  end
204
212
  end
205
213
 
214
+ desc "pull_image [NAME]", "Pull accessory image on host", hide: true
215
+ def pull_image(name)
216
+ with_lock do
217
+ with_accessory(name) do |accessory, hosts|
218
+ on(hosts) do
219
+ execute *KAMAL.auditor.record("Pull #{name} accessory image"), verbosity: :debug
220
+ execute *accessory.pull_image
221
+ end
222
+ end
223
+ end
224
+ end
225
+
206
226
  desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
207
227
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
208
228
  def remove(name)
@@ -1,8 +1,8 @@
1
1
  class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
2
  def run(instance, args = [])
3
- if (_alias = KAMAL.config.aliases[name])
3
+ if (command = KAMAL.resolve_alias(name))
4
4
  KAMAL.reset
5
- Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
+ Kamal::Cli::Main.start(Shellwords.split(command) + ARGV[1..-1])
6
6
  else
7
7
  super
8
8
  end
@@ -56,7 +56,7 @@ class Kamal::Cli::App::Boot
56
56
  raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
57
57
  execute *app.deploy(target: endpoint)
58
58
  else
59
- Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
59
+ Kamal::Cli::Healthcheck::Poller.wait_for_healthy { capture_with_info(*app.status(version: version)) }
60
60
  end
61
61
  rescue => e
62
62
  error "Failed to boot #{role} on #{host}"
data/lib/kamal/cli/app.rb CHANGED
@@ -23,10 +23,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
23
23
  host_list = Array(hosts).join(",")
24
24
  run_hook "pre-app-boot", hosts: host_list
25
25
 
26
- on(hosts) do |host|
27
- KAMAL.roles_on(host).each do |role|
28
- Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
29
- end
26
+ on_roles(KAMAL.roles, hosts: hosts, parallel: KAMAL.config.boot.parallel_roles) do |host, role|
27
+ Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
30
28
  end
31
29
 
32
30
  run_hook "post-app-boot", hosts: host_list
@@ -45,21 +43,17 @@ class Kamal::Cli::App < Kamal::Cli::Base
45
43
  desc "start", "Start existing app container on servers"
46
44
  def start
47
45
  with_lock do
48
- on(KAMAL.app_hosts) do |host|
49
- roles = KAMAL.roles_on(host)
50
-
51
- roles.each do |role|
52
- app = KAMAL.app(role: role, host: host)
53
- execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
54
- execute *app.start, raise_on_non_zero_exit: false
46
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts, parallel: KAMAL.config.boot.parallel_roles) do |host, role|
47
+ app = KAMAL.app(role: role, host: host)
48
+ execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
49
+ execute *app.start, raise_on_non_zero_exit: false
55
50
 
56
- if role.running_proxy?
57
- version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
58
- endpoint = capture_with_info(*app.container_id_for_version(version)).strip
59
- raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
51
+ if role.running_proxy?
52
+ version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
53
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
54
+ raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
60
55
 
61
- execute *app.deploy(target: endpoint)
62
- end
56
+ execute *app.deploy(target: endpoint)
63
57
  end
64
58
  end
65
59
  end
@@ -68,23 +62,19 @@ class Kamal::Cli::App < Kamal::Cli::Base
68
62
  desc "stop", "Stop app container on servers"
69
63
  def stop
70
64
  with_lock do
71
- on(KAMAL.app_hosts) do |host|
72
- roles = KAMAL.roles_on(host)
73
-
74
- roles.each do |role|
75
- app = KAMAL.app(role: role, host: host)
76
- execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
77
-
78
- if role.running_proxy?
79
- version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
80
- endpoint = capture_with_info(*app.container_id_for_version(version)).strip
81
- if endpoint.present?
82
- execute *app.remove, raise_on_non_zero_exit: false
83
- end
84
- end
65
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts, parallel: KAMAL.config.boot.parallel_roles) do |host, role|
66
+ app = KAMAL.app(role: role, host: host)
67
+ execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
85
68
 
86
- execute *app.stop, raise_on_non_zero_exit: false
69
+ if role.running_proxy?
70
+ version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
71
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
72
+ if endpoint.present?
73
+ execute *app.remove, raise_on_non_zero_exit: false
74
+ end
87
75
  end
76
+
77
+ execute *app.stop, raise_on_non_zero_exit: false
88
78
  end
89
79
  end
90
80
  end
@@ -92,12 +82,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
92
82
  # FIXME: Drop in favor of just containers?
93
83
  desc "details", "Show details about app containers"
94
84
  def details
95
- on(KAMAL.app_hosts) do |host|
96
- roles = KAMAL.roles_on(host)
97
-
98
- roles.each do |role|
99
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).info)
100
- end
85
+ quiet = options[:quiet]
86
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
87
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).info), quiet: quiet
101
88
  end
102
89
  end
103
90
 
@@ -120,6 +107,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
120
107
  cmd = Kamal::Utils.join_commands(cmd)
121
108
  env = options[:env]
122
109
  detach = options[:detach]
110
+ quiet = options[:quiet]
123
111
  case
124
112
  when options[:interactive] && options[:reuse]
125
113
  say "Get current version of running container...", :magenta unless options[:version]
@@ -143,13 +131,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
143
131
  using_version(options[:version] || current_running_version) do |version|
144
132
  say "Launching command with version #{version} from existing container...", :magenta
145
133
 
146
- on(KAMAL.app_hosts) do |host|
147
- roles = KAMAL.roles_on(host)
148
-
149
- roles.each do |role|
150
- execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
151
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_existing_container(cmd, env: env))
152
- end
134
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
135
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
136
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_existing_container(cmd, env: env)), quiet: quiet
153
137
  end
154
138
  end
155
139
 
@@ -157,15 +141,11 @@ class Kamal::Cli::App < Kamal::Cli::Base
157
141
  say "Get most recent version available as an image...", :magenta unless options[:version]
158
142
  using_version(version_or_latest) do |version|
159
143
  say "Launching command with version #{version} from new container...", :magenta
160
- on(KAMAL.app_hosts) do |host|
161
- execute *KAMAL.registry.login
162
-
163
- roles = KAMAL.roles_on(host)
144
+ on(KAMAL.app_hosts) { execute *KAMAL.registry.login }
164
145
 
165
- roles.each do |role|
166
- execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
167
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
168
- end
146
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
147
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
148
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach)), quiet: quiet
169
149
  end
170
150
  end
171
151
  end
@@ -173,30 +153,28 @@ class Kamal::Cli::App < Kamal::Cli::Base
173
153
 
174
154
  desc "containers", "Show app containers on servers"
175
155
  def containers
176
- on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
156
+ quiet = options[:quiet]
157
+ on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers), quiet: quiet }
177
158
  end
178
159
 
179
160
  desc "stale_containers", "Detect app stale containers"
180
161
  option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
181
162
  def stale_containers
163
+ quiet = options[:quiet]
182
164
  stop = options[:stop]
183
165
 
184
166
  with_lock_if_stopping do
185
- on(KAMAL.app_hosts) do |host|
186
- roles = KAMAL.roles_on(host)
187
-
188
- roles.each do |role|
189
- app = KAMAL.app(role: role, host: host)
190
- versions = capture_with_info(*app.list_versions, raise_on_non_zero_exit: false).split("\n")
191
- versions -= [ capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip ]
192
-
193
- versions.each do |version|
194
- if stop
195
- puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
196
- execute *app.stop(version: version), raise_on_non_zero_exit: false
197
- else
198
- puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)"
199
- end
167
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
168
+ app = KAMAL.app(role: role, host: host)
169
+ versions = capture_with_info(*app.list_versions, raise_on_non_zero_exit: false).split("\n")
170
+ versions -= [ capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip ]
171
+
172
+ versions.each do |version|
173
+ if stop
174
+ puts_by_host host, "Stopping stale container for role #{role} with version #{version}", quiet: quiet
175
+ execute *app.stop(version: version), raise_on_non_zero_exit: false
176
+ else
177
+ puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)", quiet: quiet
200
178
  end
201
179
  end
202
180
  end
@@ -205,7 +183,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
205
183
 
206
184
  desc "images", "Show app images on servers"
207
185
  def images
208
- on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
186
+ quiet = options[:quiet]
187
+ on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images), quiet: quiet }
209
188
  end
210
189
 
211
190
  desc "logs", "Show log lines from app on servers (use --help to show options)"
@@ -224,6 +203,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
224
203
  since = options[:since]
225
204
  container_id = options[:container_id]
226
205
  timestamps = !options[:skip_timestamps]
206
+ quiet = options[:quiet]
227
207
 
228
208
  if options[:follow]
229
209
  lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
@@ -241,15 +221,11 @@ class Kamal::Cli::App < Kamal::Cli::Base
241
221
  else
242
222
  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
243
223
 
244
- on(KAMAL.app_hosts) do |host|
245
- roles = KAMAL.roles_on(host)
246
-
247
- roles.each do |role|
248
- begin
249
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
250
- rescue SSHKit::Command::Failed
251
- puts_by_host host, "Nothing found"
252
- end
224
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
225
+ begin
226
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options)), quiet: quiet
227
+ rescue SSHKit::Command::Failed
228
+ puts_by_host host, "Nothing found", quiet: quiet
253
229
  end
254
230
  end
255
231
  end
@@ -268,12 +244,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
268
244
  desc "live", "Set the app to live mode"
269
245
  def live
270
246
  with_lock do
271
- on(KAMAL.proxy_hosts) do |host|
272
- roles = KAMAL.roles_on(host)
273
-
274
- roles.each do |role|
275
- execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
276
- end
247
+ on_roles(KAMAL.roles, hosts: KAMAL.proxy_hosts) do |host, role|
248
+ execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
277
249
  end
278
250
  end
279
251
  end
@@ -285,12 +257,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
285
257
  maintenance_options = { drain_timeout: options[:drain_timeout] || KAMAL.config.drain_timeout, message: options[:message] }
286
258
 
287
259
  with_lock do
288
- on(KAMAL.proxy_hosts) do |host|
289
- roles = KAMAL.roles_on(host)
290
-
291
- roles.each do |role|
292
- execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
293
- end
260
+ on_roles(KAMAL.roles, hosts: KAMAL.proxy_hosts) do |host, role|
261
+ execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
294
262
  end
295
263
  end
296
264
  end
@@ -298,13 +266,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
298
266
  desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
299
267
  def remove_container(version)
300
268
  with_lock do
301
- on(KAMAL.app_hosts) do |host|
302
- roles = KAMAL.roles_on(host)
303
-
304
- roles.each do |role|
305
- execute *KAMAL.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
306
- execute *KAMAL.app(role: role, host: host).remove_container(version: version)
307
- end
269
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
270
+ execute *KAMAL.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
271
+ execute *KAMAL.app(role: role, host: host).remove_container(version: version)
308
272
  end
309
273
  end
310
274
  end
@@ -312,13 +276,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
312
276
  desc "remove_containers", "Remove all app containers from servers", hide: true
313
277
  def remove_containers
314
278
  with_lock do
315
- on(KAMAL.app_hosts) do |host|
316
- roles = KAMAL.roles_on(host)
317
-
318
- roles.each do |role|
319
- execute *KAMAL.auditor.record("Removed all app containers", role: role), verbosity: :debug
320
- execute *KAMAL.app(role: role, host: host).remove_containers
321
- end
279
+ on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
280
+ execute *KAMAL.auditor.record("Removed all app containers", role: role), verbosity: :debug
281
+ execute *KAMAL.app(role: role, host: host).remove_containers
322
282
  end
323
283
  end
324
284
  end
@@ -326,7 +286,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
326
286
  desc "remove_images", "Remove all app images from servers", hide: true
327
287
  def remove_images
328
288
  with_lock do
329
- on(KAMAL.app_hosts) do
289
+ on(hosts_removing_all_roles) do
330
290
  execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
331
291
  execute *KAMAL.app.remove_images
332
292
  end
@@ -336,14 +296,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
336
296
  desc "remove_app_directories", "Remove the app directories from servers", hide: true
337
297
  def remove_app_directories
338
298
  with_lock do
339
- on(KAMAL.app_hosts) do |host|
340
- roles = KAMAL.roles_on(host)
341
-
342
- roles.each do |role|
343
- execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}", role: role), verbosity: :debug
344
- execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
345
- end
346
-
299
+ on(hosts_removing_all_roles) do |host|
300
+ execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
347
301
  execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}"), verbosity: :debug
348
302
  execute *KAMAL.app.remove_proxy_app_directory, raise_on_non_zero_exit: false
349
303
  end
@@ -352,13 +306,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
352
306
 
353
307
  desc "version", "Show app version currently running on servers"
354
308
  def version
309
+ quiet = options[:quiet]
355
310
  on(KAMAL.app_hosts) do |host|
356
311
  role = KAMAL.roles_on(host).first
357
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
312
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip, quiet: quiet
358
313
  end
359
314
  end
360
315
 
361
316
  private
317
+ def hosts_removing_all_roles
318
+ KAMAL.app_hosts.select { |host| KAMAL.roles_on(host).map(&:name).sort == KAMAL.config.host_roles(host.to_s).map(&:name).sort }
319
+ end
320
+
362
321
  def using_version(new_version)
363
322
  if new_version
364
323
  begin
@@ -5,6 +5,8 @@ module Kamal::Cli
5
5
  class Base < Thor
6
6
  include SSHKit::DSL
7
7
 
8
+ VERBOSITY = { verbose: :debug, quiet: :error }.freeze
9
+
8
10
  def self.exit_on_failure?() true end
9
11
  def self.dynamic_command_class() Kamal::Cli::Alias::Command end
10
12
 
@@ -43,11 +45,11 @@ module Kamal::Cli
43
45
  KAMAL.tap do |commander|
44
46
  if options[:verbose]
45
47
  ENV["VERBOSE"] = "1" # For backtraces via cli/start
46
- commander.verbosity = :debug
48
+ commander.verbosity = VERBOSITY[:verbose]
47
49
  end
48
50
 
49
51
  if options[:quiet]
50
- commander.verbosity = :error
52
+ commander.verbosity = VERBOSITY[:quiet]
51
53
  end
52
54
 
53
55
  commander.configure \
@@ -141,10 +143,21 @@ module Kamal::Cli
141
143
  subcommand: subcommand
142
144
  }.compact
143
145
 
144
- say "Running the #{hook} hook...", :magenta
146
+ hooks_output = KAMAL.config.hooks_output_for(hook)
147
+
148
+ # CLI flags override config: -q hides all, -v shows all
149
+ # Config setting :verbose forces output, :quiet forces silence
150
+ hook_verbosity = if KAMAL.verbosity == :info && hooks_output
151
+ VERBOSITY.fetch(hooks_output)
152
+ else
153
+ KAMAL.verbosity
154
+ end
155
+
145
156
  with_env KAMAL.hook.env(**details, **extra_details) do
146
- run_locally do
147
- execute *KAMAL.hook.run(hook)
157
+ KAMAL.with_verbosity(hook_verbosity) do
158
+ run_locally do
159
+ execute *KAMAL.hook.run(hook)
160
+ end
148
161
  end
149
162
  rescue SSHKit::Command::Failed => e
150
163
  raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
@@ -160,7 +173,7 @@ module Kamal::Cli
160
173
 
161
174
  def pre_connect_if_required
162
175
  if !KAMAL.connected?
163
- run_hook "pre-connect"
176
+ run_hook "pre-connect", secrets: true unless options[:skip_hooks]
164
177
  KAMAL.connected = true
165
178
  end
166
179
  end
@@ -1,5 +1,3 @@
1
- require "uri"
2
-
3
1
  class Kamal::Cli::Build::Clone
4
2
  attr_reader :sshkit
5
3
  delegate :info, :error, :execute, :capture_with_info, to: :sshkit
@@ -0,0 +1,66 @@
1
+ require "concurrent/atomic/count_down_latch"
2
+
3
+ class Kamal::Cli::Build::PortForwarding
4
+ attr_reader :hosts, :port, :ssh_options
5
+
6
+ def initialize(hosts, port, **ssh_options)
7
+ @hosts = hosts
8
+ @port = port
9
+ @ssh_options = ssh_options
10
+ end
11
+
12
+ def forward
13
+ @done = false
14
+ forward_ports
15
+
16
+ yield
17
+ ensure
18
+ stop
19
+ end
20
+
21
+ private
22
+ def stop
23
+ @done = true
24
+ @threads.to_a.each(&:join)
25
+ end
26
+
27
+ def forward_ports
28
+ ready = Concurrent::CountDownLatch.new(hosts.size)
29
+
30
+ @threads = hosts.map do |host|
31
+ Thread.new do
32
+ begin
33
+ Net::SSH.start(host, ssh_options[:user], **ssh_options.except(:user)) do |ssh|
34
+ ssh.forward.remote(port, "localhost", port, "127.0.0.1") do |remote_port, bind_address|
35
+ if remote_port == :error
36
+ raise "Failed to establish port forward on #{host}"
37
+ else
38
+ ready.count_down
39
+ end
40
+ end
41
+
42
+ ssh.loop(0.1) do
43
+ if @done
44
+ ssh.forward.cancel_remote(port, "127.0.0.1")
45
+ break
46
+ else
47
+ true
48
+ end
49
+ end
50
+ end
51
+ rescue Exception => e
52
+ error "Error setting up port forwarding to #{host}: #{e.class}: #{e.message}"
53
+ error e.backtrace.join("\n")
54
+
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ raise "Timed out waiting for port forwarding to be established" unless ready.wait(30)
61
+ end
62
+
63
+ def error(message)
64
+ SSHKit.config.output.error(message)
65
+ end
66
+ end