Configuration validation

Validate the Kamal configuration giving useful warning on errors.
Each section of the configuration has its own config class and a YAML
file containing documented example configuration.

You can run `kamal docs` to see the example configuration, and
`kamal docs <section>` to see the example configuration for a specific
section.

The validation matches the configuration to the example configuration
checking that there are no unknown keys and that the values are of
matching types.

Where there is more complex validation - e.g for envs and servers, we
have custom validators that implement those rules.

Additonally the configuration examples are used to generate the
configuration documentation in the kamal-site repo.

You generate them by running:

```
bundle exec bin/docs <kamal-site-checkout>
```
This commit is contained in:
Donal McBreen
2024-05-28 09:25:42 +01:00
parent 6e60ab918a
commit 4f317b8499
59 changed files with 1942 additions and 480 deletions

View File

@@ -28,7 +28,7 @@ class ConfigurationTest < ActiveSupport::TestCase
%i[ service image registry ].each do |key|
test "#{key} config required" do
assert_raise(ArgumentError) do
assert_raise(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.tap { _1.delete key }
end
end
@@ -36,19 +36,21 @@ class ConfigurationTest < ActiveSupport::TestCase
%w[ username password ].each do |key|
test "registry #{key} required" do
assert_raise(ArgumentError) do
assert_raise(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.tap { _1[:registry].delete key }
end
end
end
test "service name valid" do
assert Kamal::Configuration.new(@deploy.tap { _1[:service] = "hey-app1_primary" }).valid?
assert Kamal::Configuration.new(@deploy.tap { _1[:service] = "MyApp" }).valid?
assert_nothing_raised do
Kamal::Configuration.new(@deploy.tap { _1[:service] = "hey-app1_primary" })
Kamal::Configuration.new(@deploy.tap { _1[:service] = "MyApp" })
end
end
test "service name invalid" do
assert_raise(ArgumentError) do
assert_raise(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.tap { _1[:service] = "app.com" }
end
end
@@ -158,39 +160,34 @@ class ConfigurationTest < ActiveSupport::TestCase
assert_equal "healthcheck-app", @config.healthcheck_service
end
test "valid config" do
assert @config.valid?
assert @config_with_roles.valid?
end
test "hosts required for all roles" do
# Empty server list for implied web role
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: [])
end
# Empty server list
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => [] })
end
# Missing hosts key
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => {} })
end
# Empty hosts list
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => { "hosts" => [] } })
end
# Nil hosts
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => { "hosts" => nil } })
end
# One role with hosts, one without
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => %w[ web ], "workers" => { "hosts" => %w[ ] } })
end
end
@@ -200,7 +197,7 @@ class ConfigurationTest < ActiveSupport::TestCase
Kamal::Configuration.new @deploy.merge(servers: { "web" => %w[ web ], "workers" => { "hosts" => %w[ ] } }, allow_empty_roles: true)
end
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new @deploy.merge(servers: { "web" => %w[], "workers" => { "hosts" => %w[] } }, allow_empty_roles: true)
end
end
@@ -215,17 +212,17 @@ class ConfigurationTest < ActiveSupport::TestCase
test "logging args with configured options" do
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(logging: { "options" => { "max-size" => "100m", "max-file" => 5 } }) })
assert_equal [ "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], @config.logging_args
assert_equal [ "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], config.logging_args
end
test "logging args with configured driver and options" do
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(logging: { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => 5 } }) })
assert_equal [ "--log-driver", "\"local\"", "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], @config.logging_args
assert_equal [ "--log-driver", "\"local\"", "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], config.logging_args
end
test "erb evaluation of yml config" do
config = Kamal::Configuration.create_from config_file: Pathname.new(File.expand_path("fixtures/deploy.erb.yml", __dir__))
assert_equal "my-user", config.registry["username"]
assert_equal "my-user", config.registry.username
end
test "destination yml config merge" do
@@ -249,7 +246,7 @@ class ConfigurationTest < ActiveSupport::TestCase
test "destination required" do
dest_config_file = Pathname.new(File.expand_path("fixtures/deploy_for_required_dest.yml", __dir__))
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
config = Kamal::Configuration.create_from config_file: dest_config_file
end
@@ -272,7 +269,7 @@ class ConfigurationTest < ActiveSupport::TestCase
volume_args: [ "--volume", "/local/path:/container/path" ],
builder: {},
logging: [ "--log-opt", "max-size=\"10m\"" ],
healthcheck: { "path"=>"/up", "port"=>3000, "max_attempts" => 7, "cord" => "/tmp/kamal-cord", "log_lines" => 50 } }
healthcheck: { "cmd"=>"curl -f http://localhost:3000/up || exit 1", "interval" => "1s", "path"=>"/up", "port"=>3000, "max_attempts" => 7, "cord" => "/tmp/kamal-cord", "log_lines" => 50 } }
assert_equal expected_config, @config.to_h
end
@@ -288,7 +285,7 @@ class ConfigurationTest < ActiveSupport::TestCase
end
test "min version is higher" do
assert_raises(ArgumentError) do
assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new(@deploy.tap { |c| c.merge!(minimum_version: "10000.0.0") })
end
end
@@ -334,7 +331,7 @@ class ConfigurationTest < ActiveSupport::TestCase
end
test "primary role missing" do
error = assert_raises(ArgumentError) do
error = assert_raises(Kamal::ConfigurationError) do
Kamal::Configuration.new(@deploy.merge(primary_role: "bar"))
end
assert_match /bar isn't defined/, error.message
@@ -345,6 +342,6 @@ class ConfigurationTest < ActiveSupport::TestCase
config = Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 2))
assert_equal 2, config.retain_containers
assert_raises(ArgumentError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) }
assert_raises(Kamal::ConfigurationError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) }
end
end