kamal 2.9.0 → 2.10.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.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +8 -4
- data/lib/kamal/cli/app/boot.rb +1 -1
- data/lib/kamal/cli/app.rb +64 -112
- data/lib/kamal/cli/healthcheck/poller.rb +1 -1
- data/lib/kamal/cli/proxy.rb +41 -35
- data/lib/kamal/cli/secrets.rb +2 -1
- data/lib/kamal/commander.rb +2 -2
- data/lib/kamal/commands/app.rb +1 -1
- data/lib/kamal/commands/proxy.rb +21 -2
- data/lib/kamal/configuration/accessory.rb +63 -26
- data/lib/kamal/configuration/boot.rb +4 -0
- data/lib/kamal/configuration/docs/accessory.yml +37 -5
- data/lib/kamal/configuration/docs/boot.yml +12 -10
- data/lib/kamal/configuration/docs/configuration.yml +4 -1
- data/lib/kamal/configuration/docs/proxy.yml +24 -0
- data/lib/kamal/configuration/docs/ssh.yml +6 -3
- data/lib/kamal/configuration/env.rb +7 -3
- data/lib/kamal/configuration/proxy/boot.rb +4 -9
- data/lib/kamal/configuration/proxy/run.rb +143 -0
- data/lib/kamal/configuration/proxy.rb +2 -2
- data/lib/kamal/configuration/role.rb +14 -2
- data/lib/kamal/configuration/ssh.rb +13 -2
- data/lib/kamal/configuration/validator/proxy.rb +20 -0
- data/lib/kamal/configuration/validator.rb +20 -0
- data/lib/kamal/configuration/volume.rb +11 -4
- data/lib/kamal/configuration.rb +27 -0
- data/lib/kamal/secrets/adapters/test.rb +3 -1
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +15 -1
- data/lib/kamal/secrets.rb +14 -4
- data/lib/kamal/sshkit_with_ext.rb +55 -0
- data/lib/kamal/utils.rb +3 -3
- data/lib/kamal/version.rb +1 -1
- metadata +2 -1
|
@@ -74,25 +74,31 @@ class Kamal::Configuration::Accessory
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def files
|
|
77
|
-
accessory_config["files"]&.to_h do |
|
|
78
|
-
|
|
79
|
-
|
|
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 |
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
101
|
+
(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
|
-
|
|
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
|
|
152
|
-
env.
|
|
157
|
+
def with_env_loaded
|
|
158
|
+
env.to_h.each { |k, v| ENV[k] = v }
|
|
153
159
|
yield
|
|
154
160
|
ensure
|
|
155
|
-
env.
|
|
161
|
+
env.to_h.each { |k, v| ENV.delete(k) }
|
|
156
162
|
end
|
|
157
163
|
|
|
158
164
|
def read_dynamic_file(local_file)
|
|
@@ -164,27 +170,58 @@ class Kamal::Configuration::Accessory
|
|
|
164
170
|
end
|
|
165
171
|
|
|
166
172
|
def specific_volumes
|
|
167
|
-
accessory_config["volumes"] || []
|
|
173
|
+
(accessory_config["volumes"] || []).collect do |volume_string|
|
|
174
|
+
host_path, container_path, options = volume_string.split(":", 3)
|
|
175
|
+
Kamal::Configuration::Volume.new \
|
|
176
|
+
host_path: host_path,
|
|
177
|
+
container_path: container_path,
|
|
178
|
+
options: options
|
|
179
|
+
end
|
|
168
180
|
end
|
|
169
181
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
def path_volumes(paths)
|
|
183
|
+
paths.map do |local, config|
|
|
184
|
+
Kamal::Configuration::Volume.new \
|
|
185
|
+
host_path: config[:host_path],
|
|
186
|
+
container_path: config[:container_path],
|
|
187
|
+
options: config[:options]
|
|
188
|
+
end
|
|
175
189
|
end
|
|
176
190
|
|
|
177
|
-
def
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
def parse_path_config(config, default_mode:)
|
|
192
|
+
if config.is_a?(Hash)
|
|
193
|
+
local, remote = config["local"], config["remote"]
|
|
194
|
+
expanded = yield(local, remote)
|
|
195
|
+
[
|
|
196
|
+
expanded[:key],
|
|
197
|
+
expanded.except(:key).merge(
|
|
198
|
+
options: config["options"],
|
|
199
|
+
mode: config["mode"] || default_mode,
|
|
200
|
+
owner: config["owner"]
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
else
|
|
204
|
+
local, remote, options = config.split(":", 3)
|
|
205
|
+
expanded = yield(local, remote)
|
|
206
|
+
[
|
|
207
|
+
expanded[:key],
|
|
208
|
+
expanded.except(:key).merge(
|
|
209
|
+
options: options,
|
|
210
|
+
mode: default_mode,
|
|
211
|
+
owner: nil
|
|
212
|
+
)
|
|
213
|
+
]
|
|
214
|
+
end
|
|
182
215
|
end
|
|
183
216
|
|
|
184
217
|
def expand_host_path(host_path)
|
|
185
218
|
absolute_path?(host_path) ? host_path : File.join(service_data_directory, host_path)
|
|
186
219
|
end
|
|
187
220
|
|
|
221
|
+
def expand_host_path_for_volume(host_path)
|
|
222
|
+
absolute_path?(host_path) ? host_path : File.join(service_name, host_path)
|
|
223
|
+
end
|
|
224
|
+
|
|
188
225
|
def absolute_path?(path)
|
|
189
226
|
Pathname.new(path).absolute?
|
|
190
227
|
end
|
|
@@ -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
|
#
|
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
|
@@ -73,7 +73,10 @@ env:
|
|
|
73
73
|
# This requires that file names change when the contents change
|
|
74
74
|
# (e.g., by including a hash of the contents in the name).
|
|
75
75
|
#
|
|
76
|
-
# To configure this, set the path to the assets
|
|
76
|
+
# To configure this, set the path to the assets.
|
|
77
|
+
#
|
|
78
|
+
# You can also specify mount options after a colon, such as `ro` for read-only
|
|
79
|
+
# or `z`/`Z` for SELinux labels
|
|
77
80
|
asset_path: /path/to/assets
|
|
78
81
|
|
|
79
82
|
# Hooks path
|
|
@@ -148,6 +148,30 @@ proxy:
|
|
|
148
148
|
- X-Request-ID
|
|
149
149
|
- X-Request-Start
|
|
150
150
|
|
|
151
|
+
# Run configuration
|
|
152
|
+
#
|
|
153
|
+
# These options are used when booting the proxy container.
|
|
154
|
+
#
|
|
155
|
+
run:
|
|
156
|
+
http_port: 8080 # HTTP port to use (default 80)
|
|
157
|
+
https_port: 8443 # HTTPS port to use (default 443)
|
|
158
|
+
metrics_port: 9090 # Port for Prometheus metrics
|
|
159
|
+
debug: true # Debug logging (default: false)
|
|
160
|
+
log_max_size: "30m" # Maximum log file size (default: "10m")
|
|
161
|
+
publish: false # Publish ports to the host (default: true)
|
|
162
|
+
bind_ips: # List of IPs to bind to when publishing ports
|
|
163
|
+
- 0.0.0.0
|
|
164
|
+
registry: registry:4443 # Container registry for the kamal-proxy image
|
|
165
|
+
# (defaults to Docker Hub)
|
|
166
|
+
repository: myrepo/kamal-proxy # Container repository for the kamal-proxy image
|
|
167
|
+
# (defaults to `basecamp/kamal-proxy`)
|
|
168
|
+
version: v0.8.0 # Version tag of the kamal-proxy image to use
|
|
169
|
+
options: # Additional options to pass to `docker run`
|
|
170
|
+
label:
|
|
171
|
+
- custom.label=kamal-proxy
|
|
172
|
+
memory: 512m
|
|
173
|
+
cpus: 0.5
|
|
174
|
+
|
|
151
175
|
# Enabling/disabling the proxy on roles
|
|
152
176
|
#
|
|
153
177
|
# The proxy is enabled by default on the primary role but can be disabled by
|
|
@@ -58,9 +58,12 @@ ssh:
|
|
|
58
58
|
|
|
59
59
|
# Key data
|
|
60
60
|
#
|
|
61
|
-
# An array of strings, with each element of the array being
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# An array of strings, with each element of the array being a secret name.
|
|
62
|
+
key_data:
|
|
63
|
+
- SSH_PRIVATE_KEY
|
|
64
|
+
# You can also provide raw private key in PEM format, but this is deprecated.
|
|
65
|
+
key_data:
|
|
66
|
+
- "-----BEGIN OPENSSH PRIVATE KEY----- ..."
|
|
64
67
|
|
|
65
68
|
# Config
|
|
66
69
|
#
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Kamal::Configuration::Env
|
|
2
2
|
include Kamal::Configuration::Validation
|
|
3
3
|
|
|
4
|
-
attr_reader :context, :clear, :secret_keys
|
|
4
|
+
attr_reader :context, :clear, :secrets, :secret_keys
|
|
5
5
|
delegate :argumentize, to: Kamal::Utils
|
|
6
6
|
|
|
7
7
|
def initialize(config:, secrets:, context: "env")
|
|
@@ -23,12 +23,16 @@ class Kamal::Configuration::Env
|
|
|
23
23
|
def merge(other)
|
|
24
24
|
self.class.new \
|
|
25
25
|
config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
|
|
26
|
-
secrets:
|
|
26
|
+
secrets: secrets
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
clear.merge(aliased_secrets)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
private
|
|
30
34
|
def aliased_secrets
|
|
31
|
-
secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key|
|
|
35
|
+
secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key| secrets[secret_key] }
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def extract_alias(key)
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
class Kamal::Configuration::Proxy::Boot
|
|
2
|
-
MINIMUM_VERSION = "v0.9.0"
|
|
3
|
-
DEFAULT_HTTP_PORT = 80
|
|
4
|
-
DEFAULT_HTTPS_PORT = 443
|
|
5
|
-
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
6
|
-
|
|
7
2
|
attr_reader :config
|
|
8
3
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
9
4
|
|
|
@@ -16,8 +11,8 @@ class Kamal::Configuration::Proxy::Boot
|
|
|
16
11
|
|
|
17
12
|
(bind_ips || [ nil ]).map do |bind_ip|
|
|
18
13
|
bind_ip = format_bind_ip(bind_ip)
|
|
19
|
-
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
|
20
|
-
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
14
|
+
publish_http = [ bind_ip, http_port, Kamal::Configuration::Proxy::Run::DEFAULT_HTTP_PORT ].compact.join(":")
|
|
15
|
+
publish_https = [ bind_ip, https_port, Kamal::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
21
16
|
|
|
22
17
|
argumentize "--publish", [ publish_http, publish_https ]
|
|
23
18
|
end.join(" ")
|
|
@@ -29,8 +24,8 @@ class Kamal::Configuration::Proxy::Boot
|
|
|
29
24
|
|
|
30
25
|
def default_boot_options
|
|
31
26
|
[
|
|
32
|
-
*(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)),
|
|
33
|
-
*(logging_args(DEFAULT_LOG_MAX_SIZE))
|
|
27
|
+
*(publish_args(Kamal::Configuration::Proxy::Run::DEFAULT_HTTP_PORT, Kamal::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT, nil)),
|
|
28
|
+
*(logging_args(Kamal::Configuration::Proxy::Run::DEFAULT_LOG_MAX_SIZE))
|
|
34
29
|
]
|
|
35
30
|
end
|
|
36
31
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
class Kamal::Configuration::Proxy::Run
|
|
2
|
+
MINIMUM_VERSION = "v0.9.0"
|
|
3
|
+
DEFAULT_HTTP_PORT = 80
|
|
4
|
+
DEFAULT_HTTPS_PORT = 443
|
|
5
|
+
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
6
|
+
|
|
7
|
+
attr_reader :config, :run_config
|
|
8
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
9
|
+
|
|
10
|
+
def initialize(config, run_config:, context: "proxy/run")
|
|
11
|
+
@config = config
|
|
12
|
+
@run_config = run_config
|
|
13
|
+
@context = context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def debug?
|
|
17
|
+
run_config.fetch("debug", nil)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def publish?
|
|
21
|
+
run_config.fetch("publish", true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def http_port
|
|
25
|
+
run_config.fetch("http_port", DEFAULT_HTTP_PORT)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def https_port
|
|
29
|
+
run_config.fetch("https_port", DEFAULT_HTTPS_PORT)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bind_ips
|
|
33
|
+
run_config.fetch("bind_ips", nil)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def publish_args
|
|
37
|
+
if publish?
|
|
38
|
+
(bind_ips || [ nil ]).map do |bind_ip|
|
|
39
|
+
bind_ip = format_bind_ip(bind_ip)
|
|
40
|
+
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
|
41
|
+
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
42
|
+
|
|
43
|
+
argumentize "--publish", [ publish_http, publish_https ]
|
|
44
|
+
end.join(" ")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def log_max_size
|
|
49
|
+
run_config.fetch("log_max_size", DEFAULT_LOG_MAX_SIZE)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def logging_args
|
|
53
|
+
argumentize "--log-opt", "max-size=#{log_max_size}" if log_max_size.present?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def version
|
|
57
|
+
run_config.fetch("version", MINIMUM_VERSION)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def registry
|
|
61
|
+
run_config.fetch("registry", nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def repository
|
|
65
|
+
run_config.fetch("repository", "basecamp/kamal-proxy")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def image
|
|
69
|
+
"#{[ registry, repository ].compact.join("/")}:#{version}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def container_name
|
|
73
|
+
"kamal-proxy"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def options_args
|
|
77
|
+
if args = run_config["options"]
|
|
78
|
+
optionize args
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def run_command
|
|
83
|
+
[ "kamal-proxy", "run", *optionize(run_command_options) ].join(" ")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def metrics_port
|
|
87
|
+
run_config["metrics_port"]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def run_command_options
|
|
91
|
+
{ debug: debug? || nil, "metrics-port": metrics_port }.compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def docker_options_args
|
|
95
|
+
[
|
|
96
|
+
*apps_volume_args,
|
|
97
|
+
*publish_args,
|
|
98
|
+
*logging_args,
|
|
99
|
+
*("--expose=#{metrics_port}" if metrics_port.present?),
|
|
100
|
+
*options_args
|
|
101
|
+
].compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def host_directory
|
|
105
|
+
File.join config.run_directory, "proxy"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def apps_directory
|
|
109
|
+
File.join host_directory, "apps-config"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def apps_container_directory
|
|
113
|
+
"/home/kamal-proxy/.apps-config"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def apps_volume
|
|
117
|
+
Kamal::Configuration::Volume.new \
|
|
118
|
+
host_path: apps_directory,
|
|
119
|
+
container_path: apps_container_directory
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def apps_volume_args
|
|
123
|
+
[ apps_volume.docker_args ]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def app_directory
|
|
127
|
+
File.join apps_directory, config.service_and_destination
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def app_container_directory
|
|
131
|
+
File.join apps_container_directory, config.service_and_destination
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
def format_bind_ip(ip)
|
|
136
|
+
# Ensure IPv6 address inside square brackets - e.g. [::1]
|
|
137
|
+
if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/
|
|
138
|
+
"[#{ip}]"
|
|
139
|
+
else
|
|
140
|
+
ip
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -6,8 +6,7 @@ class Kamal::Configuration::Proxy
|
|
|
6
6
|
|
|
7
7
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
8
8
|
|
|
9
|
-
attr_reader :config, :proxy_config, :role_name, :secrets
|
|
10
|
-
|
|
9
|
+
attr_reader :config, :proxy_config, :role_name, :run, :secrets
|
|
11
10
|
def initialize(config:, proxy_config:, role_name: nil, secrets:, context: "proxy")
|
|
12
11
|
@config = config
|
|
13
12
|
@proxy_config = proxy_config
|
|
@@ -15,6 +14,7 @@ class Kamal::Configuration::Proxy
|
|
|
15
14
|
@role_name = role_name
|
|
16
15
|
@secrets = secrets
|
|
17
16
|
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
|
|
17
|
+
@run = Kamal::Configuration::Proxy::Run.new(config, run_config: @proxy_config["run"], context: "#{context}/run") if @proxy_config && @proxy_config["run"].present?
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def app_port
|
|
@@ -127,7 +127,7 @@ class Kamal::Configuration::Role
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def asset_path
|
|
130
|
-
|
|
130
|
+
asset_path_config&.dig(0)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def assets?
|
|
@@ -137,10 +137,14 @@ class Kamal::Configuration::Role
|
|
|
137
137
|
def asset_volume(version = config.version)
|
|
138
138
|
if assets?
|
|
139
139
|
Kamal::Configuration::Volume.new \
|
|
140
|
-
host_path: asset_volume_directory(version), container_path: asset_path
|
|
140
|
+
host_path: asset_volume_directory(version), container_path: asset_path, options: asset_path_options
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
+
def asset_path_options
|
|
145
|
+
asset_path_config&.dig(1)
|
|
146
|
+
end
|
|
147
|
+
|
|
144
148
|
def asset_extracted_directory(version = config.version)
|
|
145
149
|
File.join config.assets_directory, "extracted", [ name, version ].join("-")
|
|
146
150
|
end
|
|
@@ -219,4 +223,12 @@ class Kamal::Configuration::Role
|
|
|
219
223
|
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
|
220
224
|
end
|
|
221
225
|
end
|
|
226
|
+
|
|
227
|
+
def asset_path_config
|
|
228
|
+
raw_path = specializations["asset_path"] || config.asset_path
|
|
229
|
+
return nil unless raw_path.present?
|
|
230
|
+
|
|
231
|
+
parts = raw_path.split(":", 2)
|
|
232
|
+
[ parts[0], parts[1] ]
|
|
233
|
+
end
|
|
222
234
|
end
|
|
@@ -3,10 +3,11 @@ class Kamal::Configuration::Ssh
|
|
|
3
3
|
|
|
4
4
|
include Kamal::Configuration::Validation
|
|
5
5
|
|
|
6
|
-
attr_reader :ssh_config
|
|
6
|
+
attr_reader :ssh_config, :secrets
|
|
7
7
|
|
|
8
8
|
def initialize(config:)
|
|
9
9
|
@ssh_config = config.raw_config.ssh || {}
|
|
10
|
+
@secrets = config.secrets
|
|
10
11
|
validate! ssh_config
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -35,7 +36,17 @@ class Kamal::Configuration::Ssh
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def key_data
|
|
38
|
-
ssh_config["key_data"]
|
|
39
|
+
key_data = ssh_config["key_data"]
|
|
40
|
+
return unless key_data
|
|
41
|
+
|
|
42
|
+
key_data.map do |k|
|
|
43
|
+
if secrets.key?(k)
|
|
44
|
+
secrets[k]
|
|
45
|
+
else
|
|
46
|
+
warn "Inline key_data usage is deprecated and will be removed in Kamal 3. Please store your key_data in a secret."
|
|
47
|
+
k
|
|
48
|
+
end
|
|
49
|
+
end
|
|
39
50
|
end
|
|
40
51
|
|
|
41
52
|
def config
|
|
@@ -20,6 +20,26 @@ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
|
|
|
20
20
|
error "Missing certificate_pem setting (required when private_key_pem is present)"
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
if run_config = config["run"]
|
|
25
|
+
if run_config["bind_ips"].present?
|
|
26
|
+
ensure_valid_bind_ips(config["bind_ips"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if run_config["publish"] == false
|
|
30
|
+
if run_config["bind_ips"].present? || run_config["http_port"].present? || run_config["https_port"].present?
|
|
31
|
+
error "Cannot set http_port, https_port or bind_ips when publish is false"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
23
35
|
end
|
|
24
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def ensure_valid_bind_ips(bind_ips)
|
|
40
|
+
bind_ips.present? && bind_ips.each do |ip|
|
|
41
|
+
next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
|
|
42
|
+
error "Invalid publish IP address: #{ip}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
25
45
|
end
|
|
@@ -36,6 +36,8 @@ class Kamal::Configuration::Validator
|
|
|
36
36
|
validate_array_of_or_type! value, example_value.first.class
|
|
37
37
|
elsif key.to_s == "config"
|
|
38
38
|
validate_ssh_config!(value)
|
|
39
|
+
elsif key.to_s == "files" || key.to_s == "directories"
|
|
40
|
+
validate_paths!(value)
|
|
39
41
|
else
|
|
40
42
|
validate_array_of! value, example_value.first.class
|
|
41
43
|
end
|
|
@@ -141,6 +143,24 @@ class Kamal::Configuration::Validator
|
|
|
141
143
|
end
|
|
142
144
|
end
|
|
143
145
|
|
|
146
|
+
def validate_paths!(paths)
|
|
147
|
+
validate_type! paths, Array
|
|
148
|
+
|
|
149
|
+
paths.each_with_index do |path, index|
|
|
150
|
+
with_context(index) do
|
|
151
|
+
validate_type! path, String, Hash
|
|
152
|
+
|
|
153
|
+
if path.is_a?(Hash)
|
|
154
|
+
%w[local remote mode owner options].each do |key|
|
|
155
|
+
with_context(key) do
|
|
156
|
+
validate_type! path[key], String if path.key?(key)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
144
164
|
def validate_type!(value, *types)
|
|
145
165
|
type_error(*types) unless types.any? { |type| valid_type?(value, type) }
|
|
146
166
|
end
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
class Kamal::Configuration::Volume
|
|
2
|
-
attr_reader :host_path, :container_path
|
|
2
|
+
attr_reader :host_path, :container_path, :options
|
|
3
3
|
delegate :argumentize, to: Kamal::Utils
|
|
4
4
|
|
|
5
|
-
def initialize(host_path:, container_path:)
|
|
5
|
+
def initialize(host_path:, container_path:, options: nil)
|
|
6
6
|
@host_path = host_path
|
|
7
7
|
@container_path = container_path
|
|
8
|
+
@options = options
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def docker_args
|
|
11
|
-
argumentize "--volume",
|
|
12
|
+
argumentize "--volume", docker_args_string
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def docker_args_string
|
|
16
|
+
volume_string = "#{host_path_for_docker_volume}:#{container_path}"
|
|
17
|
+
volume_string += ":#{options}" if options.present?
|
|
18
|
+
volume_string
|
|
12
19
|
end
|
|
13
20
|
|
|
14
21
|
private
|
|
@@ -16,7 +23,7 @@ class Kamal::Configuration::Volume
|
|
|
16
23
|
if Pathname.new(host_path).absolute?
|
|
17
24
|
host_path
|
|
18
25
|
else
|
|
19
|
-
|
|
26
|
+
"$PWD/#{host_path}"
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
end
|