kamal 1.5.2 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|