kamal 2.8.2 → 2.10.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/lib/kamal/cli/accessory.rb +14 -7
- data/lib/kamal/cli/app/boot.rb +1 -1
- data/lib/kamal/cli/app.rb +74 -115
- data/lib/kamal/cli/healthcheck/poller.rb +1 -1
- data/lib/kamal/cli/main.rb +2 -1
- data/lib/kamal/cli/proxy.rb +42 -35
- data/lib/kamal/cli/secrets.rb +2 -1
- data/lib/kamal/cli/server.rb +2 -1
- data/lib/kamal/cli/templates/secrets +1 -0
- data/lib/kamal/commander.rb +3 -2
- data/lib/kamal/commands/app/execution.rb +7 -1
- data/lib/kamal/commands/app.rb +1 -1
- data/lib/kamal/commands/proxy.rb +21 -2
- data/lib/kamal/configuration/accessory.rb +63 -26
- data/lib/kamal/configuration/boot.rb +4 -0
- data/lib/kamal/configuration/builder.rb +10 -3
- data/lib/kamal/configuration/docs/accessory.yml +37 -5
- data/lib/kamal/configuration/docs/boot.yml +12 -10
- data/lib/kamal/configuration/docs/configuration.yml +10 -1
- data/lib/kamal/configuration/docs/proxy.yml +24 -0
- data/lib/kamal/configuration/docs/ssh.yml +7 -4
- data/lib/kamal/configuration/docs/sshkit.yml +8 -0
- data/lib/kamal/configuration/env.rb +7 -3
- data/lib/kamal/configuration/proxy/boot.rb +4 -9
- data/lib/kamal/configuration/proxy/run.rb +143 -0
- data/lib/kamal/configuration/proxy.rb +2 -2
- data/lib/kamal/configuration/role.rb +15 -3
- data/lib/kamal/configuration/ssh.rb +18 -3
- data/lib/kamal/configuration/sshkit.rb +4 -0
- data/lib/kamal/configuration/validator/proxy.rb +20 -0
- data/lib/kamal/configuration/validator.rb +34 -1
- data/lib/kamal/configuration/volume.rb +11 -4
- data/lib/kamal/configuration.rb +32 -1
- data/lib/kamal/secrets/adapters/passbolt.rb +1 -2
- data/lib/kamal/secrets/adapters/test.rb +3 -1
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +15 -1
- data/lib/kamal/secrets.rb +17 -6
- data/lib/kamal/sshkit_with_ext.rb +127 -10
- data/lib/kamal/utils.rb +3 -3
- data/lib/kamal/version.rb +1 -1
- metadata +2 -1
|
@@ -3,6 +3,7 @@ require "sshkit/dsl"
|
|
|
3
3
|
require "net/scp"
|
|
4
4
|
require "active_support/core_ext/hash/deep_merge"
|
|
5
5
|
require "json"
|
|
6
|
+
require "resolv"
|
|
6
7
|
require "concurrent/atomic/semaphore"
|
|
7
8
|
|
|
8
9
|
class SSHKit::Backend::Abstract
|
|
@@ -18,8 +19,11 @@ class SSHKit::Backend::Abstract
|
|
|
18
19
|
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
def puts_by_host(host, output, type: "App")
|
|
22
|
-
|
|
22
|
+
def puts_by_host(host, output, type: "App", quiet: false)
|
|
23
|
+
unless quiet
|
|
24
|
+
puts "#{type} Host: #{host}"
|
|
25
|
+
end
|
|
26
|
+
puts "#{output}\n\n"
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
# Our execution pattern is for the CLI execute args lists returned
|
|
@@ -58,10 +62,50 @@ class SSHKit::Backend::Abstract
|
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
class SSHKit::Backend::Netssh::Configuration
|
|
61
|
-
attr_accessor :max_concurrent_starts
|
|
65
|
+
attr_accessor :max_concurrent_starts, :dns_retries
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
class SSHKit::Backend::Netssh
|
|
69
|
+
module DnsRetriable
|
|
70
|
+
DNS_RETRY_BASE = 0.1
|
|
71
|
+
DNS_RETRY_MAX = 2.0
|
|
72
|
+
DNS_RETRY_JITTER = 0.1
|
|
73
|
+
DNS_ERROR_MESSAGE = /getaddrinfo|Temporary failure in name resolution|Name or service not known|nodename nor servname provided|No address associated|failed to look up|resolve/i
|
|
74
|
+
|
|
75
|
+
def with_dns_retry(hostname, retries: config.dns_retries, base: DNS_RETRY_BASE, max_sleep: DNS_RETRY_MAX, jitter: DNS_RETRY_JITTER)
|
|
76
|
+
attempts = 0
|
|
77
|
+
begin
|
|
78
|
+
attempts += 1
|
|
79
|
+
yield
|
|
80
|
+
rescue => error
|
|
81
|
+
raise unless retryable_dns_error?(error) && attempts <= retries
|
|
82
|
+
|
|
83
|
+
delay = dns_retry_sleep(attempts, base: base, jitter: jitter, max_sleep: max_sleep)
|
|
84
|
+
SSHKit.config.output.warn("Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}) in #{format("%0.2f", delay)}s: #{error.message}")
|
|
85
|
+
sleep delay
|
|
86
|
+
retry
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
def retryable_dns_error?(error)
|
|
92
|
+
case error
|
|
93
|
+
when Resolv::ResolvError, Resolv::ResolvTimeout
|
|
94
|
+
true
|
|
95
|
+
when SocketError
|
|
96
|
+
error.message =~ DNS_ERROR_MESSAGE
|
|
97
|
+
else
|
|
98
|
+
error.cause && retryable_dns_error?(error.cause)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def dns_retry_sleep(attempt, base:, jitter:, max_sleep:)
|
|
103
|
+
sleep_for = [ base * (2 ** (attempt - 1)), max_sleep ].min
|
|
104
|
+
sleep_for += Kernel.rand * jitter
|
|
105
|
+
sleep_for
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
65
109
|
module LimitConcurrentStartsClass
|
|
66
110
|
attr_reader :start_semaphore
|
|
67
111
|
|
|
@@ -76,14 +120,31 @@ class SSHKit::Backend::Netssh
|
|
|
76
120
|
|
|
77
121
|
class << self
|
|
78
122
|
prepend LimitConcurrentStartsClass
|
|
123
|
+
prepend DnsRetriable
|
|
79
124
|
end
|
|
80
125
|
|
|
126
|
+
module ConnectSsh
|
|
127
|
+
private
|
|
128
|
+
def connect_ssh(...)
|
|
129
|
+
Net::SSH.start(...)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
include ConnectSsh
|
|
133
|
+
|
|
134
|
+
module DnsRetriableConnection
|
|
135
|
+
private
|
|
136
|
+
def connect_ssh(...)
|
|
137
|
+
self.class.with_dns_retry(host.hostname) { super }
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
prepend DnsRetriableConnection
|
|
141
|
+
|
|
81
142
|
module LimitConcurrentStartsInstance
|
|
82
143
|
private
|
|
83
144
|
def with_ssh(&block)
|
|
84
145
|
host.ssh_options = self.class.config.ssh_options.merge(host.ssh_options || {})
|
|
85
146
|
self.class.pool.with(
|
|
86
|
-
method(:
|
|
147
|
+
method(:connect_ssh),
|
|
87
148
|
String(host.hostname),
|
|
88
149
|
host.username,
|
|
89
150
|
host.netssh_options,
|
|
@@ -91,17 +152,18 @@ class SSHKit::Backend::Netssh
|
|
|
91
152
|
)
|
|
92
153
|
end
|
|
93
154
|
|
|
94
|
-
def
|
|
155
|
+
def connect_ssh(...)
|
|
156
|
+
with_concurrency_limit { super }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def with_concurrency_limit(&block)
|
|
95
160
|
if self.class.start_semaphore
|
|
96
|
-
self.class.start_semaphore.acquire
|
|
97
|
-
Net::SSH.start(*args)
|
|
98
|
-
end
|
|
161
|
+
self.class.start_semaphore.acquire(&block)
|
|
99
162
|
else
|
|
100
|
-
|
|
163
|
+
yield
|
|
101
164
|
end
|
|
102
165
|
end
|
|
103
166
|
end
|
|
104
|
-
|
|
105
167
|
prepend LimitConcurrentStartsInstance
|
|
106
168
|
end
|
|
107
169
|
|
|
@@ -148,3 +210,58 @@ module NetSshForwardingNoPuts
|
|
|
148
210
|
end
|
|
149
211
|
|
|
150
212
|
Net::SSH::Service::Forward.prepend NetSshForwardingNoPuts
|
|
213
|
+
|
|
214
|
+
module SSHKitDslRoles
|
|
215
|
+
# Execute on hosts grouped by role.
|
|
216
|
+
#
|
|
217
|
+
# Unlike `on()` which deduplicates hosts, this allows the same host to have
|
|
218
|
+
# multiple concurrent connections when it appears in multiple roles.
|
|
219
|
+
#
|
|
220
|
+
# Options:
|
|
221
|
+
# hosts: The hosts to run on (required)
|
|
222
|
+
# parallel: When true, each role runs in its own thread with separate
|
|
223
|
+
# connections. When false, hosts run in parallel but roles on each
|
|
224
|
+
# host run sequentially (default: true)
|
|
225
|
+
#
|
|
226
|
+
# Example:
|
|
227
|
+
# on_roles(roles) do |host, role|
|
|
228
|
+
# # deploy role to host
|
|
229
|
+
# end
|
|
230
|
+
def on_roles(roles, hosts:, parallel: true, &block)
|
|
231
|
+
if parallel
|
|
232
|
+
threads = roles.filter_map do |role|
|
|
233
|
+
if (role_hosts = role.hosts & hosts).any?
|
|
234
|
+
Thread.new do
|
|
235
|
+
on(role_hosts) { |host| instance_exec(host, role, &block) }
|
|
236
|
+
rescue StandardError => e
|
|
237
|
+
raise SSHKit::Runner::ExecuteError.new(e), "Exception while executing on #{role}: #{e.message}"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
exceptions = []
|
|
243
|
+
threads.each do |t|
|
|
244
|
+
begin
|
|
245
|
+
t.join
|
|
246
|
+
rescue SSHKit::Runner::ExecuteError => e
|
|
247
|
+
exceptions << e
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
if exceptions.one?
|
|
252
|
+
raise exceptions.first
|
|
253
|
+
elsif exceptions.many?
|
|
254
|
+
raise exceptions.first, [ "Exceptions on #{exceptions.count} roles:", exceptions.map(&:message) ].join("\n")
|
|
255
|
+
end
|
|
256
|
+
else
|
|
257
|
+
# Host-first iteration: hosts run in parallel, roles on each host run sequentially
|
|
258
|
+
on(hosts) do |host|
|
|
259
|
+
roles.each do |role|
|
|
260
|
+
instance_exec(host, role, &block) if role.hosts.include?(host.to_s)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
SSHKit::DSL.prepend SSHKitDslRoles
|
data/lib/kamal/utils.rb
CHANGED
|
@@ -21,11 +21,11 @@ module Kamal::Utils
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
|
|
24
|
-
def optionize(args, with: nil)
|
|
24
|
+
def optionize(args, with: nil, escape: true)
|
|
25
25
|
options = if with
|
|
26
|
-
flatten_args(args).collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" }
|
|
26
|
+
flatten_args(args).collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape ? escape_shell_value(value) : value}" }
|
|
27
27
|
else
|
|
28
|
-
flatten_args(args).collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }
|
|
28
|
+
flatten_args(args).collect { |(key, value)| [ "--#{key}", value == true ? nil : escape ? escape_shell_value(value) : value ] }
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
options.flatten.compact
|
data/lib/kamal/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kamal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -299,6 +299,7 @@ files:
|
|
|
299
299
|
- lib/kamal/configuration/logging.rb
|
|
300
300
|
- lib/kamal/configuration/proxy.rb
|
|
301
301
|
- lib/kamal/configuration/proxy/boot.rb
|
|
302
|
+
- lib/kamal/configuration/proxy/run.rb
|
|
302
303
|
- lib/kamal/configuration/registry.rb
|
|
303
304
|
- lib/kamal/configuration/role.rb
|
|
304
305
|
- lib/kamal/configuration/servers.rb
|