dash 3.1.3 → 3.1.4
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/base.rb +101 -8
- data/lib/kamal/cli/proxy.rb +11 -11
- data/lib/kamal/cli/prune.rb +3 -3
- data/lib/kamal/commander.rb +12 -1
- data/lib/kamal/commands/lock.rb +24 -2
- data/lib/kamal/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c3e7210d642d1f0fc0d9a414cdf74a0c4a1ec9e09cf2f534c1cb1f6b02342a5
|
|
4
|
+
data.tar.gz: d91f21902a85f56d76bfde689a75d6c3d5b10aa587138de732943e3a0015433d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e62792a82328365e87efd4428cfab12808281fc617157f8699e757962b742988560ff66ebdd0c5fa7f36f6071b1a46441284038c442e74724ec0b1caa354f5c9
|
|
7
|
+
data.tar.gz: f328a6e03c4d302170ad406a6550ddd95987622b26af74442945b25cec44081074b995a633b97f546bb873b97e3b9b3905c78973526698a5e0dd4d5635f6dd13
|
data/lib/kamal/cli/base.rb
CHANGED
|
@@ -125,9 +125,13 @@ module Kamal::Cli
|
|
|
125
125
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
# Deploy lock outside, server lock inside. Every caller acquires in that
|
|
129
|
+
# order, and deploy locks are unique per destination, so no cycle forms.
|
|
130
|
+
def modify(lock: false, server_lock: false)
|
|
129
131
|
KAMAL.modify(command: command, subcommand: subcommand) do
|
|
130
|
-
|
|
132
|
+
guarded = -> { server_lock ? with_server_lock { yield } : yield }
|
|
133
|
+
|
|
134
|
+
lock ? with_lock(&guarded) : guarded.call
|
|
131
135
|
end
|
|
132
136
|
end
|
|
133
137
|
|
|
@@ -142,6 +146,95 @@ module Kamal::Cli
|
|
|
142
146
|
raw ? KAMAL.with_verbosity(:error, &block) : block.call
|
|
143
147
|
end
|
|
144
148
|
|
|
149
|
+
# kamal-proxy is one container per host, shared by every destination
|
|
150
|
+
# deployed there, but the deploy lock is per-destination — so two
|
|
151
|
+
# destinations deploying at once take different locks and both mutate the
|
|
152
|
+
# same proxy. Anything touching the proxy takes this lock as well.
|
|
153
|
+
def with_server_lock
|
|
154
|
+
if KAMAL.holding_server_lock?
|
|
155
|
+
yield
|
|
156
|
+
else
|
|
157
|
+
acquire_server_lock
|
|
158
|
+
|
|
159
|
+
begin
|
|
160
|
+
yield
|
|
161
|
+
ensure
|
|
162
|
+
release_server_lock
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Always waits rather than failing: the guarded work is short, and
|
|
168
|
+
# aborting the second destination would defeat running them in parallel.
|
|
169
|
+
#
|
|
170
|
+
# Hosts are taken one at a time and rolled back on contention. Taking them
|
|
171
|
+
# in one `on(hosts)` sweep would leave the locks we did win in place, and
|
|
172
|
+
# every retry would then collide with itself and wait out the timeout.
|
|
173
|
+
def acquire_server_lock
|
|
174
|
+
ensure_run_directory
|
|
175
|
+
|
|
176
|
+
timeout = KAMAL.lock_wait_timeout
|
|
177
|
+
interval = KAMAL.lock_wait_interval
|
|
178
|
+
deadline = Time.now + timeout
|
|
179
|
+
details_shown = false
|
|
180
|
+
|
|
181
|
+
say "Acquiring the server lock...", :magenta
|
|
182
|
+
|
|
183
|
+
loop do
|
|
184
|
+
held = []
|
|
185
|
+
|
|
186
|
+
begin
|
|
187
|
+
server_lock_hosts.each do |host|
|
|
188
|
+
execute_lock_acquire(AUTOMATIC_DEPLOY_LOCK_MESSAGE, lock: KAMAL.server_lock, hosts: host)
|
|
189
|
+
held << host
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
break
|
|
193
|
+
rescue LockHeldError
|
|
194
|
+
release_server_lock_on(held)
|
|
195
|
+
|
|
196
|
+
unless details_shown
|
|
197
|
+
say "Server lock is held by:", :magenta
|
|
198
|
+
puts capture_lock_status(lock: KAMAL.server_lock, hosts: server_lock_hosts.first)
|
|
199
|
+
details_shown = true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
remaining = (deadline - Time.now).to_i
|
|
203
|
+
if remaining <= 0
|
|
204
|
+
say "Timed out after #{timeout}s waiting for the server lock", :red
|
|
205
|
+
raise LockError, "Timed out waiting for server lock"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
say "Waiting #{interval}s for the server lock (#{remaining}s remaining)...", :magenta
|
|
209
|
+
sleep [ interval, remaining ].min
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
KAMAL.holding_server_lock = true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def release_server_lock
|
|
217
|
+
say "Releasing the server lock...", :magenta
|
|
218
|
+
release_server_lock_on(server_lock_hosts)
|
|
219
|
+
|
|
220
|
+
KAMAL.holding_server_lock = false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Only ever called with hosts this process actually locked, so a missing
|
|
224
|
+
# directory means someone already cleaned up, not that we may delete
|
|
225
|
+
# another deploy's lock.
|
|
226
|
+
def release_server_lock_on(hosts)
|
|
227
|
+
Array(hosts).each do |host|
|
|
228
|
+
execute_lock_release(lock: KAMAL.server_lock, hosts: host)
|
|
229
|
+
rescue LockMissingError
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def server_lock_hosts
|
|
235
|
+
KAMAL.proxy_hosts.presence || KAMAL.hosts
|
|
236
|
+
end
|
|
237
|
+
|
|
145
238
|
def with_lock
|
|
146
239
|
if KAMAL.holding_lock?
|
|
147
240
|
yield
|
|
@@ -239,23 +332,23 @@ module Kamal::Cli
|
|
|
239
332
|
raise LockError, "Deploy lock found. Run 'kamal lock help' for more information"
|
|
240
333
|
end
|
|
241
334
|
|
|
242
|
-
def execute_lock_acquire(message)
|
|
243
|
-
on(
|
|
335
|
+
def execute_lock_acquire(message, lock: KAMAL.lock, hosts: KAMAL.primary_host)
|
|
336
|
+
on(hosts) { execute *lock.acquire(message, KAMAL.config.version), verbosity: :debug }
|
|
244
337
|
rescue SSHKit::Runner::ExecuteError => e
|
|
245
338
|
raise LockHeldError if e.message =~ /cannot create directory/
|
|
246
339
|
raise
|
|
247
340
|
end
|
|
248
341
|
|
|
249
|
-
def execute_lock_release
|
|
250
|
-
on(
|
|
342
|
+
def execute_lock_release(lock: KAMAL.lock, hosts: KAMAL.primary_host)
|
|
343
|
+
on(hosts) { execute *lock.release, verbosity: :debug }
|
|
251
344
|
rescue SSHKit::Runner::ExecuteError => e
|
|
252
345
|
raise LockMissingError if e.message =~ /No such file or directory/
|
|
253
346
|
raise
|
|
254
347
|
end
|
|
255
348
|
|
|
256
|
-
def capture_lock_status
|
|
349
|
+
def capture_lock_status(lock: KAMAL.lock, hosts: KAMAL.primary_host)
|
|
257
350
|
status = nil
|
|
258
|
-
on(
|
|
351
|
+
on(hosts) { status = capture_with_debug(*lock.status) }
|
|
259
352
|
status
|
|
260
353
|
rescue SSHKit::Runner::ExecuteError => e
|
|
261
354
|
raise LockMissingError if e.message =~ /No such file or directory/
|
data/lib/kamal/cli/proxy.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
2
2
|
desc "boot", "Boot proxy on servers"
|
|
3
3
|
def boot
|
|
4
|
-
modify(lock: true) do
|
|
4
|
+
modify(lock: true, server_lock: true) do
|
|
5
5
|
on(KAMAL.hosts) do |host|
|
|
6
6
|
execute *KAMAL.docker.create_network
|
|
7
7
|
rescue SSHKit::Command::Failed => e
|
|
@@ -215,7 +215,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
215
215
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
216
216
|
def reboot
|
|
217
217
|
confirming "This will cause a brief outage on each host. Are you sure?" do
|
|
218
|
-
modify(lock: true) do
|
|
218
|
+
modify(lock: true, server_lock: true) do
|
|
219
219
|
# Skip proxy on loadbalancer host - it will be handled by loadbalancer reboot
|
|
220
220
|
proxy_hosts = KAMAL.proxy_hosts
|
|
221
221
|
if KAMAL.config.proxy.loadbalancer_on_proxy_host?
|
|
@@ -292,7 +292,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
292
292
|
|
|
293
293
|
desc "start", "Start existing proxy container on servers"
|
|
294
294
|
def start
|
|
295
|
-
modify(lock: true) do
|
|
295
|
+
modify(lock: true, server_lock: true) do
|
|
296
296
|
on(KAMAL.proxy_hosts) do |host|
|
|
297
297
|
execute *KAMAL.auditor.record("Started proxy"), verbosity: :debug
|
|
298
298
|
execute *KAMAL.proxy(host).start
|
|
@@ -309,7 +309,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
309
309
|
|
|
310
310
|
desc "stop", "Stop existing proxy container on servers"
|
|
311
311
|
def stop
|
|
312
|
-
modify(lock: true) do
|
|
312
|
+
modify(lock: true, server_lock: true) do
|
|
313
313
|
on(KAMAL.proxy_hosts) do |host|
|
|
314
314
|
execute *KAMAL.auditor.record("Stopped proxy"), verbosity: :debug
|
|
315
315
|
execute *KAMAL.proxy(host).stop, raise_on_non_zero_exit: false
|
|
@@ -329,7 +329,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
329
329
|
|
|
330
330
|
desc "restart", "Restart existing proxy container on servers"
|
|
331
331
|
def restart
|
|
332
|
-
modify(lock: true) do
|
|
332
|
+
modify(lock: true, server_lock: true) do
|
|
333
333
|
stop
|
|
334
334
|
start
|
|
335
335
|
end
|
|
@@ -377,7 +377,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
377
377
|
desc "remove", "Remove proxy container and image from servers"
|
|
378
378
|
option :force, type: :boolean, default: false, desc: "Force removing proxy when apps are still installed"
|
|
379
379
|
def remove
|
|
380
|
-
modify(lock: true) do
|
|
380
|
+
modify(lock: true, server_lock: true) do
|
|
381
381
|
if removal_allowed?(options[:force])
|
|
382
382
|
stop
|
|
383
383
|
remove_container
|
|
@@ -510,7 +510,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
510
510
|
|
|
511
511
|
# Under the deploy lock: a concurrent deploy could boot or reboot the
|
|
512
512
|
# proxy mid-export, and the offline read below would archive a torn store.
|
|
513
|
-
modify(lock: true) do
|
|
513
|
+
modify(lock: true, server_lock: true) do
|
|
514
514
|
on(cert_store_host) do |host|
|
|
515
515
|
execute *KAMAL.auditor.record("Exported the proxy certificate store"), verbosity: :debug
|
|
516
516
|
|
|
@@ -550,7 +550,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
550
550
|
force, verify = options[:force], options[:verify]
|
|
551
551
|
load_balancing = KAMAL.config.proxy.load_balancing?
|
|
552
552
|
|
|
553
|
-
modify(lock: true) do
|
|
553
|
+
modify(lock: true, server_lock: true) do
|
|
554
554
|
on(cert_store_host) do |host|
|
|
555
555
|
commands = load_balancing ? KAMAL.loadbalancer : KAMAL.proxy(host)
|
|
556
556
|
|
|
@@ -583,7 +583,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
583
583
|
|
|
584
584
|
desc "remove_container", "Remove proxy container from servers", hide: true
|
|
585
585
|
def remove_container
|
|
586
|
-
modify(lock: true) do
|
|
586
|
+
modify(lock: true, server_lock: true) do
|
|
587
587
|
on(KAMAL.proxy_hosts) do
|
|
588
588
|
execute *KAMAL.auditor.record("Removed proxy container"), verbosity: :debug
|
|
589
589
|
execute *KAMAL.proxy(host).remove_container
|
|
@@ -600,7 +600,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
600
600
|
|
|
601
601
|
desc "remove_image", "Remove proxy image from servers", hide: true
|
|
602
602
|
def remove_image
|
|
603
|
-
modify(lock: true) do
|
|
603
|
+
modify(lock: true, server_lock: true) do
|
|
604
604
|
on(KAMAL.proxy_hosts) do
|
|
605
605
|
execute *KAMAL.auditor.record("Removed proxy image"), verbosity: :debug
|
|
606
606
|
execute *KAMAL.proxy(host).remove_image
|
|
@@ -617,7 +617,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
617
617
|
|
|
618
618
|
desc "remove_proxy_directory", "Remove the proxy directory from servers", hide: true
|
|
619
619
|
def remove_proxy_directory
|
|
620
|
-
modify(lock: true) do
|
|
620
|
+
modify(lock: true, server_lock: true) do
|
|
621
621
|
on(KAMAL.proxy_hosts) do
|
|
622
622
|
execute *KAMAL.proxy(host).remove_proxy_directory, raise_on_non_zero_exit: false
|
|
623
623
|
end
|
data/lib/kamal/cli/prune.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
2
2
|
desc "all", "Prune unused images and stopped containers"
|
|
3
3
|
def all
|
|
4
|
-
modify(lock: true) do
|
|
4
|
+
modify(lock: true, server_lock: true) do
|
|
5
5
|
containers
|
|
6
6
|
images
|
|
7
7
|
end
|
|
@@ -9,7 +9,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
|
9
9
|
|
|
10
10
|
desc "images", "Prune unused images"
|
|
11
11
|
def images
|
|
12
|
-
modify(lock: true) do
|
|
12
|
+
modify(lock: true, server_lock: true) do
|
|
13
13
|
on(KAMAL.hosts) do
|
|
14
14
|
execute *KAMAL.auditor.record("Pruned images"), verbosity: :debug
|
|
15
15
|
execute *KAMAL.prune.dangling_images
|
|
@@ -24,7 +24,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
|
24
24
|
retain = options.fetch(:retain, KAMAL.config.retain_containers)
|
|
25
25
|
raise "retain must be at least 1" if retain < 1
|
|
26
26
|
|
|
27
|
-
modify(lock: true) do
|
|
27
|
+
modify(lock: true, server_lock: true) do
|
|
28
28
|
on(KAMAL.hosts) do |host|
|
|
29
29
|
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
|
30
30
|
|
data/lib/kamal/commander.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "active_support/broadcast_logger"
|
|
|
5
5
|
require "active_support/notifications"
|
|
6
6
|
|
|
7
7
|
class Kamal::Commander
|
|
8
|
-
attr_accessor :verbosity, :holding_lock, :connected, :logging, :lock_wait, :lock_wait_timeout, :lock_wait_interval
|
|
8
|
+
attr_accessor :verbosity, :holding_lock, :holding_server_lock, :connected, :logging, :lock_wait, :lock_wait_timeout, :lock_wait_interval
|
|
9
9
|
attr_reader :specific_roles, :specific_hosts
|
|
10
10
|
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
|
|
11
11
|
|
|
@@ -16,6 +16,7 @@ class Kamal::Commander
|
|
|
16
16
|
def reset
|
|
17
17
|
self.verbosity = :info
|
|
18
18
|
self.holding_lock = ENV["KAMAL_LOCK"] == "true"
|
|
19
|
+
self.holding_server_lock = ENV["KAMAL_SERVER_LOCK"] == "true"
|
|
19
20
|
self.connected = false
|
|
20
21
|
self.logging = false
|
|
21
22
|
self.lock_wait = false
|
|
@@ -109,6 +110,12 @@ class Kamal::Commander
|
|
|
109
110
|
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
|
110
111
|
end
|
|
111
112
|
|
|
113
|
+
# Guards the kamal-proxy container, which a host runs once for every
|
|
114
|
+
# destination deployed onto it. Every destination contends for this one.
|
|
115
|
+
def server_lock
|
|
116
|
+
@commands[:server_lock] ||= Kamal::Commands::Lock.new(config, scope: :server)
|
|
117
|
+
end
|
|
118
|
+
|
|
112
119
|
def proxy(host)
|
|
113
120
|
Kamal::Commands::Proxy.new(config, host: host)
|
|
114
121
|
end
|
|
@@ -174,6 +181,10 @@ class Kamal::Commander
|
|
|
174
181
|
output_logger << "#{line}\n" if logging
|
|
175
182
|
end
|
|
176
183
|
|
|
184
|
+
def holding_server_lock?
|
|
185
|
+
self.holding_server_lock
|
|
186
|
+
end
|
|
187
|
+
|
|
177
188
|
def holding_lock?
|
|
178
189
|
self.holding_lock
|
|
179
190
|
end
|
data/lib/kamal/commands/lock.rb
CHANGED
|
@@ -3,6 +3,21 @@ require "time"
|
|
|
3
3
|
require "base64"
|
|
4
4
|
|
|
5
5
|
class Kamal::Commands::Lock < Kamal::Commands::Base
|
|
6
|
+
# The deploy lock is scoped to service+destination, so two destinations can
|
|
7
|
+
# deploy at once. Server-scoped resources — above all the single kamal-proxy
|
|
8
|
+
# container a host runs for every destination on it — need a lock that every
|
|
9
|
+
# destination contends for, hence :server.
|
|
10
|
+
SCOPES = %i[ destination server ].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :scope
|
|
13
|
+
|
|
14
|
+
def initialize(config, scope: :destination)
|
|
15
|
+
raise ArgumentError, "Unknown lock scope: #{scope}" unless SCOPES.include?(scope)
|
|
16
|
+
|
|
17
|
+
super(config)
|
|
18
|
+
@scope = scope
|
|
19
|
+
end
|
|
20
|
+
|
|
6
21
|
def acquire(message, version)
|
|
7
22
|
combine \
|
|
8
23
|
[ :mkdir, lock_dir ],
|
|
@@ -45,9 +60,16 @@ class Kamal::Commands::Lock < Kamal::Commands::Base
|
|
|
45
60
|
end
|
|
46
61
|
|
|
47
62
|
def lock_dir
|
|
48
|
-
|
|
63
|
+
File.join(config.run_directory, lock_dir_name)
|
|
64
|
+
end
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
# A server-scoped lock deliberately carries neither service nor destination:
|
|
67
|
+
# every deploy reaching this host must collide on the same directory.
|
|
68
|
+
def lock_dir_name
|
|
69
|
+
case scope
|
|
70
|
+
when :server then "lock-server"
|
|
71
|
+
when :destination then [ "lock", config.service, config.destination ].compact.join("-")
|
|
72
|
+
end
|
|
51
73
|
end
|
|
52
74
|
|
|
53
75
|
def lock_details_file
|
data/lib/kamal/version.rb
CHANGED