kamal 2.2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7da17dcbc307380fd036004d67e3007ea6824a4b634f161c2882776b5c2020df
4
- data.tar.gz: c394f629044adecac7c2bc65a8ea6615269a010d68be8051c4fd4c579e5be686
3
+ metadata.gz: 0d5e6961984a3361505ebf35dfc52920c49af92085dd99c923dbfa801c668a95
4
+ data.tar.gz: adddf71abb26f58e5f7bb16c62553f0d3358499ac4c09f333df75b650cc37b54
5
5
  SHA512:
6
- metadata.gz: 41028ff1893c92dfea1d1de5f5289e60fd719f28445f7f827b513482463e364e3640a88003ef68b24b4fcb4f502f88dbb3daf9c4e04d956fc8a03e09d4f64d90
7
- data.tar.gz: b6db1eb9d4acbd57ce4fcce79798a2f1ad47cd043e2624c03ee940a86fc32bf49bf0446c70142e98e7d87e06cc5c6ee6dd89448533b5c2577fd2d6ee57faaa87
6
+ metadata.gz: fdbd4d88c6fe8001def4c53a9f3ee058e871e58ce99f6697a478cbbac48f646947aab415d08074668e2c41147f8fd15fa1cb76f01919d9411aa0e85be6767aba
7
+ data.tar.gz: b1716d147e84b386f8bac27deb60f70fc6a7fdfad18fc376dca1391d400de388085b654d5ec8e8e9b3b83724e0a22599bc5626d07cd3812330389add2ed915f9
@@ -14,7 +14,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
14
14
  version = capture_with_info(*KAMAL.proxy.version).strip.presence
15
15
 
16
16
  if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
17
- raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
17
+ raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
18
18
  end
19
19
  execute *KAMAL.proxy.start_or_run
20
20
  end
@@ -13,13 +13,14 @@ servers:
13
13
  # - 192.168.0.1
14
14
  # cmd: bin/jobs
15
15
 
16
- # Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
17
- # If using something like Cloudflare, it is recommended to set encryption mode
18
- # in Cloudflare's SSL/TLS setting to "Full" to enable end-to-end encryption.
16
+ # Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
17
+ # Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
18
+ #
19
+ # Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
19
20
  proxy:
20
21
  ssl: true
21
22
  host: app.example.com
22
- # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port.
23
+ # Proxy connects to your container on port 80 by default.
23
24
  # app_port: 3000
24
25
 
25
26
  # Credentials for your image host.
@@ -90,7 +91,7 @@ builder:
90
91
  # directories:
91
92
  # - data:/var/lib/mysql
92
93
  # redis:
93
- # image: redis:7.0
94
+ # image: valkey/valkey:8
94
95
  # host: 192.168.0.2
95
96
  # port: 6379
96
97
  # directories:
@@ -1,7 +1,7 @@
1
1
  class Kamal::Commands::Accessory < Kamal::Commands::Base
2
2
  attr_reader :accessory_config
3
3
  delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
4
- :publish_args, :env_args, :volume_args, :label_args, :option_args,
4
+ :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
5
5
  :secrets_io, :secrets_path, :env_directory,
6
6
  to: :accessory_config
7
7
 
@@ -15,7 +15,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
15
15
  "--name", service_name,
16
16
  "--detach",
17
17
  "--restart", "unless-stopped",
18
- "--network", "kamal",
18
+ *network_args,
19
19
  *config.logging_args,
20
20
  *publish_args,
21
21
  *env_args,
@@ -64,7 +64,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
64
64
  docker :run,
65
65
  ("-it" if interactive),
66
66
  "--rm",
67
- "--network", "kamal",
67
+ *network_args,
68
68
  *env_args,
69
69
  *volume_args,
70
70
  image,
@@ -6,7 +6,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
6
6
  delegate :argumentize, to: Kamal::Utils
7
7
  delegate \
8
8
  :args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
9
- :cache_from, :cache_to, :ssh, :driver, :docker_driver?,
9
+ :cache_from, :cache_to, :ssh, :provenance, :driver, :docker_driver?,
10
10
  to: :builder_config
11
11
 
12
12
  def clean
@@ -37,7 +37,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
37
37
  end
38
38
 
39
39
  def build_options
40
- [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
40
+ [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance ]
41
41
  end
42
42
 
43
43
  def build_context
@@ -97,6 +97,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
97
97
  argumentize "--ssh", ssh if ssh.present?
98
98
  end
99
99
 
100
+ def builder_provenance
101
+ argumentize "--provenance", provenance unless provenance.nil?
102
+ end
103
+
100
104
  def builder_config
101
105
  config.builder
102
106
  end
@@ -1,6 +1,8 @@
1
1
  class Kamal::Configuration::Accessory
2
2
  include Kamal::Configuration::Validation
3
3
 
4
+ DEFAULT_NETWORK = "kamal"
5
+
4
6
  delegate :argumentize, :optionize, to: Kamal::Utils
5
7
 
6
8
  attr_reader :name, :accessory_config, :env
@@ -38,6 +40,10 @@ class Kamal::Configuration::Accessory
38
40
  end
39
41
  end
40
42
 
43
+ def network_args
44
+ argumentize "--network", network
45
+ end
46
+
41
47
  def publish_args
42
48
  argumentize "--publish", port if port
43
49
  end
@@ -173,4 +179,8 @@ class Kamal::Configuration::Accessory
173
179
  accessory_config["roles"].flat_map { |role| config.role(role).hosts }
174
180
  end
175
181
  end
182
+
183
+ def network
184
+ accessory_config["network"] || DEFAULT_NETWORK
185
+ end
176
186
  end
@@ -111,6 +111,10 @@ class Kamal::Configuration::Builder
111
111
  builder_config["ssh"]
112
112
  end
113
113
 
114
+ def provenance
115
+ builder_config["provenance"]
116
+ end
117
+
114
118
  def git_clone?
115
119
  Kamal::Git.used? && builder_config["context"].nil?
116
120
  end
@@ -166,7 +170,7 @@ class Kamal::Configuration::Builder
166
170
  end
167
171
 
168
172
  def cache_to_config_for_registry
169
- [ "type=registry", builder_config["cache"]&.fetch("options", nil), "ref=#{cache_image_ref}" ].compact.join(",")
173
+ [ "type=registry", "ref=#{cache_image_ref}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
170
174
  end
171
175
 
172
176
  def repo_basename
@@ -90,3 +90,11 @@ accessories:
90
90
  # They are not created or copied before mounting:
91
91
  volumes:
92
92
  - /path/to/mysql-logs:/var/log/mysql
93
+
94
+ # Network
95
+ #
96
+ # The network the accessory will be attached to.
97
+ #
98
+ # Defaults to kamal:
99
+ network: custom
100
+
@@ -102,3 +102,9 @@ builder:
102
102
  #
103
103
  # The build driver to use, defaults to `docker-container`:
104
104
  driver: docker
105
+
106
+ # Provenance
107
+ #
108
+ # It is used to configure provenance attestations for the build result.
109
+ # The value can also be a boolean to enable or disable provenance attestations.
110
+ provenance: mode=max
@@ -14,7 +14,7 @@ class Kamal::Configuration
14
14
 
15
15
  include Validation
16
16
 
17
- PROXY_MINIMUM_VERSION = "v0.8.1"
17
+ PROXY_MINIMUM_VERSION = "v0.8.2"
18
18
  PROXY_HTTP_PORT = 80
19
19
  PROXY_HTTPS_PORT = 443
20
20
  PROXY_LOG_MAX_SIZE = "10m"
@@ -254,7 +254,7 @@ class Kamal::Configuration
254
254
  end
255
255
 
256
256
  def proxy_logging_args(max_size)
257
- argumentize "--log-opt", "max-size=#{max_size}"
257
+ argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
258
258
  end
259
259
 
260
260
  def proxy_options_default
@@ -37,6 +37,8 @@ class Kamal::EnvFile
37
37
  def escape_docker_env_file_ascii_value(value)
38
38
  # Doublequotes are treated literally in docker env files
39
39
  # so remove leading and trailing ones and unescape any others
40
- value.to_s.dump[1..-2].gsub(/\\"/, "\"")
40
+ value.to_s.dump[1..-2]
41
+ .gsub(/\\"/, "\"")
42
+ .gsub(/\\#/, "#")
41
43
  end
42
44
  end
@@ -2,6 +2,7 @@ class Kamal::Secrets::Adapters::Base
2
2
  delegate :optionize, to: Kamal::Utils
3
3
 
4
4
  def fetch(secrets, account:, from: nil)
5
+ check_dependencies!
5
6
  session = login(account)
6
7
  full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
7
8
  fetch_secrets(full_secrets, account: account, session: session)
@@ -15,4 +16,8 @@ class Kamal::Secrets::Adapters::Base
15
16
  def fetch_secrets(...)
16
17
  raise NotImplementedError
17
18
  end
19
+
20
+ def check_dependencies!
21
+ raise NotImplementedError
22
+ end
18
23
  end
@@ -25,18 +25,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
25
25
  {}.tap do |results|
26
26
  items_fields(secrets).each do |item, fields|
27
27
  item_json = run_command("get item #{item.shellescape}", session: session, raw: true)
28
- raise RuntimeError, "Could not read #{secret} from Bitwarden" unless $?.success?
28
+ raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success?
29
29
  item_json = JSON.parse(item_json)
30
-
31
30
  if fields.any?
32
- fields.each do |field|
33
- item_field = item_json["fields"].find { |f| f["name"] == field }
34
- raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
35
- value = item_field["value"]
36
- results["#{item}/#{field}"] = value
37
- end
31
+ results.merge! fetch_secrets_from_fields(fields, item, item_json)
38
32
  elsif item_json.dig("login", "password")
39
33
  results[item] = item_json.dig("login", "password")
34
+ elsif item_json["fields"]&.any?
35
+ fields = item_json["fields"].pluck("name")
36
+ results.merge! fetch_secrets_from_fields(fields, item, item_json)
40
37
  else
41
38
  raise RuntimeError, "Item #{item} is not a login type item and no fields were specified"
42
39
  end
@@ -44,6 +41,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
44
41
  end
45
42
  end
46
43
 
44
+ def fetch_secrets_from_fields(fields, item, item_json)
45
+ fields.to_h do |field|
46
+ item_field = item_json["fields"].find { |f| f["name"] == field }
47
+ raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
48
+ value = item_field["value"]
49
+ [ "#{item}/#{field}", value ]
50
+ end
51
+ end
52
+
47
53
  def items_fields(secrets)
48
54
  {}.tap do |items|
49
55
  secrets.each do |secret|
@@ -63,4 +69,13 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
63
69
  result = `#{full_command}`.strip
64
70
  raw ? result : JSON.parse(result)
65
71
  end
72
+
73
+ def check_dependencies!
74
+ raise RuntimeError, "Bitwarden CLI is not installed" unless cli_installed?
75
+ end
76
+
77
+ def cli_installed?
78
+ `bw --version 2> /dev/null`
79
+ $?.success?
80
+ end
66
81
  end
@@ -27,4 +27,13 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
27
27
  end
28
28
  end
29
29
  end
30
+
31
+ def check_dependencies!
32
+ raise RuntimeError, "LastPass CLI is not installed" unless cli_installed?
33
+ end
34
+
35
+ def cli_installed?
36
+ `lpass --version 2> /dev/null`
37
+ $?.success?
38
+ end
30
39
  end
@@ -58,4 +58,13 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base
58
58
  raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
59
59
  end
60
60
  end
61
+
62
+ def check_dependencies!
63
+ raise RuntimeError, "1Password CLI is not installed" unless cli_installed?
64
+ end
65
+
66
+ def cli_installed?
67
+ `op --version 2> /dev/null`
68
+ $?.success?
69
+ end
61
70
  end
@@ -7,4 +7,8 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base
7
7
  def fetch_secrets(secrets, account:, session:)
8
8
  secrets.to_h { |secret| [ secret, secret.reverse ] }
9
9
  end
10
+
11
+ def check_dependencies!
12
+ # no op
13
+ end
10
14
  end
data/lib/kamal/secrets.rb CHANGED
@@ -1,13 +1,10 @@
1
1
  require "dotenv"
2
2
 
3
3
  class Kamal::Secrets
4
- attr_reader :secrets_files
5
-
6
4
  Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
7
5
 
8
6
  def initialize(destination: nil)
9
- @secrets_files = \
10
- [ ".kamal/secrets-common", ".kamal/secrets#{(".#{destination}" if destination)}" ].select { |f| File.exist?(f) }
7
+ @destination = destination
11
8
  @mutex = Mutex.new
12
9
  end
13
10
 
@@ -17,10 +14,10 @@ class Kamal::Secrets
17
14
  secrets.fetch(key)
18
15
  end
19
16
  rescue KeyError
20
- if secrets_files
17
+ if secrets_files.present?
21
18
  raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
22
19
  else
23
- raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files provided"
20
+ raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files (#{secrets_filenames.join(", ")}) provided"
24
21
  end
25
22
  end
26
23
 
@@ -28,10 +25,18 @@ class Kamal::Secrets
28
25
  secrets
29
26
  end
30
27
 
28
+ def secrets_files
29
+ @secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
30
+ end
31
+
31
32
  private
32
33
  def secrets
33
34
  @secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
34
35
  secrets.merge!(::Dotenv.parse(secrets_file))
35
36
  end
36
37
  end
38
+
39
+ def secrets_filenames
40
+ [ ".kamal/secrets-common", ".kamal/secrets#{(".#{@destination}" if @destination)}" ]
41
+ end
37
42
  end
data/lib/kamal/utils.rb CHANGED
@@ -12,6 +12,8 @@ module Kamal::Utils
12
12
  attr = "#{key}=#{escape_shell_value(value)}"
13
13
  attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
14
14
  [ argument, attr ]
15
+ elsif value == false
16
+ [ argument, "#{key}=false" ]
15
17
  else
16
18
  [ argument, key ]
17
19
  end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "2.2.2"
2
+ VERSION = "2.3.0"
3
3
  end
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: 2.2.2
4
+ version: 2.3.0
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-10-10 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '7.0'
53
+ version: '7.3'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '7.0'
60
+ version: '7.3'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: thor
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -90,16 +90,22 @@ dependencies:
90
90
  name: zeitwerk
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.6.18
96
+ - - "<"
94
97
  - !ruby/object:Gem::Version
95
- version: '2.5'
98
+ version: '3.0'
96
99
  type: :runtime
97
100
  prerelease: false
98
101
  version_requirements: !ruby/object:Gem::Requirement
99
102
  requirements:
100
- - - "~>"
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 2.6.18
106
+ - - "<"
101
107
  - !ruby/object:Gem::Version
102
- version: '2.5'
108
+ version: '3.0'
103
109
  - !ruby/object:Gem::Dependency
104
110
  name: ed25519
105
111
  requirement: !ruby/object:Gem::Requirement