kamal 2.7.0 → 2.11.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +27 -7
  4. data/lib/kamal/cli/alias/command.rb +2 -2
  5. data/lib/kamal/cli/app/boot.rb +1 -1
  6. data/lib/kamal/cli/app.rb +74 -115
  7. data/lib/kamal/cli/base.rb +19 -6
  8. data/lib/kamal/cli/build/clone.rb +0 -2
  9. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  10. data/lib/kamal/cli/build.rb +70 -35
  11. data/lib/kamal/cli/healthcheck/poller.rb +1 -1
  12. data/lib/kamal/cli/main.rb +9 -3
  13. data/lib/kamal/cli/proxy.rb +42 -35
  14. data/lib/kamal/cli/registry.rb +37 -7
  15. data/lib/kamal/cli/secrets.rb +2 -1
  16. data/lib/kamal/cli/server.rb +12 -1
  17. data/lib/kamal/cli/templates/deploy.yml +4 -3
  18. data/lib/kamal/cli/templates/secrets +2 -1
  19. data/lib/kamal/commander.rb +21 -19
  20. data/lib/kamal/commands/accessory.rb +5 -0
  21. data/lib/kamal/commands/app/execution.rb +7 -1
  22. data/lib/kamal/commands/app.rb +1 -0
  23. data/lib/kamal/commands/base.rb +15 -2
  24. data/lib/kamal/commands/builder/base.rb +20 -1
  25. data/lib/kamal/commands/builder/hybrid.rb +3 -3
  26. data/lib/kamal/commands/builder/local.rb +8 -2
  27. data/lib/kamal/commands/builder/pack.rb +5 -5
  28. data/lib/kamal/commands/builder/remote.rb +15 -3
  29. data/lib/kamal/commands/builder.rb +8 -2
  30. data/lib/kamal/commands/docker.rb +17 -1
  31. data/lib/kamal/commands/proxy.rb +22 -3
  32. data/lib/kamal/commands/registry.rb +22 -0
  33. data/lib/kamal/configuration/accessory.rb +56 -25
  34. data/lib/kamal/configuration/boot.rb +4 -0
  35. data/lib/kamal/configuration/builder.rb +10 -3
  36. data/lib/kamal/configuration/docs/accessory.yml +37 -5
  37. data/lib/kamal/configuration/docs/alias.yml +3 -0
  38. data/lib/kamal/configuration/docs/boot.yml +12 -10
  39. data/lib/kamal/configuration/docs/configuration.yml +30 -1
  40. data/lib/kamal/configuration/docs/proxy.yml +48 -16
  41. data/lib/kamal/configuration/docs/registry.yml +12 -4
  42. data/lib/kamal/configuration/docs/ssh.yml +7 -4
  43. data/lib/kamal/configuration/docs/sshkit.yml +8 -0
  44. data/lib/kamal/configuration/env.rb +7 -3
  45. data/lib/kamal/configuration/proxy/boot.rb +4 -9
  46. data/lib/kamal/configuration/proxy/run.rb +143 -0
  47. data/lib/kamal/configuration/proxy.rb +7 -3
  48. data/lib/kamal/configuration/registry.rb +8 -0
  49. data/lib/kamal/configuration/role.rb +15 -3
  50. data/lib/kamal/configuration/ssh.rb +18 -3
  51. data/lib/kamal/configuration/sshkit.rb +4 -0
  52. data/lib/kamal/configuration/validator/proxy.rb +20 -0
  53. data/lib/kamal/configuration/validator/registry.rb +5 -3
  54. data/lib/kamal/configuration/validator.rb +52 -4
  55. data/lib/kamal/configuration/volume.rb +11 -4
  56. data/lib/kamal/configuration.rb +89 -5
  57. data/lib/kamal/secrets/adapters/one_password.rb +1 -1
  58. data/lib/kamal/secrets/adapters/passbolt.rb +1 -2
  59. data/lib/kamal/secrets/adapters/test.rb +3 -1
  60. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +15 -1
  61. data/lib/kamal/secrets.rb +17 -6
  62. data/lib/kamal/sshkit_with_ext.rb +135 -10
  63. data/lib/kamal/utils.rb +3 -3
  64. data/lib/kamal/version.rb +1 -1
  65. data/lib/kamal.rb +1 -0
  66. metadata +18 -2
@@ -68,6 +68,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
68
68
  *network_args,
69
69
  *env_args,
70
70
  *volume_args,
71
+ *option_args,
71
72
  image,
72
73
  *command
73
74
  end
@@ -90,6 +91,10 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
90
91
  end
91
92
  end
92
93
 
94
+ def pull_image
95
+ docker :image, :pull, image
96
+ end
97
+
93
98
  def remove_service_directory
94
99
  [ :rm, "-rf", service_name ]
95
100
  end
@@ -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, env:)
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
@@ -23,6 +23,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
23
23
  "--env", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
24
24
  "--env", "KAMAL_VERSION=\"#{config.version}\"",
25
25
  "--env", "KAMAL_HOST=\"#{host}\"",
26
+ *([ "--env", "KAMAL_DESTINATION=\"#{config.destination}\"" ] if config.destination),
26
27
  *role.env_args(host),
27
28
  *role.logging_args,
28
29
  *config.volume_args,
@@ -11,11 +11,11 @@ module Kamal::Commands
11
11
  end
12
12
 
13
13
  def run_over_ssh(*command, host:)
14
- "ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
14
+ "ssh#{ssh_config_args}#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
15
15
  end
16
16
 
17
17
  def container_id_for(container_name:, only_running: false)
18
- docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
18
+ docker :container, :ls, *("--all" unless only_running), "--filter", "'name=^#{container_name}$'", "--quiet"
19
19
  end
20
20
 
21
21
  def make_directory_for(remote_file)
@@ -100,6 +100,19 @@ module Kamal::Commands
100
100
  Kamal::Tags.from_config(config, **details)
101
101
  end
102
102
 
103
+ def ssh_config_args
104
+ case config.ssh.config
105
+ when Array
106
+ config.ssh.config.map { |file| " -F #{file}" }.join
107
+ when String
108
+ " -F #{config.ssh.config}"
109
+ when true
110
+ "" # Use default SSH config
111
+ when false
112
+ " -F /dev/null" # Ignore SSH config
113
+ end
114
+ end
115
+
103
116
  def ssh_proxy_args
104
117
  case config.ssh.proxy
105
118
  when Net::SSH::Proxy::Jump
@@ -14,13 +14,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
14
14
  docker :image, :rm, "--force", config.absolute_image
15
15
  end
16
16
 
17
- def push(export_action = "registry", tag_as_dirty: false)
17
+ def push(export_action = "registry", tag_as_dirty: false, no_cache: false)
18
18
  docker :buildx, :build,
19
19
  "--output=type=#{export_action}",
20
20
  *platform_options(arches),
21
21
  *([ "--builder", builder_name ] unless docker_driver?),
22
22
  *build_tag_options(tag_as_dirty: tag_as_dirty),
23
23
  *build_options,
24
+ *([ "--no-cache" ] if no_cache),
24
25
  build_context,
25
26
  "2>&1"
26
27
  end
@@ -60,6 +61,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
60
61
  docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
61
62
  end
62
63
 
64
+ def login_to_registry_locally?
65
+ true
66
+ end
67
+
68
+ def push_env
69
+ {}
70
+ end
71
+
63
72
  private
64
73
  def build_tag_names(tag_as_dirty: false)
65
74
  tag_names = [ config.absolute_image, config.latest_image ]
@@ -118,6 +127,16 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
118
127
  config.builder
119
128
  end
120
129
 
130
+ def registry_config
131
+ config.registry
132
+ end
133
+
134
+ def driver_options
135
+ if registry_config.local?
136
+ [ "--driver-opt", "network=host" ]
137
+ end
138
+ end
139
+
121
140
  def platform_options(arches)
122
141
  argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
123
142
  end
@@ -8,14 +8,14 @@ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
8
8
 
9
9
  private
10
10
  def builder_name
11
- "kamal-hybrid-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
11
+ "kamal-hybrid-#{driver}-#{remote_builder_name_suffix}"
12
12
  end
13
13
 
14
14
  def create_local_buildx
15
- docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
15
+ docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}", *driver_options
16
16
  end
17
17
 
18
18
  def append_remote_buildx
19
- docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, remote_context_name
19
+ docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, *driver_options, remote_context_name
20
20
  end
21
21
  end
@@ -1,6 +1,8 @@
1
1
  class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
2
2
  def create
3
- docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
3
+ return if docker_driver?
4
+
5
+ docker :buildx, :create, "--name", builder_name, "--driver=#{driver}", *driver_options
4
6
  end
5
7
 
6
8
  def remove
@@ -9,6 +11,10 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
9
11
 
10
12
  private
11
13
  def builder_name
12
- "kamal-local-#{driver}"
14
+ if registry_config.local?
15
+ "kamal-local-registry-#{driver}"
16
+ else
17
+ "kamal-local-#{driver}"
18
+ end
13
19
  end
14
20
  end
@@ -1,7 +1,7 @@
1
1
  class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
2
- def push(export_action = "registry")
2
+ def push(export_action = "registry", tag_as_dirty: false, no_cache: false)
3
3
  combine \
4
- build,
4
+ build(tag_as_dirty: tag_as_dirty, no_cache: no_cache),
5
5
  export(export_action)
6
6
  end
7
7
 
@@ -13,15 +13,15 @@ class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
13
13
  alias_method :inspect_builder, :info
14
14
 
15
15
  private
16
- def build
16
+ def build(tag_as_dirty: false, no_cache: false)
17
17
  pack(:build,
18
18
  config.repository,
19
19
  "--platform", platform,
20
20
  "--creation-time", "now",
21
21
  "--builder", pack_builder,
22
22
  buildpacks,
23
- "-t", config.absolute_image,
24
- "-t", config.latest_image,
23
+ *build_tag_options(tag_as_dirty: tag_as_dirty),
24
+ *([ "--clear-cache" ] if no_cache),
25
25
  "--env", "BP_IMAGE_LABELS=service=#{config.service}",
26
26
  *argumentize("--env", args),
27
27
  *argumentize("--env", secrets, sensitive: true),
@@ -19,20 +19,32 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
19
19
 
20
20
  def inspect_builder
21
21
  combine \
22
- combine inspect_buildx, inspect_remote_context,
22
+ combine(inspect_buildx, inspect_remote_context),
23
23
  [ "(echo no compatible builder && exit 1)" ],
24
24
  by: "||"
25
25
  end
26
26
 
27
+ def login_to_registry_locally?
28
+ false
29
+ end
30
+
31
+ def push_env
32
+ { "BUILDKIT_NO_CLIENT_TOKEN" => "1" }
33
+ end
34
+
27
35
  private
28
36
  def builder_name
29
- "kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
37
+ "kamal-remote-#{remote_builder_name_suffix}"
30
38
  end
31
39
 
32
40
  def remote_context_name
33
41
  "#{builder_name}-context"
34
42
  end
35
43
 
44
+ def remote_builder_name_suffix
45
+ "#{remote.gsub(/[^a-z0-9_-]/, "-")}#{registry_config.local? ? "-local-registry" : "" }"
46
+ end
47
+
36
48
  def inspect_buildx
37
49
  pipe \
38
50
  docker(:buildx, :inspect, builder_name),
@@ -54,7 +66,7 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
54
66
  end
55
67
 
56
68
  def create_buildx
57
- docker :buildx, :create, "--name", builder_name, remote_context_name
69
+ docker :buildx, :create, "--name", builder_name, *driver_options, remote_context_name
58
70
  end
59
71
 
60
72
  def remove_buildx
@@ -1,8 +1,14 @@
1
1
  require "active_support/core_ext/string/filters"
2
2
 
3
3
  class Kamal::Commands::Builder < Kamal::Commands::Base
4
- delegate :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
5
- delegate :local?, :remote?, :pack?, :cloud?, to: "config.builder"
4
+ delegate \
5
+ :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder,
6
+ :validate_image, :first_mirror, :login_to_registry_locally?, :push_env,
7
+ to: :target
8
+
9
+ delegate \
10
+ :local?, :remote?, :pack?, :cloud?,
11
+ to: "config.builder"
6
12
 
7
13
  include Clone
8
14
 
@@ -16,7 +16,23 @@ class Kamal::Commands::Docker < Kamal::Commands::Base
16
16
 
17
17
  # Do we have superuser access to install Docker and start system services?
18
18
  def superuser?
19
- [ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
19
+ [ '[ "${EUID:-$(id -u)}" -eq 0 ] || sudo -nl usermod >/dev/null' ]
20
+ end
21
+
22
+ def root?
23
+ [ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
24
+ end
25
+
26
+ def in_docker_group?
27
+ [ 'id -nG "${USER:-$(id -un)}" | grep -qw docker' ]
28
+ end
29
+
30
+ def add_to_docker_group
31
+ [ 'sudo -n usermod -aG docker "${USER:-$(id -un)}"' ]
32
+ end
33
+
34
+ def refresh_session
35
+ [ "kill -HUP $PPID" ]
20
36
  end
21
37
 
22
38
  def create_network
@@ -1,8 +1,27 @@
1
1
  class Kamal::Commands::Proxy < Kamal::Commands::Base
2
2
  delegate :argumentize, :optionize, to: Kamal::Utils
3
+ attr_reader :proxy_run_config
4
+
5
+ def initialize(config, host:)
6
+ super(config)
7
+ @proxy_run_config = config.proxy_run(host)
8
+ end
3
9
 
4
10
  def run
5
- pipe boot_config, xargs(docker_run)
11
+ if proxy_run_config
12
+ docker \
13
+ :run,
14
+ "--name", container_name,
15
+ "--network", "kamal",
16
+ "--detach",
17
+ "--restart", "unless-stopped",
18
+ "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
19
+ *proxy_run_config.docker_options_args,
20
+ *proxy_run_config.image,
21
+ *proxy_run_config.run_command
22
+ else
23
+ pipe boot_config, xargs(docker_run)
24
+ end
6
25
  end
7
26
 
8
27
  def start
@@ -18,7 +37,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
18
37
  end
19
38
 
20
39
  def info
21
- docker :ps, "--filter", "name=^#{container_name}$"
40
+ docker :ps, "--filter", "'name=^#{container_name}$'"
22
41
  end
23
42
 
24
43
  def version
@@ -82,7 +101,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
82
101
  end
83
102
 
84
103
  def read_image_version
85
- read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
104
+ read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Run::MINIMUM_VERSION)
86
105
  end
87
106
 
88
107
  def read_run_command
@@ -2,6 +2,8 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
2
2
  def login(registry_config: nil)
3
3
  registry_config ||= config.registry
4
4
 
5
+ return if registry_config.local?
6
+
5
7
  docker :login,
6
8
  registry_config.server,
7
9
  "-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
@@ -13,4 +15,24 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
13
15
 
14
16
  docker :logout, registry_config.server
15
17
  end
18
+
19
+ def setup(registry_config: nil)
20
+ registry_config ||= config.registry
21
+
22
+ combine \
23
+ docker(:start, "kamal-docker-registry"),
24
+ docker(:run, "--detach", "-p", "127.0.0.1:#{registry_config.local_port}:5000", "--name", "kamal-docker-registry", "registry:3"),
25
+ by: "||"
26
+ end
27
+
28
+ def remove
29
+ combine \
30
+ docker(:stop, "kamal-docker-registry"),
31
+ docker(:rm, "kamal-docker-registry"),
32
+ by: "&&"
33
+ end
34
+
35
+ def local?
36
+ config.registry.local?
37
+ end
16
38
  end
@@ -74,25 +74,31 @@ class Kamal::Configuration::Accessory
74
74
  end
75
75
 
76
76
  def files
77
- accessory_config["files"]&.to_h do |local_to_remote_mapping|
78
- local_file, remote_file = local_to_remote_mapping.split(":")
79
- [ expand_local_file(local_file), expand_remote_file(remote_file) ]
77
+ accessory_config["files"]&.to_h do |config|
78
+ parse_path_config(config, default_mode: "755") do |local, remote|
79
+ {
80
+ key: expand_local_file(local),
81
+ host_path: expand_remote_file(remote),
82
+ container_path: remote
83
+ }
84
+ end
80
85
  end || {}
81
86
  end
82
87
 
83
88
  def directories
84
- accessory_config["directories"]&.to_h do |host_to_container_mapping|
85
- host_path, container_path = host_to_container_mapping.split(":")
86
- [ expand_host_path(host_path), container_path ]
89
+ accessory_config["directories"]&.to_h do |config|
90
+ parse_path_config(config, default_mode: nil) do |local, remote|
91
+ {
92
+ key: expand_host_path(local),
93
+ host_path: expand_host_path_for_volume(local),
94
+ container_path: remote
95
+ }
96
+ end
87
97
  end || {}
88
98
  end
89
99
 
90
- def volumes
91
- specific_volumes + remote_files_as_volumes + remote_directories_as_volumes
92
- end
93
-
94
100
  def volume_args
95
- argumentize "--volume", volumes
101
+ argumentize("--volume", specific_volumes) + (path_volumes(files) + path_volumes(directories)).flat_map(&:docker_args)
96
102
  end
97
103
 
98
104
  def option_args
@@ -142,17 +148,17 @@ class Kamal::Configuration::Accessory
142
148
 
143
149
  def expand_local_file(local_file)
144
150
  if local_file.end_with?("erb")
145
- with_clear_env_loaded { read_dynamic_file(local_file) }
151
+ with_env_loaded { read_dynamic_file(local_file) }
146
152
  else
147
153
  Pathname.new(File.expand_path(local_file)).to_s
148
154
  end
149
155
  end
150
156
 
151
- def with_clear_env_loaded
152
- env.clear.each { |k, v| ENV[k] = v }
157
+ def with_env_loaded
158
+ env.to_h.each { |k, v| ENV[k] = v }
153
159
  yield
154
160
  ensure
155
- env.clear.each { |k, v| ENV.delete(k) }
161
+ env.to_h.each { |k, v| ENV.delete(k) }
156
162
  end
157
163
 
158
164
  def read_dynamic_file(local_file)
@@ -167,24 +173,49 @@ class Kamal::Configuration::Accessory
167
173
  accessory_config["volumes"] || []
168
174
  end
169
175
 
170
- def remote_files_as_volumes
171
- accessory_config["files"]&.collect do |local_to_remote_mapping|
172
- _, remote_file = local_to_remote_mapping.split(":")
173
- "#{service_data_directory + remote_file}:#{remote_file}"
174
- end || []
176
+ def path_volumes(paths)
177
+ paths.map do |local, config|
178
+ Kamal::Configuration::Volume.new \
179
+ host_path: config[:host_path],
180
+ container_path: config[:container_path],
181
+ options: config[:options]
182
+ end
175
183
  end
176
184
 
177
- def remote_directories_as_volumes
178
- accessory_config["directories"]&.collect do |host_to_container_mapping|
179
- host_path, container_path = host_to_container_mapping.split(":")
180
- [ expand_host_path(host_path), container_path ].join(":")
181
- end || []
185
+ def parse_path_config(config, default_mode:)
186
+ if config.is_a?(Hash)
187
+ local, remote = config["local"], config["remote"]
188
+ expanded = yield(local, remote)
189
+ [
190
+ expanded[:key],
191
+ expanded.except(:key).merge(
192
+ options: config["options"],
193
+ mode: config["mode"] || default_mode,
194
+ owner: config["owner"]
195
+ )
196
+ ]
197
+ else
198
+ local, remote, options = config.split(":", 3)
199
+ expanded = yield(local, remote)
200
+ [
201
+ expanded[:key],
202
+ expanded.except(:key).merge(
203
+ options: options,
204
+ mode: default_mode,
205
+ owner: nil
206
+ )
207
+ ]
208
+ end
182
209
  end
183
210
 
184
211
  def expand_host_path(host_path)
185
212
  absolute_path?(host_path) ? host_path : File.join(service_data_directory, host_path)
186
213
  end
187
214
 
215
+ def expand_host_path_for_volume(host_path)
216
+ absolute_path?(host_path) ? host_path : File.join(service_name, host_path)
217
+ end
218
+
188
219
  def absolute_path?(path)
189
220
  Pathname.new(path).absolute?
190
221
  end
@@ -22,4 +22,8 @@ class Kamal::Configuration::Boot
22
22
  def wait
23
23
  boot_config["wait"]
24
24
  end
25
+
26
+ def parallel_roles
27
+ boot_config["parallel_roles"]
28
+ end
25
29
  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
- "type=gha"
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", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
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}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
200
+ [ "type=registry", "ref=#{cache_image_ref}", cache_options ].compact.join(",")
194
201
  end
195
202
 
196
203
  def repo_basename
@@ -90,22 +90,54 @@ accessories:
90
90
  # Copying files
91
91
  #
92
92
  # You can specify files to mount into the container.
93
- # The format is `local:remote`, where `local` is the path to the file on the local machine
94
- # and `remote` is the path to the file in the container.
95
93
  #
96
94
  # They will be uploaded from the local repo to the host and then mounted.
97
- #
98
95
  # ERB files will be evaluated before being copied.
96
+ #
97
+ # You can use the string format: `local:remote` or `local:remote:options`
98
+ # where the options can be `ro` for read-only or `z`/`Z` for SELinux labels
99
99
  files:
100
100
  - config/my.cnf.erb:/etc/mysql/my.cnf
101
- - config/myoptions.cnf:/etc/mysql/myoptions.cnf
101
+ - config/myoptions.cnf:/etc/mysql/myoptions.cnf:ro
102
+ - config/certs:/etc/mysql/certs:ro,Z
103
+ #
104
+ # Or you can use the hash format for custom mode and ownership.
105
+ #
106
+ # Note: Setting `owner` requires root access:
107
+ files:
108
+ - local: config/secret.key
109
+ remote: /etc/mysql/secret.key
110
+ mode: "0600"
111
+ owner: "mysql:mysql"
112
+ - local: config/ca-cert.pem
113
+ remote: /etc/mysql/certs/ca-cert.pem
114
+ mode: "0644"
115
+ owner: "1000:1000"
116
+ options: "Z"
102
117
 
103
118
  # Directories
104
119
  #
105
120
  # You can specify directories to mount into the container. They will be created on the host
106
- # before being mounted:
121
+ # before being mounted.
122
+ #
123
+ # You can use the string format: `local:remote` or `local:remote:options`
124
+ # where the options can be `ro` for read-only or `z`/`Z` for SELinux labels
107
125
  directories:
108
126
  - mysql-logs:/var/log/mysql
127
+ - mysql-data:/var/lib/mysql:z
128
+ #
129
+ # Or you can use the hash format for custom mode and ownership.
130
+ #
131
+ # Note: Setting `owner` requires root access:
132
+ directories:
133
+ - local: mysql-data
134
+ remote: /var/lib/mysql
135
+ mode: "0750"
136
+ owner: "mysql:mysql"
137
+ - local: mysql-logs
138
+ remote: /var/log/mysql
139
+ mode: "0755"
140
+ options: "z"
109
141
 
110
142
  # Volumes
111
143
  #
@@ -24,3 +24,6 @@ aliases:
24
24
  # Each alias is named and can only contain lowercase letters, numbers, dashes, and underscores:
25
25
  aliases:
26
26
  uname: app exec -p -q -r web "uname -a"
27
+ #
28
+ # Aliases can include a destination with the `-d` flag:
29
+ staging_deploy: deploy -d staging
@@ -4,16 +4,18 @@
4
4
  #
5
5
  # Kamal’s default is to boot new containers on all hosts in parallel. However, you can control this with the boot configuration.
6
6
 
7
- # Fixed group sizes
8
- #
9
- # Here, we boot 2 hosts at a time with a 10-second gap between each group:
10
7
  boot:
11
- limit: 2
12
- wait: 10
13
8
 
14
- # Percentage of hosts
15
- #
16
- # Here, we boot 25% of the hosts at a time with a 2-second gap between each group:
17
- boot:
9
+ # The number or percentage of hosts to boot at a time.
10
+ # This can be an integer (e.g., 3) or a percentage string (e.g., 25%).
18
11
  limit: 25%
19
- wait: 2
12
+
13
+ # The number of seconds to wait between booting each group of hosts.
14
+ wait: 10
15
+
16
+ # Whether to boot roles in parallel on a host.
17
+ #
18
+ # If a host has multiple roles, control whether they are booted in parallel or sequentially on that host.
19
+ #
20
+ # Defaults to false.
21
+ parallel_roles: true