kamal 1.4.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +3 -2
- data/lib/kamal/cli/app/boot.rb +67 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +20 -61
- data/lib/kamal/cli/base.rb +21 -7
- data/lib/kamal/cli/env.rb +3 -3
- data/lib/kamal/cli/main.rb +1 -1
- data/lib/kamal/cli/templates/deploy.yml +1 -1
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +0 -0
- data/lib/kamal/cli/traefik.rb +15 -12
- data/lib/kamal/commander/specifics.rb +49 -0
- data/lib/kamal/commander.rb +9 -33
- data/lib/kamal/commands/accessory.rb +2 -2
- data/lib/kamal/commands/app/assets.rb +4 -4
- data/lib/kamal/commands/app/cord.rb +2 -2
- data/lib/kamal/commands/app/execution.rb +8 -6
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app.rb +29 -8
- data/lib/kamal/commands/app.rb.orig +127 -0
- data/lib/kamal/commands/auditor.rb +1 -1
- data/lib/kamal/commands/base.rb +6 -2
- data/lib/kamal/commands/builder/base.rb +14 -4
- data/lib/kamal/commands/builder/multiarch.rb +9 -9
- data/lib/kamal/commands/builder/native/cached.rb +7 -6
- data/lib/kamal/commands/builder/native/remote.rb +9 -9
- data/lib/kamal/commands/builder/native.rb +8 -7
- data/lib/kamal/commands/healthcheck.rb +0 -1
- data/lib/kamal/commands/hook.rb +1 -1
- data/lib/kamal/commands/lock.rb +19 -9
- data/lib/kamal/commands/prune.rb +2 -2
- data/lib/kamal/commands/server.rb +1 -1
- data/lib/kamal/commands/traefik.rb +8 -14
- data/lib/kamal/configuration/accessory.rb +9 -19
- data/lib/kamal/configuration/boot.rb +1 -1
- data/lib/kamal/configuration/builder.rb +7 -3
- data/lib/kamal/configuration/env.rb +40 -0
- data/lib/kamal/configuration/role.rb +12 -42
- data/lib/kamal/configuration.rb +20 -8
- data/lib/kamal/env_file.rb +12 -15
- data/lib/kamal/utils.rb +8 -5
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +1 -1
- metadata +7 -2
@@ -0,0 +1,40 @@
|
|
1
|
+
class Kamal::Configuration::Env
|
2
|
+
attr_reader :secrets_keys, :clear, :secrets_file
|
3
|
+
delegate :argumentize, to: Kamal::Utils
|
4
|
+
|
5
|
+
def self.from_config(config:, secrets_file: nil)
|
6
|
+
secrets_keys = config.fetch("secret", [])
|
7
|
+
clear = config.fetch("clear", config.key?("secret") ? {} : config)
|
8
|
+
|
9
|
+
new clear: clear, secrets_keys: secrets_keys, secrets_file: secrets_file
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(clear:, secrets_keys:, secrets_file:)
|
13
|
+
@clear = clear
|
14
|
+
@secrets_keys = secrets_keys
|
15
|
+
@secrets_file = secrets_file
|
16
|
+
end
|
17
|
+
|
18
|
+
def args
|
19
|
+
[ "--env-file", secrets_file, *argumentize("--env", clear) ]
|
20
|
+
end
|
21
|
+
|
22
|
+
def secrets_io
|
23
|
+
StringIO.new(Kamal::EnvFile.new(secrets).to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
def secrets
|
27
|
+
@secrets ||= secrets_keys.to_h { |key| [ key, ENV.fetch(key) ] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def secrets_directory
|
31
|
+
File.dirname(secrets_file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge(other)
|
35
|
+
self.class.new \
|
36
|
+
clear: @clear.merge(other.clear),
|
37
|
+
secrets_keys: @secrets_keys | other.secrets_keys,
|
38
|
+
secrets_file: secrets_file
|
39
|
+
end
|
40
|
+
end
|
@@ -51,27 +51,11 @@ class Kamal::Configuration::Role
|
|
51
51
|
|
52
52
|
|
53
53
|
def env
|
54
|
-
|
55
|
-
merged_env_with_secrets
|
56
|
-
else
|
57
|
-
merged_env
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def env_file
|
62
|
-
Kamal::EnvFile.new(env)
|
63
|
-
end
|
64
|
-
|
65
|
-
def host_env_directory
|
66
|
-
File.join config.host_env_directory, "roles"
|
67
|
-
end
|
68
|
-
|
69
|
-
def host_env_file_path
|
70
|
-
File.join host_env_directory, "#{[config.service, name, config.destination].compact.join("-")}.env"
|
54
|
+
@env ||= base_env.merge(specialized_env)
|
71
55
|
end
|
72
56
|
|
73
57
|
def env_args
|
74
|
-
|
58
|
+
env.args
|
75
59
|
end
|
76
60
|
|
77
61
|
def asset_volume_args
|
@@ -123,13 +107,13 @@ class Kamal::Configuration::Role
|
|
123
107
|
end
|
124
108
|
|
125
109
|
def cord_host_directory
|
126
|
-
File.join config.run_directory_as_docker_volume, "cords", [container_prefix, config.run_id].join("-")
|
110
|
+
File.join config.run_directory_as_docker_volume, "cords", [ container_prefix, config.run_id ].join("-")
|
127
111
|
end
|
128
112
|
|
129
113
|
def cord_volume
|
130
114
|
if (cord = health_check_options["cord"])
|
131
115
|
@cord_volume ||= Kamal::Configuration::Volume.new \
|
132
|
-
host_path: File.join(config.run_directory, "cords", [container_prefix, config.run_id].join("-")),
|
116
|
+
host_path: File.join(config.run_directory, "cords", [ container_prefix, config.run_id ].join("-")),
|
133
117
|
container_path: cord
|
134
118
|
end
|
135
119
|
end
|
@@ -192,11 +176,7 @@ class Kamal::Configuration::Role
|
|
192
176
|
end
|
193
177
|
|
194
178
|
def default_labels
|
195
|
-
|
196
|
-
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
197
|
-
else
|
198
|
-
{ "service" => config.service, "role" => name }
|
199
|
-
end
|
179
|
+
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
200
180
|
end
|
201
181
|
|
202
182
|
def traefik_labels
|
@@ -217,7 +197,7 @@ class Kamal::Configuration::Role
|
|
217
197
|
end
|
218
198
|
|
219
199
|
def traefik_service
|
220
|
-
|
200
|
+
container_prefix
|
221
201
|
end
|
222
202
|
|
223
203
|
def custom_labels
|
@@ -229,31 +209,21 @@ class Kamal::Configuration::Role
|
|
229
209
|
|
230
210
|
def specializations
|
231
211
|
if config.servers.is_a?(Array) || config.servers[name].is_a?(Array)
|
232
|
-
{
|
212
|
+
{}
|
233
213
|
else
|
234
214
|
config.servers[name].except("hosts")
|
235
215
|
end
|
236
216
|
end
|
237
217
|
|
238
218
|
def specialized_env
|
239
|
-
specializations
|
240
|
-
end
|
241
|
-
|
242
|
-
def merged_env
|
243
|
-
config.env&.merge(specialized_env) || {}
|
219
|
+
Kamal::Configuration::Env.from_config config: specializations.fetch("env", {})
|
244
220
|
end
|
245
221
|
|
246
222
|
# Secrets are stored in an array, which won't merge by default, so have to do it by hand.
|
247
|
-
def
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
# If there's no secret/clear split, everything is clear
|
252
|
-
clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env)
|
253
|
-
clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env)
|
254
|
-
|
255
|
-
new_env["clear"] = clear_app_env.to_h.merge(clear_role_env.to_h)
|
256
|
-
end
|
223
|
+
def base_env
|
224
|
+
Kamal::Configuration::Env.from_config \
|
225
|
+
config: config.env,
|
226
|
+
secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env")
|
257
227
|
end
|
258
228
|
|
259
229
|
def http_health_check(port:, path:)
|
data/lib/kamal/configuration.rb
CHANGED
@@ -6,7 +6,7 @@ require "erb"
|
|
6
6
|
require "net/ssh/proxy/jump"
|
7
7
|
|
8
8
|
class Kamal::Configuration
|
9
|
-
delegate :service, :image, :servers, :
|
9
|
+
delegate :service, :image, :servers, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
|
10
10
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
11
11
|
|
12
12
|
attr_reader :destination, :raw_config
|
@@ -88,7 +88,7 @@ class Kamal::Configuration
|
|
88
88
|
|
89
89
|
|
90
90
|
def all_hosts
|
91
|
-
roles.flat_map(&:hosts).uniq
|
91
|
+
(roles + accessories).flat_map(&:hosts).uniq
|
92
92
|
end
|
93
93
|
|
94
94
|
def primary_host
|
@@ -128,7 +128,11 @@ class Kamal::Configuration
|
|
128
128
|
end
|
129
129
|
|
130
130
|
def latest_image
|
131
|
-
"#{repository}
|
131
|
+
"#{repository}:#{latest_tag}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def latest_tag
|
135
|
+
[ "latest", *destination ].join("-")
|
132
136
|
end
|
133
137
|
|
134
138
|
def service_with_version
|
@@ -216,12 +220,17 @@ class Kamal::Configuration
|
|
216
220
|
raw_config.hooks_path || ".kamal/hooks"
|
217
221
|
end
|
218
222
|
|
223
|
+
def asset_path
|
224
|
+
raw_config.asset_path
|
225
|
+
end
|
226
|
+
|
227
|
+
|
219
228
|
def host_env_directory
|
220
|
-
"
|
229
|
+
File.join(run_directory, "env")
|
221
230
|
end
|
222
231
|
|
223
|
-
def
|
224
|
-
raw_config.
|
232
|
+
def env
|
233
|
+
raw_config.env || {}
|
225
234
|
end
|
226
235
|
|
227
236
|
|
@@ -292,7 +301,7 @@ class Kamal::Configuration
|
|
292
301
|
end
|
293
302
|
|
294
303
|
def ensure_valid_service_name
|
295
|
-
raise ArgumentError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/
|
304
|
+
raise ArgumentError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
|
296
305
|
|
297
306
|
true
|
298
307
|
end
|
@@ -319,7 +328,10 @@ class Kamal::Configuration
|
|
319
328
|
def git_version
|
320
329
|
@git_version ||=
|
321
330
|
if Kamal::Git.used?
|
322
|
-
|
331
|
+
if Kamal::Git.uncommitted_changes.present? && !builder.git_archive?
|
332
|
+
uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
|
333
|
+
end
|
334
|
+
[ Kamal::Git.revision, uncommitted_suffix ].compact.join
|
323
335
|
else
|
324
336
|
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
325
337
|
end
|
data/lib/kamal/env_file.rb
CHANGED
@@ -3,21 +3,11 @@ class Kamal::EnvFile
|
|
3
3
|
def initialize(env)
|
4
4
|
@env = env
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
def to_s
|
8
8
|
env_file = StringIO.new.tap do |contents|
|
9
|
-
|
10
|
-
|
11
|
-
contents << docker_env_file_line(key, ENV.fetch(key))
|
12
|
-
end
|
13
|
-
|
14
|
-
@env["clear"]&.each do |key, value|
|
15
|
-
contents << docker_env_file_line(key, value)
|
16
|
-
end
|
17
|
-
else
|
18
|
-
@env.fetch("clear", @env)&.each do |key, value|
|
19
|
-
contents << docker_env_file_line(key, value)
|
20
|
-
end
|
9
|
+
@env.each do |key, value|
|
10
|
+
contents << docker_env_file_line(key, value)
|
21
11
|
end
|
22
12
|
end.string
|
23
13
|
|
@@ -26,14 +16,21 @@ class Kamal::EnvFile
|
|
26
16
|
end
|
27
17
|
|
28
18
|
alias to_str to_s
|
29
|
-
|
19
|
+
|
30
20
|
private
|
31
21
|
def docker_env_file_line(key, value)
|
32
|
-
"#{key
|
22
|
+
"#{key}=#{escape_docker_env_file_value(value)}\n"
|
33
23
|
end
|
34
24
|
|
35
25
|
# Escape a value to make it safe to dump in a docker file.
|
36
26
|
def escape_docker_env_file_value(value)
|
27
|
+
# keep non-ascii(UTF-8) characters as it is
|
28
|
+
value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/).map do |part|
|
29
|
+
part.ascii_only? ? escape_docker_env_file_ascii_value(part) : part
|
30
|
+
end.join
|
31
|
+
end
|
32
|
+
|
33
|
+
def escape_docker_env_file_ascii_value(value)
|
37
34
|
# Doublequotes are treated literally in docker env files
|
38
35
|
# so remove leading and trailing ones and unescape any others
|
39
36
|
value.to_s.dump[1..-2].gsub(/\\"/, "\"")
|
data/lib/kamal/utils.rb
CHANGED
@@ -9,7 +9,7 @@ module Kamal::Utils
|
|
9
9
|
if value.present?
|
10
10
|
attr = "#{key}=#{escape_shell_value(value)}"
|
11
11
|
attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
|
12
|
-
[ argument, attr]
|
12
|
+
[ argument, attr ]
|
13
13
|
else
|
14
14
|
[ argument, key ]
|
15
15
|
end
|
@@ -29,7 +29,7 @@ module Kamal::Utils
|
|
29
29
|
|
30
30
|
# Flattens a one-to-many structure into an array of two-element arrays each containing a key-value pair
|
31
31
|
def flatten_args(args)
|
32
|
-
args.flat_map { |key, value| value.try(:map) { |entry| [key, entry] } || [ [ key, value ] ] }
|
32
|
+
args.flat_map { |key, value| value.try(:map) { |entry| [ key, entry ] } || [ [ key, value ] ] }
|
33
33
|
end
|
34
34
|
|
35
35
|
# Marks sensitive values for redaction in logs and human-visible output.
|
@@ -66,12 +66,15 @@ module Kamal::Utils
|
|
66
66
|
Array(filters).select do |filter|
|
67
67
|
matches += Array(items).select do |item|
|
68
68
|
# Only allow * for a wildcard
|
69
|
-
pattern = Regexp.escape(filter).gsub('\*', '.*')
|
70
69
|
# items are roles or hosts
|
71
|
-
|
70
|
+
File.fnmatch(filter, item.to_s, File::FNM_EXTGLOB)
|
72
71
|
end
|
73
72
|
end
|
74
73
|
|
75
|
-
matches
|
74
|
+
matches.uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
def stable_sort!(elements, &block)
|
78
|
+
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
|
76
79
|
end
|
77
80
|
end
|
data/lib/kamal/version.rb
CHANGED
data/lib/kamal.rb
CHANGED
@@ -5,6 +5,6 @@ require "active_support"
|
|
5
5
|
require "zeitwerk"
|
6
6
|
|
7
7
|
loader = Zeitwerk::Loader.for_gem
|
8
|
-
loader.ignore("
|
8
|
+
loader.ignore(File.join(__dir__, "kamal", "sshkit_with_ext.rb"))
|
9
9
|
loader.setup
|
10
10
|
loader.eager_load # We need all commands loaded.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kamal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -206,6 +206,8 @@ files:
|
|
206
206
|
- lib/kamal/cli.rb
|
207
207
|
- lib/kamal/cli/accessory.rb
|
208
208
|
- lib/kamal/cli/app.rb
|
209
|
+
- lib/kamal/cli/app/boot.rb
|
210
|
+
- lib/kamal/cli/app/prepare_assets.rb
|
209
211
|
- lib/kamal/cli/base.rb
|
210
212
|
- lib/kamal/cli/build.rb
|
211
213
|
- lib/kamal/cli/env.rb
|
@@ -227,9 +229,11 @@ files:
|
|
227
229
|
- lib/kamal/cli/templates/template.env
|
228
230
|
- lib/kamal/cli/traefik.rb
|
229
231
|
- lib/kamal/commander.rb
|
232
|
+
- lib/kamal/commander/specifics.rb
|
230
233
|
- lib/kamal/commands.rb
|
231
234
|
- lib/kamal/commands/accessory.rb
|
232
235
|
- lib/kamal/commands/app.rb
|
236
|
+
- lib/kamal/commands/app.rb.orig
|
233
237
|
- lib/kamal/commands/app/assets.rb
|
234
238
|
- lib/kamal/commands/app/containers.rb
|
235
239
|
- lib/kamal/commands/app/cord.rb
|
@@ -257,6 +261,7 @@ files:
|
|
257
261
|
- lib/kamal/configuration/accessory.rb
|
258
262
|
- lib/kamal/configuration/boot.rb
|
259
263
|
- lib/kamal/configuration/builder.rb
|
264
|
+
- lib/kamal/configuration/env.rb
|
260
265
|
- lib/kamal/configuration/role.rb
|
261
266
|
- lib/kamal/configuration/ssh.rb
|
262
267
|
- lib/kamal/configuration/sshkit.rb
|