kamal 2.11.0 → 2.12.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.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +48 -39
- data/lib/kamal/cli/app.rb +57 -48
- data/lib/kamal/cli/base.rb +99 -11
- data/lib/kamal/cli/build.rb +9 -6
- data/lib/kamal/cli/lock.rb +5 -16
- data/lib/kamal/cli/main.rb +59 -53
- data/lib/kamal/cli/proxy.rb +9 -9
- data/lib/kamal/cli/prune.rb +3 -3
- data/lib/kamal/cli/server.rb +24 -15
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +1 -1
- data/lib/kamal/cli/templates/secrets +4 -0
- data/lib/kamal/commander.rb +54 -1
- data/lib/kamal/commands/accessory.rb +2 -2
- data/lib/kamal/commands/app/logging.rb +1 -1
- data/lib/kamal/commands/app.rb +1 -1
- data/lib/kamal/commands/builder/clone.rb +2 -1
- data/lib/kamal/configuration/accessory.rb +13 -5
- data/lib/kamal/configuration/docs/configuration.yml +18 -3
- data/lib/kamal/configuration/docs/env.yml +6 -4
- data/lib/kamal/configuration/docs/output.yml +25 -0
- data/lib/kamal/configuration/docs/role.yml +1 -0
- data/lib/kamal/configuration/docs/ssh.yml +8 -0
- data/lib/kamal/configuration/output.rb +34 -0
- data/lib/kamal/configuration/proxy/run.rb +9 -0
- data/lib/kamal/configuration/role.rb +18 -6
- data/lib/kamal/configuration/ssh.rb +5 -1
- data/lib/kamal/configuration/validator.rb +14 -2
- data/lib/kamal/configuration.rb +6 -1
- data/lib/kamal/git.rb +1 -1
- data/lib/kamal/otel_shipper.rb +176 -0
- data/lib/kamal/output/base_logger.rb +29 -0
- data/lib/kamal/output/file_logger.rb +51 -0
- data/lib/kamal/output/formatter.rb +36 -0
- data/lib/kamal/output/otel_logger.rb +70 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +10 -2
- data/lib/kamal/secrets/adapters/passbolt.rb +1 -1
- data/lib/kamal/secrets.rb +1 -1
- data/lib/kamal/sshkit_with_ext.rb +9 -4
- data/lib/kamal/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d5aa65d23b2e2b68f8942617bb5ab754d6cb39e0444a61a95134590b6aadcf3
|
|
4
|
+
data.tar.gz: ecc576e1a0b82a0cc339d16a90cac74ecf47b7cdfb793fd5ff588fca61dc1d69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8fe2acb803dd2b1930d3a9ecf62a6f35a2e1078fcd3e2950dfac0095e7ab5354d85e004b80581151e75a7b3cef247a0fefd442059a96ed39ffa9b10b67b12adc
|
|
7
|
+
data.tar.gz: 9ce3a70b368f59a310403f3c73e9ae438436dcaa174a039248d6b976f0e345a380d185d7409bdc7b84371a3708fd413fd9e96f682909b2820ddf12edb7a0dd7e
|
data/lib/kamal/cli/accessory.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "concurrent/array"
|
|
|
4
4
|
class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
5
5
|
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
|
6
6
|
def boot(name, prepare: true)
|
|
7
|
-
|
|
7
|
+
modify(lock: true) do
|
|
8
8
|
if name == "all"
|
|
9
9
|
KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
|
|
10
10
|
else
|
|
@@ -42,7 +42,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
42
42
|
|
|
43
43
|
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
|
44
44
|
def upload(name)
|
|
45
|
-
|
|
45
|
+
modify(lock: true) do
|
|
46
46
|
with_accessory(name) do |accessory, hosts|
|
|
47
47
|
on(hosts) do
|
|
48
48
|
accessory.files.each do |(local, config)|
|
|
@@ -61,7 +61,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
61
61
|
|
|
62
62
|
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
|
63
63
|
def directories(name)
|
|
64
|
-
|
|
64
|
+
modify(lock: true) do
|
|
65
65
|
with_accessory(name) do |accessory, hosts|
|
|
66
66
|
on(hosts) do
|
|
67
67
|
accessory.directories.each do |(local, config)|
|
|
@@ -76,7 +76,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
76
76
|
|
|
77
77
|
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
|
|
78
78
|
def reboot(name)
|
|
79
|
-
|
|
79
|
+
modify(lock: true) do
|
|
80
80
|
if name == "all"
|
|
81
81
|
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
|
|
82
82
|
else
|
|
@@ -91,7 +91,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
91
91
|
|
|
92
92
|
desc "start [NAME]", "Start existing accessory container on host"
|
|
93
93
|
def start(name)
|
|
94
|
-
|
|
94
|
+
modify(lock: true) do
|
|
95
95
|
with_accessory(name) do |accessory, hosts|
|
|
96
96
|
on(hosts) do
|
|
97
97
|
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
|
@@ -107,7 +107,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
107
107
|
|
|
108
108
|
desc "stop [NAME]", "Stop existing accessory container on host"
|
|
109
109
|
def stop(name)
|
|
110
|
-
|
|
110
|
+
modify(lock: true) do
|
|
111
111
|
with_accessory(name) do |accessory, hosts|
|
|
112
112
|
on(hosts) do
|
|
113
113
|
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
|
@@ -124,7 +124,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
124
124
|
|
|
125
125
|
desc "restart [NAME]", "Restart existing accessory container on host"
|
|
126
126
|
def restart(name)
|
|
127
|
-
|
|
127
|
+
modify(lock: true) do
|
|
128
128
|
stop(name)
|
|
129
129
|
start(name)
|
|
130
130
|
end
|
|
@@ -146,36 +146,45 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
146
146
|
desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
|
|
147
147
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
|
148
148
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
|
149
|
+
option :raw, type: :boolean, default: false, desc: "Output raw, unmodified stdout"
|
|
149
150
|
def exec(name, *cmd)
|
|
150
|
-
|
|
151
|
+
raw = options[:raw]
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
if raw && options[:interactive]
|
|
154
|
+
raise ArgumentError, "Raw is not compatible with interactive"
|
|
155
|
+
end
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
when options[:interactive] && options[:reuse]
|
|
158
|
-
say "Launching interactive command via SSH from existing container...", :magenta
|
|
159
|
-
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
|
160
|
-
|
|
161
|
-
when options[:interactive]
|
|
162
|
-
say "Launching interactive command via SSH from new container...", :magenta
|
|
163
|
-
on(accessory.hosts.first) { execute *KAMAL.registry.login }
|
|
164
|
-
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
|
165
|
-
|
|
166
|
-
when options[:reuse]
|
|
167
|
-
say "Launching command from existing container...", :magenta
|
|
168
|
-
on(hosts) do |host|
|
|
169
|
-
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
|
170
|
-
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd)), quiet: quiet
|
|
171
|
-
end
|
|
157
|
+
with_raw_output(raw) do
|
|
158
|
+
pre_connect_if_required
|
|
172
159
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
160
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
|
161
|
+
quiet = options[:quiet]
|
|
162
|
+
|
|
163
|
+
with_accessory(name) do |accessory, hosts|
|
|
164
|
+
case
|
|
165
|
+
when options[:interactive] && options[:reuse]
|
|
166
|
+
say "Launching interactive command via SSH from existing container...", :magenta
|
|
167
|
+
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
|
168
|
+
|
|
169
|
+
when options[:interactive]
|
|
170
|
+
say "Launching interactive command via SSH from new container...", :magenta
|
|
171
|
+
on(accessory.hosts.first) { execute *KAMAL.registry.login }
|
|
172
|
+
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
|
173
|
+
|
|
174
|
+
when options[:reuse]
|
|
175
|
+
say "Launching command from existing container...", :magenta
|
|
176
|
+
on(hosts) do |host|
|
|
177
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
|
178
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd), strip: !raw), quiet: quiet, raw: raw
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
else
|
|
182
|
+
say "Launching command from new container...", :magenta
|
|
183
|
+
on(hosts) do |host|
|
|
184
|
+
execute *KAMAL.registry.login
|
|
185
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
|
186
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd), strip: !raw), quiet: quiet, raw: raw
|
|
187
|
+
end
|
|
179
188
|
end
|
|
180
189
|
end
|
|
181
190
|
end
|
|
@@ -213,7 +222,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
213
222
|
|
|
214
223
|
desc "pull_image [NAME]", "Pull accessory image on host", hide: true
|
|
215
224
|
def pull_image(name)
|
|
216
|
-
|
|
225
|
+
modify(lock: true) do
|
|
217
226
|
with_accessory(name) do |accessory, hosts|
|
|
218
227
|
on(hosts) do
|
|
219
228
|
execute *KAMAL.auditor.record("Pull #{name} accessory image"), verbosity: :debug
|
|
@@ -227,7 +236,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
227
236
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
228
237
|
def remove(name)
|
|
229
238
|
confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
|
|
230
|
-
|
|
239
|
+
modify(lock: true) do
|
|
231
240
|
if name == "all"
|
|
232
241
|
KAMAL.accessory_names.each { |accessory_name| remove_accessory(accessory_name) }
|
|
233
242
|
else
|
|
@@ -239,7 +248,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
239
248
|
|
|
240
249
|
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
|
241
250
|
def remove_container(name)
|
|
242
|
-
|
|
251
|
+
modify(lock: true) do
|
|
243
252
|
with_accessory(name) do |accessory, hosts|
|
|
244
253
|
on(hosts) do
|
|
245
254
|
execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
|
@@ -251,7 +260,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
251
260
|
|
|
252
261
|
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
|
253
262
|
def remove_image(name)
|
|
254
|
-
|
|
263
|
+
modify(lock: true) do
|
|
255
264
|
with_accessory(name) do |accessory, hosts|
|
|
256
265
|
on(hosts) do
|
|
257
266
|
execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
|
@@ -263,7 +272,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
263
272
|
|
|
264
273
|
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
|
265
274
|
def remove_service_directory(name)
|
|
266
|
-
|
|
275
|
+
modify(lock: true) do
|
|
267
276
|
with_accessory(name) do |accessory, hosts|
|
|
268
277
|
on(hosts) do
|
|
269
278
|
execute *accessory.remove_service_directory
|
|
@@ -277,7 +286,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
277
286
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
278
287
|
def upgrade(name)
|
|
279
288
|
confirming "This will restart all accessories" do
|
|
280
|
-
|
|
289
|
+
modify(lock: true) do
|
|
281
290
|
host_groups = options[:rolling] ? KAMAL.accessory_hosts : [ KAMAL.accessory_hosts ]
|
|
282
291
|
host_groups.each do |hosts|
|
|
283
292
|
host_list = Array(hosts).join(",")
|
data/lib/kamal/cli/app.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Kamal::Cli::App < Kamal::Cli::Base
|
|
2
2
|
desc "boot", "Boot app on servers (or reboot app if already running)"
|
|
3
3
|
def boot
|
|
4
|
-
|
|
4
|
+
modify(lock: true) do
|
|
5
5
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
|
6
6
|
using_version(version_or_latest) do |version|
|
|
7
7
|
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
|
@@ -42,7 +42,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
42
42
|
|
|
43
43
|
desc "start", "Start existing app container on servers"
|
|
44
44
|
def start
|
|
45
|
-
|
|
45
|
+
modify(lock: true) do
|
|
46
46
|
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts, parallel: KAMAL.config.boot.parallel_roles) do |host, role|
|
|
47
47
|
app = KAMAL.app(role: role, host: host)
|
|
48
48
|
execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
|
|
@@ -61,7 +61,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
61
61
|
|
|
62
62
|
desc "stop", "Stop app container on servers"
|
|
63
63
|
def stop
|
|
64
|
-
|
|
64
|
+
modify(lock: true) do
|
|
65
65
|
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts, parallel: KAMAL.config.boot.parallel_roles) do |host, role|
|
|
66
66
|
app = KAMAL.app(role: role, host: host)
|
|
67
67
|
execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
|
|
@@ -93,59 +93,68 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
93
93
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
|
94
94
|
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
|
95
95
|
option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
|
|
96
|
+
option :raw, type: :boolean, default: false, desc: "Output raw, unmodified stdout"
|
|
96
97
|
def exec(*cmd)
|
|
97
|
-
|
|
98
|
+
raw = options[:raw]
|
|
98
99
|
|
|
99
100
|
if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
|
|
100
101
|
raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
|
|
101
102
|
end
|
|
102
103
|
|
|
104
|
+
if raw && (incompatible_options = [ :interactive, :detach ].select { |key| options[key] }.presence)
|
|
105
|
+
raise ArgumentError, "Raw is not compatible with #{incompatible_options.join(" or ")}"
|
|
106
|
+
end
|
|
107
|
+
|
|
103
108
|
if cmd.empty?
|
|
104
109
|
raise ArgumentError, "No command provided. You must specify a command to execute."
|
|
105
110
|
end
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
with_raw_output(raw) do
|
|
113
|
+
pre_connect_if_required
|
|
114
|
+
|
|
115
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
|
116
|
+
env = options[:env]
|
|
117
|
+
detach = options[:detach]
|
|
118
|
+
quiet = options[:quiet]
|
|
119
|
+
case
|
|
120
|
+
when options[:interactive] && options[:reuse]
|
|
121
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
|
122
|
+
using_version(options[:version] || current_running_version) do |version|
|
|
123
|
+
say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
|
|
124
|
+
run_locally { exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_existing_container_over_ssh(cmd, env: env) }
|
|
125
|
+
end
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
when options[:interactive]
|
|
128
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
|
129
|
+
using_version(version_or_latest) do |version|
|
|
130
|
+
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
|
131
|
+
on(KAMAL.primary_host) { execute *KAMAL.registry.login }
|
|
132
|
+
run_locally do
|
|
133
|
+
exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_new_container_over_ssh(cmd, env: env)
|
|
134
|
+
end
|
|
126
135
|
end
|
|
127
|
-
end
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
when options[:reuse]
|
|
138
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
|
139
|
+
using_version(options[:version] || current_running_version) do |version|
|
|
140
|
+
say "Launching command with version #{version} from existing container...", :magenta
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
|
|
143
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
|
|
144
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_existing_container(cmd, env: env), strip: !raw), quiet: quiet, raw: raw
|
|
145
|
+
end
|
|
137
146
|
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
else
|
|
141
|
-
say "Get most recent version available as an image...", :magenta unless options[:version]
|
|
142
|
-
using_version(version_or_latest) do |version|
|
|
143
|
-
say "Launching command with version #{version} from new container...", :magenta
|
|
144
|
-
on(KAMAL.app_hosts) { execute *KAMAL.registry.login }
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
else
|
|
149
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
|
150
|
+
using_version(version_or_latest) do |version|
|
|
151
|
+
say "Launching command with version #{version} from new container...", :magenta
|
|
152
|
+
on(KAMAL.app_hosts) { execute *KAMAL.registry.login }
|
|
153
|
+
|
|
154
|
+
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
|
|
155
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
|
156
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach), strip: !raw), quiet: quiet, raw: raw
|
|
157
|
+
end
|
|
149
158
|
end
|
|
150
159
|
end
|
|
151
160
|
end
|
|
@@ -233,7 +242,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
233
242
|
|
|
234
243
|
desc "remove", "Remove app containers and images from servers"
|
|
235
244
|
def remove
|
|
236
|
-
|
|
245
|
+
modify(lock: true) do
|
|
237
246
|
stop
|
|
238
247
|
remove_containers
|
|
239
248
|
remove_images
|
|
@@ -243,7 +252,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
243
252
|
|
|
244
253
|
desc "live", "Set the app to live mode"
|
|
245
254
|
def live
|
|
246
|
-
|
|
255
|
+
modify(lock: true) do
|
|
247
256
|
on_roles(KAMAL.roles, hosts: KAMAL.proxy_hosts) do |host, role|
|
|
248
257
|
execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
|
|
249
258
|
end
|
|
@@ -256,7 +265,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
256
265
|
def maintenance
|
|
257
266
|
maintenance_options = { drain_timeout: options[:drain_timeout] || KAMAL.config.drain_timeout, message: options[:message] }
|
|
258
267
|
|
|
259
|
-
|
|
268
|
+
modify(lock: true) do
|
|
260
269
|
on_roles(KAMAL.roles, hosts: KAMAL.proxy_hosts) do |host, role|
|
|
261
270
|
execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
|
|
262
271
|
end
|
|
@@ -265,7 +274,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
265
274
|
|
|
266
275
|
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
|
267
276
|
def remove_container(version)
|
|
268
|
-
|
|
277
|
+
modify(lock: true) do
|
|
269
278
|
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
|
|
270
279
|
execute *KAMAL.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
|
|
271
280
|
execute *KAMAL.app(role: role, host: host).remove_container(version: version)
|
|
@@ -275,7 +284,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
275
284
|
|
|
276
285
|
desc "remove_containers", "Remove all app containers from servers", hide: true
|
|
277
286
|
def remove_containers
|
|
278
|
-
|
|
287
|
+
modify(lock: true) do
|
|
279
288
|
on_roles(KAMAL.roles, hosts: KAMAL.app_hosts) do |host, role|
|
|
280
289
|
execute *KAMAL.auditor.record("Removed all app containers", role: role), verbosity: :debug
|
|
281
290
|
execute *KAMAL.app(role: role, host: host).remove_containers
|
|
@@ -285,7 +294,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
285
294
|
|
|
286
295
|
desc "remove_images", "Remove all app images from servers", hide: true
|
|
287
296
|
def remove_images
|
|
288
|
-
|
|
297
|
+
modify(lock: true) do
|
|
289
298
|
on(hosts_removing_all_roles) do
|
|
290
299
|
execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
|
|
291
300
|
execute *KAMAL.app.remove_images
|
|
@@ -295,7 +304,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
295
304
|
|
|
296
305
|
desc "remove_app_directories", "Remove the app directories from servers", hide: true
|
|
297
306
|
def remove_app_directories
|
|
298
|
-
|
|
307
|
+
modify(lock: true) do
|
|
299
308
|
on(hosts_removing_all_roles) do |host|
|
|
300
309
|
execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
|
|
301
310
|
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}"), verbosity: :debug
|
|
@@ -347,7 +356,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
347
356
|
|
|
348
357
|
def with_lock_if_stopping
|
|
349
358
|
if options[:stop]
|
|
350
|
-
|
|
359
|
+
modify(lock: true) { yield }
|
|
351
360
|
else
|
|
352
361
|
yield
|
|
353
362
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Kamal::Cli
|
|
|
6
6
|
include SSHKit::DSL
|
|
7
7
|
|
|
8
8
|
VERBOSITY = { verbose: :debug, quiet: :error }.freeze
|
|
9
|
+
AUTOMATIC_DEPLOY_LOCK_MESSAGE = "Automatic deploy lock"
|
|
10
|
+
|
|
11
|
+
class LockHeldError < StandardError; end
|
|
12
|
+
class LockMissingError < StandardError; end
|
|
9
13
|
|
|
10
14
|
def self.exit_on_failure?() true end
|
|
11
15
|
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
|
|
@@ -24,6 +28,10 @@ module Kamal::Cli
|
|
|
24
28
|
|
|
25
29
|
class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
|
|
26
30
|
|
|
31
|
+
class_option :lock_wait, type: :boolean, default: false, desc: "Wait for the deploy lock if it's already held instead of failing immediately"
|
|
32
|
+
class_option :lock_wait_timeout, type: :numeric, default: 900, desc: "Maximum seconds to wait for the deploy lock when --lock-wait is set"
|
|
33
|
+
class_option :lock_wait_interval, type: :numeric, default: 15, desc: "Seconds between deploy lock polls when --lock-wait is set"
|
|
34
|
+
|
|
27
35
|
def initialize(args = [], local_options = {}, config = {})
|
|
28
36
|
if config[:current_command].is_a?(Kamal::Cli::Alias::Command)
|
|
29
37
|
# When Thor generates a dynamic command, it doesn't attempt to parse the arguments.
|
|
@@ -60,6 +68,10 @@ module Kamal::Cli
|
|
|
60
68
|
commander.specific_hosts = options[:hosts]&.split(",")
|
|
61
69
|
commander.specific_roles = options[:roles]&.split(",")
|
|
62
70
|
commander.specific_primary! if options[:primary]
|
|
71
|
+
|
|
72
|
+
commander.lock_wait = options[:lock_wait]
|
|
73
|
+
commander.lock_wait_timeout = options[:lock_wait_timeout]
|
|
74
|
+
commander.lock_wait_interval = options[:lock_wait_interval]
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
|
|
@@ -72,6 +84,23 @@ module Kamal::Cli
|
|
|
72
84
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
|
73
85
|
end
|
|
74
86
|
|
|
87
|
+
def modify(lock: false)
|
|
88
|
+
KAMAL.modify(command: command, subcommand: subcommand) do
|
|
89
|
+
lock ? with_lock { yield } : yield
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def say(message = "", *)
|
|
94
|
+
super unless options[:raw]
|
|
95
|
+
KAMAL.log(message.to_s)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Raw output is written straight to stdout for piping, so silence SSHKit's
|
|
99
|
+
# command echoing that would otherwise corrupt the byte stream.
|
|
100
|
+
def with_raw_output(raw, &block)
|
|
101
|
+
raw ? KAMAL.with_verbosity(:error, &block) : block.call
|
|
102
|
+
end
|
|
103
|
+
|
|
75
104
|
def with_lock
|
|
76
105
|
if KAMAL.holding_lock?
|
|
77
106
|
yield
|
|
@@ -106,31 +135,90 @@ module Kamal::Cli
|
|
|
106
135
|
def acquire_lock
|
|
107
136
|
ensure_run_directory
|
|
108
137
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
if KAMAL.lock_wait
|
|
139
|
+
acquire_lock_with_wait
|
|
140
|
+
else
|
|
141
|
+
raise_if_locked do
|
|
142
|
+
say "Acquiring the deploy lock...", :magenta
|
|
143
|
+
execute_lock_acquire(AUTOMATIC_DEPLOY_LOCK_MESSAGE)
|
|
144
|
+
end
|
|
112
145
|
end
|
|
113
146
|
|
|
114
147
|
KAMAL.holding_lock = true
|
|
115
148
|
end
|
|
116
149
|
|
|
150
|
+
def acquire_lock_with_wait
|
|
151
|
+
timeout = KAMAL.lock_wait_timeout
|
|
152
|
+
interval = KAMAL.lock_wait_interval
|
|
153
|
+
deadline = Time.now + timeout
|
|
154
|
+
details_shown = false
|
|
155
|
+
|
|
156
|
+
say "Acquiring the deploy lock (waiting up to #{timeout}s)...", :magenta
|
|
157
|
+
|
|
158
|
+
loop do
|
|
159
|
+
execute_lock_acquire(AUTOMATIC_DEPLOY_LOCK_MESSAGE)
|
|
160
|
+
break
|
|
161
|
+
rescue LockHeldError
|
|
162
|
+
unless details_shown
|
|
163
|
+
status = capture_lock_status
|
|
164
|
+
|
|
165
|
+
say "Deploy lock is held by:", :magenta
|
|
166
|
+
puts status
|
|
167
|
+
|
|
168
|
+
unless status.include?(AUTOMATIC_DEPLOY_LOCK_MESSAGE)
|
|
169
|
+
raise LockError, "Deploy lock held manually, not waiting. Run 'kamal lock help' for more information"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
details_shown = true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
remaining = (deadline - Time.now).to_i
|
|
176
|
+
if remaining <= 0
|
|
177
|
+
say "Timed out after #{timeout}s waiting for the deploy lock", :red
|
|
178
|
+
raise LockError, "Timed out waiting for deploy lock"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
say "Retrying in #{interval}s (#{remaining}s remaining)...", :magenta
|
|
182
|
+
sleep [ interval, remaining ].min
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
117
186
|
def release_lock
|
|
118
187
|
say "Releasing the deploy lock...", :magenta
|
|
119
|
-
|
|
188
|
+
execute_lock_release
|
|
120
189
|
|
|
121
190
|
KAMAL.holding_lock = false
|
|
122
191
|
end
|
|
123
192
|
|
|
124
193
|
def raise_if_locked
|
|
125
194
|
yield
|
|
195
|
+
rescue LockHeldError
|
|
196
|
+
say "Deploy lock already in place!", :red
|
|
197
|
+
puts capture_lock_status
|
|
198
|
+
raise LockError, "Deploy lock found. Run 'kamal lock help' for more information"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def execute_lock_acquire(message)
|
|
202
|
+
on(KAMAL.primary_host) { execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug }
|
|
126
203
|
rescue SSHKit::Runner::ExecuteError => e
|
|
127
|
-
if e.message =~ /cannot create directory/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
204
|
+
raise LockHeldError if e.message =~ /cannot create directory/
|
|
205
|
+
raise
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def execute_lock_release
|
|
209
|
+
on(KAMAL.primary_host) { execute *KAMAL.lock.release, verbosity: :debug }
|
|
210
|
+
rescue SSHKit::Runner::ExecuteError => e
|
|
211
|
+
raise LockMissingError if e.message =~ /No such file or directory/
|
|
212
|
+
raise
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def capture_lock_status
|
|
216
|
+
status = nil
|
|
217
|
+
on(KAMAL.primary_host) { status = capture_with_debug(*KAMAL.lock.status) }
|
|
218
|
+
status
|
|
219
|
+
rescue SSHKit::Runner::ExecuteError => e
|
|
220
|
+
raise LockMissingError if e.message =~ /No such file or directory/
|
|
221
|
+
raise
|
|
134
222
|
end
|
|
135
223
|
|
|
136
224
|
def run_hook(hook, **extra_details)
|
data/lib/kamal/cli/build.rb
CHANGED
|
@@ -18,7 +18,8 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
|
18
18
|
pre_connect_if_required
|
|
19
19
|
|
|
20
20
|
ensure_docker_installed
|
|
21
|
-
|
|
21
|
+
setup_local_registry if KAMAL.registry.local?
|
|
22
|
+
login_to_registry_locally if !KAMAL.registry.local? && KAMAL.builder.login_to_registry_locally?
|
|
22
23
|
|
|
23
24
|
run_hook "pre-build"
|
|
24
25
|
|
|
@@ -194,13 +195,15 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
|
194
195
|
end
|
|
195
196
|
end
|
|
196
197
|
|
|
198
|
+
def setup_local_registry
|
|
199
|
+
run_locally do
|
|
200
|
+
execute *KAMAL.registry.setup
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
197
204
|
def login_to_registry_locally
|
|
198
205
|
run_locally do
|
|
199
|
-
|
|
200
|
-
execute *KAMAL.registry.setup
|
|
201
|
-
else
|
|
202
|
-
execute *KAMAL.registry.login
|
|
203
|
-
end
|
|
206
|
+
execute *KAMAL.registry.login
|
|
204
207
|
end
|
|
205
208
|
end
|
|
206
209
|
|
data/lib/kamal/cli/lock.rb
CHANGED
|
@@ -2,22 +2,17 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|
|
2
2
|
desc "status", "Report lock status"
|
|
3
3
|
def status
|
|
4
4
|
handle_missing_lock do
|
|
5
|
-
|
|
6
|
-
puts capture_with_debug(*KAMAL.lock.status)
|
|
7
|
-
end
|
|
5
|
+
puts capture_lock_status
|
|
8
6
|
end
|
|
9
7
|
end
|
|
10
8
|
|
|
11
9
|
desc "acquire", "Acquire the deploy lock"
|
|
12
10
|
option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
|
|
13
11
|
def acquire
|
|
14
|
-
message = options[:message]
|
|
15
12
|
ensure_run_directory
|
|
16
13
|
|
|
17
14
|
raise_if_locked do
|
|
18
|
-
|
|
19
|
-
execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
|
|
20
|
-
end
|
|
15
|
+
execute_lock_acquire(options[:message])
|
|
21
16
|
say "Acquired the deploy lock"
|
|
22
17
|
end
|
|
23
18
|
end
|
|
@@ -25,9 +20,7 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|
|
25
20
|
desc "release", "Release the deploy lock"
|
|
26
21
|
def release
|
|
27
22
|
handle_missing_lock do
|
|
28
|
-
|
|
29
|
-
execute *KAMAL.lock.release, verbosity: :debug
|
|
30
|
-
end
|
|
23
|
+
execute_lock_release
|
|
31
24
|
say "Released the deploy lock"
|
|
32
25
|
end
|
|
33
26
|
end
|
|
@@ -35,11 +28,7 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|
|
35
28
|
private
|
|
36
29
|
def handle_missing_lock
|
|
37
30
|
yield
|
|
38
|
-
rescue
|
|
39
|
-
|
|
40
|
-
say "There is no deploy lock"
|
|
41
|
-
else
|
|
42
|
-
raise
|
|
43
|
-
end
|
|
31
|
+
rescue LockMissingError
|
|
32
|
+
say "There is no deploy lock"
|
|
44
33
|
end
|
|
45
34
|
end
|