kamal 1.6.0 → 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 +5 -3
- data/lib/kamal/cli/app.rb +6 -3
- data/lib/kamal/cli/build.rb +13 -10
- data/lib/kamal/cli/healthcheck/poller.rb +2 -2
- data/lib/kamal/cli/main.rb +14 -2
- data/lib/kamal/cli/registry.rb +9 -10
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
- data/lib/kamal/cli/traefik.rb +5 -3
- data/lib/kamal/cli.rb +1 -1
- data/lib/kamal/commands/accessory.rb +4 -4
- data/lib/kamal/commands/app/logging.rb +4 -4
- data/lib/kamal/commands/builder/base.rb +13 -0
- data/lib/kamal/commands/builder/multiarch/remote.rb +10 -0
- data/lib/kamal/commands/builder/multiarch.rb +4 -0
- data/lib/kamal/commands/builder/native/cached.rb +10 -1
- data/lib/kamal/commands/builder/native/remote.rb +8 -0
- data/lib/kamal/commands/builder.rb +17 -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 +33 -33
- 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 +1 -1
- 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 +53 -65
- 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 +41 -66
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +2 -0
- metadata +49 -3
@@ -0,0 +1,140 @@
|
|
1
|
+
class Kamal::Configuration::Validator
|
2
|
+
attr_reader :config, :example, :context
|
3
|
+
|
4
|
+
def initialize(config, example:, context:)
|
5
|
+
@config = config
|
6
|
+
@example = example
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
validate_against_example! config, example
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def validate_against_example!(validation_config, example)
|
16
|
+
validate_type! validation_config, Hash
|
17
|
+
|
18
|
+
if (unknown_keys = validation_config.keys - example.keys).any?
|
19
|
+
unknown_keys_error unknown_keys
|
20
|
+
end
|
21
|
+
|
22
|
+
validation_config.each do |key, value|
|
23
|
+
with_context(key) do
|
24
|
+
example_value = example[key]
|
25
|
+
|
26
|
+
if example_value == "..."
|
27
|
+
validate_type! value, *(Array if key == :servers), Hash
|
28
|
+
elsif key == "hosts"
|
29
|
+
validate_servers! value
|
30
|
+
elsif example_value.is_a?(Array)
|
31
|
+
validate_array_of! value, example_value.first.class
|
32
|
+
elsif example_value.is_a?(Hash)
|
33
|
+
case key.to_s
|
34
|
+
when "options"
|
35
|
+
validate_type! value, Hash
|
36
|
+
when "args", "labels"
|
37
|
+
validate_hash_of! value, example_value.first[1].class
|
38
|
+
else
|
39
|
+
validate_against_example! value, example_value
|
40
|
+
end
|
41
|
+
else
|
42
|
+
validate_type! value, example_value.class
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def valid_type?(value, type)
|
50
|
+
value.is_a?(type) ||
|
51
|
+
(type == String && stringish?(value)) ||
|
52
|
+
(boolean?(type) && boolean?(value.class))
|
53
|
+
end
|
54
|
+
|
55
|
+
def type_description(type)
|
56
|
+
if type == Integer || type == Array
|
57
|
+
"an #{type.name.downcase}"
|
58
|
+
elsif type == TrueClass || type == FalseClass
|
59
|
+
"a boolean"
|
60
|
+
else
|
61
|
+
"a #{type.name.downcase}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def boolean?(type)
|
66
|
+
type == TrueClass || type == FalseClass
|
67
|
+
end
|
68
|
+
|
69
|
+
def stringish?(value)
|
70
|
+
value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_array_of!(array, type)
|
74
|
+
validate_type! array, Array
|
75
|
+
|
76
|
+
array.each_with_index do |value, index|
|
77
|
+
with_context(index) do
|
78
|
+
validate_type! value, type
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate_hash_of!(hash, type)
|
84
|
+
validate_type! hash, Hash
|
85
|
+
|
86
|
+
hash.each do |key, value|
|
87
|
+
with_context(key) do
|
88
|
+
validate_type! value, type
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_servers!(servers)
|
94
|
+
validate_type! servers, Array
|
95
|
+
|
96
|
+
servers.each_with_index do |server, index|
|
97
|
+
with_context(index) do
|
98
|
+
validate_type! server, String, Hash
|
99
|
+
|
100
|
+
if server.is_a?(Hash)
|
101
|
+
error "multiple hosts found" unless server.size == 1
|
102
|
+
host, tags = server.first
|
103
|
+
|
104
|
+
with_context(host) do
|
105
|
+
validate_type! tags, String, Array
|
106
|
+
validate_array_of! tags, String if tags.is_a?(Array)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate_type!(value, *types)
|
114
|
+
type_error(*types) unless types.any? { |type| valid_type?(value, type) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def error(message)
|
118
|
+
raise Kamal::ConfigurationError, "#{error_context}#{message}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def type_error(*expected_types)
|
122
|
+
error "should be #{expected_types.map { |type| type_description(type) }.join(" or ")}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def unknown_keys_error(unknown_keys)
|
126
|
+
error "unknown #{"key".pluralize(unknown_keys.count)}: #{unknown_keys.join(", ")}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def error_context
|
130
|
+
"#{context}: " if context.present?
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_context(context)
|
134
|
+
old_context = @context
|
135
|
+
@context = [ @context, context ].select(&:present?).join("/")
|
136
|
+
yield
|
137
|
+
ensure
|
138
|
+
@context = old_context
|
139
|
+
end
|
140
|
+
end
|
data/lib/kamal/configuration.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
require "active_support/ordered_options"
|
2
2
|
require "active_support/core_ext/string/inquiry"
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
4
5
|
require "pathname"
|
5
6
|
require "erb"
|
6
7
|
require "net/ssh/proxy/jump"
|
7
8
|
|
8
9
|
class Kamal::Configuration
|
9
|
-
delegate :service, :image, :
|
10
|
+
delegate :service, :image, :labels, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
10
11
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
11
12
|
|
12
13
|
attr_reader :destination, :raw_config
|
14
|
+
attr_reader :accessories, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
|
15
|
+
|
16
|
+
include Validation
|
13
17
|
|
14
18
|
class << self
|
15
19
|
def create_from(config_file:, destination: nil, version: nil)
|
@@ -42,7 +46,29 @@ class Kamal::Configuration
|
|
42
46
|
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
|
43
47
|
@destination = destination
|
44
48
|
@declared_version = version
|
45
|
-
|
49
|
+
|
50
|
+
validate! raw_config, example: validation_yml.symbolize_keys, context: ""
|
51
|
+
|
52
|
+
# Eager load config to validate it, these are first as they have dependencies later on
|
53
|
+
@servers = Servers.new(config: self)
|
54
|
+
@registry = Registry.new(config: self)
|
55
|
+
|
56
|
+
@accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
|
57
|
+
@boot = Boot.new(config: self)
|
58
|
+
@builder = Builder.new(config: self)
|
59
|
+
@env = Env.new(config: @raw_config.env || {})
|
60
|
+
|
61
|
+
@healthcheck = Healthcheck.new(healthcheck_config: @raw_config.healthcheck)
|
62
|
+
@logging = Logging.new(logging_config: @raw_config.logging)
|
63
|
+
@traefik = Traefik.new(config: self)
|
64
|
+
@ssh = Ssh.new(config: self)
|
65
|
+
@sshkit = Sshkit.new(config: self)
|
66
|
+
|
67
|
+
ensure_destination_if_required
|
68
|
+
ensure_required_keys_present
|
69
|
+
ensure_valid_kamal_version
|
70
|
+
ensure_retain_containers_valid
|
71
|
+
ensure_valid_service_name
|
46
72
|
end
|
47
73
|
|
48
74
|
|
@@ -71,17 +97,13 @@ class Kamal::Configuration
|
|
71
97
|
|
72
98
|
|
73
99
|
def roles
|
74
|
-
|
100
|
+
servers.roles
|
75
101
|
end
|
76
102
|
|
77
103
|
def role(name)
|
78
104
|
roles.detect { |r| r.name == name.to_s }
|
79
105
|
end
|
80
106
|
|
81
|
-
def accessories
|
82
|
-
@accessories ||= raw_config.accessories&.keys&.collect { |name| Kamal::Configuration::Accessory.new(name, config: self) } || []
|
83
|
-
end
|
84
|
-
|
85
107
|
def accessory(name)
|
86
108
|
accessories.detect { |a| a.name == name.to_s }
|
87
109
|
end
|
@@ -120,7 +142,7 @@ class Kamal::Configuration
|
|
120
142
|
end
|
121
143
|
|
122
144
|
def repository
|
123
|
-
[
|
145
|
+
[ registry.server, image ].compact.join("/")
|
124
146
|
end
|
125
147
|
|
126
148
|
def absolute_image
|
@@ -157,40 +179,10 @@ class Kamal::Configuration
|
|
157
179
|
end
|
158
180
|
|
159
181
|
def logging_args
|
160
|
-
|
161
|
-
optionize({ "log-driver" => logging["driver"] }.compact) +
|
162
|
-
argumentize("--log-opt", logging["options"])
|
163
|
-
else
|
164
|
-
argumentize("--log-opt", { "max-size" => "10m" })
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
|
169
|
-
def boot
|
170
|
-
Kamal::Configuration::Boot.new(config: self)
|
171
|
-
end
|
172
|
-
|
173
|
-
def builder
|
174
|
-
Kamal::Configuration::Builder.new(config: self)
|
175
|
-
end
|
176
|
-
|
177
|
-
def traefik
|
178
|
-
raw_config.traefik || {}
|
179
|
-
end
|
180
|
-
|
181
|
-
def ssh
|
182
|
-
Kamal::Configuration::Ssh.new(config: self)
|
183
|
-
end
|
184
|
-
|
185
|
-
def sshkit
|
186
|
-
Kamal::Configuration::Sshkit.new(config: self)
|
182
|
+
logging.args
|
187
183
|
end
|
188
184
|
|
189
185
|
|
190
|
-
def healthcheck
|
191
|
-
{ "path" => "/up", "port" => 3000, "max_attempts" => 7, "cord" => "/tmp/kamal-cord", "log_lines" => 50 }.merge(raw_config.healthcheck || {})
|
192
|
-
end
|
193
|
-
|
194
186
|
def healthcheck_service
|
195
187
|
[ "healthcheck", service, destination ].compact.join("-")
|
196
188
|
end
|
@@ -229,13 +221,9 @@ class Kamal::Configuration
|
|
229
221
|
File.join(run_directory, "env")
|
230
222
|
end
|
231
223
|
|
232
|
-
def env
|
233
|
-
raw_config.env || {}
|
234
|
-
end
|
235
|
-
|
236
224
|
def env_tags
|
237
225
|
@env_tags ||= if (tags = raw_config.env["tags"])
|
238
|
-
tags.collect { |name, config|
|
226
|
+
tags.collect { |name, config| Env::Tag.new(name, config: config) }
|
239
227
|
else
|
240
228
|
[]
|
241
229
|
end
|
@@ -246,10 +234,6 @@ class Kamal::Configuration
|
|
246
234
|
end
|
247
235
|
|
248
236
|
|
249
|
-
def valid?
|
250
|
-
ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version && ensure_retain_containers_valid && ensure_valid_service_name
|
251
|
-
end
|
252
|
-
|
253
237
|
def to_h
|
254
238
|
{
|
255
239
|
roles: role_names,
|
@@ -265,11 +249,10 @@ class Kamal::Configuration
|
|
265
249
|
builder: builder.to_h,
|
266
250
|
accessories: raw_config.accessories,
|
267
251
|
logging: logging_args,
|
268
|
-
healthcheck: healthcheck
|
252
|
+
healthcheck: healthcheck.to_h
|
269
253
|
}.compact
|
270
254
|
end
|
271
255
|
|
272
|
-
|
273
256
|
private
|
274
257
|
# Will raise ArgumentError if any required config keys are missing
|
275
258
|
def ensure_destination_if_required
|
@@ -282,29 +265,21 @@ class Kamal::Configuration
|
|
282
265
|
|
283
266
|
def ensure_required_keys_present
|
284
267
|
%i[ service image registry servers ].each do |key|
|
285
|
-
raise
|
286
|
-
end
|
287
|
-
|
288
|
-
if raw_config.registry["username"].blank?
|
289
|
-
raise ArgumentError, "You must specify a username for the registry in config/deploy.yml"
|
290
|
-
end
|
291
|
-
|
292
|
-
if raw_config.registry["password"].blank?
|
293
|
-
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
268
|
+
raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
294
269
|
end
|
295
270
|
|
296
|
-
unless
|
297
|
-
raise
|
271
|
+
unless role(primary_role_name).present?
|
272
|
+
raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
|
298
273
|
end
|
299
274
|
|
300
275
|
if primary_role.hosts.empty?
|
301
|
-
raise
|
276
|
+
raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
|
302
277
|
end
|
303
278
|
|
304
279
|
unless allow_empty_roles?
|
305
280
|
roles.each do |role|
|
306
281
|
if role.hosts.empty?
|
307
|
-
raise
|
282
|
+
raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
308
283
|
end
|
309
284
|
end
|
310
285
|
end
|
@@ -313,21 +288,21 @@ class Kamal::Configuration
|
|
313
288
|
end
|
314
289
|
|
315
290
|
def ensure_valid_service_name
|
316
|
-
raise
|
291
|
+
raise Kamal::ConfigurationError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
|
317
292
|
|
318
293
|
true
|
319
294
|
end
|
320
295
|
|
321
296
|
def ensure_valid_kamal_version
|
322
297
|
if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
|
323
|
-
raise
|
298
|
+
raise Kamal::ConfigurationError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
|
324
299
|
end
|
325
300
|
|
326
301
|
true
|
327
302
|
end
|
328
303
|
|
329
304
|
def ensure_retain_containers_valid
|
330
|
-
raise
|
305
|
+
raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1
|
331
306
|
|
332
307
|
true
|
333
308
|
end
|
data/lib/kamal/version.rb
CHANGED
data/lib/kamal.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kamal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-06-
|
11
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -114,6 +114,26 @@ dependencies:
|
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '1.2'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: x25519
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1.0'
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 1.0.10
|
127
|
+
type: :runtime
|
128
|
+
prerelease: false
|
129
|
+
version_requirements: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - "~>"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '1.0'
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 1.0.10
|
117
137
|
- !ruby/object:Gem::Dependency
|
118
138
|
name: bcrypt_pbkdf
|
119
139
|
requirement: !ruby/object:Gem::Requirement
|
@@ -268,11 +288,37 @@ files:
|
|
268
288
|
- lib/kamal/configuration/accessory.rb
|
269
289
|
- lib/kamal/configuration/boot.rb
|
270
290
|
- lib/kamal/configuration/builder.rb
|
291
|
+
- lib/kamal/configuration/docs/accessory.yml
|
292
|
+
- lib/kamal/configuration/docs/boot.yml
|
293
|
+
- lib/kamal/configuration/docs/builder.yml
|
294
|
+
- lib/kamal/configuration/docs/configuration.yml
|
295
|
+
- lib/kamal/configuration/docs/env.yml
|
296
|
+
- lib/kamal/configuration/docs/healthcheck.yml
|
297
|
+
- lib/kamal/configuration/docs/logging.yml
|
298
|
+
- lib/kamal/configuration/docs/registry.yml
|
299
|
+
- lib/kamal/configuration/docs/role.yml
|
300
|
+
- lib/kamal/configuration/docs/servers.yml
|
301
|
+
- lib/kamal/configuration/docs/ssh.yml
|
302
|
+
- lib/kamal/configuration/docs/sshkit.yml
|
303
|
+
- lib/kamal/configuration/docs/traefik.yml
|
271
304
|
- lib/kamal/configuration/env.rb
|
272
305
|
- lib/kamal/configuration/env/tag.rb
|
306
|
+
- lib/kamal/configuration/healthcheck.rb
|
307
|
+
- lib/kamal/configuration/logging.rb
|
308
|
+
- lib/kamal/configuration/registry.rb
|
273
309
|
- lib/kamal/configuration/role.rb
|
310
|
+
- lib/kamal/configuration/servers.rb
|
274
311
|
- lib/kamal/configuration/ssh.rb
|
275
312
|
- lib/kamal/configuration/sshkit.rb
|
313
|
+
- lib/kamal/configuration/traefik.rb
|
314
|
+
- lib/kamal/configuration/validation.rb
|
315
|
+
- lib/kamal/configuration/validator.rb
|
316
|
+
- lib/kamal/configuration/validator/accessory.rb
|
317
|
+
- lib/kamal/configuration/validator/builder.rb
|
318
|
+
- lib/kamal/configuration/validator/env.rb
|
319
|
+
- lib/kamal/configuration/validator/registry.rb
|
320
|
+
- lib/kamal/configuration/validator/role.rb
|
321
|
+
- lib/kamal/configuration/validator/servers.rb
|
276
322
|
- lib/kamal/configuration/volume.rb
|
277
323
|
- lib/kamal/env_file.rb
|
278
324
|
- lib/kamal/git.rb
|
@@ -300,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
300
346
|
- !ruby/object:Gem::Version
|
301
347
|
version: '0'
|
302
348
|
requirements: []
|
303
|
-
rubygems_version: 3.5.
|
349
|
+
rubygems_version: 3.5.11
|
304
350
|
signing_key:
|
305
351
|
specification_version: 4
|
306
352
|
summary: Deploy web apps in containers to servers running Docker with zero downtime.
|