dash 4.0.5 → 4.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee8ed58f4d81341ced43dc7d970c081a4c92becc3492860cf3ec8fd1a80687ec
4
- data.tar.gz: 588e1b33ca3f041a54beb773472cab9b8144892a32972b71ae9c58306cbb8612
3
+ metadata.gz: a417156134dd79a4724f87192f70baa55fdbc4478a115e021845e3742bacee88
4
+ data.tar.gz: ebdbb8d7f192b692c31c8cdaa9834584c01dec3653b744b1a481672fee179d28
5
5
  SHA512:
6
- metadata.gz: b24d5758340f3e3b7b7b6ecf1df765ffabab7360ddd5bc0df99dad3b90ba3662fc65dcbbe6bfa99af29dea90efb977f4b3d5a2d68454c1e652d57602857f9166
7
- data.tar.gz: bbc5bfea941e10310fd01776548b462e9e0e82d4228ed25bb66a0c16b35083979473cb2232ccb7bab3820ccaa45ae32303f424ffebfe76ac321b84354c54cf2e
6
+ metadata.gz: 3b263730b7ccf4eeeb857304477945fd9d2476bdf52cdb30a964b12b09143b36b5f585ae672d776eac29a6e009606add151aa6e9dd98385f9146980bbc40a9db
7
+ data.tar.gz: a3ebb3e0141a0f692bd70205aa3d438f9a3cbd1022d1b92c229bf1daf19294c4826a7420c0fefbb077f5a6d6cb46016ee6ab3926bb08f11383bb7a6eb0aaa23e
@@ -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
- result :proxy_version, :fail, "#{version} is older than the minimum #{minimum}, run `dash proxy reboot` to update"
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}
@@ -79,3 +79,11 @@ ssh:
79
79
  # host or tunnel that does not support agent forwarding (for example,
80
80
  # Cloudflare Access for Infrastructure with SSH).
81
81
  forward_agent: false
82
+
83
+ # Connect timeout
84
+ #
85
+ # Seconds to wait for the TCP connection and the SSH handshake with a
86
+ # host before giving up with `Net::SSH::ConnectionTimeout`. Defaults to
87
+ # 30. Without a bound, a host that accepts the connection but never
88
+ # answers the handshake hangs the deploy indefinitely.
89
+ connect_timeout: 30
@@ -23,7 +23,8 @@ sshkit:
23
23
  #
24
24
  # A pooled connection that the network dropped while idle is evicted and the command
25
25
  # retried once on a fresh connection, so a long wait elsewhere (a server-lock poll, a
26
- # loadbalancer reboot) does not fail the next command on another host.
26
+ # loadbalancer reboot) does not fail the next command on another host. A host that
27
+ # stops answering keepalives mid-command is not retried; that deploy fails.
27
28
  pool_idle_timeout: 300
28
29
 
29
30
  # DNS retry settings
@@ -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
- secrets[registry_config[key].first]
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
- registry_config[key]
58
+ raise Dash::ConfigurationError, "#{context}/#{key}: is blank"
38
59
  end
39
60
  end
40
61
  end
@@ -57,8 +57,15 @@ class Dash::Configuration::Ssh
57
57
  ssh_config["forward_agent"]
58
58
  end
59
59
 
60
+ # net-ssh only bounds the TCP connect and the version/key exchange when
61
+ # `timeout` is set. Without it, a host that accepts the TCP connection but
62
+ # never finishes the handshake blocks the deploy forever.
63
+ def connect_timeout
64
+ ssh_config.fetch("connect_timeout", 30)
65
+ end
66
+
60
67
  def options
61
- { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data, config: config, forward_agent: forward_agent }.compact
68
+ { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data, config: config, forward_agent: forward_agent, timeout: connect_timeout }.compact
62
69
  end
63
70
 
64
71
  def to_h
@@ -181,8 +181,15 @@ class SSHKit::Backend::Netssh
181
181
  # The block is rerun, so a drop *mid-command* runs the command twice. That
182
182
  # is accepted: the errors here are the idle-drop ones, where nothing reached
183
183
  # the server, and the alternative is the deploy failing on the next host.
184
+ #
185
+ # Net::SSH::Timeout (a Disconnect subclass) is deliberately excluded. net-ssh
186
+ # raises it when a host has ignored `keepalive_maxcount` keepalives in a row,
187
+ # which is a host that stopped answering mid-command, not a dropped idle
188
+ # socket. Retrying that reconnects to a host that has just proven unresponsive
189
+ # and, if it accepts the connection but never finishes the handshake or the
190
+ # command, hangs the deploy with no further guard. Let it fail.
184
191
  module ReconnectOnStaleConnection
185
- STALE_CONNECTION_ERRORS = [ Errno::ECONNRESET, Errno::EPIPE, Net::SSH::Disconnect, Net::SSH::Timeout ].freeze
192
+ STALE_CONNECTION_ERRORS = [ Errno::ECONNRESET, Errno::EPIPE, Net::SSH::Disconnect ].freeze
186
193
 
187
194
  private
188
195
  def with_ssh
@@ -191,12 +198,12 @@ class SSHKit::Backend::Netssh
191
198
  begin
192
199
  super do |ssh|
193
200
  yield ssh
194
- rescue *STALE_CONNECTION_ERRORS
195
- evict_stale_session(ssh)
201
+ rescue *STALE_CONNECTION_ERRORS => e
202
+ evict_stale_session(ssh) if stale_connection_error?(e)
196
203
  raise
197
204
  end
198
205
  rescue *STALE_CONNECTION_ERRORS => e
199
- raise if reconnected
206
+ raise if reconnected || !stale_connection_error?(e)
200
207
 
201
208
  reconnected = true
202
209
  SSHKit.config.output.warn("Reconnecting to #{host}: #{e.message}")
@@ -204,6 +211,10 @@ class SSHKit::Backend::Netssh
204
211
  end
205
212
  end
206
213
 
214
+ def stale_connection_error?(error)
215
+ !error.is_a?(Net::SSH::Timeout)
216
+ end
217
+
207
218
  # `close` waits for channel-close acknowledgements, which never come over
208
219
  # a dead socket. `shutdown!` just closes the socket, and a closed session
209
220
  # is what makes the pool drop it instead of caching it again.
data/lib/dash/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dash
2
- VERSION = "4.0.5"
2
+ VERSION = "4.0.8"
3
3
  end
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.5
4
+ version: 4.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson