kamal 2.8.2 → 2.9.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 +6 -3
- data/lib/kamal/cli/app.rb +17 -10
- data/lib/kamal/cli/main.rb +2 -1
- data/lib/kamal/cli/proxy.rb +2 -1
- data/lib/kamal/cli/server.rb +2 -1
- data/lib/kamal/cli/templates/secrets +1 -0
- data/lib/kamal/commander.rb +1 -0
- data/lib/kamal/commands/app/execution.rb +7 -1
- data/lib/kamal/configuration/builder.rb +10 -3
- data/lib/kamal/configuration/docs/configuration.yml +6 -0
- data/lib/kamal/configuration/docs/ssh.yml +1 -1
- data/lib/kamal/configuration/docs/sshkit.yml +8 -0
- data/lib/kamal/configuration/role.rb +1 -1
- data/lib/kamal/configuration/ssh.rb +5 -1
- data/lib/kamal/configuration/sshkit.rb +4 -0
- data/lib/kamal/configuration/validator.rb +14 -1
- data/lib/kamal/configuration.rb +5 -1
- data/lib/kamal/secrets/adapters/passbolt.rb +1 -2
- data/lib/kamal/secrets.rb +3 -2
- data/lib/kamal/sshkit_with_ext.rb +72 -10
- data/lib/kamal/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd7c6e6e18ca7cc95712642dfdb5ec45c33e06b6a02750e5ee7a3f1ac3428ea2
|
|
4
|
+
data.tar.gz: 1a8acfe5ec44636827ca70da1263289e6082359c58601acce9870d1d46ba750b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1fb62392ddafc5b1fe6b54055121654bd73625eaf9808848ed60d7d40fdadf2938675b0bc881fca1297ee089f78b3768eefb96f5c10e9e8346265f20d9663e2
|
|
7
|
+
data.tar.gz: 6fc4f87a3ea64ebdee5c3c666cdafa5b15a0f7b243f575b402d82a2c8eec5b3802a05c0b5a80f6856eb92c24647b65a7bbdb8a68fd208dc873f1cd55d725894e
|
data/lib/kamal/cli/accessory.rb
CHANGED
|
@@ -128,12 +128,13 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
128
128
|
|
|
129
129
|
desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
|
|
130
130
|
def details(name)
|
|
131
|
+
quiet = options[:quiet]
|
|
131
132
|
if name == "all"
|
|
132
133
|
KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
|
|
133
134
|
else
|
|
134
135
|
type = "Accessory #{name}"
|
|
135
136
|
with_accessory(name) do |accessory, hosts|
|
|
136
|
-
on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type }
|
|
137
|
+
on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type, quiet: quiet }
|
|
137
138
|
end
|
|
138
139
|
end
|
|
139
140
|
end
|
|
@@ -145,6 +146,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
145
146
|
pre_connect_if_required
|
|
146
147
|
|
|
147
148
|
cmd = Kamal::Utils.join_commands(cmd)
|
|
149
|
+
quiet = options[:quiet]
|
|
150
|
+
|
|
148
151
|
with_accessory(name) do |accessory, hosts|
|
|
149
152
|
case
|
|
150
153
|
when options[:interactive] && options[:reuse]
|
|
@@ -160,7 +163,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
160
163
|
say "Launching command from existing container...", :magenta
|
|
161
164
|
on(hosts) do |host|
|
|
162
165
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
|
163
|
-
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
|
|
166
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd)), quiet: quiet
|
|
164
167
|
end
|
|
165
168
|
|
|
166
169
|
else
|
|
@@ -168,7 +171,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
|
168
171
|
on(hosts) do |host|
|
|
169
172
|
execute *KAMAL.registry.login
|
|
170
173
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
|
171
|
-
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
|
|
174
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd)), quiet: quiet
|
|
172
175
|
end
|
|
173
176
|
end
|
|
174
177
|
end
|
data/lib/kamal/cli/app.rb
CHANGED
|
@@ -92,11 +92,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
92
92
|
# FIXME: Drop in favor of just containers?
|
|
93
93
|
desc "details", "Show details about app containers"
|
|
94
94
|
def details
|
|
95
|
+
quiet = options[:quiet]
|
|
95
96
|
on(KAMAL.app_hosts) do |host|
|
|
96
97
|
roles = KAMAL.roles_on(host)
|
|
97
98
|
|
|
98
99
|
roles.each do |role|
|
|
99
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).info)
|
|
100
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).info), quiet: quiet
|
|
100
101
|
end
|
|
101
102
|
end
|
|
102
103
|
end
|
|
@@ -120,6 +121,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
120
121
|
cmd = Kamal::Utils.join_commands(cmd)
|
|
121
122
|
env = options[:env]
|
|
122
123
|
detach = options[:detach]
|
|
124
|
+
quiet = options[:quiet]
|
|
123
125
|
case
|
|
124
126
|
when options[:interactive] && options[:reuse]
|
|
125
127
|
say "Get current version of running container...", :magenta unless options[:version]
|
|
@@ -148,7 +150,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
148
150
|
|
|
149
151
|
roles.each do |role|
|
|
150
152
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
|
|
151
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_existing_container(cmd, env: env))
|
|
153
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_existing_container(cmd, env: env)), quiet: quiet
|
|
152
154
|
end
|
|
153
155
|
end
|
|
154
156
|
end
|
|
@@ -164,7 +166,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
164
166
|
|
|
165
167
|
roles.each do |role|
|
|
166
168
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
|
167
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
|
|
169
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach)), quiet: quiet
|
|
168
170
|
end
|
|
169
171
|
end
|
|
170
172
|
end
|
|
@@ -173,12 +175,14 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
173
175
|
|
|
174
176
|
desc "containers", "Show app containers on servers"
|
|
175
177
|
def containers
|
|
176
|
-
|
|
178
|
+
quiet = options[:quiet]
|
|
179
|
+
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers), quiet: quiet }
|
|
177
180
|
end
|
|
178
181
|
|
|
179
182
|
desc "stale_containers", "Detect app stale containers"
|
|
180
183
|
option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
|
|
181
184
|
def stale_containers
|
|
185
|
+
quiet = options[:quiet]
|
|
182
186
|
stop = options[:stop]
|
|
183
187
|
|
|
184
188
|
with_lock_if_stopping do
|
|
@@ -192,10 +196,10 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
192
196
|
|
|
193
197
|
versions.each do |version|
|
|
194
198
|
if stop
|
|
195
|
-
puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
|
|
199
|
+
puts_by_host host, "Stopping stale container for role #{role} with version #{version}", quiet: quiet
|
|
196
200
|
execute *app.stop(version: version), raise_on_non_zero_exit: false
|
|
197
201
|
else
|
|
198
|
-
puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)"
|
|
202
|
+
puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)", quiet: quiet
|
|
199
203
|
end
|
|
200
204
|
end
|
|
201
205
|
end
|
|
@@ -205,7 +209,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
205
209
|
|
|
206
210
|
desc "images", "Show app images on servers"
|
|
207
211
|
def images
|
|
208
|
-
|
|
212
|
+
quiet = options[:quiet]
|
|
213
|
+
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images), quiet: quiet }
|
|
209
214
|
end
|
|
210
215
|
|
|
211
216
|
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
|
@@ -224,6 +229,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
224
229
|
since = options[:since]
|
|
225
230
|
container_id = options[:container_id]
|
|
226
231
|
timestamps = !options[:skip_timestamps]
|
|
232
|
+
quiet = options[:quiet]
|
|
227
233
|
|
|
228
234
|
if options[:follow]
|
|
229
235
|
lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
|
|
@@ -246,9 +252,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
246
252
|
|
|
247
253
|
roles.each do |role|
|
|
248
254
|
begin
|
|
249
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
|
255
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options)), quiet: quiet
|
|
250
256
|
rescue SSHKit::Command::Failed
|
|
251
|
-
puts_by_host host, "Nothing found"
|
|
257
|
+
puts_by_host host, "Nothing found", quiet: quiet
|
|
252
258
|
end
|
|
253
259
|
end
|
|
254
260
|
end
|
|
@@ -352,9 +358,10 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
|
352
358
|
|
|
353
359
|
desc "version", "Show app version currently running on servers"
|
|
354
360
|
def version
|
|
361
|
+
quiet = options[:quiet]
|
|
355
362
|
on(KAMAL.app_hosts) do |host|
|
|
356
363
|
role = KAMAL.roles_on(host).first
|
|
357
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
|
|
364
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip, quiet: quiet
|
|
358
365
|
end
|
|
359
366
|
end
|
|
360
367
|
|
data/lib/kamal/cli/main.rb
CHANGED
|
@@ -112,8 +112,9 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
|
112
112
|
|
|
113
113
|
desc "audit", "Show audit log from servers"
|
|
114
114
|
def audit
|
|
115
|
+
quiet = options[:quiet]
|
|
115
116
|
on(KAMAL.hosts) do |host|
|
|
116
|
-
puts_by_host host, capture_with_info(*KAMAL.auditor.reveal)
|
|
117
|
+
puts_by_host host, capture_with_info(*KAMAL.auditor.reveal), quiet: quiet
|
|
117
118
|
end
|
|
118
119
|
end
|
|
119
120
|
|
data/lib/kamal/cli/proxy.rb
CHANGED
|
@@ -197,7 +197,8 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
|
197
197
|
|
|
198
198
|
desc "details", "Show details about proxy container from servers"
|
|
199
199
|
def details
|
|
200
|
-
|
|
200
|
+
quiet = options[:quiet]
|
|
201
|
+
on(KAMAL.proxy_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.proxy.info), type: "Proxy", quiet: quiet }
|
|
201
202
|
end
|
|
202
203
|
|
|
203
204
|
desc "logs", "Show log lines from proxy on servers"
|
data/lib/kamal/cli/server.rb
CHANGED
|
@@ -6,6 +6,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
|
6
6
|
|
|
7
7
|
cmd = Kamal::Utils.join_commands(cmd)
|
|
8
8
|
hosts = KAMAL.hosts
|
|
9
|
+
quiet = options[:quiet]
|
|
9
10
|
|
|
10
11
|
case
|
|
11
12
|
when options[:interactive]
|
|
@@ -19,7 +20,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
|
19
20
|
|
|
20
21
|
on(hosts) do |host|
|
|
21
22
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
|
|
22
|
-
puts_by_host host, capture_with_info(cmd)
|
|
23
|
+
puts_by_host host, capture_with_info(cmd), quiet: quiet
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
end
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
# Option 2: Read secrets via a command
|
|
9
9
|
# RAILS_MASTER_KEY=$(cat config/master.key)
|
|
10
|
+
# KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
|
|
10
11
|
|
|
11
12
|
# Option 3: Read secrets via kamal secrets helpers
|
|
12
13
|
# These will handle logging in and fetching the secrets in as few calls as possible
|
data/lib/kamal/commander.rb
CHANGED
|
@@ -155,6 +155,7 @@ class Kamal::Commander
|
|
|
155
155
|
SSHKit::Backend::Netssh.pool.idle_timeout = config.sshkit.pool_idle_timeout
|
|
156
156
|
SSHKit::Backend::Netssh.configure do |sshkit|
|
|
157
157
|
sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts
|
|
158
|
+
sshkit.dns_retries = config.sshkit.dns_retries
|
|
158
159
|
sshkit.ssh_options = config.ssh.options
|
|
159
160
|
end
|
|
160
161
|
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
|
|
@@ -12,6 +12,7 @@ module Kamal::Commands::App::Execution
|
|
|
12
12
|
(docker_interactive_args if interactive),
|
|
13
13
|
("--detach" if detach),
|
|
14
14
|
("--rm" unless detach),
|
|
15
|
+
"--name", container_name_for_exec,
|
|
15
16
|
"--network", "kamal",
|
|
16
17
|
*role&.env_args(host),
|
|
17
18
|
*argumentize("--env", env),
|
|
@@ -22,11 +23,16 @@ module Kamal::Commands::App::Execution
|
|
|
22
23
|
*command
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
def execute_in_existing_container_over_ssh(*command,
|
|
26
|
+
def execute_in_existing_container_over_ssh(*command, env:)
|
|
26
27
|
run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def execute_in_new_container_over_ssh(*command, env:)
|
|
30
31
|
run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
|
|
31
32
|
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
def container_name_for_exec
|
|
36
|
+
[ role.container_prefix, "exec", config.version, SecureRandom.hex(3) ].compact.join("-")
|
|
37
|
+
end
|
|
32
38
|
end
|
|
@@ -177,8 +177,15 @@ class Kamal::Configuration::Builder
|
|
|
177
177
|
[ server, cache_image ].compact.join("/")
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
+
def cache_options
|
|
181
|
+
builder_config["cache"]&.fetch("options", nil)
|
|
182
|
+
end
|
|
183
|
+
|
|
180
184
|
def cache_from_config_for_gha
|
|
181
|
-
|
|
185
|
+
individual_options = cache_options&.split(",") || []
|
|
186
|
+
allowed_options = individual_options.select { |option| option =~ /^(url|url_v2|token|scope|timeout)=/ }
|
|
187
|
+
|
|
188
|
+
[ "type=gha", *allowed_options ].compact.join(",")
|
|
182
189
|
end
|
|
183
190
|
|
|
184
191
|
def cache_from_config_for_registry
|
|
@@ -186,11 +193,11 @@ class Kamal::Configuration::Builder
|
|
|
186
193
|
end
|
|
187
194
|
|
|
188
195
|
def cache_to_config_for_gha
|
|
189
|
-
[ "type=gha",
|
|
196
|
+
[ "type=gha", cache_options ].compact.join(",")
|
|
190
197
|
end
|
|
191
198
|
|
|
192
199
|
def cache_to_config_for_registry
|
|
193
|
-
[ "type=registry", "ref=#{cache_image_ref}",
|
|
200
|
+
[ "type=registry", "ref=#{cache_image_ref}", cache_options ].compact.join(",")
|
|
194
201
|
end
|
|
195
202
|
|
|
196
203
|
def repo_basename
|
|
@@ -82,6 +82,12 @@ asset_path: /path/to/assets
|
|
|
82
82
|
# See https://kamal-deploy.org/docs/hooks for more information:
|
|
83
83
|
hooks_path: /user_home/kamal/hooks
|
|
84
84
|
|
|
85
|
+
# Secrets path
|
|
86
|
+
#
|
|
87
|
+
# Path to secrets, defaults to `.kamal/secrets`.
|
|
88
|
+
# Kamal will look for `<secrets_path>-common` and `<secrets_path>` (or `<secrets_path>.<destination>` when using destinations):
|
|
89
|
+
secrets_path: /user_home/kamal/secrets
|
|
90
|
+
|
|
85
91
|
# Error pages
|
|
86
92
|
#
|
|
87
93
|
# A directory relative to the app root to find error pages for the proxy to serve.
|
|
@@ -67,4 +67,4 @@ ssh:
|
|
|
67
67
|
# Set to true to load the default OpenSSH config files (~/.ssh/config,
|
|
68
68
|
# /etc/ssh_config), to false ignore config files, or to a file path
|
|
69
69
|
# (or array of paths) to load specific configuration. Defaults to true.
|
|
70
|
-
config:
|
|
70
|
+
config: [ "~/.ssh/myconfig" ]
|
|
@@ -21,3 +21,11 @@ sshkit:
|
|
|
21
21
|
# Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
|
|
22
22
|
# re-connection storms after an idle period, such as building an image or waiting for CI.
|
|
23
23
|
pool_idle_timeout: 300
|
|
24
|
+
|
|
25
|
+
# DNS retry settings
|
|
26
|
+
#
|
|
27
|
+
# Some resolvers (mDNSResponder, systemd-resolved, Tailscale) can drop lookups during
|
|
28
|
+
# bursts of concurrent SSH starts. Kamal will retry DNS failures automatically.
|
|
29
|
+
#
|
|
30
|
+
# Number of retries after the initial attempt. Set to 0 to disable.
|
|
31
|
+
dns_retries: 3
|
|
@@ -38,8 +38,12 @@ class Kamal::Configuration::Ssh
|
|
|
38
38
|
ssh_config["key_data"]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def config
|
|
42
|
+
ssh_config["config"]
|
|
43
|
+
end
|
|
44
|
+
|
|
41
45
|
def options
|
|
42
|
-
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
|
|
46
|
+
{ 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 }.compact
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def to_h
|
|
@@ -34,6 +34,8 @@ class Kamal::Configuration::Validator
|
|
|
34
34
|
elsif example_value.is_a?(Array)
|
|
35
35
|
if key == "arch"
|
|
36
36
|
validate_array_of_or_type! value, example_value.first.class
|
|
37
|
+
elsif key.to_s == "config"
|
|
38
|
+
validate_ssh_config!(value)
|
|
37
39
|
else
|
|
38
40
|
validate_array_of! value, example_value.first.class
|
|
39
41
|
end
|
|
@@ -129,6 +131,16 @@ class Kamal::Configuration::Validator
|
|
|
129
131
|
end
|
|
130
132
|
end
|
|
131
133
|
|
|
134
|
+
def validate_ssh_config!(config)
|
|
135
|
+
if config.is_a?(Array)
|
|
136
|
+
validate_array_of! config, String
|
|
137
|
+
elsif boolean?(config.class) || config.is_a?(String)
|
|
138
|
+
# Booleans and Strings are allowed
|
|
139
|
+
else
|
|
140
|
+
type_error(TrueClass, FalseClass, String, Array)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
132
144
|
def validate_type!(value, *types)
|
|
133
145
|
type_error(*types) unless types.any? { |type| valid_type?(value, type) }
|
|
134
146
|
end
|
|
@@ -138,7 +150,8 @@ class Kamal::Configuration::Validator
|
|
|
138
150
|
end
|
|
139
151
|
|
|
140
152
|
def type_error(*expected_types)
|
|
141
|
-
|
|
153
|
+
descriptions = expected_types.map { |type| type_description(type) }.uniq
|
|
154
|
+
error "should be #{descriptions.join(" or ")}"
|
|
142
155
|
end
|
|
143
156
|
|
|
144
157
|
def unknown_keys_error(unknown_keys)
|
data/lib/kamal/configuration.rb
CHANGED
|
@@ -50,7 +50,7 @@ class Kamal::Configuration
|
|
|
50
50
|
|
|
51
51
|
validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration
|
|
52
52
|
|
|
53
|
-
@secrets = Kamal::Secrets.new(destination: destination)
|
|
53
|
+
@secrets = Kamal::Secrets.new(destination: destination, secrets_path: secrets_path)
|
|
54
54
|
|
|
55
55
|
# Eager load config to validate it, these are first as they have dependencies later on
|
|
56
56
|
@servers = Servers.new(config: self)
|
|
@@ -241,6 +241,10 @@ class Kamal::Configuration
|
|
|
241
241
|
raw_config.hooks_path || ".kamal/hooks"
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
+
def secrets_path
|
|
245
|
+
raw_config.secrets_path || ".kamal/secrets"
|
|
246
|
+
end
|
|
247
|
+
|
|
244
248
|
def asset_path
|
|
245
249
|
raw_config.asset_path
|
|
246
250
|
end
|
|
@@ -47,9 +47,8 @@ class Kamal::Secrets::Adapters::Passbolt < Kamal::Secrets::Adapters::Base
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
filter_condition = filter_conditions.any? ? "--filter '#{filter_conditions.join(" || ")}'" : ""
|
|
50
|
-
items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"]}" }.join(" ")} --json`
|
|
50
|
+
items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"].to_s.shellescape}" }.join(" ")} --json`
|
|
51
51
|
raise RuntimeError, "Could not read #{secrets} from Passbolt" unless $?.success?
|
|
52
|
-
|
|
53
52
|
items = JSON.parse(items)
|
|
54
53
|
found_names = items.map { |item| item["name"] }
|
|
55
54
|
missing_secrets = secret_names - found_names
|
data/lib/kamal/secrets.rb
CHANGED
|
@@ -3,8 +3,9 @@ require "dotenv"
|
|
|
3
3
|
class Kamal::Secrets
|
|
4
4
|
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
|
5
5
|
|
|
6
|
-
def initialize(destination: nil)
|
|
6
|
+
def initialize(destination: nil, secrets_path:)
|
|
7
7
|
@destination = destination
|
|
8
|
+
@secrets_path = secrets_path
|
|
8
9
|
@mutex = Mutex.new
|
|
9
10
|
end
|
|
10
11
|
|
|
@@ -37,6 +38,6 @@ class Kamal::Secrets
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def secrets_filenames
|
|
40
|
-
[ "
|
|
41
|
+
[ "#{@secrets_path}-common", "#{@secrets_path}#{(".#{@destination}" if @destination)}" ]
|
|
41
42
|
end
|
|
42
43
|
end
|
|
@@ -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
|
|
data/lib/kamal/version.rb
CHANGED