dash 3.2.0 → 4.0.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/README.md +1 -1
- data/bin/dash +2 -2
- data/lib/{kamal → dash}/cli/accessory.rb +24 -24
- data/lib/dash/cli/alias/command.rb +10 -0
- data/lib/{kamal → dash}/cli/app/assets.rb +2 -2
- data/lib/{kamal → dash}/cli/app/boot.rb +16 -16
- data/lib/{kamal → dash}/cli/app/error_pages.rb +6 -6
- data/lib/{kamal → dash}/cli/app/rollout_boot.rb +6 -6
- data/lib/{kamal → dash}/cli/app/ssl_certificates.rb +3 -3
- data/lib/{kamal → dash}/cli/app.rb +87 -87
- data/lib/{kamal → dash}/cli/base.rb +72 -48
- data/lib/dash/cli/build/clone.rb +59 -0
- data/lib/{kamal → dash}/cli/build/port_forwarding.rb +1 -1
- data/lib/{kamal → dash}/cli/build.rb +45 -45
- data/lib/{kamal → dash}/cli/doctor/config_checks.rb +3 -3
- data/lib/{kamal → dash}/cli/doctor/endpoint_checks.rb +26 -8
- data/lib/{kamal → dash}/cli/doctor/host_checks.rb +22 -22
- data/lib/{kamal → dash}/cli/doctor.rb +9 -9
- data/lib/{kamal → dash}/cli/healthcheck/barrier.rb +2 -2
- data/lib/{kamal → dash}/cli/healthcheck/drift_error.rb +2 -2
- data/lib/dash/cli/healthcheck/error.rb +2 -0
- data/lib/{kamal → dash}/cli/healthcheck/poller.rb +7 -7
- data/lib/{kamal → dash}/cli/lock.rb +1 -1
- data/lib/dash/cli/main/migrate.rb +77 -0
- data/lib/{kamal → dash}/cli/main.rb +82 -71
- data/lib/{kamal → dash}/cli/proxy/drift.rb +3 -3
- data/lib/dash/cli/proxy/legacy_rename.rb +58 -0
- data/lib/{kamal → dash}/cli/proxy/loadbalancer_claim.rb +3 -3
- data/lib/{kamal → dash}/cli/proxy/loadbalancer_reboot.rb +23 -23
- data/lib/{kamal → dash}/cli/proxy/reboot.rb +21 -21
- data/lib/{kamal → dash}/cli/proxy.rb +178 -162
- data/lib/{kamal → dash}/cli/prune.rb +10 -10
- data/lib/{kamal → dash}/cli/registry.rb +11 -11
- data/lib/{kamal → dash}/cli/secrets.rb +3 -3
- data/lib/{kamal → dash}/cli/server.rb +14 -14
- data/lib/{kamal → dash}/cli/templates/deploy.yml +4 -4
- data/lib/dash/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/dash/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/dash/cli/templates/sample_hooks/post-app-stop.sample +9 -0
- data/lib/dash/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/dash/cli/templates/sample_hooks/post-proxy-deploy.sample +3 -0
- data/lib/dash/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/dash/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-app-stop.sample +2 -2
- data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-build.sample +8 -8
- data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-connect.sample +8 -8
- data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-deploy.sample +9 -9
- data/lib/dash/cli/templates/sample_hooks/pre-proxy-deploy.sample +3 -0
- data/lib/dash/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/{kamal → dash}/cli/templates/secrets +6 -6
- data/lib/{kamal → dash}/cli.rb +2 -2
- data/lib/{kamal → dash}/commander/specifics.rb +2 -2
- data/lib/{kamal → dash}/commander.rb +32 -24
- data/lib/{kamal → dash}/commands/accessory/proxy.rb +2 -2
- data/lib/{kamal → dash}/commands/accessory.rb +2 -2
- data/lib/{kamal → dash}/commands/app/assets.rb +1 -1
- data/lib/{kamal → dash}/commands/app/containers.rb +1 -1
- data/lib/{kamal → dash}/commands/app/error_pages.rb +2 -2
- data/lib/{kamal → dash}/commands/app/execution.rb +2 -2
- data/lib/{kamal → dash}/commands/app/images.rb +1 -1
- data/lib/{kamal → dash}/commands/app/logging.rb +1 -1
- data/lib/{kamal → dash}/commands/app/proxy.rb +2 -2
- data/lib/{kamal → dash}/commands/app.rb +2 -2
- data/lib/{kamal → dash}/commands/auditor.rb +3 -7
- data/lib/{kamal → dash}/commands/base.rb +39 -3
- data/lib/{kamal → dash}/commands/builder/base.rb +2 -2
- data/lib/{kamal → dash}/commands/builder/clone.rb +3 -3
- data/lib/{kamal → dash}/commands/builder/cloud.rb +1 -1
- data/lib/{kamal → dash}/commands/builder/hybrid.rb +1 -1
- data/lib/{kamal → dash}/commands/builder/local.rb +1 -1
- data/lib/{kamal → dash}/commands/builder/pack.rb +1 -1
- data/lib/{kamal → dash}/commands/builder/remote.rb +1 -1
- data/lib/{kamal → dash}/commands/builder.rb +7 -7
- data/lib/dash/commands/docker.rb +89 -0
- data/lib/{kamal → dash}/commands/hook.rb +1 -1
- data/lib/{kamal → dash}/commands/loadbalancer.rb +55 -24
- data/lib/{kamal → dash}/commands/lock.rb +3 -3
- data/lib/{kamal → dash}/commands/proxy/cert_transfer.rb +6 -6
- data/lib/dash/commands/proxy.rb +375 -0
- data/lib/{kamal → dash}/commands/prune.rb +2 -2
- data/lib/dash/commands/registry.rb +44 -0
- data/lib/{kamal → dash}/commands/server.rb +1 -5
- data/lib/dash/commands.rb +2 -0
- data/lib/{kamal → dash}/configuration/accessory.rb +13 -13
- data/lib/{kamal → dash}/configuration/alias.rb +3 -3
- data/lib/{kamal → dash}/configuration/boot.rb +2 -2
- data/lib/{kamal → dash}/configuration/builder.rb +7 -7
- data/lib/{kamal → dash}/configuration/docs/accessory.yml +3 -3
- data/lib/{kamal → dash}/configuration/docs/builder.yml +3 -3
- data/lib/{kamal → dash}/configuration/docs/configuration.yml +13 -6
- data/lib/{kamal → dash}/configuration/docs/env.yml +10 -10
- data/lib/{kamal → dash}/configuration/docs/proxy.yml +68 -45
- data/lib/{kamal → dash}/configuration/docs/registry.yml +5 -5
- data/lib/{kamal → dash}/configuration/env/tag.rb +2 -2
- data/lib/{kamal → dash}/configuration/env.rb +5 -5
- data/lib/{kamal → dash}/configuration/loadbalancer.rb +5 -5
- data/lib/{kamal → dash}/configuration/logging.rb +3 -3
- data/lib/{kamal → dash}/configuration/output.rb +4 -4
- data/lib/{kamal → dash}/configuration/proxy/acme.rb +9 -8
- data/lib/{kamal → dash}/configuration/proxy/boot.rb +9 -11
- data/lib/{kamal → dash}/configuration/proxy/run.rb +20 -20
- data/lib/{kamal → dash}/configuration/proxy.rb +51 -29
- data/lib/{kamal → dash}/configuration/registry.rb +3 -3
- data/lib/{kamal → dash}/configuration/role/healthcheck.rb +3 -3
- data/lib/{kamal → dash}/configuration/role.rb +14 -14
- data/lib/{kamal → dash}/configuration/servers.rb +4 -4
- data/lib/{kamal → dash}/configuration/ssh.rb +2 -2
- data/lib/{kamal → dash}/configuration/sshkit.rb +2 -2
- data/lib/{kamal → dash}/configuration/validation.rb +2 -2
- data/lib/{kamal → dash}/configuration/validator/accessory.rb +1 -1
- data/lib/{kamal → dash}/configuration/validator/alias.rb +2 -2
- data/lib/{kamal → dash}/configuration/validator/builder.rb +1 -1
- data/lib/dash/configuration/validator/configuration.rb +6 -0
- data/lib/{kamal → dash}/configuration/validator/env.rb +2 -2
- data/lib/{kamal → dash}/configuration/validator/proxy.rb +31 -31
- data/lib/{kamal → dash}/configuration/validator/registry.rb +1 -1
- data/lib/{kamal → dash}/configuration/validator/role.rb +1 -1
- data/lib/{kamal → dash}/configuration/validator/servers.rb +1 -1
- data/lib/{kamal → dash}/configuration/validator.rb +4 -4
- data/lib/{kamal → dash}/configuration/volume.rb +2 -2
- data/lib/{kamal → dash}/configuration.rb +47 -37
- data/lib/{kamal → dash}/docker.rb +1 -1
- data/lib/{kamal → dash}/env_file.rb +1 -1
- data/lib/{kamal → dash}/git.rb +1 -1
- data/lib/{kamal → dash}/otel_shipper.rb +3 -3
- data/lib/{kamal → dash}/output/base_logger.rb +1 -1
- data/lib/{kamal → dash}/output/file_logger.rb +1 -1
- data/lib/{kamal → dash}/output/formatter.rb +1 -1
- data/lib/{kamal → dash}/output/otel_logger.rb +3 -3
- data/lib/dash/project_directory.rb +28 -0
- data/lib/{kamal → dash}/secrets/adapters/aws_secrets_manager.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/base.rb +2 -2
- data/lib/{kamal → dash}/secrets/adapters/bitwarden.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/bitwarden_secrets_manager.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/doppler.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/enpass.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/gcp_secret_manager.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/last_pass.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/one_password.rb +2 -2
- data/lib/{kamal → dash}/secrets/adapters/passbolt.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters/test.rb +1 -1
- data/lib/{kamal → dash}/secrets/adapters.rb +2 -2
- data/lib/{kamal → dash}/secrets/dotenv/inline_command_substitution.rb +2 -2
- data/lib/{kamal → dash}/secrets.rb +6 -6
- data/lib/{kamal → dash}/tags.rb +8 -3
- data/lib/{kamal → dash}/utils/sensitive.rb +1 -1
- data/lib/{kamal → dash}/utils.rb +16 -6
- data/lib/dash/version.rb +3 -0
- data/lib/{kamal.rb → dash.rb} +4 -4
- metadata +157 -154
- data/lib/kamal/cli/alias/command.rb +0 -10
- data/lib/kamal/cli/build/clone.rb +0 -59
- data/lib/kamal/cli/healthcheck/error.rb +0 -2
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/post-app-stop.sample +0 -9
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +0 -14
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-deploy.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-deploy.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +0 -3
- data/lib/kamal/commands/docker.rb +0 -55
- data/lib/kamal/commands/proxy.rb +0 -266
- data/lib/kamal/commands/registry.rb +0 -38
- data/lib/kamal/commands.rb +0 -2
- data/lib/kamal/configuration/validator/configuration.rb +0 -6
- data/lib/kamal/version.rb +0 -3
- /data/lib/{kamal → dash}/configuration/docs/alias.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/boot.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/logging.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/output.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/role.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/servers.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/ssh.yml +0 -0
- /data/lib/{kamal → dash}/configuration/docs/sshkit.yml +0 -0
- /data/lib/{kamal → dash}/sshkit_with_ext.rb +0 -0
|
@@ -2,9 +2,9 @@ require "resolv"
|
|
|
2
2
|
require "socket"
|
|
3
3
|
require "openssl"
|
|
4
4
|
|
|
5
|
-
# Local (no SSH) readiness checks for the domains
|
|
5
|
+
# Local (no SSH) readiness checks for the domains dash-proxy will serve:
|
|
6
6
|
# DNS resolution against the configured hosts and TLS certificate expiry.
|
|
7
|
-
class
|
|
7
|
+
class Dash::Cli::Doctor::EndpointChecks
|
|
8
8
|
CERTIFICATE_EXPIRY_WARN_DAYS = 14
|
|
9
9
|
SECONDS_PER_DAY = 86_400
|
|
10
10
|
TLS_CONNECT_TIMEOUT = 5
|
|
@@ -15,22 +15,39 @@ class Kamal::Cli::Doctor::EndpointChecks
|
|
|
15
15
|
|
|
16
16
|
private
|
|
17
17
|
def result(check, target, status, detail)
|
|
18
|
-
|
|
18
|
+
Dash::Cli::Doctor::Result.new(check, target, status, detail)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# Roles and accessories that run behind the proxy with custom domains.
|
|
22
22
|
def proxied_units
|
|
23
|
-
@proxied_units ||= (
|
|
23
|
+
@proxied_units ||= (DASH.roles + DASH.config.proxy_accessories).select do |unit|
|
|
24
24
|
unit.running_proxy? && unit.proxy.hosts.any?
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def dns_results
|
|
29
29
|
proxied_units.flat_map do |unit|
|
|
30
|
-
unit.proxy.hosts.map { |domain| dns_check(domain, unit.hosts) }
|
|
30
|
+
unit.proxy.hosts.map { |domain| dns_check(domain, unit.hosts, acme_issued: acme_issued?(unit)) }
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# Whether this unit's certificate comes from ACME rather than the operator.
|
|
35
|
+
# Only then does where the domain points decide whether a certificate can
|
|
36
|
+
# exist — a supplied certificate is unaffected by DNS.
|
|
37
|
+
def acme_issued?(unit)
|
|
38
|
+
unit.proxy.ssl? && !unit.proxy.custom_ssl_certificate?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The two states a DNS cutover passes through — not resolving yet, and still
|
|
42
|
+
# resolving to the old host — are exactly when an operator wonders whether
|
|
43
|
+
# something is broken. Nothing is: the proxy refuses to spend an ACME order
|
|
44
|
+
# it cannot win, re-checks held domains, and issues once the domain arrives.
|
|
45
|
+
ISSUANCE_PENDING = "no certificate can be issued until it points here, and the proxy issues automatically once it does".freeze
|
|
46
|
+
|
|
47
|
+
def with_issuance_note(detail, acme_issued)
|
|
48
|
+
acme_issued ? "#{detail} - #{ISSUANCE_PENDING}" : detail
|
|
49
|
+
end
|
|
50
|
+
|
|
34
51
|
def certificate_results
|
|
35
52
|
proxied_units.flat_map do |unit|
|
|
36
53
|
next [] unless unit.proxy.ssl?
|
|
@@ -41,17 +58,18 @@ class Kamal::Cli::Doctor::EndpointChecks
|
|
|
41
58
|
end
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
def dns_check(domain, unit_hosts)
|
|
61
|
+
def dns_check(domain, unit_hosts, acme_issued: false)
|
|
45
62
|
resolved = Resolv.getaddresses(domain)
|
|
46
63
|
expected = expected_ips(unit_hosts)
|
|
47
64
|
matching = resolved & expected
|
|
48
65
|
|
|
49
66
|
if resolved.empty?
|
|
50
|
-
result :dns, domain, :fail, "does not resolve"
|
|
67
|
+
result :dns, domain, :fail, with_issuance_note("does not resolve", acme_issued)
|
|
51
68
|
elsif matching.any?
|
|
52
69
|
result :dns, domain, :ok, "resolves to #{matching.join(", ")}"
|
|
53
70
|
else
|
|
54
|
-
result :dns, domain, :warn,
|
|
71
|
+
result :dns, domain, :warn,
|
|
72
|
+
with_issuance_note("resolves to #{resolved.join(", ")}, expected one of #{expected.join(", ")} (fine when fronted by a CDN or load balancer)", acme_issued)
|
|
55
73
|
end
|
|
56
74
|
rescue StandardError => e
|
|
57
75
|
result :dns, domain, :fail, "could not resolve (#{e.message})"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Every check rescues StandardError at its boundary: the doctor's contract is
|
|
4
4
|
# to report broken environments, not crash on them, and a failed connection can
|
|
5
5
|
# raise anything from Net::SSH errors to Errno and DNS resolution errors.
|
|
6
|
-
class
|
|
6
|
+
class Dash::Cli::Doctor::HostChecks
|
|
7
7
|
attr_reader :host, :sshkit, :proxy_host
|
|
8
8
|
delegate :execute, :capture_with_info, to: :sshkit
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
def result(check, status, detail)
|
|
30
|
-
|
|
30
|
+
Dash::Cli::Doctor::Result.new(check, host, status, detail)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def ssh_check
|
|
@@ -38,7 +38,7 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def docker_check
|
|
41
|
-
if execute(*
|
|
41
|
+
if execute(*DASH.docker.running?, raise_on_non_zero_exit: false)
|
|
42
42
|
result :docker, :ok, "docker is installed and running"
|
|
43
43
|
else
|
|
44
44
|
result :docker, :fail, "docker is not installed or not running (run `dash server bootstrap`)"
|
|
@@ -48,9 +48,9 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def registry_check
|
|
51
|
-
return result(:registry, :ok, "local registry, no login required") if
|
|
51
|
+
return result(:registry, :ok, "local registry, no login required") if DASH.registry.local?
|
|
52
52
|
|
|
53
|
-
if execute(*
|
|
53
|
+
if execute(*DASH.registry.login, raise_on_non_zero_exit: false)
|
|
54
54
|
result :registry, :ok, "logged in to #{registry_name}"
|
|
55
55
|
else
|
|
56
56
|
result :registry, :fail, "docker login to #{registry_name} failed (check registry credentials)"
|
|
@@ -71,7 +71,7 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def capture_proxy_version
|
|
74
|
-
[ capture_with_info(*
|
|
74
|
+
[ capture_with_info(*DASH.proxy(host).version).strip.presence, nil ]
|
|
75
75
|
rescue StandardError => e
|
|
76
76
|
[ nil, e ]
|
|
77
77
|
end
|
|
@@ -79,7 +79,7 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
79
79
|
def proxy_image_check
|
|
80
80
|
image = expected_proxy_image
|
|
81
81
|
|
|
82
|
-
if execute(*
|
|
82
|
+
if execute(*DASH.docker.manifest_available?(image), raise_on_non_zero_exit: false)
|
|
83
83
|
result :proxy_image, :ok, "#{image} manifest is fetchable"
|
|
84
84
|
else
|
|
85
85
|
result :proxy_image, :fail, "cannot fetch the manifest for #{image} (registry unreachable or unauthorized)"
|
|
@@ -89,13 +89,13 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def proxy_version_check(version, error)
|
|
92
|
-
minimum =
|
|
92
|
+
minimum = Dash::Configuration::Proxy::Run::MINIMUM_VERSION
|
|
93
93
|
|
|
94
94
|
if error
|
|
95
95
|
result :proxy_version, :warn, "could not determine the running version (#{error.message})"
|
|
96
96
|
elsif version.nil?
|
|
97
97
|
result :proxy_version, :ok, "not running (will be started on deploy)"
|
|
98
|
-
elsif
|
|
98
|
+
elsif Dash::Utils.older_version?(version, minimum)
|
|
99
99
|
result :proxy_version, :fail, "#{version} is older than the minimum #{minimum}, run `dash proxy reboot` to update"
|
|
100
100
|
else
|
|
101
101
|
result :proxy_version, :ok, "#{version} (minimum #{minimum})"
|
|
@@ -114,26 +114,26 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
114
114
|
# mode is one hung request when a sleeping service never wakes - and one
|
|
115
115
|
# from before it was removed keeps root-equivalent host access.
|
|
116
116
|
def proxy_socket_check(proxy_running:)
|
|
117
|
-
expected =
|
|
117
|
+
expected = DASH.config.proxy_run(host)&.docker_socket
|
|
118
118
|
|
|
119
119
|
unless proxy_running
|
|
120
120
|
detail = expected ? "docker socket #{expected} will be mounted on boot" : "no docker socket configured"
|
|
121
121
|
return result(:proxy_socket, :ok, detail)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
mounted = capture_with_info(*
|
|
124
|
+
mounted = capture_with_info(*DASH.proxy(host).mount_destinations, raise_on_non_zero_exit: false).split("\n").map(&:strip)
|
|
125
125
|
|
|
126
126
|
if expected
|
|
127
127
|
if mounted.include?(expected)
|
|
128
128
|
result :proxy_socket, :ok, "docker socket #{expected} is mounted"
|
|
129
129
|
elsif sleep_configured?
|
|
130
|
-
result :proxy_socket, :fail, "the running
|
|
130
|
+
result :proxy_socket, :fail, "the running dash-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
|
|
131
131
|
else
|
|
132
132
|
# Nothing sleeps yet, so nothing hangs - drift rather than breakage.
|
|
133
|
-
result :proxy_socket, :warn, "the running
|
|
133
|
+
result :proxy_socket, :warn, "the running dash-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
|
|
134
134
|
end
|
|
135
135
|
elsif (stray = mounted.grep(DOCKER_SOCKET_PATTERN).first)
|
|
136
|
-
result :proxy_socket, :warn, "the running
|
|
136
|
+
result :proxy_socket, :warn, "the running dash-proxy mounts #{stray} but the config no longer asks for it - " \
|
|
137
137
|
"the socket is root-equivalent host access; `dash proxy reboot` removes it"
|
|
138
138
|
else
|
|
139
139
|
result :proxy_socket, :ok, "no docker socket configured or mounted"
|
|
@@ -143,18 +143,18 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def sleep_configured?
|
|
146
|
-
|
|
146
|
+
DASH.config.roles.any? { |role| role.running_proxy? && role.proxy.proxy_config["sleep"].present? }
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
def ports_check(proxy_running:)
|
|
150
|
-
run_config =
|
|
150
|
+
run_config = DASH.config.proxy_run(host)
|
|
151
151
|
return result(:ports, :ok, "proxy ports are not published, nothing to check") if run_config && !run_config.publish?
|
|
152
152
|
|
|
153
|
-
http_port = run_config&.http_port ||
|
|
154
|
-
https_port = run_config&.https_port ||
|
|
153
|
+
http_port = run_config&.http_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTP_PORT
|
|
154
|
+
https_port = run_config&.https_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT
|
|
155
155
|
|
|
156
156
|
if proxy_running
|
|
157
|
-
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running
|
|
157
|
+
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running dash-proxy"
|
|
158
158
|
elsif (busy = busy_ports(http_port, https_port)).any?
|
|
159
159
|
result :ports, :fail, "port(s) #{busy.join(", ")} already in use by another process"
|
|
160
160
|
else
|
|
@@ -165,14 +165,14 @@ class Kamal::Cli::Doctor::HostChecks
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
def busy_ports(*ports)
|
|
168
|
-
ports.select { |port| capture_with_info(*
|
|
168
|
+
ports.select { |port| capture_with_info(*DASH.server.listeners_on(port), raise_on_non_zero_exit: false).present? }
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def expected_proxy_image
|
|
172
|
-
|
|
172
|
+
DASH.config.proxy_run(host)&.image || "#{DASH.config.proxy_boot.image_default}:#{Dash::Configuration::Proxy::Run::MINIMUM_VERSION}"
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
def registry_name
|
|
176
|
-
|
|
176
|
+
DASH.config.registry.server || "Docker Hub"
|
|
177
177
|
end
|
|
178
178
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "dash/sshkit_with_ext"
|
|
2
2
|
|
|
3
3
|
# Runs read-only deploy readiness checks and collects the results, without ever
|
|
4
4
|
# raising on a broken environment - failures become failing results instead.
|
|
5
|
-
class
|
|
5
|
+
class Dash::Cli::Doctor
|
|
6
6
|
include SSHKit::DSL
|
|
7
7
|
|
|
8
8
|
HOST_CHECKS = %i[ ssh docker registry proxy_image proxy_version proxy_socket ports ]
|
|
@@ -71,19 +71,19 @@ class Kamal::Cli::Doctor
|
|
|
71
71
|
results_by_host = collect_host_checks
|
|
72
72
|
|
|
73
73
|
HOST_CHECKS.flat_map do |check|
|
|
74
|
-
|
|
74
|
+
DASH.hosts.filter_map { |host| results_by_host.dig(host, check) }
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def collect_host_checks
|
|
79
79
|
results_by_host = {}
|
|
80
80
|
mutex = Mutex.new
|
|
81
|
-
proxy_hosts =
|
|
81
|
+
proxy_hosts = DASH.proxy_hosts
|
|
82
82
|
error = nil
|
|
83
83
|
|
|
84
84
|
begin
|
|
85
|
-
on(
|
|
86
|
-
checks =
|
|
85
|
+
on(DASH.hosts) do |host|
|
|
86
|
+
checks = Dash::Cli::Doctor::HostChecks.new(host.hostname, self, proxy_host: proxy_hosts.include?(host.hostname)).run
|
|
87
87
|
mutex.synchronize { results_by_host[host.hostname] = checks }
|
|
88
88
|
end
|
|
89
89
|
# Only ExecuteError: sshkit 1.25 has no MultipleExecuteError, and naming
|
|
@@ -95,7 +95,7 @@ class Kamal::Cli::Doctor
|
|
|
95
95
|
error = e
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
DASH.hosts.each do |host|
|
|
99
99
|
results_by_host[host] ||= { ssh: Result.new(:ssh, host, :fail, "could not run checks (#{error&.message || "connection failed"})") }
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -103,10 +103,10 @@ class Kamal::Cli::Doctor
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def endpoint_check_results
|
|
106
|
-
|
|
106
|
+
Dash::Cli::Doctor::EndpointChecks.new.run
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def config_check_results
|
|
110
|
-
|
|
110
|
+
Dash::Cli::Doctor::ConfigChecks.new.run
|
|
111
111
|
end
|
|
112
112
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require "concurrent/ivar"
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class Dash::Cli::Healthcheck::Barrier
|
|
4
4
|
def initialize
|
|
5
5
|
@ivar = Concurrent::IVar.new
|
|
6
6
|
end
|
|
@@ -15,7 +15,7 @@ class Kamal::Cli::Healthcheck::Barrier
|
|
|
15
15
|
|
|
16
16
|
def wait
|
|
17
17
|
unless opened?
|
|
18
|
-
raise
|
|
18
|
+
raise Dash::Cli::Healthcheck::Error.new("Halted at barrier")
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Raised when the config declares a healthcheck the running container does not have.
|
|
2
2
|
#
|
|
3
|
-
# Deliberately NOT a
|
|
3
|
+
# Deliberately NOT a Dash::Cli::Healthcheck::Error: the poller retries that class until
|
|
4
4
|
# deploy_timeout, and drift is deterministic — `.State.Health` exists from the moment a
|
|
5
5
|
# container with a healthcheck is created, so retrying only burns the timeout before
|
|
6
6
|
# reporting the same thing.
|
|
7
|
-
class
|
|
7
|
+
class Dash::Cli::Healthcheck::DriftError < StandardError; end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
module
|
|
1
|
+
module Dash::Cli::Healthcheck::Poller
|
|
2
2
|
extend self
|
|
3
3
|
|
|
4
|
-
NO_HEALTHCHECK =
|
|
4
|
+
NO_HEALTHCHECK = Dash::Commands::Base::NO_HEALTHCHECK
|
|
5
5
|
|
|
6
6
|
def wait_for_healthy(role:, &block)
|
|
7
7
|
attempt = 1
|
|
8
|
-
timeout_at = Time.now +
|
|
8
|
+
timeout_at = Time.now + DASH.config.deploy_timeout
|
|
9
9
|
readiness_delay = role.readiness_delay
|
|
10
10
|
|
|
11
11
|
begin
|
|
@@ -27,13 +27,13 @@ module Kamal::Cli::Healthcheck::Poller
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
unless acceptable?(status)
|
|
30
|
-
raise
|
|
30
|
+
raise Dash::Cli::Healthcheck::Error, "container not ready after #{DASH.config.deploy_timeout} seconds (#{status})"
|
|
31
31
|
end
|
|
32
|
-
rescue
|
|
32
|
+
rescue Dash::Cli::Healthcheck::Error => e
|
|
33
33
|
time_left = timeout_at - Time.now
|
|
34
34
|
if time_left > 0
|
|
35
35
|
sleep_for = [ attempt, time_left ].min
|
|
36
|
-
elapsed =
|
|
36
|
+
elapsed = DASH.config.deploy_timeout - time_left
|
|
37
37
|
info "Container not ready yet, retrying in #{sleep_for.ceil}s (#{elapsed.round}s elapsed, #{time_left.round}s left)"
|
|
38
38
|
sleep sleep_for
|
|
39
39
|
attempt += 1
|
|
@@ -65,7 +65,7 @@ module Kamal::Cli::Healthcheck::Poller
|
|
|
65
65
|
def ensure_no_healthcheck_drift(role, status)
|
|
66
66
|
return unless %i[ healthcheck docker_options ].include?(role.readiness_source)
|
|
67
67
|
|
|
68
|
-
raise
|
|
68
|
+
raise Dash::Cli::Healthcheck::DriftError,
|
|
69
69
|
"#{role.name} declares a healthcheck but the container reports none (#{status}) — the flags never reached docker. " \
|
|
70
70
|
"Compare `servers/#{role.name}` in your deploy config against `docker inspect --format '{{json .Config.Healthcheck}}'` " \
|
|
71
71
|
"on the container; the deploy would otherwise be accepted without ever probing it."
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
# Moves a project from the legacy `.kamal` directory to `.dash`. Purely local -
|
|
4
|
+
# it never opens an SSH connection and never reads deploy.yml - and only ever
|
|
5
|
+
# runs when the operator asks for it, because silently rewriting someone's
|
|
6
|
+
# working tree is not a deploy tool's job.
|
|
7
|
+
#
|
|
8
|
+
# Returns [message, color] pairs rather than printing: output belongs to the Thor
|
|
9
|
+
# command, and reaching into the Thor instance for `say` couples this class to a
|
|
10
|
+
# shell helper whose visibility changes between Thor versions.
|
|
11
|
+
class Dash::Cli::Main::Migrate
|
|
12
|
+
CURRENT = Dash::ProjectDirectory::CURRENT
|
|
13
|
+
LEGACY = Dash::ProjectDirectory::LEGACY
|
|
14
|
+
|
|
15
|
+
LEGACY_ENV_PATTERN = /\bKAMAL_[A-Z0-9_]+/
|
|
16
|
+
EXTRA_SCANNED_FILES = [ "config/deploy.yml" ].freeze
|
|
17
|
+
|
|
18
|
+
attr_reader :dry_run
|
|
19
|
+
|
|
20
|
+
def initialize(dry_run: false)
|
|
21
|
+
@dry_run = dry_run
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
return [ [ "No #{LEGACY} directory to migrate.", nil ] ] unless Dir.exist?(LEGACY)
|
|
26
|
+
|
|
27
|
+
if Dir.exist?(CURRENT)
|
|
28
|
+
return [ [ "Both #{CURRENT} and #{LEGACY} exist. Merge them by hand, then remove #{LEGACY}.", :yellow ] ]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if dry_run
|
|
32
|
+
[ [ "Would move #{LEGACY} to #{CURRENT}#{" with git mv" if tracked_by_git?}", nil ], *legacy_env_reference_lines ]
|
|
33
|
+
else
|
|
34
|
+
move
|
|
35
|
+
[ [ "Moved #{LEGACY} to #{CURRENT}", :green ], *legacy_env_reference_lines ]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
# `git mv` stages the rename, so the operator is left with a clean rename in
|
|
41
|
+
# `git status` rather than a delete plus a pile of untracked files.
|
|
42
|
+
def move
|
|
43
|
+
if tracked_by_git?
|
|
44
|
+
system("git", "mv", LEGACY, CURRENT, exception: true)
|
|
45
|
+
else
|
|
46
|
+
FileUtils.mv LEGACY, CURRENT
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def tracked_by_git?
|
|
51
|
+
return @tracked_by_git if defined?(@tracked_by_git)
|
|
52
|
+
@tracked_by_git = !`git ls-files -- #{LEGACY} 2> /dev/null`.strip.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reported, never rewritten: both prefixes carry the same value until 5.0,
|
|
56
|
+
# so there is nothing to fix today and an automatic rewrite of hook scripts
|
|
57
|
+
# would be a much bigger promise than this command makes.
|
|
58
|
+
def legacy_env_reference_lines
|
|
59
|
+
references = scanned_files.filter_map do |file|
|
|
60
|
+
names = File.read(file, encoding: Encoding::BINARY).scan(LEGACY_ENV_PATTERN).uniq
|
|
61
|
+
[ file, names ] if names.any?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return [] if references.empty?
|
|
65
|
+
|
|
66
|
+
[
|
|
67
|
+
[ "\nFound legacy KAMAL_* references. dash sets both prefixes until 5.0, so nothing is urgent:", nil ],
|
|
68
|
+
*references.map { |file, names| [ " #{file}: #{names.sort.join(", ")}", nil ] }
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def scanned_files
|
|
73
|
+
directory = dry_run ? LEGACY : CURRENT
|
|
74
|
+
|
|
75
|
+
(Dir.glob("#{directory}/**/*") + EXTRA_SCANNED_FILES).select { |file| File.file?(file) && File.readable?(file) }
|
|
76
|
+
end
|
|
77
|
+
end
|