kamal 1.4.0 → 1.5.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +3 -2
  3. data/lib/kamal/cli/app/boot.rb +67 -0
  4. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  5. data/lib/kamal/cli/app.rb +20 -61
  6. data/lib/kamal/cli/base.rb +21 -7
  7. data/lib/kamal/cli/env.rb +3 -3
  8. data/lib/kamal/cli/main.rb +1 -1
  9. data/lib/kamal/cli/templates/deploy.yml +1 -1
  10. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +0 -0
  11. data/lib/kamal/cli/traefik.rb +15 -12
  12. data/lib/kamal/commander/specifics.rb +49 -0
  13. data/lib/kamal/commander.rb +9 -33
  14. data/lib/kamal/commands/accessory.rb +2 -2
  15. data/lib/kamal/commands/app/assets.rb +4 -4
  16. data/lib/kamal/commands/app/cord.rb +2 -2
  17. data/lib/kamal/commands/app/execution.rb +8 -6
  18. data/lib/kamal/commands/app/images.rb +1 -1
  19. data/lib/kamal/commands/app.rb +29 -8
  20. data/lib/kamal/commands/app.rb.orig +127 -0
  21. data/lib/kamal/commands/auditor.rb +1 -1
  22. data/lib/kamal/commands/base.rb +6 -2
  23. data/lib/kamal/commands/builder/base.rb +14 -4
  24. data/lib/kamal/commands/builder/multiarch.rb +9 -9
  25. data/lib/kamal/commands/builder/native/cached.rb +7 -6
  26. data/lib/kamal/commands/builder/native/remote.rb +9 -9
  27. data/lib/kamal/commands/builder/native.rb +8 -7
  28. data/lib/kamal/commands/healthcheck.rb +0 -1
  29. data/lib/kamal/commands/hook.rb +1 -1
  30. data/lib/kamal/commands/lock.rb +19 -9
  31. data/lib/kamal/commands/prune.rb +2 -2
  32. data/lib/kamal/commands/server.rb +1 -1
  33. data/lib/kamal/commands/traefik.rb +8 -14
  34. data/lib/kamal/configuration/accessory.rb +9 -19
  35. data/lib/kamal/configuration/boot.rb +1 -1
  36. data/lib/kamal/configuration/builder.rb +7 -3
  37. data/lib/kamal/configuration/env.rb +40 -0
  38. data/lib/kamal/configuration/role.rb +12 -42
  39. data/lib/kamal/configuration.rb +20 -8
  40. data/lib/kamal/env_file.rb +12 -15
  41. data/lib/kamal/utils.rb +8 -5
  42. data/lib/kamal/version.rb +1 -1
  43. data/lib/kamal.rb +1 -1
  44. 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
- if config.env && config.env["secret"]
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
- argumentize "--env-file", host_env_file_path
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
- if config.destination
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
- [ config.service, name, config.destination ].compact.join("-")
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["env"] || {}
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 merged_env_with_secrets
248
- merged_env.tap do |new_env|
249
- new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
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:)
@@ -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, :env, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
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}:latest"
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
- "#{run_directory}/env"
229
+ File.join(run_directory, "env")
221
230
  end
222
231
 
223
- def asset_path
224
- raw_config.asset_path
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
- [ Kamal::Git.revision, Kamal::Git.uncommitted_changes.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : "" ].join
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
@@ -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
- if (secrets = @env["secret"]).present?
10
- @env.fetch("secret", @env)&.each do |key|
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.to_s}=#{escape_docker_env_file_value(value)}\n"
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
- (item.respond_to?(:name) ? item.name : item).match(/^#{pattern}$/)
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
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.1"
3
3
  end
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("#{__dir__}/kamal/sshkit_with_ext.rb")
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.0
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-03-20 00:00:00.000000000 Z
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