dash 4.0.4 → 4.0.7
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/doctor/host_checks.rb +18 -1
- data/lib/dash/cli/main.rb +15 -15
- 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/registry.rb +24 -3
- 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: d13e0fdb207715506d85fa85be2a719ca70640130f22ca948c075fd787b3dd88
|
|
4
|
+
data.tar.gz: cc42a4346178f4ffbcf7b7b2f03820e49f99e9d54c78ab864493a0c0b8b214e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdf2135081cc1acc7de1d73d7c7156f93262ad82a5848a6db6aaf484bf4859ed3e529fede60ac0c67df99ec021acdd2cbf6a9175982af8f31873afa03cc2113b
|
|
7
|
+
data.tar.gz: 30eae7e9f41f2b216be4dd8d16ce583f5a8e2392d3b05b3ad15fcd4ba40645441c05c39a643cca2bf01876ee28aec2fa668a7ffe2a474e27c69b46edc341dbc6
|
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
|
|
@@ -113,7 +113,12 @@ class Dash::Cli::Doctor::HostChecks
|
|
|
113
113
|
elsif version.nil?
|
|
114
114
|
result :proxy_version, :ok, "not running (will be started on deploy)"
|
|
115
115
|
elsif Dash::Utils.older_version?(version, minimum)
|
|
116
|
-
|
|
116
|
+
if deploy_reboots_proxy?
|
|
117
|
+
result :proxy_version, :ok,
|
|
118
|
+
"#{version} is older than the minimum #{minimum}; the next deploy reboots the proxy to update it"
|
|
119
|
+
else
|
|
120
|
+
result :proxy_version, :fail, "#{version} is older than the minimum #{minimum}, run `dash proxy reboot` to update"
|
|
121
|
+
end
|
|
117
122
|
else
|
|
118
123
|
result :proxy_version, :ok, "#{version} (minimum #{minimum})"
|
|
119
124
|
end
|
|
@@ -121,6 +126,18 @@ class Dash::Cli::Doctor::HostChecks
|
|
|
121
126
|
result :proxy_version, :warn, "running image tag #{version} is not a version number"
|
|
122
127
|
end
|
|
123
128
|
|
|
129
|
+
# Mirrors `proxy boot`: with reboot_on_deploy enabled it reboots a drifted
|
|
130
|
+
# proxy on its own, and a MINIMUM_VERSION bump moves the expected config
|
|
131
|
+
# digest, so a merely-stale proxy is converged by the very deploy this
|
|
132
|
+
# doctor gates. Only a version the deploy would not touch — pinned in the
|
|
133
|
+
# config, or with automatic reboots disabled — is a real failure. Any error
|
|
134
|
+
# probing drift falls back to the conservative answer.
|
|
135
|
+
def deploy_reboots_proxy?
|
|
136
|
+
DASH.config.proxy.reboot_on_deploy? && Dash::Cli::Proxy::Drift.new(host, sshkit).drifted?
|
|
137
|
+
rescue StandardError
|
|
138
|
+
false
|
|
139
|
+
end
|
|
140
|
+
|
|
124
141
|
# What a container path has to look like to count as a container runtime
|
|
125
142
|
# socket when the config no longer names one.
|
|
126
143
|
DOCKER_SOCKET_PATTERN = %r{docker\.sock\z}
|
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
|
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
|
|
@@ -4,6 +4,7 @@ class Dash::Configuration::Registry
|
|
|
4
4
|
def initialize(config:, secrets:, context: "registry")
|
|
5
5
|
@registry_config = config["registry"] || {}
|
|
6
6
|
@secrets = secrets
|
|
7
|
+
@context = context
|
|
7
8
|
validate! registry_config, context: context, with: Dash::Configuration::Validator::Registry
|
|
8
9
|
end
|
|
9
10
|
|
|
@@ -28,13 +29,33 @@ class Dash::Configuration::Registry
|
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
private
|
|
31
|
-
attr_reader :registry_config, :secrets
|
|
32
|
+
attr_reader :registry_config, :secrets, :context
|
|
32
33
|
|
|
34
|
+
# A blank credential would reach the remote host as an empty `docker login -p`,
|
|
35
|
+
# where docker falls back to prompting and dies with the opaque "Cannot perform
|
|
36
|
+
# an interactive login from a non TTY device" — fail locally instead.
|
|
33
37
|
def lookup(key)
|
|
38
|
+
value =
|
|
39
|
+
if registry_config[key].is_a?(Array)
|
|
40
|
+
secrets[registry_config[key].first]
|
|
41
|
+
else
|
|
42
|
+
registry_config[key]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
validate_present! key, value unless local?
|
|
46
|
+
value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_present!(key, value)
|
|
50
|
+
return if value.present?
|
|
51
|
+
|
|
34
52
|
if registry_config[key].is_a?(Array)
|
|
35
|
-
|
|
53
|
+
secret = registry_config[key].first
|
|
54
|
+
raise Dash::ConfigurationError,
|
|
55
|
+
"#{context}/#{key}: secret '#{secret}' resolved to an empty value — " \
|
|
56
|
+
"if your secrets file forwards it from the environment (#{secret}=$#{secret}), export the variable before running dash"
|
|
36
57
|
else
|
|
37
|
-
|
|
58
|
+
raise Dash::ConfigurationError, "#{context}/#{key}: is blank"
|
|
38
59
|
end
|
|
39
60
|
end
|
|
40
61
|
end
|
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.7
|
|
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
|