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

@@ -1,30 +1,39 @@
class Kamal::Configuration::Accessory
include Kamal::Configuration::Validation
delegate :argumentize, :optionize, to: Kamal::Utils
attr_accessor :name, :specifics
attr_reader :name, :accessory_config, :env
def initialize(name, config:)
@name, @config, @specifics = name.inquiry, config, config.raw_config["accessories"][name]
@name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
validate! \
accessory_config,
example: validation_yml["accessories"]["mysql"],
context: "accessories/#{name}",
with: Kamal::Configuration::Validator::Accessory
@env = Kamal::Configuration::Env.new \
config: accessory_config.fetch("env", {}),
secrets_file: File.join(config.host_env_directory, "accessories", "#{service_name}.env"),
context: "accessories/#{name}/env"
end
def service_name
specifics["service"] || "#{config.service}-#{name}"
accessory_config["service"] || "#{config.service}-#{name}"
end
def image
specifics["image"]
accessory_config["image"]
end
def hosts
if (specifics.keys & [ "host", "hosts", "roles" ]).size != 1
raise ArgumentError, "Specify one of `host`, `hosts` or `roles` for accessory `#{name}`"
end
hosts_from_host || hosts_from_hosts || hosts_from_roles
end
def port
if port = specifics["port"]&.to_s
if port = accessory_config["port"]&.to_s
port.include?(":") ? port : "#{port}:#{port}"
end
end
@@ -34,32 +43,26 @@ class Kamal::Configuration::Accessory
end
def labels
default_labels.merge(specifics["labels"] || {})
default_labels.merge(accessory_config["labels"] || {})
end
def label_args
argumentize "--label", labels
end
def env
Kamal::Configuration::Env.from_config \
config: specifics.fetch("env", {}),
secrets_file: File.join(config.host_env_directory, "accessories", "#{service_name}.env")
end
def env_args
env.args
end
def files
specifics["files"]&.to_h do |local_to_remote_mapping|
accessory_config["files"]&.to_h do |local_to_remote_mapping|
local_file, remote_file = local_to_remote_mapping.split(":")
[ expand_local_file(local_file), expand_remote_file(remote_file) ]
end || {}
end
def directories
specifics["directories"]&.to_h do |host_to_container_mapping|
accessory_config["directories"]&.to_h do |host_to_container_mapping|
host_path, container_path = host_to_container_mapping.split(":")
[ expand_host_path(host_path), container_path ]
end || {}
@@ -74,7 +77,7 @@ class Kamal::Configuration::Accessory
end
def option_args
if args = specifics["options"]
if args = accessory_config["options"]
optionize args
else
[]
@@ -82,7 +85,7 @@ class Kamal::Configuration::Accessory
end
def cmd
specifics["cmd"]
accessory_config["cmd"]
end
private
@@ -116,18 +119,18 @@ class Kamal::Configuration::Accessory
end
def specific_volumes
specifics["volumes"] || []
accessory_config["volumes"] || []
end
def remote_files_as_volumes
specifics["files"]&.collect do |local_to_remote_mapping|
accessory_config["files"]&.collect do |local_to_remote_mapping|
_, remote_file = local_to_remote_mapping.split(":")
"#{service_data_directory + remote_file}:#{remote_file}"
end || []
end
def remote_directories_as_volumes
specifics["directories"]&.collect do |host_to_container_mapping|
accessory_config["directories"]&.collect do |host_to_container_mapping|
host_path, container_path = host_to_container_mapping.split(":")
[ expand_host_path(host_path), container_path ].join(":")
end || []
@@ -146,30 +149,16 @@ class Kamal::Configuration::Accessory
end
def hosts_from_host
if specifics.key?("host")
host = specifics["host"]
if host
[ host ]
else
raise ArgumentError, "Missing host for accessory `#{name}`"
end
end
[ accessory_config["host"] ] if accessory_config.key?("host")
end
def hosts_from_hosts
if specifics.key?("hosts")
hosts = specifics["hosts"]
if hosts.is_a?(Array)
hosts
else
raise ArgumentError, "Hosts should be an Array for accessory `#{name}`"
end
end
accessory_config["hosts"] if accessory_config.key?("hosts")
end
def hosts_from_roles
if specifics.key?("roles")
specifics["roles"].flat_map { |role| config.role(role).hosts }
if accessory_config.key?("roles")
accessory_config["roles"].flat_map { |role| config.role(role).hosts }
end
end
end