kamal 2.9.0 → 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 +8 -4
- data/lib/kamal/cli/app/boot.rb +1 -1
- data/lib/kamal/cli/app.rb +64 -112
- data/lib/kamal/cli/healthcheck/poller.rb +1 -1
- data/lib/kamal/cli/proxy.rb +41 -35
- data/lib/kamal/cli/secrets.rb +2 -1
- data/lib/kamal/commander.rb +2 -2
- 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/docs/accessory.yml +37 -5
- data/lib/kamal/configuration/docs/boot.yml +12 -10
- data/lib/kamal/configuration/docs/configuration.yml +4 -1
- data/lib/kamal/configuration/docs/proxy.yml +24 -0
- data/lib/kamal/configuration/docs/ssh.yml +6 -3
- 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 +14 -2
- data/lib/kamal/configuration/ssh.rb +13 -2
- data/lib/kamal/configuration/validator/proxy.rb +20 -0
- data/lib/kamal/configuration/validator.rb +20 -0
- data/lib/kamal/configuration/volume.rb +11 -4
- data/lib/kamal/configuration.rb +27 -0
- 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 +14 -4
- data/lib/kamal/sshkit_with_ext.rb +55 -0
- data/lib/kamal/utils.rb +3 -3
- data/lib/kamal/version.rb +1 -1
- metadata +2 -1
data/lib/kamal/configuration.rb
CHANGED
|
@@ -77,6 +77,7 @@ class Kamal::Configuration
|
|
|
77
77
|
ensure_one_host_for_ssl_roles
|
|
78
78
|
ensure_unique_hosts_for_ssl_roles
|
|
79
79
|
ensure_local_registry_remote_builder_has_ssh_url
|
|
80
|
+
ensure_no_conflicting_proxy_runs
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
def version=(version)
|
|
@@ -122,6 +123,14 @@ class Kamal::Configuration
|
|
|
122
123
|
(roles + accessories).flat_map(&:hosts).uniq
|
|
123
124
|
end
|
|
124
125
|
|
|
126
|
+
def host_roles(host)
|
|
127
|
+
roles.select { |role| role.hosts.include?(host) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def host_accessories(host)
|
|
131
|
+
accessories.select { |accessory| accessory.hosts.include?(host) }
|
|
132
|
+
end
|
|
133
|
+
|
|
125
134
|
def app_hosts
|
|
126
135
|
roles.flat_map(&:hosts).uniq
|
|
127
136
|
end
|
|
@@ -165,6 +174,11 @@ class Kamal::Configuration
|
|
|
165
174
|
name
|
|
166
175
|
end
|
|
167
176
|
|
|
177
|
+
def proxy_run(host)
|
|
178
|
+
# We validate that all the config are identical for a host
|
|
179
|
+
proxy_runs(host.to_s).first
|
|
180
|
+
end
|
|
181
|
+
|
|
168
182
|
def repository
|
|
169
183
|
[ registry.server, image ].compact.join("/")
|
|
170
184
|
end
|
|
@@ -378,6 +392,19 @@ class Kamal::Configuration
|
|
|
378
392
|
true
|
|
379
393
|
end
|
|
380
394
|
|
|
395
|
+
def ensure_no_conflicting_proxy_runs
|
|
396
|
+
all_hosts.each do |host|
|
|
397
|
+
run_configs = proxy_runs(host)
|
|
398
|
+
if run_configs.uniq.size > 1
|
|
399
|
+
raise Kamal::ConfigurationError, "Conflicting proxy run configurations for host #{host}"
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def proxy_runs(host)
|
|
405
|
+
(host_roles(host) + host_accessories(host)).map(&:proxy).compact.map(&:run).compact
|
|
406
|
+
end
|
|
407
|
+
|
|
381
408
|
def role_names
|
|
382
409
|
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
|
383
410
|
end
|
|
@@ -5,7 +5,9 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def fetch_secrets(secrets, from:, account:, session:)
|
|
8
|
-
prefixed_secrets(secrets, from: from).to_h
|
|
8
|
+
prefixed_secrets(secrets, from: from).to_h do |secret|
|
|
9
|
+
[ secret, secret.gsub("LPAREN", "(").gsub("RPAREN", ")").reverse ]
|
|
10
|
+
end
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def check_dependencies!
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
|
2
|
+
# Unlike dotenv, this regex does not match escaped
|
|
3
|
+
# parentheses when looking for command substitutions.
|
|
4
|
+
INTERPOLATED_SHELL_COMMAND = /
|
|
5
|
+
(?<backslash>\\)? # is it escaped with a backslash?
|
|
6
|
+
\$ # literal $
|
|
7
|
+
(?<cmd> # collect command content for eval
|
|
8
|
+
\( # require opening paren
|
|
9
|
+
(?:\\.|[^()\\]|\g<cmd>)+ # allow any number of non-parens or escaped
|
|
10
|
+
# parens (by nesting the <cmd> expression
|
|
11
|
+
# recursively)
|
|
12
|
+
\) # require closing paren
|
|
13
|
+
)
|
|
14
|
+
/x
|
|
15
|
+
|
|
2
16
|
class << self
|
|
3
17
|
def install!
|
|
4
18
|
::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub }
|
|
@@ -6,7 +20,7 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
|
|
6
20
|
|
|
7
21
|
def call(value, env, overwrite: false)
|
|
8
22
|
# Process interpolated shell commands
|
|
9
|
-
value.gsub(
|
|
23
|
+
value.gsub(INTERPOLATED_SHELL_COMMAND) do |*|
|
|
10
24
|
# Eliminate opening and closing parentheses
|
|
11
25
|
command = $LAST_MATCH_INFO[:cmd][1..-2]
|
|
12
26
|
|
data/lib/kamal/secrets.rb
CHANGED
|
@@ -10,10 +10,7 @@ class Kamal::Secrets
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def [](key)
|
|
13
|
-
|
|
14
|
-
@mutex.synchronize do
|
|
15
|
-
secrets.fetch(key)
|
|
16
|
-
end
|
|
13
|
+
synchronized_fetch(key)
|
|
17
14
|
rescue KeyError
|
|
18
15
|
if secrets_files.present?
|
|
19
16
|
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
|
|
@@ -30,6 +27,12 @@ class Kamal::Secrets
|
|
|
30
27
|
@secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
|
|
31
28
|
end
|
|
32
29
|
|
|
30
|
+
def key?(key)
|
|
31
|
+
synchronized_fetch(key).present?
|
|
32
|
+
rescue KeyError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
33
36
|
private
|
|
34
37
|
def secrets
|
|
35
38
|
@secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
|
|
@@ -40,4 +43,11 @@ class Kamal::Secrets
|
|
|
40
43
|
def secrets_filenames
|
|
41
44
|
[ "#{@secrets_path}-common", "#{@secrets_path}#{(".#{@destination}" if @destination)}" ]
|
|
42
45
|
end
|
|
46
|
+
|
|
47
|
+
def synchronized_fetch(key)
|
|
48
|
+
# Fetching secrets may ask the user for input, so ensure only one thread does that
|
|
49
|
+
@mutex.synchronize do
|
|
50
|
+
secrets.fetch(key)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
43
53
|
end
|
|
@@ -210,3 +210,58 @@ module NetSshForwardingNoPuts
|
|
|
210
210
|
end
|
|
211
211
|
|
|
212
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
|