diff --git a/Gemfile.lock b/Gemfile.lock index 17b354ee..b85c77fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,15 +82,15 @@ GEM net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) net-ssh (7.3.0) - nokogiri (1.18.4-aarch64-linux-musl) + nokogiri (1.18.8-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.4-arm64-darwin) + nokogiri (1.18.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.4-x86_64-darwin) + nokogiri (1.18.8-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.4-x86_64-linux-gnu) + nokogiri (1.18.8-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.4-x86_64-linux-musl) + nokogiri (1.18.8-x86_64-linux-musl) racc (~> 1.4) ostruct (0.6.1) parallel (1.26.3) diff --git a/lib/kamal/cli/app/error_pages.rb b/lib/kamal/cli/app/error_pages.rb index 664f7eff..d15ddf7a 100644 --- a/lib/kamal/cli/app/error_pages.rb +++ b/lib/kamal/cli/app/error_pages.rb @@ -13,7 +13,7 @@ class Kamal::Cli::App::ErrorPages if KAMAL.config.error_pages_path with_error_pages_tmpdir do |local_error_pages_dir| execute *KAMAL.app.create_error_pages_directory - upload! local_error_pages_dir, KAMAL.config.proxy_error_pages_directory, mode: "0700", recursive: true + upload! local_error_pages_dir, KAMAL.config.proxy_boot.error_pages_directory, mode: "0700", recursive: true end end end diff --git a/lib/kamal/cli/base.rb b/lib/kamal/cli/base.rb index 7e5dd77b..9005e1c7 100644 --- a/lib/kamal/cli/base.rb +++ b/lib/kamal/cli/base.rb @@ -133,7 +133,13 @@ module Kamal::Cli def run_hook(hook, **extra_details) if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook) - details = { hosts: KAMAL.hosts.join(","), roles: KAMAL.specific_roles&.join(","), command: command, subcommand: subcommand }.compact + details = { + hosts: KAMAL.hosts.join(","), + roles: KAMAL.specific_roles&.join(","), + lock: KAMAL.holding_lock?.to_s, + command: command, + subcommand: subcommand + }.compact say "Running the #{hook} hook...", :magenta with_env KAMAL.hook.env(**details, **extra_details) do diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 1ed17740..73707572 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -13,9 +13,10 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base version = capture_with_info(*KAMAL.proxy.version).strip.presence - if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION) - raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" + if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION) + raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}" end + execute *KAMAL.proxy.ensure_apps_config_directory execute *KAMAL.proxy.start_or_run end end @@ -24,49 +25,63 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base desc "boot_config ", "Manage kamal-proxy boot configuration" option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host" option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces" - option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host" - option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host" - option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs" + option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host" + option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host" + option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs" option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image" option :repository, type: :string, default: nil, desc: "Repository for the proxy image" option :image_version, type: :string, default: nil, desc: "Version of the proxy to run" + option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on" + option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode" option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2" def boot_config(subcommand) + proxy_boot_config = KAMAL.config.proxy_boot + case subcommand when "set" boot_options = [ - *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]), - *(KAMAL.config.proxy_logging_args(options[:log_max_size])), + *(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]), + *(proxy_boot_config.logging_args(options[:log_max_size])), + *("--expose=#{options[:metrics_port]}" if options[:metrics_port]), *options[:docker_options].map { |option| "--#{option}" } ] image = [ options[:registry].presence, - options[:repository].presence || KAMAL.config.proxy_repository_name, - KAMAL.config.proxy_image_name + options[:repository].presence || proxy_boot_config.repository_name, + proxy_boot_config.image_name ].compact.join("/") image_version = options[:image_version] + run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact + run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any? + on(KAMAL.proxy_hosts) do |host| execute(*KAMAL.proxy.ensure_proxy_directory) - if boot_options != KAMAL.config.proxy_default_boot_options - upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file + if boot_options != proxy_boot_config.default_boot_options + upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file else execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false end - if image != KAMAL.config.proxy_image_default - upload! StringIO.new(image), KAMAL.config.proxy_image_file + if image != proxy_boot_config.image_default + upload! StringIO.new(image), proxy_boot_config.image_file else execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false end if image_version - upload! StringIO.new(image_version), KAMAL.config.proxy_image_version_file + upload! StringIO.new(image_version), proxy_boot_config.image_version_file else execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false end + + if run_command + upload! StringIO.new(run_command), proxy_boot_config.run_command_file + else + execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false + end end when "get" @@ -78,6 +93,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false + execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false end else raise ArgumentError, "Unknown boot_config subcommand #{subcommand}" @@ -101,6 +117,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base "Stopping and removing kamal-proxy on #{host}, if running..." execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false execute *KAMAL.proxy.remove_container + execute *KAMAL.proxy.ensure_apps_config_directory execute *KAMAL.proxy.run diff --git a/lib/kamal/cli/templates/sample_hooks/post-deploy.sample b/lib/kamal/cli/templates/sample_hooks/post-deploy.sample index 75efafc1..fd364c2a 100755 --- a/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +++ b/lib/kamal/cli/templates/sample_hooks/post-deploy.sample @@ -7,7 +7,7 @@ # KAMAL_PERFORMER # KAMAL_VERSION # KAMAL_HOSTS -# KAMAL_ROLE (if set) +# KAMAL_ROLES (if set) # KAMAL_DESTINATION (if set) # KAMAL_RUNTIME diff --git a/lib/kamal/cli/templates/sample_hooks/pre-build.sample b/lib/kamal/cli/templates/sample_hooks/pre-build.sample index f87d8113..c5a55678 100755 --- a/lib/kamal/cli/templates/sample_hooks/pre-build.sample +++ b/lib/kamal/cli/templates/sample_hooks/pre-build.sample @@ -13,7 +13,7 @@ # KAMAL_PERFORMER # KAMAL_VERSION # KAMAL_HOSTS -# KAMAL_ROLE (if set) +# KAMAL_ROLES (if set) # KAMAL_DESTINATION (if set) if [ -n "$(git status --porcelain)" ]; then diff --git a/lib/kamal/cli/templates/sample_hooks/pre-connect.sample b/lib/kamal/cli/templates/sample_hooks/pre-connect.sample index 18e61d7e..77744bdc 100755 --- a/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +++ b/lib/kamal/cli/templates/sample_hooks/pre-connect.sample @@ -9,7 +9,7 @@ # KAMAL_PERFORMER # KAMAL_VERSION # KAMAL_HOSTS -# KAMAL_ROLE (if set) +# KAMAL_ROLES (if set) # KAMAL_DESTINATION (if set) # KAMAL_RUNTIME diff --git a/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample b/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample index 06b86aa1..665197f1 100755 --- a/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +++ b/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample @@ -13,7 +13,7 @@ # KAMAL_HOSTS # KAMAL_COMMAND # KAMAL_SUBCOMMAND -# KAMAL_ROLE (if set) +# KAMAL_ROLES (if set) # KAMAL_DESTINATION (if set) # Only check the build status for production deployments diff --git a/lib/kamal/commander.rb b/lib/kamal/commander.rb index 8a4356ed..0882311d 100644 --- a/lib/kamal/commander.rb +++ b/lib/kamal/commander.rb @@ -13,7 +13,7 @@ class Kamal::Commander def reset self.verbosity = :info - self.holding_lock = false + self.holding_lock = ENV["KAMAL_LOCK"] == "true" self.connected = false @specifics = @specific_roles = @specific_hosts = nil @config = @config_kwargs = nil diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 60279dfc..4a76d6f6 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -6,7 +6,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry, to: :accessory_config - delegate :proxy_container_name, to: :config def initialize(config, name:) super(config) diff --git a/lib/kamal/commands/accessory/proxy.rb b/lib/kamal/commands/accessory/proxy.rb index 195a321b..833088da 100644 --- a/lib/kamal/commands/accessory/proxy.rb +++ b/lib/kamal/commands/accessory/proxy.rb @@ -1,5 +1,5 @@ module Kamal::Commands::Accessory::Proxy - delegate :proxy_container_name, to: :config + delegate :container_name, to: :"config.proxy_boot", prefix: :proxy def deploy(target:) proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target) diff --git a/lib/kamal/commands/app/error_pages.rb b/lib/kamal/commands/app/error_pages.rb index d51dba3f..3a21bdaf 100644 --- a/lib/kamal/commands/app/error_pages.rb +++ b/lib/kamal/commands/app/error_pages.rb @@ -1,9 +1,9 @@ module Kamal::Commands::App::ErrorPages def create_error_pages_directory - make_directory(config.proxy_error_pages_directory) + make_directory(config.proxy_boot.error_pages_directory) end def clean_up_error_pages - [ :find, config.proxy_error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ] + [ :find, config.proxy_boot.error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ] end end diff --git a/lib/kamal/commands/app/proxy.rb b/lib/kamal/commands/app/proxy.rb index 56d0ccea..6d015a0b 100644 --- a/lib/kamal/commands/app/proxy.rb +++ b/lib/kamal/commands/app/proxy.rb @@ -1,5 +1,5 @@ module Kamal::Commands::App::Proxy - delegate :proxy_container_name, to: :config + delegate :container_name, to: :"config.proxy_boot", prefix: :proxy def deploy(target:) proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target) @@ -18,7 +18,7 @@ module Kamal::Commands::App::Proxy end def remove_proxy_app_directory - remove_directory config.proxy_app_directory + remove_directory config.proxy_boot.app_directory end private diff --git a/lib/kamal/commands/auditor.rb b/lib/kamal/commands/auditor.rb index 9846d8e2..e4981650 100644 --- a/lib/kamal/commands/auditor.rb +++ b/lib/kamal/commands/auditor.rb @@ -1,5 +1,6 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base attr_reader :details + delegate :escape_shell_value, to: Kamal::Utils def initialize(config, **details) super(config) @@ -9,11 +10,8 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base # Runs remotely def record(line, **details) combine \ - [ :mkdir, "-p", config.run_directory ], - append( - [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ], - audit_log_file - ) + make_run_directory, + append([ :echo, escape_shell_value(audit_line(line, **details)) ], audit_log_file) end def reveal @@ -30,4 +28,12 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base def audit_tags(**details) tags(**self.details, **details) end + + def make_run_directory + [ :mkdir, "-p", config.run_directory ] + end + + def audit_line(line, **details) + "#{audit_tags(**details).except(:version, :service_version, :service)} #{line}" + end end diff --git a/lib/kamal/commands/proxy.rb b/lib/kamal/commands/proxy.rb index 658252de..7699dde2 100644 --- a/lib/kamal/commands/proxy.rb +++ b/lib/kamal/commands/proxy.rb @@ -58,44 +58,56 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base end def ensure_proxy_directory - make_directory config.proxy_directory + make_directory config.proxy_boot.host_directory end def remove_proxy_directory - remove_directory config.proxy_directory + remove_directory config.proxy_boot.host_directory + end + + def ensure_apps_config_directory + make_directory config.proxy_boot.apps_directory end def boot_config - [ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)}" ] + [ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ] end def read_boot_options - read_file(config.proxy_options_file, default: config.proxy_options_default.join(" ")) + read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" ")) end def read_image - read_file(config.proxy_image_file, default: config.proxy_image_default) + read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default) end def read_image_version - read_file(config.proxy_image_version_file, default: Kamal::Configuration::PROXY_MINIMUM_VERSION) + read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION) + end + + def read_run_command + read_file(config.proxy_boot.run_command_file) end def reset_boot_options - remove_file config.proxy_options_file + remove_file config.proxy_boot.options_file end def reset_image - remove_file config.proxy_image_file + remove_file config.proxy_boot.image_file end def reset_image_version - remove_file config.proxy_image_version_file + remove_file config.proxy_boot.image_version_file + end + + def reset_run_command + remove_file config.proxy_boot.run_command_file end private def container_name - config.proxy_container_name + config.proxy_boot.container_name end def read_file(file, default: nil) @@ -110,6 +122,6 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base "--detach", "--restart", "unless-stopped", "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", - *config.proxy_apps_volume.docker_args + *config.proxy_boot.apps_volume.docker_args end end diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 326e1f71..26450170 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -10,15 +10,10 @@ class Kamal::Configuration delegate :argumentize, :optionize, to: Kamal::Utils attr_reader :destination, :raw_config, :secrets - attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :servers, :ssh, :sshkit, :registry + attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :proxy_boot, :servers, :ssh, :sshkit, :registry include Validation - PROXY_MINIMUM_VERSION = "v0.8.7" - PROXY_HTTP_PORT = 80 - PROXY_HTTPS_PORT = 443 - PROXY_LOG_MAX_SIZE = "10m" - class << self def create_from(config_file:, destination: nil, version: nil) ENV["KAMAL_DESTINATION"] = destination @@ -69,6 +64,7 @@ class Kamal::Configuration @logging = Logging.new(logging_config: @raw_config.logging) @proxy = Proxy.new(config: self, proxy_config: @raw_config.key?(:proxy) ? @raw_config.proxy : {}) + @proxy_boot = Proxy::Boot.new(config: self) @ssh = Ssh.new(config: self) @sshkit = Sshkit.new(config: self) @@ -257,95 +253,6 @@ class Kamal::Configuration env_tags.detect { |t| t.name == name.to_s } end - def proxy_publish_args(http_port, https_port, bind_ips = nil) - ensure_valid_bind_ips(bind_ips) - - (bind_ips || [ nil ]).map do |bind_ip| - bind_ip = format_bind_ip(bind_ip) - publish_http = [ bind_ip, http_port, PROXY_HTTP_PORT ].compact.join(":") - publish_https = [ bind_ip, https_port, PROXY_HTTPS_PORT ].compact.join(":") - - argumentize "--publish", [ publish_http, publish_https ] - end.join(" ") - end - - def proxy_logging_args(max_size) - argumentize "--log-opt", "max-size=#{max_size}" if max_size.present? - end - - def proxy_default_boot_options - [ - *(KAMAL.config.proxy_publish_args(Kamal::Configuration::PROXY_HTTP_PORT, Kamal::Configuration::PROXY_HTTPS_PORT, nil)), - *(KAMAL.config.proxy_logging_args(Kamal::Configuration::PROXY_LOG_MAX_SIZE)) - ] - end - - def proxy_options_default - [ *proxy_publish_args(PROXY_HTTP_PORT, PROXY_HTTPS_PORT), *proxy_logging_args(PROXY_LOG_MAX_SIZE) ] - end - - def proxy_repository_name - "basecamp" - end - - def proxy_image_name - "kamal-proxy" - end - - def proxy_image_default - "#{proxy_repository_name}/#{proxy_image_name}" - end - - def proxy_container_name - "kamal-proxy" - end - - def proxy_directory - File.join run_directory, "proxy" - end - - def proxy_options_file - File.join proxy_directory, "options" - end - - def proxy_image_file - File.join proxy_directory, "image" - end - - def proxy_image_version_file - File.join proxy_directory, "image_version" - end - - def proxy_apps_directory - File.join proxy_directory, "apps-config" - end - - def proxy_apps_container_directory - "/home/kamal-proxy/.apps-config" - end - - def proxy_apps_volume - Volume.new \ - host_path: proxy_apps_directory, - container_path: proxy_apps_container_directory - end - - def proxy_app_directory - File.join proxy_apps_directory, service_and_destination - end - - def proxy_app_container_directory - File.join proxy_apps_container_directory, service_and_destination - end - - def proxy_error_pages_directory - File.join proxy_app_directory, "error_pages" - end - - def proxy_error_pages_container_directory - File.join proxy_app_container_directory, "error_pages" - end - def to_h { roles: role_names, @@ -416,15 +323,6 @@ class Kamal::Configuration true end - def ensure_valid_bind_ips(bind_ips) - bind_ips.present? && bind_ips.each do |ip| - next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex - raise ArgumentError, "Invalid publish IP address: #{ip}" - end - - true - end - def ensure_retain_containers_valid raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1 @@ -456,15 +354,6 @@ class Kamal::Configuration true end - def format_bind_ip(ip) - # Ensure IPv6 address inside square brackets - e.g. [::1] - if ip =~ Resolv::IPv6::Regex && ip !~ /\[.*\]/ - "[#{ip}]" - else - ip - end - end - def role_names raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort end diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index 19bc25f7..b5afbaae 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -74,6 +74,6 @@ class Kamal::Configuration::Proxy end def error_pages - File.join config.proxy_error_pages_container_directory, config.version if config.error_pages_path + File.join config.proxy_boot.error_pages_container_directory, config.version if config.error_pages_path end end diff --git a/lib/kamal/configuration/proxy/boot.rb b/lib/kamal/configuration/proxy/boot.rb new file mode 100644 index 00000000..9f8c6f5c --- /dev/null +++ b/lib/kamal/configuration/proxy/boot.rb @@ -0,0 +1,121 @@ +class Kamal::Configuration::Proxy::Boot + MINIMUM_VERSION = "v0.8.7" + DEFAULT_HTTP_PORT = 80 + DEFAULT_HTTPS_PORT = 443 + DEFAULT_LOG_MAX_SIZE = "10m" + + attr_reader :config + delegate :argumentize, :optionize, to: Kamal::Utils + + def initialize(config:) + @config = config + end + + def publish_args(http_port, https_port, bind_ips = nil) + ensure_valid_bind_ips(bind_ips) + + (bind_ips || [ nil ]).map do |bind_ip| + bind_ip = format_bind_ip(bind_ip) + publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":") + publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":") + + argumentize "--publish", [ publish_http, publish_https ] + end.join(" ") + end + + def logging_args(max_size) + argumentize "--log-opt", "max-size=#{max_size}" if max_size.present? + end + + def default_boot_options + [ + *(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)), + *(logging_args(DEFAULT_LOG_MAX_SIZE)) + ] + end + + def repository_name + "basecamp" + end + + def image_name + "kamal-proxy" + end + + def image_default + "#{repository_name}/#{image_name}" + end + + def container_name + "kamal-proxy" + end + + def host_directory + File.join config.run_directory, "proxy" + end + + def options_file + File.join host_directory, "options" + end + + def image_file + File.join host_directory, "image" + end + + def image_version_file + File.join host_directory, "image_version" + end + + def run_command_file + File.join host_directory, "run_command" + end + + def apps_directory + File.join host_directory, "apps-config" + end + + def apps_container_directory + "/home/kamal-proxy/.apps-config" + end + + def apps_volume + Kamal::Configuration::Volume.new \ + host_path: apps_directory, + container_path: apps_container_directory + end + + def app_directory + File.join apps_directory, config.service_and_destination + end + + def app_container_directory + File.join apps_container_directory, config.service_and_destination + end + + def error_pages_directory + File.join app_directory, "error_pages" + end + + def error_pages_container_directory + File.join app_container_directory, "error_pages" + end + + private + def ensure_valid_bind_ips(bind_ips) + bind_ips.present? && bind_ips.each do |ip| + next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex + raise ArgumentError, "Invalid publish IP address: #{ip}" + end + + true + end + + def format_bind_ip(ip) + # Ensure IPv6 address inside square brackets - e.g. [::1] + if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/ + "[#{ip}]" + else + ip + end + end +end diff --git a/lib/kamal/secrets/dotenv/inline_command_substitution.rb b/lib/kamal/secrets/dotenv/inline_command_substitution.rb index c9ef9879..16498ffb 100644 --- a/lib/kamal/secrets/dotenv/inline_command_substitution.rb +++ b/lib/kamal/secrets/dotenv/inline_command_substitution.rb @@ -4,7 +4,7 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution ::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub } end - def call(value, _env, overwrite: false) + def call(value, env, overwrite: false) # Process interpolated shell commands value.gsub(Dotenv::Substitutions::Command.singleton_class::INTERPOLATED_SHELL_COMMAND) do |*| # Eliminate opening and closing parentheses @@ -14,6 +14,7 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution # Command is escaped, don't replace it. $LAST_MATCH_INFO[0][1..] else + command = ::Dotenv::Substitutions::Variable.call(command, env) if command =~ /\A\s*kamal\s*secrets\s+/ # Inline the command inline_secrets_command(command) diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index d37c9352..735fbd00 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -43,7 +43,7 @@ class CliMainTest < CliTestCase with_test_secrets("secrets" => "DB_PASSWORD=secret") do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true } - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options) @@ -53,7 +53,7 @@ class CliMainTest < CliTestCase run_command("deploy", "--verbose").tap do |output| assert_hook_ran "pre-connect", output - assert_match /Build and push app image/, output + assert_match /Build and push app image/, output assert_hook_ran "pre-deploy", output assert_match /Ensure kamal-proxy is running/, output assert_match /Detect stale containers/, output @@ -116,6 +116,32 @@ class CliMainTest < CliTestCase end end + test "deploy when inheriting lock" do + Thread.report_on_exception = false + + invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false } + + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options) + + Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) + + with_kamal_lock_env do + KAMAL.reset + run_command("deploy").tap do |output| + assert_no_match /Acquiring the deploy lock/, output + assert_match /Build and push app image/, output + assert_match /Ensure kamal-proxy is running/, output + assert_match /Detect stale containers/, output + assert_match /Prune old containers and images/, output + assert_no_match /Releasing the deploy lock/, output + end + end + end + test "deploy error when locking" do Thread.report_on_exception = false @@ -562,4 +588,11 @@ class CliMainTest < CliTestCase def assert_file(file, content) assert_match content, File.read(file) end + + def with_kamal_lock_env + ENV["KAMAL_LOCK"] = "true" + yield + ensure + ENV.delete("KAMAL_LOCK") + end end diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index 2ec4c726..fd131984 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -4,7 +4,8 @@ class CliProxyTest < CliTestCase test "boot" do run_command("boot").tap do |output| assert_match "docker login", output - assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output + assert_match "mkdir -p .kamal/proxy/apps-config", output + assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output end end @@ -18,11 +19,11 @@ class CliProxyTest < CliTestCase exception = assert_raises do run_command("boot").tap do |output| assert_match "docker login", output - assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output + assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output end end - assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" + assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}" ensure Thread.report_on_exception = false end @@ -31,12 +32,12 @@ class CliProxyTest < CliTestCase Thread.report_on_exception = false SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'") - .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION) + .returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION) .at_least_once run_command("boot").tap do |output| assert_match "docker login", output - assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output + assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output end ensure Thread.report_on_exception = false @@ -56,12 +57,14 @@ class CliProxyTest < CliTestCase run_command("reboot", "-y").tap do |output| assert_match "docker container stop kamal-proxy on 1.1.1.1", output assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output - assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.1", output + assert_match "mkdir -p .kamal/proxy/apps-config on 1.1.1.1", output + assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.1", output assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.1", output assert_match "docker container stop kamal-proxy on 1.1.1.2", output assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.2", output - assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.2", output + assert_match "mkdir -p .kamal/proxy/apps-config on 1.1.1.1", output + assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.2", output assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.2", output end end @@ -180,7 +183,7 @@ class CliProxyTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'") - .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION) + .returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'") @@ -196,7 +199,7 @@ class CliProxyTest < CliTestCase assert_match "/usr/bin/env mkdir -p .kamal", output assert_match "docker network create kamal", output assert_match "docker login -u [REDACTED] -p [REDACTED]", output - assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output + assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output assert_match "/usr/bin/env mkdir -p .kamal", output assert_match %r{docker rename app-web-latest app-web-latest_replaced_.*}, output assert_match "/usr/bin/env mkdir -p .kamal/apps/app/env/roles", output @@ -219,7 +222,7 @@ class CliProxyTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'") - .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION) + .returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'") @@ -240,7 +243,9 @@ class CliProxyTest < CliTestCase assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output - assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output end + assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output + end end end @@ -251,6 +256,7 @@ class CliProxyTest < CliTestCase assert_match "Uploading \"--log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -262,6 +268,7 @@ class CliProxyTest < CliTestCase assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=100m\" to .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -273,6 +280,7 @@ class CliProxyTest < CliTestCase assert_match "Uploading \"--publish 80:80 --publish 443:443\" to .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -319,6 +327,7 @@ class CliProxyTest < CliTestCase assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --label=foo=bar --add_host=thishost:thathost\" to .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -330,6 +339,7 @@ class CliProxyTest < CliTestCase assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output assert_match "Uploading \"myreg/basecamp/kamal-proxy\" to .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -341,6 +351,7 @@ class CliProxyTest < CliTestCase assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output assert_match "Uploading \"myrepo/kamal-proxy\" to .kamal/proxy/image on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output end end end @@ -352,23 +363,37 @@ class CliProxyTest < CliTestCase assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output assert_match "Uploading \"0.9.9\" to .kamal/proxy/image_version on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output + end + end + end + + test "boot_config set run_command" do + run_command("boot_config", "set", "--metrics_port", "9000", "--debug", "true").tap do |output| + %w[ 1.1.1.1 1.1.1.2 ].each do |host| + assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output + assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --expose=9000\" to .kamal/proxy/options on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output + assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output + assert_match "Uploading \"kamal-proxy run --debug --metrics-port \\\"9000\\\"\" to .kamal/proxy/run_command on #{host}", output end end end test "boot_config set all" do - run_command("boot_config", "set", "--docker_options", "label=foo=bar", "--registry", "myreg", "--repository", "myrepo", "--image_version", "0.9.9").tap do |output| + run_command("boot_config", "set", "--docker_options", "label=foo=bar", "--registry", "myreg", "--repository", "myrepo", "--image_version", "0.9.9", "--metrics_port", "9000", "--debug", "true").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| - assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --label=foo=bar\" to .kamal/proxy/options on #{host}", output + assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --expose=9000 --label=foo=bar\" to .kamal/proxy/options on #{host}", output assert_match "Uploading \"myreg/myrepo/kamal-proxy\" to .kamal/proxy/image on #{host}", output assert_match "Uploading \"0.9.9\" to .kamal/proxy/image_version on #{host}", output + assert_match "Uploading \"kamal-proxy run --debug --metrics-port \\\"9000\\\"\" to .kamal/proxy/run_command on #{host}", output end end end test "boot_config get" do SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) - .with(:echo, "$(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"v0.8.7\")") + .with(:echo, "$(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\")") .returns("--publish 80:80 --publish 8443:443 --label=foo=bar basecamp/kamal-proxy:v1.0.0") .twice diff --git a/test/commands/auditor_test.rb b/test/commands/auditor_test.rb index 2abc8d81..d1cab610 100644 --- a/test/commands/auditor_test.rb +++ b/test/commands/auditor_test.rb @@ -20,8 +20,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase assert_equal [ :mkdir, "-p", ".kamal", "&&", :echo, - "[#{@recorded_at}] [#{@performer}]", - "app removed container", + "\"[#{@recorded_at}] [#{@performer}] app removed container\"", ">>", ".kamal/app-audit.log" ], @auditor.record("app removed container") end @@ -31,8 +30,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase assert_equal [ :mkdir, "-p", ".kamal", "&&", :echo, - "[#{@recorded_at}] [#{@performer}] [staging]", - "app removed container", + "\"[#{@recorded_at}] [#{@performer}] [staging] app removed container\"", ">>", ".kamal/app-staging-audit.log" ], auditor.record("app removed container") end @@ -43,8 +41,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase assert_equal [ :mkdir, "-p", ".kamal", "&&", :echo, - "[#{@recorded_at}] [#{@performer}] [web]", - "app removed container", + "\"[#{@recorded_at}] [#{@performer}] [web] app removed container\"", ">>", ".kamal/app-audit.log" ], auditor.record("app removed container") end @@ -54,8 +51,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase assert_equal [ :mkdir, "-p", ".kamal", "&&", :echo, - "[#{@recorded_at}] [#{@performer}] [value]", - "app removed container", + "\"[#{@recorded_at}] [#{@performer}] [value] app removed container\"", ">>", ".kamal/app-audit.log" ], @auditor.record("app removed container", detail: "value") end diff --git a/test/commands/proxy_test.rb b/test/commands/proxy_test.rb index 21863fc6..2bb2dadb 100644 --- a/test/commands/proxy_test.rb +++ b/test/commands/proxy_test.rb @@ -15,7 +15,7 @@ class CommandsProxyTest < ActiveSupport::TestCase test "run" do assert_equal \ - "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config", + "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config", new_command.run.join(" ") end @@ -23,7 +23,7 @@ class CommandsProxyTest < ActiveSupport::TestCase @config.delete(:proxy) assert_equal \ - "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config", + "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config", new_command.run.join(" ") end @@ -125,10 +125,16 @@ class CommandsProxyTest < ActiveSupport::TestCase test "read_image_version" do assert_equal \ - "cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::PROXY_MINIMUM_VERSION}\"", + "cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\"", new_command.read_image_version.join(" ") end + test "read_run_command" do + assert_equal \ + "cat .kamal/proxy/run_command 2> /dev/null || echo \"\"", + new_command.read_run_command.join(" ") + end + test "reset_boot_options" do assert_equal \ "rm .kamal/proxy/options", @@ -147,6 +153,18 @@ class CommandsProxyTest < ActiveSupport::TestCase new_command.reset_image_version.join(" ") end + test "ensure_apps_config_directory" do + assert_equal \ + "mkdir -p .kamal/proxy/apps-config", + new_command.ensure_apps_config_directory.join(" ") + end + + test "reset_run_command" do + assert_equal \ + "rm .kamal/proxy/run_command", + new_command.reset_run_command.join(" ") + end + private def new_command Kamal::Commands::Proxy.new(Kamal::Configuration.new(@config, version: "123")) diff --git a/test/configuration/proxy/boot_test.rb b/test/configuration/proxy/boot_test.rb new file mode 100644 index 00000000..90dee1b7 --- /dev/null +++ b/test/configuration/proxy/boot_test.rb @@ -0,0 +1,29 @@ +require "test_helper" + +class ConfigurationProxyBootTest < ActiveSupport::TestCase + setup do + ENV["RAILS_MASTER_KEY"] = "456" + ENV["VERSION"] = "missing" + + @deploy = { + service: "app", image: "dhh/app", + registry: { "username" => "dhh", "password" => "secret" }, + builder: { "arch" => "amd64" }, + env: { "REDIS_URL" => "redis://x/y" }, + servers: [ "1.1.1.1", "1.1.1.2" ], + volumes: [ "/local/path:/container/path" ] + } + + @config = Kamal::Configuration.new(@deploy) + @proxy_boot_config = @config.proxy_boot + end + + test "proxy directories" do + assert_equal ".kamal/proxy/apps-config", @proxy_boot_config.apps_directory + assert_equal "/home/kamal-proxy/.apps-config", @proxy_boot_config.apps_container_directory + assert_equal ".kamal/proxy/apps-config/app", @proxy_boot_config.app_directory + assert_equal "/home/kamal-proxy/.apps-config/app", @proxy_boot_config.app_container_directory + assert_equal ".kamal/proxy/apps-config/app/error_pages", @proxy_boot_config.error_pages_directory + assert_equal "/home/kamal-proxy/.apps-config/app/error_pages", @proxy_boot_config.error_pages_container_directory + end +end diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 571ed0a7..73756c88 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -410,13 +410,4 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal "Different roles can't share the same host for SSL: foo.example.com", exception.message end - - test "proxy directories" do - assert_equal ".kamal/proxy/apps-config", @config.proxy_apps_directory - assert_equal "/home/kamal-proxy/.apps-config", @config.proxy_apps_container_directory - assert_equal ".kamal/proxy/apps-config/app", @config.proxy_app_directory - assert_equal "/home/kamal-proxy/.apps-config/app", @config.proxy_app_container_directory - assert_equal ".kamal/proxy/apps-config/app/error_pages", @config.proxy_error_pages_directory - assert_equal "/home/kamal-proxy/.apps-config/app/error_pages", @config.proxy_error_pages_container_directory - end end diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/.kamal/hooks/pre-deploy b/test/integration/docker/deployer/app_with_proxied_accessory/.kamal/hooks/pre-deploy new file mode 100755 index 00000000..af8d0b4a --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/.kamal/hooks/pre-deploy @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +kamal proxy boot_config set --registry registry:4443 diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 3b56fbb3..604bd78c 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -32,7 +32,7 @@ class MainTest < IntegrationTest assert_match /Proxy Host: vm2/, details assert_match /App Host: vm1/, details assert_match /App Host: vm2/, details - assert_match /basecamp\/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}/, details + assert_match /basecamp\/kamal-proxy:#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}/, details assert_match /registry:4443\/app:#{first_version}/, details audit = kamal :audit, capture: true