Rename to Kamal
This commit is contained in:
169
lib/kamal/configuration/accessory.rb
Normal file
169
lib/kamal/configuration/accessory.rb
Normal file
@@ -0,0 +1,169 @@
|
||||
class Kamal::Configuration::Accessory
|
||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
||||
|
||||
attr_accessor :name, :specifics
|
||||
|
||||
def initialize(name, config:)
|
||||
@name, @config, @specifics = name.inquiry, config, config.raw_config["accessories"][name]
|
||||
end
|
||||
|
||||
def service_name
|
||||
"#{config.service}-#{name}"
|
||||
end
|
||||
|
||||
def image
|
||||
specifics["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
|
||||
port.include?(":") ? port : "#{port}:#{port}"
|
||||
end
|
||||
end
|
||||
|
||||
def publish_args
|
||||
argumentize "--publish", port if port
|
||||
end
|
||||
|
||||
def labels
|
||||
default_labels.merge(specifics["labels"] || {})
|
||||
end
|
||||
|
||||
def label_args
|
||||
argumentize "--label", labels
|
||||
end
|
||||
|
||||
def env
|
||||
specifics["env"] || {}
|
||||
end
|
||||
|
||||
def env_args
|
||||
argumentize_env_with_secrets env
|
||||
end
|
||||
|
||||
def files
|
||||
specifics["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|
|
||||
host_relative_path, container_path = host_to_container_mapping.split(":")
|
||||
[ expand_host_path(host_relative_path), container_path ]
|
||||
end || {}
|
||||
end
|
||||
|
||||
def volumes
|
||||
specific_volumes + remote_files_as_volumes + remote_directories_as_volumes
|
||||
end
|
||||
|
||||
def volume_args
|
||||
argumentize "--volume", volumes
|
||||
end
|
||||
|
||||
def option_args
|
||||
if args = specifics["options"]
|
||||
optionize args
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def cmd
|
||||
specifics["cmd"]
|
||||
end
|
||||
|
||||
private
|
||||
attr_accessor :config
|
||||
|
||||
def default_labels
|
||||
{ "service" => service_name }
|
||||
end
|
||||
|
||||
def expand_local_file(local_file)
|
||||
if local_file.end_with?("erb")
|
||||
with_clear_env_loaded { read_dynamic_file(local_file) }
|
||||
else
|
||||
Pathname.new(File.expand_path(local_file)).to_s
|
||||
end
|
||||
end
|
||||
|
||||
def with_clear_env_loaded
|
||||
(env["clear"] || env).each { |k, v| ENV[k] = v }
|
||||
yield
|
||||
ensure
|
||||
(env["clear"] || env).each { |k, v| ENV.delete(k) }
|
||||
end
|
||||
|
||||
def read_dynamic_file(local_file)
|
||||
StringIO.new(ERB.new(IO.read(local_file)).result)
|
||||
end
|
||||
|
||||
def expand_remote_file(remote_file)
|
||||
service_name + remote_file
|
||||
end
|
||||
|
||||
def specific_volumes
|
||||
specifics["volumes"] || []
|
||||
end
|
||||
|
||||
def remote_files_as_volumes
|
||||
specifics["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|
|
||||
host_relative_path, container_path = host_to_container_mapping.split(":")
|
||||
[ expand_host_path(host_relative_path), container_path ].join(":")
|
||||
end || []
|
||||
end
|
||||
|
||||
def expand_host_path(host_relative_path)
|
||||
"#{service_data_directory}/#{host_relative_path}"
|
||||
end
|
||||
|
||||
def service_data_directory
|
||||
"$PWD/#{service_name}"
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
def hosts_from_roles
|
||||
if specifics.key?("roles")
|
||||
specifics["roles"].flat_map { |role| config.role(role).hosts }
|
||||
end
|
||||
end
|
||||
end
|
||||
20
lib/kamal/configuration/boot.rb
Normal file
20
lib/kamal/configuration/boot.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class Kamal::Configuration::Boot
|
||||
def initialize(config:)
|
||||
@options = config.raw_config.boot || {}
|
||||
@host_count = config.all_hosts.count
|
||||
end
|
||||
|
||||
def limit
|
||||
limit = @options["limit"]
|
||||
|
||||
if limit.to_s.end_with?("%")
|
||||
@host_count * limit.to_i / 100
|
||||
else
|
||||
limit
|
||||
end
|
||||
end
|
||||
|
||||
def wait
|
||||
@options["wait"]
|
||||
end
|
||||
end
|
||||
114
lib/kamal/configuration/builder.rb
Normal file
114
lib/kamal/configuration/builder.rb
Normal file
@@ -0,0 +1,114 @@
|
||||
class Kamal::Configuration::Builder
|
||||
def initialize(config:)
|
||||
@options = config.raw_config.builder || {}
|
||||
@image = config.image
|
||||
@server = config.registry["server"]
|
||||
|
||||
valid?
|
||||
end
|
||||
|
||||
def to_h
|
||||
@options
|
||||
end
|
||||
|
||||
def multiarch?
|
||||
@options["multiarch"] != false
|
||||
end
|
||||
|
||||
def local?
|
||||
!!@options["local"]
|
||||
end
|
||||
|
||||
def remote?
|
||||
!!@options["remote"]
|
||||
end
|
||||
|
||||
def cached?
|
||||
!!@options["cache"]
|
||||
end
|
||||
|
||||
def args
|
||||
@options["args"] || {}
|
||||
end
|
||||
|
||||
def secrets
|
||||
@options["secrets"] || []
|
||||
end
|
||||
|
||||
def dockerfile
|
||||
@options["dockerfile"] || "Dockerfile"
|
||||
end
|
||||
|
||||
def context
|
||||
@options["context"] || "."
|
||||
end
|
||||
|
||||
def local_arch
|
||||
@options["local"]["arch"] if local?
|
||||
end
|
||||
|
||||
def local_host
|
||||
@options["local"]["host"] if local?
|
||||
end
|
||||
|
||||
def remote_arch
|
||||
@options["remote"]["arch"] if remote?
|
||||
end
|
||||
|
||||
def remote_host
|
||||
@options["remote"]["host"] if remote?
|
||||
end
|
||||
|
||||
def cache_from
|
||||
if cached?
|
||||
case @options["cache"]["type"]
|
||||
when "gha"
|
||||
cache_from_config_for_gha
|
||||
when "registry"
|
||||
cache_from_config_for_registry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cache_to
|
||||
if cached?
|
||||
case @options["cache"]["type"]
|
||||
when "gha"
|
||||
cache_to_config_for_gha
|
||||
when "registry"
|
||||
cache_to_config_for_registry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def valid?
|
||||
if @options["cache"] && @options["cache"]["type"]
|
||||
raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless ["gha", "registry"].include?(@options["cache"]["type"])
|
||||
end
|
||||
end
|
||||
|
||||
def cache_image
|
||||
@options["cache"]&.fetch("image", nil) || "#{@image}-build-cache"
|
||||
end
|
||||
|
||||
def cache_image_ref
|
||||
[ @server, cache_image ].compact.join("/")
|
||||
end
|
||||
|
||||
def cache_from_config_for_gha
|
||||
"type=gha"
|
||||
end
|
||||
|
||||
def cache_from_config_for_registry
|
||||
[ "type=registry", "ref=#{cache_image_ref}" ].compact.join(",")
|
||||
end
|
||||
|
||||
def cache_to_config_for_gha
|
||||
[ "type=gha", @options["cache"]&.fetch("options", nil)].compact.join(",")
|
||||
end
|
||||
|
||||
def cache_to_config_for_registry
|
||||
[ "type=registry", @options["cache"]&.fetch("options", nil), "ref=#{cache_image_ref}" ].compact.join(",")
|
||||
end
|
||||
end
|
||||
155
lib/kamal/configuration/role.rb
Normal file
155
lib/kamal/configuration/role.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
class Kamal::Configuration::Role
|
||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
||||
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name, config:)
|
||||
@name, @config = name.inquiry, config
|
||||
end
|
||||
|
||||
def primary_host
|
||||
hosts.first
|
||||
end
|
||||
|
||||
def hosts
|
||||
@hosts ||= extract_hosts_from_config
|
||||
end
|
||||
|
||||
def labels
|
||||
default_labels.merge(traefik_labels).merge(custom_labels)
|
||||
end
|
||||
|
||||
def label_args
|
||||
argumentize "--label", labels
|
||||
end
|
||||
|
||||
def env
|
||||
if config.env && config.env["secret"]
|
||||
merged_env_with_secrets
|
||||
else
|
||||
merged_env
|
||||
end
|
||||
end
|
||||
|
||||
def env_args
|
||||
argumentize_env_with_secrets env
|
||||
end
|
||||
|
||||
def health_check_args
|
||||
if health_check_cmd.present?
|
||||
optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def health_check_cmd
|
||||
options = specializations["healthcheck"] || {}
|
||||
options = config.healthcheck.merge(options) if running_traefik?
|
||||
|
||||
options["cmd"] || http_health_check(port: options["port"], path: options["path"])
|
||||
end
|
||||
|
||||
def health_check_interval
|
||||
options = specializations["healthcheck"] || {}
|
||||
options = config.healthcheck.merge(options) if running_traefik?
|
||||
|
||||
options["interval"] || "1s"
|
||||
end
|
||||
|
||||
def cmd
|
||||
specializations["cmd"]
|
||||
end
|
||||
|
||||
def option_args
|
||||
if args = specializations["options"]
|
||||
optionize args
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def running_traefik?
|
||||
name.web? || specializations["traefik"]
|
||||
end
|
||||
|
||||
private
|
||||
attr_accessor :config
|
||||
|
||||
def extract_hosts_from_config
|
||||
if config.servers.is_a?(Array)
|
||||
config.servers
|
||||
else
|
||||
servers = config.servers[name]
|
||||
servers.is_a?(Array) ? servers : Array(servers["hosts"])
|
||||
end
|
||||
end
|
||||
|
||||
def default_labels
|
||||
if config.destination
|
||||
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
||||
else
|
||||
{ "service" => config.service, "role" => name }
|
||||
end
|
||||
end
|
||||
|
||||
def traefik_labels
|
||||
if running_traefik?
|
||||
{
|
||||
# Setting a service property ensures that the generated service name will be consistent between versions
|
||||
"traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
|
||||
|
||||
"traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
|
||||
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
||||
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
||||
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def traefik_service
|
||||
[ config.service, name, config.destination ].compact.join("-")
|
||||
end
|
||||
|
||||
def custom_labels
|
||||
Hash.new.tap do |labels|
|
||||
labels.merge!(config.labels) if config.labels.present?
|
||||
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
||||
end
|
||||
end
|
||||
|
||||
def specializations
|
||||
if config.servers.is_a?(Array) || config.servers[name].is_a?(Array)
|
||||
{ }
|
||||
else
|
||||
config.servers[name].except("hosts")
|
||||
end
|
||||
end
|
||||
|
||||
def specialized_env
|
||||
specializations["env"] || {}
|
||||
end
|
||||
|
||||
def merged_env
|
||||
config.env&.merge(specialized_env) || {}
|
||||
end
|
||||
|
||||
# Secrets are stored in an array, which won't merge by default, so have to do it by hand.
|
||||
def merged_env_with_secrets
|
||||
merged_env.tap do |new_env|
|
||||
new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
|
||||
|
||||
# If there's no secret/clear split, everything is clear
|
||||
clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env)
|
||||
clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env)
|
||||
|
||||
new_env["clear"] = (clear_app_env + clear_role_env).uniq
|
||||
end
|
||||
end
|
||||
|
||||
def http_health_check(port:, path:)
|
||||
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
||||
end
|
||||
end
|
||||
38
lib/kamal/configuration/ssh.rb
Normal file
38
lib/kamal/configuration/ssh.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
class Kamal::Configuration::Ssh
|
||||
LOGGER = ::Logger.new(STDERR)
|
||||
|
||||
def initialize(config:)
|
||||
@config = config.raw_config.ssh || {}
|
||||
end
|
||||
|
||||
def user
|
||||
config.fetch("user", "root")
|
||||
end
|
||||
|
||||
def proxy
|
||||
if (proxy = config["proxy"])
|
||||
Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
|
||||
elsif (proxy_command = config["proxy_command"])
|
||||
Net::SSH::Proxy::Command.new(proxy_command)
|
||||
end
|
||||
end
|
||||
|
||||
def options
|
||||
{ user: user, proxy: proxy, auth_methods: [ "publickey" ], logger: logger, keepalive: true, keepalive_interval: 30 }.compact
|
||||
end
|
||||
|
||||
def to_h
|
||||
options.except(:logger).merge(log_level: log_level)
|
||||
end
|
||||
|
||||
private
|
||||
attr_accessor :config
|
||||
|
||||
def logger
|
||||
LOGGER.tap { |logger| logger.level = log_level }
|
||||
end
|
||||
|
||||
def log_level
|
||||
config.fetch("log_level", :fatal)
|
||||
end
|
||||
end
|
||||
20
lib/kamal/configuration/sshkit.rb
Normal file
20
lib/kamal/configuration/sshkit.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class Kamal::Configuration::Sshkit
|
||||
def initialize(config:)
|
||||
@options = config.raw_config.sshkit || {}
|
||||
end
|
||||
|
||||
def max_concurrent_starts
|
||||
options.fetch("max_concurrent_starts", 30)
|
||||
end
|
||||
|
||||
def pool_idle_timeout
|
||||
options.fetch("pool_idle_timeout", 900)
|
||||
end
|
||||
|
||||
def to_h
|
||||
options
|
||||
end
|
||||
|
||||
private
|
||||
attr_accessor :options
|
||||
end
|
||||
Reference in New Issue
Block a user