dash 4.0.3 → 4.0.5
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/bin/dash +5 -0
- data/lib/dash/cli/app/boot.rb +27 -14
- data/lib/dash/cli/app.rb +16 -2
- data/lib/dash/cli/base.rb +9 -0
- data/lib/dash/cli/main.rb +15 -15
- data/lib/dash/cli/proxy/loadbalancer_reboot.rb +11 -0
- data/lib/dash/cli/proxy.rb +14 -3
- data/lib/dash/commander.rb +2 -1
- data/lib/dash/configuration/boot.rb +33 -0
- data/lib/dash/configuration/docs/boot.yml +14 -0
- data/lib/dash/configuration/docs/configuration.yml +1 -1
- data/lib/dash/configuration/docs/env.yml +1 -1
- data/lib/dash/configuration/docs/proxy.yml +1 -1
- data/lib/dash/configuration/proxy/run.rb +1 -1
- data/lib/dash/configuration.rb +3 -3
- data/lib/dash/timings.rb +45 -0
- data/lib/dash/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee8ed58f4d81341ced43dc7d970c081a4c92becc3492860cf3ec8fd1a80687ec
|
|
4
|
+
data.tar.gz: 588e1b33ca3f041a54beb773472cab9b8144892a32972b71ae9c58306cbb8612
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b24d5758340f3e3b7b7b6ecf1df765ffabab7360ddd5bc0df99dad3b90ba3662fc65dcbbe6bfa99af29dea90efb977f4b3d5a2d68454c1e652d57602857f9166
|
|
7
|
+
data.tar.gz: bbc5bfea941e10310fd01776548b462e9e0e82d4228ed25bb66a0c16b35083979473cb2232ccb7bab3820ccaa45ae32303f424ffebfe76ac321b84354c54cf2e
|
data/bin/dash
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
# Prevent failures from being reported twice.
|
|
4
4
|
Thread.report_on_exception = false
|
|
5
5
|
|
|
6
|
+
# Ruby block-buffers stdout when it is not a TTY, which is every CI runner. Line-by-line
|
|
7
|
+
# writes keep the runner's timestamps attributable to the command that printed them.
|
|
8
|
+
$stdout.sync = true
|
|
9
|
+
$stderr.sync = true
|
|
10
|
+
|
|
6
11
|
require "dash"
|
|
7
12
|
|
|
8
13
|
begin
|
data/lib/dash/cli/app/boot.rb
CHANGED
|
@@ -14,22 +14,26 @@ class Dash::Cli::App::Boot
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def run
|
|
17
|
-
|
|
17
|
+
DASH.timings.phase("#{role} #{host}", depth: 1) do |timing|
|
|
18
|
+
@timing = timing
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
old_version = old_version_renamed_if_clashing
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
wait_at_barrier if queuer?
|
|
23
|
+
|
|
24
|
+
begin
|
|
25
|
+
start_new_version
|
|
26
|
+
rescue => e
|
|
27
|
+
close_barrier if gatekeeper?
|
|
28
|
+
stop_new_version
|
|
29
|
+
raise
|
|
30
|
+
end
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
release_barrier if gatekeeper?
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
if old_version
|
|
35
|
+
stop_old_version(old_version)
|
|
36
|
+
end
|
|
33
37
|
end
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -59,10 +63,10 @@ class Dash::Cli::App::Boot
|
|
|
59
63
|
|
|
60
64
|
run_hook "pre-proxy-deploy", hosts: host.to_s, role: role.name
|
|
61
65
|
info "Deploying #{role} on #{host} via dash-proxy (waiting up to #{DASH.config.deploy_timeout}s for it to become healthy)..."
|
|
62
|
-
execute *app.deploy(target: endpoint)
|
|
66
|
+
timing_healthy { execute *app.deploy(target: endpoint) }
|
|
63
67
|
run_hook "post-proxy-deploy", hosts: host.to_s, role: role.name
|
|
64
68
|
else
|
|
65
|
-
Dash::Cli::Healthcheck::Poller.wait_for_healthy(role: role) { health_status }
|
|
69
|
+
timing_healthy { Dash::Cli::Healthcheck::Poller.wait_for_healthy(role: role) { health_status } }
|
|
66
70
|
end
|
|
67
71
|
rescue => e
|
|
68
72
|
error "Failed to boot #{role} on #{host}"
|
|
@@ -136,6 +140,15 @@ class Dash::Cli::App::Boot
|
|
|
136
140
|
end
|
|
137
141
|
end
|
|
138
142
|
|
|
143
|
+
# The readiness wait is the part of a host's boot an operator can actually tune
|
|
144
|
+
# (health check interval, app boot time), so it gets called out on the host's entry.
|
|
145
|
+
def timing_healthy
|
|
146
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
147
|
+
yield
|
|
148
|
+
ensure
|
|
149
|
+
@timing.detail = format("healthy after %.1fs", Process.clock_gettime(Process::CLOCK_MONOTONIC) - started)
|
|
150
|
+
end
|
|
151
|
+
|
|
139
152
|
def barrier_role?
|
|
140
153
|
role == DASH.primary_role
|
|
141
154
|
end
|
data/lib/dash/cli/app.rb
CHANGED
|
@@ -426,10 +426,24 @@ class Dash::Cli::App < Dash::Cli::Base
|
|
|
426
426
|
end
|
|
427
427
|
end
|
|
428
428
|
|
|
429
|
+
# Canary hosts each form a group of one and go first; whatever is left is sliced by
|
|
430
|
+
# `limit` (a percentage counts those remaining hosts, the set it actually slices) or
|
|
431
|
+
# boots as a single group. No canary and no limit is the one-group default.
|
|
429
432
|
def host_boot_groups
|
|
430
433
|
hosts = DASH.app_hosts
|
|
431
|
-
|
|
434
|
+
canary_hosts = canary_boot_hosts(hosts)
|
|
435
|
+
rest = hosts - canary_hosts
|
|
436
|
+
limit = DASH.config.boot.limit_for(rest)
|
|
432
437
|
|
|
433
|
-
limit ?
|
|
438
|
+
rest_groups = limit ? rest.each_slice(limit).to_a : [ rest ]
|
|
439
|
+
|
|
440
|
+
canary_hosts.map { |host| [ host ] } + rest_groups.reject(&:empty?)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# The first N primary-role hosts of this run. `--roles`/`--hosts` may leave none, in
|
|
444
|
+
# which case there is nothing to prove ahead of the others and no canary group.
|
|
445
|
+
def canary_boot_hosts(hosts)
|
|
446
|
+
count = DASH.config.boot.canary
|
|
447
|
+
count ? (DASH.primary_role.hosts & hosts).first(count) : []
|
|
434
448
|
end
|
|
435
449
|
end
|
data/lib/dash/cli/base.rb
CHANGED
|
@@ -140,13 +140,22 @@ module Dash::Cli
|
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# `setup` wraps `deploy`, so this nests. Each level reports its own total; the phase
|
|
144
|
+
# table belongs to the outermost, which is the one that saw every phase.
|
|
143
145
|
def print_runtime
|
|
144
146
|
started_at = Time.now
|
|
147
|
+
@print_runtime_depth = @print_runtime_depth.to_i + 1
|
|
145
148
|
yield
|
|
146
149
|
Time.now - started_at
|
|
147
150
|
ensure
|
|
151
|
+
@print_runtime_depth -= 1
|
|
148
152
|
runtime = Time.now - started_at
|
|
149
153
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
|
154
|
+
puts DASH.timings.lines if @print_runtime_depth.zero? && DASH.timings.any?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def timed(name, depth: 0, &block)
|
|
158
|
+
DASH.timings.phase(name, depth: depth, &block)
|
|
150
159
|
end
|
|
151
160
|
|
|
152
161
|
# Deploy lock outside, server lock inside. Every caller acquires in that
|
data/lib/dash/cli/main.rb
CHANGED
|
@@ -8,7 +8,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
8
8
|
invoke_options = deploy_options
|
|
9
9
|
|
|
10
10
|
say "Ensure Docker is installed...", :magenta
|
|
11
|
-
invoke "dash:cli:server:bootstrap", [], invoke_options
|
|
11
|
+
timed("Ensure Docker is installed") { invoke "dash:cli:server:bootstrap", [], invoke_options }
|
|
12
12
|
|
|
13
13
|
deploy(boot_accessories: true)
|
|
14
14
|
end
|
|
@@ -30,32 +30,32 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
30
30
|
|
|
31
31
|
if options[:skip_push]
|
|
32
32
|
say "Pull app image...", :magenta
|
|
33
|
-
invoke "dash:cli:build:pull", [], invoke_options
|
|
33
|
+
timed("Pull app image") { invoke "dash:cli:build:pull", [], invoke_options }
|
|
34
34
|
else
|
|
35
35
|
say "Build and push app image...", :magenta
|
|
36
|
-
invoke "dash:cli:build:deliver", [], invoke_options
|
|
36
|
+
timed("Build and push app image") { invoke "dash:cli:build:deliver", [], invoke_options }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
modify(lock: true) do
|
|
40
40
|
run_hook "pre-deploy", secrets: true
|
|
41
41
|
|
|
42
42
|
say "Ensure dash-proxy is running...", :magenta
|
|
43
|
-
invoke "dash:cli:proxy:boot", [], invoke_options
|
|
43
|
+
timed("Ensure dash-proxy") { invoke "dash:cli:proxy:boot", [], invoke_options }
|
|
44
44
|
|
|
45
|
-
invoke "dash:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
|
|
45
|
+
timed("Boot accessories") { invoke "dash:cli:accessory:boot", [ "all" ], invoke_options } if boot_accessories
|
|
46
46
|
|
|
47
47
|
say "Detect stale containers...", :magenta
|
|
48
|
-
invoke "dash:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
|
48
|
+
timed("Detect stale containers") { invoke "dash:cli:app:stale_containers", [], invoke_options.merge(stop: true) }
|
|
49
49
|
|
|
50
|
-
invoke "dash:cli:app:boot", [], invoke_options
|
|
50
|
+
timed("Boot") { invoke "dash:cli:app:boot", [], invoke_options }
|
|
51
51
|
|
|
52
52
|
if DASH.config.proxy.load_balancing?
|
|
53
53
|
say "Updating loadbalancer configuration...", :magenta
|
|
54
|
-
invoke "dash:cli:proxy:loadbalancer", [ "deploy" ], invoke_options
|
|
54
|
+
timed("Loadbalancer") { invoke "dash:cli:proxy:loadbalancer", [ "deploy" ], invoke_options }
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
say "Prune old containers and images...", :magenta
|
|
58
|
-
invoke "dash:cli:prune:all", [], invoke_options
|
|
58
|
+
timed("Prune") { invoke "dash:cli:prune:all", [], invoke_options }
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -78,23 +78,23 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
78
78
|
|
|
79
79
|
if options[:skip_push]
|
|
80
80
|
say "Pull app image...", :magenta
|
|
81
|
-
invoke "dash:cli:build:pull", [], invoke_options
|
|
81
|
+
timed("Pull app image") { invoke "dash:cli:build:pull", [], invoke_options }
|
|
82
82
|
else
|
|
83
83
|
say "Build and push app image...", :magenta
|
|
84
|
-
invoke "dash:cli:build:deliver", [], invoke_options
|
|
84
|
+
timed("Build and push app image") { invoke "dash:cli:build:deliver", [], invoke_options }
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
modify(lock: true) do
|
|
88
88
|
run_hook "pre-deploy", secrets: true
|
|
89
89
|
|
|
90
90
|
say "Detect stale containers...", :magenta
|
|
91
|
-
invoke "dash:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
|
91
|
+
timed("Detect stale containers") { invoke "dash:cli:app:stale_containers", [], invoke_options.merge(stop: true) }
|
|
92
92
|
|
|
93
|
-
invoke "dash:cli:app:boot", [], invoke_options
|
|
93
|
+
timed("Boot") { invoke "dash:cli:app:boot", [], invoke_options }
|
|
94
94
|
|
|
95
95
|
if DASH.config.proxy.load_balancing?
|
|
96
96
|
say "Updating loadbalancer configuration...", :magenta
|
|
97
|
-
invoke "dash:cli:proxy:loadbalancer", [ "deploy" ], invoke_options
|
|
97
|
+
timed("Loadbalancer") { invoke "dash:cli:proxy:loadbalancer", [ "deploy" ], invoke_options }
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
end
|
|
@@ -117,7 +117,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
117
117
|
if container_available?(version)
|
|
118
118
|
run_hook "pre-deploy", secrets: true
|
|
119
119
|
|
|
120
|
-
invoke "dash:cli:app:boot", [], invoke_options.merge(version: version)
|
|
120
|
+
timed("Boot") { invoke "dash:cli:app:boot", [], invoke_options.merge(version: version) }
|
|
121
121
|
rolled_back = true
|
|
122
122
|
else
|
|
123
123
|
say "The app version '#{version}' is not available as a container (use 'dash app containers' for available versions)", :red
|
|
@@ -19,6 +19,7 @@ class Dash::Cli::Proxy::LoadbalancerReboot
|
|
|
19
19
|
def run
|
|
20
20
|
execute *DASH.auditor.record("Rebooted loadbalancer"), verbosity: :debug
|
|
21
21
|
execute *DASH.registry.login
|
|
22
|
+
ensure_network
|
|
22
23
|
|
|
23
24
|
info "Stopping and removing #{DASH.loadbalancer.container_name} on #{host}, if running..."
|
|
24
25
|
execute *DASH.loadbalancer.stop, raise_on_non_zero_exit: false
|
|
@@ -45,6 +46,16 @@ class Dash::Cli::Proxy::LoadbalancerReboot
|
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
private
|
|
49
|
+
# Before the old container is stopped: `docker run` against a missing
|
|
50
|
+
# network would otherwise fail with the edge already down. A dedicated
|
|
51
|
+
# loadbalancer host has no other path that creates the network
|
|
52
|
+
# (zoolutions/dash#140).
|
|
53
|
+
def ensure_network
|
|
54
|
+
execute *DASH.docker.create_network
|
|
55
|
+
rescue SSHKit::Command::Failed => e
|
|
56
|
+
raise unless e.message.include?("already exists")
|
|
57
|
+
end
|
|
58
|
+
|
|
48
59
|
def wait_until_ready
|
|
49
60
|
deadline = Time.now + READY_TIMEOUT
|
|
50
61
|
|
data/lib/dash/cli/proxy.rb
CHANGED
|
@@ -2,7 +2,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
2
2
|
desc "boot", "Boot proxy on servers"
|
|
3
3
|
def boot
|
|
4
4
|
modify(lock: true, server_lock: true) do
|
|
5
|
-
on(
|
|
5
|
+
on(network_hosts) do |host|
|
|
6
6
|
execute *DASH.docker.create_network
|
|
7
7
|
rescue SSHKit::Command::Failed => e
|
|
8
8
|
raise unless e.message.include?("already exists")
|
|
@@ -76,8 +76,10 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
76
76
|
info "Starting loadbalancer on #{host}..."
|
|
77
77
|
execute *DASH.registry.login
|
|
78
78
|
|
|
79
|
-
# Bring a pre-rename
|
|
80
|
-
# against an empty
|
|
79
|
+
# Bring a pre-rename host across before the container can be created
|
|
80
|
+
# against an empty volume or a network nothing else joined. A no-op
|
|
81
|
+
# once it has been.
|
|
82
|
+
execute *DASH.docker.connect_legacy_network_containers
|
|
81
83
|
execute *DASH.loadbalancer.copy_legacy_config_volume
|
|
82
84
|
|
|
83
85
|
# The load balancer terminates TLS and owns the cache, so its host
|
|
@@ -653,6 +655,15 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
653
655
|
end
|
|
654
656
|
|
|
655
657
|
private
|
|
658
|
+
# Every host that runs a container on the proxy network. A dedicated
|
|
659
|
+
# loadbalancer host is not in DASH.hosts, and nothing else creates the
|
|
660
|
+
# network there (zoolutions/dash#140).
|
|
661
|
+
def network_hosts
|
|
662
|
+
hosts = DASH.hosts
|
|
663
|
+
hosts |= [ DASH.config.proxy.effective_loadbalancer ] if DASH.config.proxy.load_balancing?
|
|
664
|
+
hosts
|
|
665
|
+
end
|
|
666
|
+
|
|
656
667
|
# The host that owns TLS, and so the certificate store: the loadbalancer
|
|
657
668
|
# host when load balancing (TLS terminates at the edge), else the primary
|
|
658
669
|
# host - the same host `loadbalancer: true` would resolve to.
|
data/lib/dash/commander.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "active_support/notifications"
|
|
|
6
6
|
|
|
7
7
|
class Dash::Commander
|
|
8
8
|
attr_accessor :verbosity, :holding_lock, :holding_server_lock, :connected, :logging, :lock_wait, :lock_wait_timeout, :lock_wait_interval
|
|
9
|
-
attr_reader :specific_roles, :specific_hosts
|
|
9
|
+
attr_reader :specific_roles, :specific_hosts, :timings
|
|
10
10
|
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
@@ -23,6 +23,7 @@ class Dash::Commander
|
|
|
23
23
|
self.lock_wait_timeout = 900
|
|
24
24
|
self.lock_wait_interval = 15
|
|
25
25
|
@modify_depth = 0
|
|
26
|
+
@timings = Dash::Timings.new
|
|
26
27
|
@specifics = @specific_roles = @specific_hosts = nil
|
|
27
28
|
@config = @config_kwargs = nil
|
|
28
29
|
@output_logger = nil
|
|
@@ -8,6 +8,7 @@ class Dash::Configuration::Boot
|
|
|
8
8
|
def initialize(config:, boot_config: nil, context: nil)
|
|
9
9
|
@boot_config = boot_config || config.raw_config.boot || {}
|
|
10
10
|
validate! @boot_config, context: context
|
|
11
|
+
validate_canary!(context || self.class.validation_config_key)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
# The group size to slice `hosts` by. A percentage only means something against the set
|
|
@@ -35,6 +36,22 @@ class Dash::Configuration::Boot
|
|
|
35
36
|
boot_config["wait"]
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
# How many primary-role hosts boot alone, one at a time, before the rest boot together.
|
|
40
|
+
# Whole-deploy only — Cli::App#host_boot_groups is the sole consumer.
|
|
41
|
+
def canary
|
|
42
|
+
boot_config["canary"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def canary?
|
|
46
|
+
canary.present?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Whether this config splits hosts into more than one group at all — what `wait` needs
|
|
50
|
+
# in order to mean anything.
|
|
51
|
+
def groups?
|
|
52
|
+
limit? || canary?
|
|
53
|
+
end
|
|
54
|
+
|
|
38
55
|
def parallel_roles
|
|
39
56
|
boot_config["parallel_roles"]
|
|
40
57
|
end
|
|
@@ -57,4 +74,20 @@ class Dash::Configuration::Boot
|
|
|
57
74
|
{ in: :groups, limit: limit, wait: wait.to_i }
|
|
58
75
|
end
|
|
59
76
|
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
# The example-driven validator has already checked the type; the range and the
|
|
80
|
+
# top-level-only rule are ours. A role-scoped Boot always carries a context.
|
|
81
|
+
def validate_canary!(context)
|
|
82
|
+
return unless canary?
|
|
83
|
+
|
|
84
|
+
if context != self.class.validation_config_key
|
|
85
|
+
raise Dash::ConfigurationError, "#{context}/canary is only supported at the top-level boot: " \
|
|
86
|
+
"it slices the primary role's hosts ahead of every other role"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if canary < 1
|
|
90
|
+
raise Dash::ConfigurationError, "#{context}/canary must be at least 1, got #{canary}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
60
93
|
end
|
|
@@ -26,6 +26,20 @@ boot:
|
|
|
26
26
|
# moves on as soon as the last host is up.
|
|
27
27
|
wait: 10
|
|
28
28
|
|
|
29
|
+
# The number of primary-role hosts to boot one at a time before the rest of the hosts boot.
|
|
30
|
+
#
|
|
31
|
+
# A canary is the gate people reach for `limit: 1` to get — prove the new version on one host
|
|
32
|
+
# before touching the fleet — without serialising every host after it. The first N hosts of the
|
|
33
|
+
# primary role each boot alone; a boot failure there stops the deploy before any other host is
|
|
34
|
+
# touched. The remaining hosts of every role then boot together, or in groups when `limit` is
|
|
35
|
+
# also set (a percentage limit counts those remaining hosts). `wait` paces canary and remaining
|
|
36
|
+
# groups alike. Behind dash-proxy this never reduces capacity: each host keeps serving the old
|
|
37
|
+
# container until the new one passes its health check.
|
|
38
|
+
#
|
|
39
|
+
# Top-level only. A role-scoped `boot` refuses it: the canary slices the primary role's hosts
|
|
40
|
+
# ahead of every other role, which a single role cannot express.
|
|
41
|
+
canary: 1
|
|
42
|
+
|
|
29
43
|
# Whether to boot roles in parallel on a host.
|
|
30
44
|
#
|
|
31
45
|
# If a host has multiple roles, control whether they are booted in parallel or sequentially on that host.
|
|
@@ -82,7 +82,7 @@ asset_path: /path/to/assets
|
|
|
82
82
|
# Hooks path
|
|
83
83
|
#
|
|
84
84
|
# Path to hooks, defaults to `.dash/hooks` (`.kamal/hooks` is still read when only that exists).
|
|
85
|
-
# See https://
|
|
85
|
+
# See https://dash.zoolutions.llc/docs/hooks for every hook, when it fires, and its environment:
|
|
86
86
|
hooks_path: /user_home/kamal/hooks
|
|
87
87
|
|
|
88
88
|
# Hook output
|
|
@@ -31,7 +31,7 @@ env:
|
|
|
31
31
|
# RAILS_MASTER_KEY=$(cat config/master.key)
|
|
32
32
|
# ```
|
|
33
33
|
#
|
|
34
|
-
# You can also use [secret helpers](
|
|
34
|
+
# You can also use [secret helpers](https://dash.zoolutions.llc/docs/secrets) for common password managers and secret stores.
|
|
35
35
|
#
|
|
36
36
|
# ```shell
|
|
37
37
|
# SECRETS=$(dash secrets fetch ...)
|
|
@@ -834,7 +834,7 @@ proxy:
|
|
|
834
834
|
# below already embeds its ghcr.io host
|
|
835
835
|
repository: ghcr.io/zoolutions/dash-proxy # Container repository for the
|
|
836
836
|
# dash-proxy image (this is the default)
|
|
837
|
-
version: v1.1.0.
|
|
837
|
+
version: v1.1.0.1 # Version tag of the dash-proxy image to use.
|
|
838
838
|
# Defaults to the minimum version this gem
|
|
839
839
|
# requires - only pin it to roll forward early,
|
|
840
840
|
# never below the default
|
data/lib/dash/configuration.rb
CHANGED
|
@@ -592,17 +592,17 @@ class Dash::Configuration
|
|
|
592
592
|
end
|
|
593
593
|
|
|
594
594
|
# `wait` only ever fills the gap between one group of hosts and the next, and without a
|
|
595
|
-
# `limit` there are no gaps — every host boots in one group. Before #47 that silently
|
|
595
|
+
# `limit` or a `canary` there are no gaps — every host boots in one group. Before #47 that silently
|
|
596
596
|
# cost a deploy one `wait` interval; now it silently does nothing at all. Either way the
|
|
597
597
|
# operator asked for staggering and is not getting it, so say so.
|
|
598
598
|
def ensure_boot_wait_paces_something
|
|
599
599
|
offenders = [ [ "boot", boot ], *roles.filter_map { |role| [ "servers/#{role.name}/boot", role.boot ] if role.boot } ]
|
|
600
|
-
.select { |_context, boot_config| boot_config.wait.present? && !boot_config.
|
|
600
|
+
.select { |_context, boot_config| boot_config.wait.present? && !boot_config.groups? }
|
|
601
601
|
|
|
602
602
|
offenders.each do |context, boot_config|
|
|
603
603
|
warn "#{context}/wait is set to #{boot_config.wait} but #{context}/limit is not, so it does nothing. " \
|
|
604
604
|
"`wait` paces one group of hosts against the next, and without a limit every host boots in a single group. " \
|
|
605
|
-
"Set `#{context}/limit` to stagger the boot, or remove `#{context}/wait`."
|
|
605
|
+
"Set `#{context}/limit` or `#{context}/canary` to stagger the boot, or remove `#{context}/wait`."
|
|
606
606
|
end
|
|
607
607
|
|
|
608
608
|
true
|
data/lib/dash/timings.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Wall-clock accounting for a deploy, printed under "Finished all in". The total on its
|
|
2
|
+
# own says nothing about where the time went — a serialised boot, a slow pull, or a proxy
|
|
3
|
+
# waiting on a health check all look the same from the outside.
|
|
4
|
+
#
|
|
5
|
+
# Entries land in start order and carry a depth, so a parent phase (Boot) prints above
|
|
6
|
+
# the host entries it wraps even though the hosts finish first. Boot runs one thread per
|
|
7
|
+
# host, so every mutation is behind the mutex.
|
|
8
|
+
class Dash::Timings
|
|
9
|
+
Entry = Struct.new(:name, :seconds, :detail, :depth)
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@entries = []
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Times the block. The entry is yielded so the block can annotate it — a boot can say
|
|
17
|
+
# how long of its total was spent waiting for the container to become healthy.
|
|
18
|
+
def phase(name, depth: 0)
|
|
19
|
+
entry = Entry.new(name, nil, nil, depth)
|
|
20
|
+
@mutex.synchronize { @entries << entry }
|
|
21
|
+
started = clock
|
|
22
|
+
|
|
23
|
+
yield entry
|
|
24
|
+
ensure
|
|
25
|
+
entry.seconds = clock - started
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def any?
|
|
29
|
+
@mutex.synchronize { @entries.any? }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def lines
|
|
33
|
+
@mutex.synchronize do
|
|
34
|
+
@entries.map do |entry|
|
|
35
|
+
line = format(" %s%-36s %6.1fs", " " * entry.depth, entry.name, entry.seconds.to_f)
|
|
36
|
+
entry.detail ? "#{line} (#{entry.detail})" : line
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def clock
|
|
43
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/dash/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -329,6 +329,7 @@ files:
|
|
|
329
329
|
- lib/dash/secrets/dotenv/inline_command_substitution.rb
|
|
330
330
|
- lib/dash/sshkit_with_ext.rb
|
|
331
331
|
- lib/dash/tags.rb
|
|
332
|
+
- lib/dash/timings.rb
|
|
332
333
|
- lib/dash/utils.rb
|
|
333
334
|
- lib/dash/utils/sensitive.rb
|
|
334
335
|
- lib/dash/version.rb
|