dash 2.12.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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/dash +18 -0
  5. data/bin/kamal +18 -0
  6. data/lib/kamal/cli/accessory.rb +342 -0
  7. data/lib/kamal/cli/alias/command.rb +10 -0
  8. data/lib/kamal/cli/app/assets.rb +24 -0
  9. data/lib/kamal/cli/app/boot.rb +126 -0
  10. data/lib/kamal/cli/app/error_pages.rb +33 -0
  11. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  12. data/lib/kamal/cli/app.rb +368 -0
  13. data/lib/kamal/cli/base.rb +324 -0
  14. data/lib/kamal/cli/build/clone.rb +59 -0
  15. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  16. data/lib/kamal/cli/build.rb +242 -0
  17. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  18. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  19. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  20. data/lib/kamal/cli/lock.rb +34 -0
  21. data/lib/kamal/cli/main.rb +299 -0
  22. data/lib/kamal/cli/proxy.rb +419 -0
  23. data/lib/kamal/cli/prune.rb +34 -0
  24. data/lib/kamal/cli/registry.rb +49 -0
  25. data/lib/kamal/cli/secrets.rb +50 -0
  26. data/lib/kamal/cli/server.rb +70 -0
  27. data/lib/kamal/cli/templates/deploy.yml +102 -0
  28. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  31. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  35. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  36. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  37. data/lib/kamal/cli/templates/secrets +22 -0
  38. data/lib/kamal/cli.rb +9 -0
  39. data/lib/kamal/commander/specifics.rb +62 -0
  40. data/lib/kamal/commander.rb +230 -0
  41. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  42. data/lib/kamal/commands/accessory.rb +118 -0
  43. data/lib/kamal/commands/app/assets.rb +51 -0
  44. data/lib/kamal/commands/app/containers.rb +31 -0
  45. data/lib/kamal/commands/app/error_pages.rb +9 -0
  46. data/lib/kamal/commands/app/execution.rb +38 -0
  47. data/lib/kamal/commands/app/images.rb +13 -0
  48. data/lib/kamal/commands/app/logging.rb +28 -0
  49. data/lib/kamal/commands/app/proxy.rb +32 -0
  50. data/lib/kamal/commands/app.rb +125 -0
  51. data/lib/kamal/commands/auditor.rb +39 -0
  52. data/lib/kamal/commands/base.rb +147 -0
  53. data/lib/kamal/commands/builder/base.rb +143 -0
  54. data/lib/kamal/commands/builder/clone.rb +32 -0
  55. data/lib/kamal/commands/builder/cloud.rb +22 -0
  56. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  57. data/lib/kamal/commands/builder/local.rb +20 -0
  58. data/lib/kamal/commands/builder/pack.rb +46 -0
  59. data/lib/kamal/commands/builder/remote.rb +75 -0
  60. data/lib/kamal/commands/builder.rb +54 -0
  61. data/lib/kamal/commands/docker.rb +50 -0
  62. data/lib/kamal/commands/hook.rb +20 -0
  63. data/lib/kamal/commands/loadbalancer.rb +130 -0
  64. data/lib/kamal/commands/lock.rb +70 -0
  65. data/lib/kamal/commands/proxy.rb +150 -0
  66. data/lib/kamal/commands/prune.rb +38 -0
  67. data/lib/kamal/commands/registry.rb +38 -0
  68. data/lib/kamal/commands/server.rb +15 -0
  69. data/lib/kamal/commands.rb +2 -0
  70. data/lib/kamal/configuration/accessory.rb +280 -0
  71. data/lib/kamal/configuration/alias.rb +15 -0
  72. data/lib/kamal/configuration/boot.rb +29 -0
  73. data/lib/kamal/configuration/builder.rb +218 -0
  74. data/lib/kamal/configuration/docs/accessory.yml +160 -0
  75. data/lib/kamal/configuration/docs/alias.yml +29 -0
  76. data/lib/kamal/configuration/docs/boot.yml +21 -0
  77. data/lib/kamal/configuration/docs/builder.yml +132 -0
  78. data/lib/kamal/configuration/docs/configuration.yml +228 -0
  79. data/lib/kamal/configuration/docs/env.yml +118 -0
  80. data/lib/kamal/configuration/docs/logging.yml +21 -0
  81. data/lib/kamal/configuration/docs/output.yml +25 -0
  82. data/lib/kamal/configuration/docs/proxy.yml +207 -0
  83. data/lib/kamal/configuration/docs/registry.yml +64 -0
  84. data/lib/kamal/configuration/docs/role.yml +54 -0
  85. data/lib/kamal/configuration/docs/servers.yml +27 -0
  86. data/lib/kamal/configuration/docs/ssh.yml +81 -0
  87. data/lib/kamal/configuration/docs/sshkit.yml +31 -0
  88. data/lib/kamal/configuration/env/tag.rb +13 -0
  89. data/lib/kamal/configuration/env.rb +42 -0
  90. data/lib/kamal/configuration/loadbalancer.rb +34 -0
  91. data/lib/kamal/configuration/logging.rb +33 -0
  92. data/lib/kamal/configuration/output.rb +34 -0
  93. data/lib/kamal/configuration/proxy/boot.rb +124 -0
  94. data/lib/kamal/configuration/proxy/run.rb +152 -0
  95. data/lib/kamal/configuration/proxy.rb +156 -0
  96. data/lib/kamal/configuration/registry.rb +40 -0
  97. data/lib/kamal/configuration/role.rb +247 -0
  98. data/lib/kamal/configuration/servers.rb +25 -0
  99. data/lib/kamal/configuration/ssh.rb +76 -0
  100. data/lib/kamal/configuration/sshkit.rb +26 -0
  101. data/lib/kamal/configuration/validation.rb +27 -0
  102. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  103. data/lib/kamal/configuration/validator/alias.rb +15 -0
  104. data/lib/kamal/configuration/validator/builder.rb +15 -0
  105. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  106. data/lib/kamal/configuration/validator/env.rb +54 -0
  107. data/lib/kamal/configuration/validator/proxy.rb +47 -0
  108. data/lib/kamal/configuration/validator/registry.rb +27 -0
  109. data/lib/kamal/configuration/validator/role.rb +13 -0
  110. data/lib/kamal/configuration/validator/servers.rb +7 -0
  111. data/lib/kamal/configuration/validator.rb +251 -0
  112. data/lib/kamal/configuration/volume.rb +29 -0
  113. data/lib/kamal/configuration.rb +465 -0
  114. data/lib/kamal/docker.rb +30 -0
  115. data/lib/kamal/env_file.rb +44 -0
  116. data/lib/kamal/git.rb +37 -0
  117. data/lib/kamal/otel_shipper.rb +176 -0
  118. data/lib/kamal/output/base_logger.rb +29 -0
  119. data/lib/kamal/output/file_logger.rb +51 -0
  120. data/lib/kamal/output/formatter.rb +36 -0
  121. data/lib/kamal/output/otel_logger.rb +70 -0
  122. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
  123. data/lib/kamal/secrets/adapters/base.rb +33 -0
  124. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  125. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  126. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  127. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  128. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  129. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  130. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  131. data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
  132. data/lib/kamal/secrets/adapters/test.rb +16 -0
  133. data/lib/kamal/secrets/adapters.rb +16 -0
  134. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
  135. data/lib/kamal/secrets.rb +53 -0
  136. data/lib/kamal/sshkit_with_ext.rb +273 -0
  137. data/lib/kamal/tags.rb +40 -0
  138. data/lib/kamal/utils/sensitive.rb +20 -0
  139. data/lib/kamal/utils.rb +110 -0
  140. data/lib/kamal/version.rb +3 -0
  141. data/lib/kamal.rb +15 -0
  142. metadata +388 -0
@@ -0,0 +1,150 @@
1
+ class Kamal::Commands::Proxy < Kamal::Commands::Base
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
9
+
10
+ def 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
25
+ end
26
+
27
+ def start
28
+ docker :container, :start, container_name
29
+ end
30
+
31
+ def stop(name: container_name)
32
+ docker :container, :stop, name
33
+ end
34
+
35
+ def start_or_run
36
+ combine start, run, by: "||"
37
+ end
38
+
39
+ def info
40
+ docker :ps, "--filter", "'name=^#{container_name}$'"
41
+ end
42
+
43
+ def version
44
+ pipe \
45
+ docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
46
+ [ :awk, "-F:", "'{print \$NF}'" ]
47
+ end
48
+
49
+ def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
50
+ pipe \
51
+ docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
52
+ ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
53
+ end
54
+
55
+ def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
56
+ run_over_ssh pipe(
57
+ docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
58
+ (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
59
+ ).join(" "), host: host
60
+ end
61
+
62
+ def remove_container
63
+ docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
64
+ end
65
+
66
+ def remove_image
67
+ docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
68
+ end
69
+
70
+ def cleanup_traefik
71
+ chain \
72
+ docker(:container, :stop, "traefik"),
73
+ combine(
74
+ docker(:container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"),
75
+ docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik")
76
+ )
77
+ end
78
+
79
+ def ensure_proxy_directory
80
+ make_directory config.proxy_boot.host_directory
81
+ end
82
+
83
+ def remove_proxy_directory
84
+ remove_directory config.proxy_boot.host_directory
85
+ end
86
+
87
+ def ensure_apps_config_directory
88
+ make_directory config.proxy_boot.apps_directory
89
+ end
90
+
91
+ def boot_config
92
+ [ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ]
93
+ end
94
+
95
+ def read_boot_options
96
+ read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" "))
97
+ end
98
+
99
+ def read_image
100
+ read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default)
101
+ end
102
+
103
+ def read_image_version
104
+ read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Run::MINIMUM_VERSION)
105
+ end
106
+
107
+ def read_run_command
108
+ read_file(config.proxy_boot.run_command_file)
109
+ end
110
+
111
+ def reset_boot_options
112
+ remove_file config.proxy_boot.options_file
113
+ end
114
+
115
+ def reset_image
116
+ remove_file config.proxy_boot.image_file
117
+ end
118
+
119
+ def reset_image_version
120
+ remove_file config.proxy_boot.image_version_file
121
+ end
122
+
123
+ def reset_run_command
124
+ remove_file config.proxy_boot.run_command_file
125
+ end
126
+
127
+ def loadbalancer
128
+ @loadbalancer ||= Kamal::Commands::Loadbalancer.new(config, loadbalancer_config: KAMAL.loadbalancer_config)
129
+ end
130
+
131
+ private
132
+ def container_name
133
+ config.proxy_boot.container_name
134
+ end
135
+
136
+ def read_file(file, default: nil)
137
+ combine [ :cat, file, "2>", "/dev/null" ], [ :echo, "\"#{default}\"" ], by: "||"
138
+ end
139
+
140
+ def docker_run
141
+ docker \
142
+ :run,
143
+ "--name", container_name,
144
+ "--network", "kamal",
145
+ "--detach",
146
+ "--restart", "unless-stopped",
147
+ "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
148
+ *config.proxy_boot.apps_volume.docker_args
149
+ end
150
+ end
@@ -0,0 +1,38 @@
1
+ require "active_support/duration"
2
+ require "active_support/core_ext/numeric/time"
3
+
4
+ class Kamal::Commands::Prune < Kamal::Commands::Base
5
+ def dangling_images
6
+ docker :image, :prune, "--force", "--filter", "label=service=#{config.service}"
7
+ end
8
+
9
+ def tagged_images
10
+ pipe \
11
+ docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
12
+ grep("-v -w \"#{active_image_list}\""),
13
+ "while read image tag; do docker rmi $tag; done"
14
+ end
15
+
16
+ def app_containers(retain:)
17
+ pipe \
18
+ docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
19
+ "tail -n +#{retain + 1}",
20
+ "while read container_id; do docker rm $container_id; done"
21
+ end
22
+
23
+ private
24
+ def stopped_containers_filters
25
+ [ "created", "exited", "dead" ].flat_map { |status| [ "--filter", "status=#{status}" ] }
26
+ end
27
+
28
+ def active_image_list
29
+ # Pull the images that are used by any containers
30
+ # Append repo:latest - to avoid deleting the latest tag
31
+ # Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
32
+ "$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
33
+ end
34
+
35
+ def service_filter
36
+ [ "--filter", "label=service=#{config.service}" ]
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ class Kamal::Commands::Registry < Kamal::Commands::Base
2
+ def login(registry_config: nil)
3
+ registry_config ||= config.registry
4
+
5
+ return if registry_config.local?
6
+
7
+ docker :login,
8
+ registry_config.server,
9
+ "-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
10
+ "-p", sensitive(Kamal::Utils.escape_shell_value(registry_config.password))
11
+ end
12
+
13
+ def logout(registry_config: nil)
14
+ registry_config ||= config.registry
15
+
16
+ docker :logout, registry_config.server
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
38
+ end
@@ -0,0 +1,15 @@
1
+ class Kamal::Commands::Server < Kamal::Commands::Base
2
+ def ensure_run_directory
3
+ make_directory config.run_directory
4
+ end
5
+
6
+ def remove_app_directory
7
+ remove_directory config.app_directory
8
+ end
9
+
10
+ def app_directory_count
11
+ pipe \
12
+ [ :ls, config.apps_directory ],
13
+ [ :wc, "-l" ]
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ module Kamal::Commands
2
+ end
@@ -0,0 +1,280 @@
1
+ class Kamal::Configuration::Accessory
2
+ include Kamal::Configuration::Validation
3
+
4
+ DEFAULT_NETWORK = "kamal"
5
+
6
+ delegate :argumentize, :optionize, to: Kamal::Utils
7
+
8
+ attr_reader :name, :env, :proxy, :registry
9
+
10
+ def initialize(name, config:)
11
+ @name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
12
+
13
+ validate! \
14
+ accessory_config,
15
+ example: validation_yml["accessories"]["mysql"],
16
+ context: "accessories/#{name}",
17
+ with: Kamal::Configuration::Validator::Accessory
18
+
19
+ ensure_valid_roles
20
+
21
+ @env = initialize_env
22
+ @proxy = initialize_proxy if running_proxy?
23
+ @registry = initialize_registry if accessory_config["registry"].present?
24
+ end
25
+
26
+ def service_name
27
+ accessory_config["service"] || "#{config.service}-#{name}"
28
+ end
29
+
30
+ def image
31
+ [ registry&.server, accessory_config["image"] ].compact.join("/")
32
+ end
33
+
34
+ def hosts
35
+ hosts_from_host || hosts_from_hosts || hosts_from_roles || hosts_from_tags
36
+ end
37
+
38
+ def port
39
+ if port = accessory_config["port"]&.to_s
40
+ port.include?(":") ? port : "#{port}:#{port}"
41
+ end
42
+ end
43
+
44
+ def network_args
45
+ argumentize "--network", network
46
+ end
47
+
48
+ def publish_args
49
+ argumentize "--publish", port if port
50
+ end
51
+
52
+ def labels
53
+ default_labels.merge(accessory_config["labels"] || {})
54
+ end
55
+
56
+ def label_args
57
+ argumentize "--label", labels
58
+ end
59
+
60
+ def env_args
61
+ [ *env.clear_args, *argumentize("--env-file", secrets_path) ]
62
+ end
63
+
64
+ def env_directory
65
+ File.join(config.env_directory, "accessories")
66
+ end
67
+
68
+ def secrets_io
69
+ env.secrets_io
70
+ end
71
+
72
+ def secrets_path
73
+ File.join(config.env_directory, "accessories", "#{name}.env")
74
+ end
75
+
76
+ def files
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
85
+ end || {}
86
+ end
87
+
88
+ def directories
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
97
+ end || {}
98
+ end
99
+
100
+ def volume_args
101
+ argumentize("--volume", specific_volumes) + (path_volumes(files) + path_volumes(directories)).flat_map(&:docker_args)
102
+ end
103
+
104
+ def option_args
105
+ optionize docker_options.reject { |key, _| key.to_s == "restart" }
106
+ end
107
+
108
+ def restart_policy
109
+ restart_policy_option || "unless-stopped"
110
+ end
111
+
112
+ def cmd
113
+ accessory_config["cmd"]
114
+ end
115
+
116
+ def running_proxy?
117
+ accessory_config["proxy"].present?
118
+ end
119
+
120
+ private
121
+ attr_reader :config, :accessory_config
122
+
123
+ def initialize_env
124
+ Kamal::Configuration::Env.new \
125
+ config: accessory_config.fetch("env", {}),
126
+ secrets: config.secrets,
127
+ context: "accessories/#{name}/env"
128
+ end
129
+
130
+ def initialize_proxy
131
+ Kamal::Configuration::Proxy.new \
132
+ config: config,
133
+ proxy_config: accessory_config["proxy"],
134
+ context: "accessories/#{name}/proxy",
135
+ secrets: config.secrets
136
+ end
137
+
138
+ def initialize_registry
139
+ Kamal::Configuration::Registry.new \
140
+ config: accessory_config,
141
+ secrets: config.secrets,
142
+ context: "accessories/#{name}/registry"
143
+ end
144
+
145
+ def default_labels
146
+ { "service" => service_name }
147
+ end
148
+
149
+ def expand_local_file(local_file)
150
+ if local_file.end_with?("erb")
151
+ with_env_loaded { read_dynamic_file(local_file) }
152
+ else
153
+ Pathname.new(File.expand_path(local_file)).to_s
154
+ end
155
+ end
156
+
157
+ def with_env_loaded
158
+ env.to_h.each { |k, v| ENV[k] = v }
159
+ yield
160
+ ensure
161
+ env.to_h.each { |k, v| ENV.delete(k) }
162
+ end
163
+
164
+ def read_dynamic_file(local_file)
165
+ StringIO.new(ERB.new(File.read(local_file)).result)
166
+ end
167
+
168
+ def expand_remote_file(remote_file)
169
+ service_name + remote_file
170
+ end
171
+
172
+ def specific_volumes
173
+ accessory_config["volumes"] || []
174
+ end
175
+
176
+ def docker_options
177
+ accessory_config["options"] || {}
178
+ end
179
+
180
+ def restart_policy_option
181
+ docker_options.find { |key, _| key.to_s == "restart" }&.last
182
+ end
183
+
184
+ def path_volumes(paths)
185
+ paths.map do |local, config|
186
+ Kamal::Configuration::Volume.new \
187
+ host_path: config[:host_path],
188
+ container_path: config[:container_path],
189
+ options: config[:options]
190
+ end
191
+ end
192
+
193
+ def parse_path_config(config, default_mode:)
194
+ if config.is_a?(Hash)
195
+ local, remote = config["local"], config["remote"]
196
+ expanded = yield(local, remote)
197
+ [
198
+ expanded[:key],
199
+ expanded.except(:key).merge(
200
+ options: config["options"],
201
+ mode: config["mode"] || default_mode,
202
+ owner: config["owner"]
203
+ )
204
+ ]
205
+ else
206
+ local, remote, options = config.split(":", 3)
207
+ expanded = yield(local, remote)
208
+ [
209
+ expanded[:key],
210
+ expanded.except(:key).merge(
211
+ options: options,
212
+ mode: default_mode,
213
+ owner: nil
214
+ )
215
+ ]
216
+ end
217
+ end
218
+
219
+ def expand_host_path(host_path)
220
+ absolute_path?(host_path) ? host_path : File.join(service_data_directory, host_path)
221
+ end
222
+
223
+ def expand_host_path_for_volume(host_path)
224
+ absolute_path?(host_path) ? host_path : File.join(service_name, host_path)
225
+ end
226
+
227
+ def absolute_path?(path)
228
+ Pathname.new(path).absolute?
229
+ end
230
+
231
+ def service_data_directory
232
+ "$PWD/#{service_name}"
233
+ end
234
+
235
+ def hosts_from_host
236
+ [ accessory_config["host"] ] if accessory_config.key?("host")
237
+ end
238
+
239
+ def hosts_from_hosts
240
+ accessory_config["hosts"] if accessory_config.key?("hosts")
241
+ end
242
+
243
+ def hosts_from_roles
244
+ if accessory_config.key?("role")
245
+ config.role(accessory_config["role"])&.hosts
246
+ elsif accessory_config.key?("roles")
247
+ accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
248
+ end
249
+ end
250
+
251
+ def hosts_from_tags
252
+ if accessory_config.key?("tag")
253
+ extract_hosts_from_config_with_tag(accessory_config["tag"])
254
+ elsif accessory_config.key?("tags")
255
+ accessory_config["tags"].flat_map { |tag| extract_hosts_from_config_with_tag(tag) }
256
+ end
257
+ end
258
+
259
+ def extract_hosts_from_config_with_tag(tag)
260
+ if (servers_with_roles = config.raw_config.servers).is_a?(Hash)
261
+ servers_with_roles.flat_map do |role, servers_in_role|
262
+ servers_in_role.filter_map do |host|
263
+ host.keys.first if host.is_a?(Hash) && host.values.first.include?(tag)
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ def network
270
+ accessory_config["network"] || DEFAULT_NETWORK
271
+ end
272
+
273
+ def ensure_valid_roles
274
+ if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
275
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
276
+ elsif accessory_config["role"] && !config.role(accessory_config["role"])
277
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown role #{accessory_config["role"]}"
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,15 @@
1
+ class Kamal::Configuration::Alias
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :name, :command
5
+
6
+ def initialize(name, config:)
7
+ @name, @command = name.inquiry, config.raw_config["aliases"][name]
8
+
9
+ validate! \
10
+ command,
11
+ example: validation_yml["aliases"]["uname"],
12
+ context: "aliases/#{name}",
13
+ with: Kamal::Configuration::Validator::Alias
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ class Kamal::Configuration::Boot
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :boot_config, :host_count
5
+
6
+ def initialize(config:)
7
+ @boot_config = config.raw_config.boot || {}
8
+ @host_count = config.all_hosts.count
9
+ validate! boot_config
10
+ end
11
+
12
+ def limit
13
+ limit = boot_config["limit"]
14
+
15
+ if limit.to_s.end_with?("%")
16
+ [ host_count * limit.to_i / 100, 1 ].max
17
+ else
18
+ limit
19
+ end
20
+ end
21
+
22
+ def wait
23
+ boot_config["wait"]
24
+ end
25
+
26
+ def parallel_roles
27
+ boot_config["parallel_roles"]
28
+ end
29
+ end