kamal 1.5.2 → 1.7.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 +30 -24
- data/lib/kamal/cli/app/boot.rb +70 -18
- data/lib/kamal/cli/app/prepare_assets.rb +1 -1
- data/lib/kamal/cli/app.rb +60 -47
- data/lib/kamal/cli/base.rb +26 -28
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +64 -53
- data/lib/kamal/cli/env.rb +5 -5
- data/lib/kamal/cli/healthcheck/barrier.rb +31 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +6 -7
- data/lib/kamal/cli/main.rb +49 -44
- data/lib/kamal/cli/prune.rb +3 -3
- data/lib/kamal/cli/registry.rb +9 -10
- data/lib/kamal/cli/server.rb +39 -15
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
- data/lib/kamal/cli/traefik.rb +13 -11
- data/lib/kamal/cli.rb +1 -1
- data/lib/kamal/commander.rb +6 -6
- data/lib/kamal/commands/accessory.rb +4 -4
- data/lib/kamal/commands/app/containers.rb +8 -0
- data/lib/kamal/commands/app/execution.rb +3 -3
- data/lib/kamal/commands/app/logging.rb +5 -5
- data/lib/kamal/commands/app.rb +6 -5
- data/lib/kamal/commands/base.rb +2 -3
- data/lib/kamal/commands/builder/base.rb +19 -12
- data/lib/kamal/commands/builder/clone.rb +28 -0
- data/lib/kamal/commands/builder/multiarch/remote.rb +10 -0
- data/lib/kamal/commands/builder/multiarch.rb +13 -9
- data/lib/kamal/commands/builder/native/cached.rb +14 -6
- data/lib/kamal/commands/builder/native/remote.rb +17 -9
- data/lib/kamal/commands/builder/native.rb +6 -7
- data/lib/kamal/commands/builder.rb +19 -11
- data/lib/kamal/commands/registry.rb +4 -13
- data/lib/kamal/commands/traefik.rb +8 -47
- data/lib/kamal/configuration/accessory.rb +30 -41
- data/lib/kamal/configuration/boot.rb +9 -4
- data/lib/kamal/configuration/builder.rb +61 -30
- data/lib/kamal/configuration/docs/accessory.yml +90 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +107 -0
- data/lib/kamal/configuration/docs/configuration.yml +157 -0
- data/lib/kamal/configuration/docs/env.yml +72 -0
- data/lib/kamal/configuration/docs/healthcheck.yml +59 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/registry.yml +49 -0
- data/lib/kamal/configuration/docs/role.yml +52 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +46 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/docs/traefik.yml +62 -0
- data/lib/kamal/configuration/env/tag.rb +12 -0
- data/lib/kamal/configuration/env.rb +10 -14
- data/lib/kamal/configuration/healthcheck.rb +63 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/registry.rb +31 -0
- data/lib/kamal/configuration/role.rb +72 -61
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +11 -8
- data/lib/kamal/configuration/sshkit.rb +9 -7
- data/lib/kamal/configuration/traefik.rb +60 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/builder.rb +9 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +140 -0
- data/lib/kamal/configuration.rb +50 -63
- data/lib/kamal/git.rb +4 -0
- data/lib/kamal/sshkit_with_ext.rb +36 -0
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +2 -0
- metadata +64 -9
- data/lib/kamal/cli/healthcheck.rb +0 -21
- data/lib/kamal/commands/healthcheck.rb +0 -59
@@ -0,0 +1,33 @@
|
|
1
|
+
class Kamal::Configuration::Logging
|
2
|
+
delegate :optionize, :argumentize, to: Kamal::Utils
|
3
|
+
|
4
|
+
include Kamal::Configuration::Validation
|
5
|
+
|
6
|
+
attr_reader :logging_config
|
7
|
+
|
8
|
+
def initialize(logging_config:, context: "logging")
|
9
|
+
@logging_config = logging_config || {}
|
10
|
+
validate! @logging_config, context: context
|
11
|
+
end
|
12
|
+
|
13
|
+
def driver
|
14
|
+
logging_config["driver"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def options
|
18
|
+
logging_config.fetch("options", {})
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge(other)
|
22
|
+
self.class.new logging_config: logging_config.deep_merge(other.logging_config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def args
|
26
|
+
if driver.present? || options.present?
|
27
|
+
optionize({ "log-driver" => driver }.compact) +
|
28
|
+
argumentize("--log-opt", options)
|
29
|
+
else
|
30
|
+
argumentize("--log-opt", { "max-size" => "10m" })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Kamal::Configuration::Registry
|
2
|
+
include Kamal::Configuration::Validation
|
3
|
+
|
4
|
+
attr_reader :registry_config
|
5
|
+
|
6
|
+
def initialize(config:)
|
7
|
+
@registry_config = config.raw_config.registry || {}
|
8
|
+
validate! registry_config, with: Kamal::Configuration::Validator::Registry
|
9
|
+
end
|
10
|
+
|
11
|
+
def server
|
12
|
+
registry_config["server"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def username
|
16
|
+
lookup("username")
|
17
|
+
end
|
18
|
+
|
19
|
+
def password
|
20
|
+
lookup("password")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def lookup(key)
|
25
|
+
if registry_config[key].is_a?(Array)
|
26
|
+
ENV.fetch(registry_config[key].first).dup
|
27
|
+
else
|
28
|
+
registry_config[key]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,12 +1,33 @@
|
|
1
1
|
class Kamal::Configuration::Role
|
2
|
+
include Kamal::Configuration::Validation
|
3
|
+
|
2
4
|
CORD_FILE = "cord"
|
3
5
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
4
6
|
|
5
|
-
|
7
|
+
attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_healthcheck
|
8
|
+
|
6
9
|
alias to_s name
|
7
10
|
|
8
11
|
def initialize(name, config:)
|
9
12
|
@name, @config = name.inquiry, config
|
13
|
+
validate! \
|
14
|
+
specializations,
|
15
|
+
example: validation_yml["servers"]["workers"],
|
16
|
+
context: "servers/#{name}",
|
17
|
+
with: Kamal::Configuration::Validator::Role
|
18
|
+
|
19
|
+
@specialized_env = Kamal::Configuration::Env.new \
|
20
|
+
config: specializations.fetch("env", {}),
|
21
|
+
secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env"),
|
22
|
+
context: "servers/#{name}/env"
|
23
|
+
|
24
|
+
@specialized_logging = Kamal::Configuration::Logging.new \
|
25
|
+
logging_config: specializations.fetch("logging", {}),
|
26
|
+
context: "servers/#{name}/logging"
|
27
|
+
|
28
|
+
@specialized_healthcheck = Kamal::Configuration::Healthcheck.new \
|
29
|
+
healthcheck_config: specializations.fetch("healthcheck", {}),
|
30
|
+
context: "servers/#{name}/healthcheck"
|
10
31
|
end
|
11
32
|
|
12
33
|
def primary_host
|
@@ -14,7 +35,11 @@ class Kamal::Configuration::Role
|
|
14
35
|
end
|
15
36
|
|
16
37
|
def hosts
|
17
|
-
|
38
|
+
tagged_hosts.keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def env_tags(host)
|
42
|
+
tagged_hosts.fetch(host).collect { |tag| config.env_tag(tag) }
|
18
43
|
end
|
19
44
|
|
20
45
|
def cmd
|
@@ -38,24 +63,21 @@ class Kamal::Configuration::Role
|
|
38
63
|
end
|
39
64
|
|
40
65
|
def logging_args
|
41
|
-
args
|
42
|
-
|
66
|
+
logging.args
|
67
|
+
end
|
43
68
|
|
44
|
-
|
45
|
-
|
46
|
-
argumentize("--log-opt", args["options"])
|
47
|
-
else
|
48
|
-
config.logging_args
|
49
|
-
end
|
69
|
+
def logging
|
70
|
+
@logging ||= config.logging.merge(specialized_logging)
|
50
71
|
end
|
51
72
|
|
52
73
|
|
53
|
-
def env
|
54
|
-
@
|
74
|
+
def env(host)
|
75
|
+
@envs ||= {}
|
76
|
+
@envs[host] ||= [ config.env, specialized_env, *env_tags(host).map(&:env) ].reduce(:merge)
|
55
77
|
end
|
56
78
|
|
57
|
-
def env_args
|
58
|
-
env.args
|
79
|
+
def env_args(host)
|
80
|
+
env(host).args
|
59
81
|
end
|
60
82
|
|
61
83
|
def asset_volume_args
|
@@ -64,28 +86,29 @@ class Kamal::Configuration::Role
|
|
64
86
|
|
65
87
|
|
66
88
|
def health_check_args(cord: true)
|
67
|
-
if
|
89
|
+
if running_traefik? || healthcheck.set_port_or_path?
|
68
90
|
if cord && uses_cord?
|
69
|
-
optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" =>
|
91
|
+
optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" => healthcheck.interval })
|
70
92
|
.concat(cord_volume.docker_args)
|
71
93
|
else
|
72
|
-
optionize({ "health-cmd" =>
|
94
|
+
optionize({ "health-cmd" => healthcheck.cmd, "health-interval" => healthcheck.interval })
|
73
95
|
end
|
74
96
|
else
|
75
97
|
[]
|
76
98
|
end
|
77
99
|
end
|
78
100
|
|
79
|
-
def
|
80
|
-
|
101
|
+
def healthcheck
|
102
|
+
@healthcheck ||=
|
103
|
+
if running_traefik?
|
104
|
+
config.healthcheck.merge(specialized_healthcheck)
|
105
|
+
else
|
106
|
+
specialized_healthcheck
|
107
|
+
end
|
81
108
|
end
|
82
109
|
|
83
110
|
def health_check_cmd_with_cord
|
84
|
-
"(#{
|
85
|
-
end
|
86
|
-
|
87
|
-
def health_check_interval
|
88
|
-
health_check_options["interval"] || "1s"
|
111
|
+
"(#{healthcheck.cmd}) && (stat #{cord_container_file} > /dev/null || exit 1)"
|
89
112
|
end
|
90
113
|
|
91
114
|
|
@@ -103,7 +126,7 @@ class Kamal::Configuration::Role
|
|
103
126
|
|
104
127
|
|
105
128
|
def uses_cord?
|
106
|
-
running_traefik? && cord_volume &&
|
129
|
+
running_traefik? && cord_volume && healthcheck.cmd.present?
|
107
130
|
end
|
108
131
|
|
109
132
|
def cord_host_directory
|
@@ -111,7 +134,7 @@ class Kamal::Configuration::Role
|
|
111
134
|
end
|
112
135
|
|
113
136
|
def cord_volume
|
114
|
-
if (cord =
|
137
|
+
if (cord = healthcheck.cord)
|
115
138
|
@cord_volume ||= Kamal::Configuration::Volume.new \
|
116
139
|
host_path: File.join(config.run_directory, "cords", [ container_prefix, config.run_id ].join("-")),
|
117
140
|
container_path: cord
|
@@ -164,13 +187,24 @@ class Kamal::Configuration::Role
|
|
164
187
|
end
|
165
188
|
|
166
189
|
private
|
167
|
-
|
190
|
+
def tagged_hosts
|
191
|
+
{}.tap do |tagged_hosts|
|
192
|
+
extract_hosts_from_config.map do |host_config|
|
193
|
+
if host_config.is_a?(Hash)
|
194
|
+
host, tags = host_config.first
|
195
|
+
tagged_hosts[host] = Array(tags)
|
196
|
+
elsif host_config.is_a?(String)
|
197
|
+
tagged_hosts[host_config] = []
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
168
202
|
|
169
203
|
def extract_hosts_from_config
|
170
|
-
if config.servers.is_a?(Array)
|
171
|
-
config.servers
|
204
|
+
if config.raw_config.servers.is_a?(Array)
|
205
|
+
config.raw_config.servers
|
172
206
|
else
|
173
|
-
servers = config.servers[name]
|
207
|
+
servers = config.raw_config.servers[name]
|
174
208
|
servers.is_a?(Array) ? servers : Array(servers["hosts"])
|
175
209
|
end
|
176
210
|
end
|
@@ -179,6 +213,14 @@ class Kamal::Configuration::Role
|
|
179
213
|
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
180
214
|
end
|
181
215
|
|
216
|
+
def specializations
|
217
|
+
if config.raw_config.servers.is_a?(Array) || config.raw_config.servers[name].is_a?(Array)
|
218
|
+
{}
|
219
|
+
else
|
220
|
+
config.raw_config.servers[name]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
182
224
|
def traefik_labels
|
183
225
|
if running_traefik?
|
184
226
|
{
|
@@ -206,35 +248,4 @@ class Kamal::Configuration::Role
|
|
206
248
|
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
207
249
|
end
|
208
250
|
end
|
209
|
-
|
210
|
-
def specializations
|
211
|
-
if config.servers.is_a?(Array) || config.servers[name].is_a?(Array)
|
212
|
-
{}
|
213
|
-
else
|
214
|
-
config.servers[name].except("hosts")
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def specialized_env
|
219
|
-
Kamal::Configuration::Env.from_config config: specializations.fetch("env", {})
|
220
|
-
end
|
221
|
-
|
222
|
-
# Secrets are stored in an array, which won't merge by default, so have to do it by hand.
|
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")
|
227
|
-
end
|
228
|
-
|
229
|
-
def http_health_check(port:, path:)
|
230
|
-
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
231
|
-
end
|
232
|
-
|
233
|
-
def health_check_options
|
234
|
-
@health_check_options ||= begin
|
235
|
-
options = specializations["healthcheck"] || {}
|
236
|
-
options = config.healthcheck.merge(options) if running_traefik?
|
237
|
-
options
|
238
|
-
end
|
239
|
-
end
|
240
251
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Kamal::Configuration::Servers
|
2
|
+
include Kamal::Configuration::Validation
|
3
|
+
|
4
|
+
attr_reader :config, :servers_config, :roles
|
5
|
+
|
6
|
+
def initialize(config:)
|
7
|
+
@config = config
|
8
|
+
@servers_config = config.raw_config.servers
|
9
|
+
validate! servers_config, with: Kamal::Configuration::Validator::Servers
|
10
|
+
|
11
|
+
@roles = role_names.map { |role_name| Kamal::Configuration::Role.new role_name, config: config }
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def role_names
|
16
|
+
servers_config.is_a?(Array) ? [ "web" ] : servers_config.keys.sort
|
17
|
+
end
|
18
|
+
end
|
@@ -1,22 +1,27 @@
|
|
1
1
|
class Kamal::Configuration::Ssh
|
2
2
|
LOGGER = ::Logger.new(STDERR)
|
3
3
|
|
4
|
+
include Kamal::Configuration::Validation
|
5
|
+
|
6
|
+
attr_reader :ssh_config
|
7
|
+
|
4
8
|
def initialize(config:)
|
5
|
-
@
|
9
|
+
@ssh_config = config.raw_config.ssh || {}
|
10
|
+
validate! ssh_config
|
6
11
|
end
|
7
12
|
|
8
13
|
def user
|
9
|
-
|
14
|
+
ssh_config.fetch("user", "root")
|
10
15
|
end
|
11
16
|
|
12
17
|
def port
|
13
|
-
|
18
|
+
ssh_config.fetch("port", 22)
|
14
19
|
end
|
15
20
|
|
16
21
|
def proxy
|
17
|
-
if (proxy =
|
22
|
+
if (proxy = ssh_config["proxy"])
|
18
23
|
Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
|
19
|
-
elsif (proxy_command =
|
24
|
+
elsif (proxy_command = ssh_config["proxy_command"])
|
20
25
|
Net::SSH::Proxy::Command.new(proxy_command)
|
21
26
|
end
|
22
27
|
end
|
@@ -30,13 +35,11 @@ class Kamal::Configuration::Ssh
|
|
30
35
|
end
|
31
36
|
|
32
37
|
private
|
33
|
-
attr_accessor :config
|
34
|
-
|
35
38
|
def logger
|
36
39
|
LOGGER.tap { |logger| logger.level = log_level }
|
37
40
|
end
|
38
41
|
|
39
42
|
def log_level
|
40
|
-
|
43
|
+
ssh_config.fetch("log_level", :fatal)
|
41
44
|
end
|
42
45
|
end
|
@@ -1,20 +1,22 @@
|
|
1
1
|
class Kamal::Configuration::Sshkit
|
2
|
+
include Kamal::Configuration::Validation
|
3
|
+
|
4
|
+
attr_reader :sshkit_config
|
5
|
+
|
2
6
|
def initialize(config:)
|
3
|
-
@
|
7
|
+
@sshkit_config = config.raw_config.sshkit || {}
|
8
|
+
validate! sshkit_config
|
4
9
|
end
|
5
10
|
|
6
11
|
def max_concurrent_starts
|
7
|
-
|
12
|
+
sshkit_config.fetch("max_concurrent_starts", 30)
|
8
13
|
end
|
9
14
|
|
10
15
|
def pool_idle_timeout
|
11
|
-
|
16
|
+
sshkit_config.fetch("pool_idle_timeout", 900)
|
12
17
|
end
|
13
18
|
|
14
19
|
def to_h
|
15
|
-
|
20
|
+
sshkit_config
|
16
21
|
end
|
17
|
-
|
18
|
-
private
|
19
|
-
attr_accessor :options
|
20
22
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Kamal::Configuration::Traefik
|
2
|
+
DEFAULT_IMAGE = "traefik:v2.10"
|
3
|
+
CONTAINER_PORT = 80
|
4
|
+
DEFAULT_ARGS = {
|
5
|
+
"log.level" => "DEBUG"
|
6
|
+
}
|
7
|
+
DEFAULT_LABELS = {
|
8
|
+
# These ensure we serve a 502 rather than a 404 if no containers are available
|
9
|
+
"traefik.http.routers.catchall.entryPoints" => "http",
|
10
|
+
"traefik.http.routers.catchall.rule" => "PathPrefix(`/`)",
|
11
|
+
"traefik.http.routers.catchall.service" => "unavailable",
|
12
|
+
"traefik.http.routers.catchall.priority" => 1,
|
13
|
+
"traefik.http.services.unavailable.loadbalancer.server.port" => "0"
|
14
|
+
}
|
15
|
+
|
16
|
+
include Kamal::Configuration::Validation
|
17
|
+
|
18
|
+
attr_reader :config, :traefik_config
|
19
|
+
|
20
|
+
def initialize(config:)
|
21
|
+
@config = config
|
22
|
+
@traefik_config = config.raw_config.traefik || {}
|
23
|
+
validate! traefik_config
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish?
|
27
|
+
traefik_config["publish"] != false
|
28
|
+
end
|
29
|
+
|
30
|
+
def labels
|
31
|
+
DEFAULT_LABELS.merge(traefik_config["labels"] || {})
|
32
|
+
end
|
33
|
+
|
34
|
+
def env
|
35
|
+
Kamal::Configuration::Env.new \
|
36
|
+
config: traefik_config.fetch("env", {}),
|
37
|
+
secrets_file: File.join(config.host_env_directory, "traefik", "traefik.env"),
|
38
|
+
context: "traefik/env"
|
39
|
+
end
|
40
|
+
|
41
|
+
def host_port
|
42
|
+
traefik_config.fetch("host_port", CONTAINER_PORT)
|
43
|
+
end
|
44
|
+
|
45
|
+
def options
|
46
|
+
traefik_config.fetch("options", {})
|
47
|
+
end
|
48
|
+
|
49
|
+
def port
|
50
|
+
"#{host_port}:#{CONTAINER_PORT}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def args
|
54
|
+
DEFAULT_ARGS.merge(traefik_config.fetch("args", {}))
|
55
|
+
end
|
56
|
+
|
57
|
+
def image
|
58
|
+
traefik_config.fetch("image", DEFAULT_IMAGE)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "active_support/inflector"
|
3
|
+
|
4
|
+
module Kamal::Configuration::Validation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def validation_doc
|
9
|
+
@validation_doc ||= File.read(File.join(File.dirname(__FILE__), "docs", "#{validation_config_key}.yml"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def validation_config_key
|
13
|
+
@validation_config_key ||= name.demodulize.underscore
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate!(config, example: nil, context: nil, with: Kamal::Configuration::Validator)
|
18
|
+
context ||= self.class.validation_config_key
|
19
|
+
example ||= validation_yml[self.class.validation_config_key]
|
20
|
+
|
21
|
+
with.new(config, example: example, context: context).validate!
|
22
|
+
end
|
23
|
+
|
24
|
+
def validation_yml
|
25
|
+
@validation_yml ||= YAML.load(self.class.validation_doc)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
|
2
|
+
def validate!
|
3
|
+
super
|
4
|
+
|
5
|
+
if config["cache"] && config["cache"]["type"]
|
6
|
+
error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Env < Kamal::Configuration::Validator
|
2
|
+
SPECIAL_KEYS = [ "clear", "secret", "tags" ]
|
3
|
+
|
4
|
+
def validate!
|
5
|
+
if known_keys.any?
|
6
|
+
validate_complex_env!
|
7
|
+
else
|
8
|
+
validate_simple_env!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def validate_simple_env!
|
14
|
+
validate_hash_of!(config, String)
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_complex_env!
|
18
|
+
unknown_keys_error unknown_keys if unknown_keys.any?
|
19
|
+
|
20
|
+
with_context("clear") { validate_hash_of!(config["clear"], String) } if config.key?("clear")
|
21
|
+
with_context("secret") { validate_array_of!(config["secret"], String) } if config.key?("secret")
|
22
|
+
validate_tags! if config.key?("tags")
|
23
|
+
end
|
24
|
+
|
25
|
+
def known_keys
|
26
|
+
@known_keys ||= config.keys & SPECIAL_KEYS
|
27
|
+
end
|
28
|
+
|
29
|
+
def unknown_keys
|
30
|
+
@unknown_keys ||= config.keys - SPECIAL_KEYS
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_tags!
|
34
|
+
if context == "env"
|
35
|
+
with_context("tags") do
|
36
|
+
validate_type! config["tags"], Hash
|
37
|
+
|
38
|
+
config["tags"].each do |tag, value|
|
39
|
+
with_context(tag) do
|
40
|
+
validate_type! value, Hash
|
41
|
+
|
42
|
+
Kamal::Configuration::Validator::Env.new(
|
43
|
+
value,
|
44
|
+
example: example["tags"].values[1],
|
45
|
+
context: context
|
46
|
+
).validate!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
error "tags are only allowed in the root env"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validator
|
2
|
+
STRING_OR_ONE_ITEM_ARRAY_KEYS = [ "username", "password" ]
|
3
|
+
|
4
|
+
def validate!
|
5
|
+
validate_against_example! \
|
6
|
+
config.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS),
|
7
|
+
example.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS)
|
8
|
+
|
9
|
+
validate_string_or_one_item_array! "username"
|
10
|
+
validate_string_or_one_item_array! "password"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def validate_string_or_one_item_array!(key)
|
15
|
+
with_context(key) do
|
16
|
+
value = config[key]
|
17
|
+
|
18
|
+
error "is required" unless value.present?
|
19
|
+
|
20
|
+
unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
|
21
|
+
error "should be a string or an array with one string (for secret lookup)"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|