All role specific proxy configuration

By default only the primary role runs the proxy. To disable the proxy
for that role, you can set `proxy: false` under it.

For other roles they default to not running the proxy, but you can
enable it by setting `proxy: true` for the role, or alternatively
setting a proxy configuration.

The proxy configuration will be merged into the root proxy configuration.
This commit is contained in:
Donal McBreen
2024-09-18 17:25:35 +01:00
parent d218264b69
commit fd0cdc1ca1
21 changed files with 210 additions and 98 deletions

View File

@@ -47,7 +47,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
endpoint = capture_with_info(*app.container_id_for_version(version)).strip endpoint = capture_with_info(*app.container_id_for_version(version)).strip
raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty? raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
execute *KAMAL.proxy.deploy(role.container_prefix, target: endpoint) execute *app.deploy(target: endpoint)
end end
end end
end end
@@ -68,7 +68,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
endpoint = capture_with_info(*app.container_id_for_version(version)).strip endpoint = capture_with_info(*app.container_id_for_version(version)).strip
if endpoint.present? if endpoint.present?
execute *KAMAL.proxy.remove(role.container_prefix, target: endpoint), raise_on_non_zero_exit: false execute *app.remove(target: endpoint), raise_on_non_zero_exit: false
end end
end end

View File

@@ -54,7 +54,7 @@ class Kamal::Cli::App::Boot
if running_proxy? if running_proxy?
endpoint = capture_with_info(*app.container_id_for_version(version)).strip endpoint = capture_with_info(*app.container_id_for_version(version)).strip
raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty? raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
execute *KAMAL.proxy.deploy(role.container_prefix, target: endpoint) execute *app.deploy(target: endpoint)
else else
Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) } Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
end end

View File

@@ -13,8 +13,8 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
version = capture_with_info(*KAMAL.proxy.version).strip.presence version = capture_with_info(*KAMAL.proxy.version).strip.presence
if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::MINIMUM_VERSION) if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::Proxy::MINIMUM_VERSION}" raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
end end
execute *KAMAL.proxy.start_or_run execute *KAMAL.proxy.start_or_run
end end
@@ -52,7 +52,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
if endpoint.present? if endpoint.present?
info "Deploying #{endpoint} for role `#{role}` on #{host}..." info "Deploying #{endpoint} for role `#{role}` on #{host}..."
execute *KAMAL.proxy.deploy(role.container_prefix, target: endpoint) execute *app.deploy(target: endpoint)
end end
end end
end end

View File

@@ -1,5 +1,5 @@
class Kamal::Commands::App < Kamal::Commands::Base class Kamal::Commands::App < Kamal::Commands::Base
include Assets, Containers, Execution, Images, Logging include Assets, Containers, Execution, Images, Logging, Proxy
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]

View File

@@ -0,0 +1,16 @@
module Kamal::Commands::App::Proxy
delegate :proxy_container_name, to: :config
def deploy(target:)
proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
end
def remove(target:)
proxy_exec :remove, role.container_prefix, *role.proxy.remove_command_args(target: target)
end
private
def proxy_exec(*command)
docker :exec, proxy_container_name, "kamal-proxy", *command
end
end

View File

@@ -1,13 +1,5 @@
class Kamal::Commands::Proxy < Kamal::Commands::Base class Kamal::Commands::Proxy < Kamal::Commands::Base
delegate :argumentize, :optionize, to: Kamal::Utils delegate :argumentize, :optionize, to: Kamal::Utils
delegate :container_name, :app_port, to: :proxy_config
attr_reader :proxy_config
def initialize(config)
super
@proxy_config = config.proxy
end
def run def run
docker :run, docker :run,
@@ -15,11 +7,11 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
"--network", "kamal", "--network", "kamal",
"--detach", "--detach",
"--restart", "unless-stopped", "--restart", "unless-stopped",
*proxy_config.publish_args, *config.proxy_publish_args,
"--volume", "/var/run/docker.sock:/var/run/docker.sock", "--volume", "/var/run/docker.sock:/var/run/docker.sock",
*proxy_config.config_volume.docker_args, *config.proxy_config_volume.docker_args,
*config.logging_args, *config.logging_args,
proxy_config.image config.proxy_image
end end
def start def start
@@ -34,14 +26,6 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
combine start, run, by: "||" combine start, run, by: "||"
end end
def deploy(service, target:)
docker :exec, container_name, "kamal-proxy", :deploy, service, *optionize({ target: "#{target}:#{app_port}" }), *proxy_config.deploy_command_args
end
def remove(service, target:)
docker :exec, container_name, "kamal-proxy", :remove, service, *optionize({ target: "#{target}:#{app_port}" })
end
def info def info
docker :ps, "--filter", "name=^#{container_name}$" docker :ps, "--filter", "name=^#{container_name}$"
end end
@@ -85,4 +69,9 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik") docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik")
) )
end end
private
def container_name
config.proxy_container_name
end
end end

View File

@@ -14,6 +14,10 @@ class Kamal::Configuration
include Validation include Validation
PROXY_MINIMUM_VERSION = "v0.3.0"
PROXY_HTTP_PORT = 80
PROXY_HTTPS_PORT = 443
class << self class << self
def create_from(config_file:, destination: nil, version: nil) def create_from(config_file:, destination: nil, version: nil)
raw_config = load_config_files(config_file, *destination_config_file(config_file, destination)) raw_config = load_config_files(config_file, *destination_config_file(config_file, destination))
@@ -61,7 +65,7 @@ class Kamal::Configuration
@env = Env.new(config: @raw_config.env || {}, secrets: secrets) @env = Env.new(config: @raw_config.env || {}, secrets: secrets)
@logging = Logging.new(logging_config: @raw_config.logging) @logging = Logging.new(logging_config: @raw_config.logging)
@proxy = Proxy.new(config: self) @proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy || {})
@ssh = Ssh.new(config: self) @ssh = Ssh.new(config: self)
@sshkit = Sshkit.new(config: self) @sshkit = Sshkit.new(config: self)
@@ -244,6 +248,24 @@ class Kamal::Configuration
env_tags.detect { |t| t.name == name.to_s } env_tags.detect { |t| t.name == name.to_s }
end end
def proxy_publish_args
argumentize "--publish", [ "#{PROXY_HTTP_PORT}:#{PROXY_HTTP_PORT}", "#{PROXY_HTTPS_PORT}:#{PROXY_HTTPS_PORT}" ]
end
def proxy_image
"basecamp/kamal-proxy:#{PROXY_MINIMUM_VERSION}"
end
def proxy_container_name
"kamal-proxy"
end
def proxy_config_volume
Kamal::Configuration::Volume.new \
host_path: File.join(proxy_directory, "config"),
container_path: "/home/kamal-proxy/.config/kamal-proxy"
end
def to_h def to_h
{ {

View File

@@ -9,6 +9,12 @@
# #
# They are application specific, so are not shared when multiple applications # They are application specific, so are not shared when multiple applications
# run on the same proxy. # run on the same proxy.
#
# The proxy is enabled by default on the primary role, but can be disabled by
# setting `proxy: false`.
#
# It is disabled by default on all other roles, but can be enabled by setting
# `proxy: true`, or providing a proxy configuration.
proxy: proxy:
# Host # Host

View File

@@ -35,13 +35,14 @@ servers:
hosts: hosts:
- 172.1.0.3 - 172.1.0.3
- 172.1.0.4: experiment1 - 172.1.0.4: experiment1
proxy: true
cmd: "bin/jobs" cmd: "bin/jobs"
options: options:
memory: 2g memory: 2g
cpus: 4 cpus: 4
logging: logging:
... ...
proxy:
...
labels: labels:
my-label: workers my-label: workers
env: env:

View File

@@ -1,36 +1,23 @@
class Kamal::Configuration::Proxy class Kamal::Configuration::Proxy
include Kamal::Configuration::Validation include Kamal::Configuration::Validation
MINIMUM_VERSION = "v0.3.0"
DEFAULT_HTTP_PORT = 80
DEFAULT_HTTPS_PORT = 443
DEFAULT_IMAGE = "basecamp/kamal-proxy:#{MINIMUM_VERSION}"
DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ] DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
CONTAINER_NAME = "kamal-proxy"
delegate :argumentize, :optionize, to: Kamal::Utils delegate :argumentize, :optionize, to: Kamal::Utils
def initialize(config:) attr_reader :config, :proxy_config
def initialize(config:, proxy_config:, context: "proxy")
@config = config @config = config
@proxy_config = config.raw_config.proxy || {} @proxy_config = proxy_config
validate! proxy_config, with: Kamal::Configuration::Validator::Proxy validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
end end
def app_port def app_port
proxy_config.fetch("app_port", 80) proxy_config.fetch("app_port", 80)
end end
def image
proxy_config.fetch("image", DEFAULT_IMAGE)
end
def container_name
"kamal-proxy"
end
def publish_args
argumentize "--publish", [ "#{DEFAULT_HTTP_PORT}:#{DEFAULT_HTTP_PORT}", "#{DEFAULT_HTTPS_PORT}:#{DEFAULT_HTTPS_PORT}" ]
end
def ssl? def ssl?
proxy_config.fetch("ssl", false) proxy_config.fetch("ssl", false)
end end
@@ -56,19 +43,19 @@ class Kamal::Configuration::Proxy
}.compact }.compact
end end
def deploy_command_args def deploy_command_args(target:)
optionize deploy_options optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options)
end end
def config_volume def remove_command_args(target:)
Kamal::Configuration::Volume.new \ optionize({ target: "#{target}:#{app_port}" })
host_path: File.join(config.proxy_directory, "config"), end
container_path: "/home/kamal-proxy/.config/kamal-proxy"
def merge(other)
self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
end end
private private
attr_reader :config, :proxy_config
def seconds_duration(value) def seconds_duration(value)
value ? "#{value}s" : nil value ? "#{value}s" : nil
end end

View File

@@ -3,7 +3,7 @@ class Kamal::Configuration::Role
delegate :argumentize, :optionize, to: Kamal::Utils delegate :argumentize, :optionize, to: Kamal::Utils
attr_reader :name, :config, :specialized_env, :specialized_logging attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_proxy
alias to_s name alias to_s name
@@ -23,6 +23,8 @@ class Kamal::Configuration::Role
@specialized_logging = Kamal::Configuration::Logging.new \ @specialized_logging = Kamal::Configuration::Logging.new \
logging_config: specializations.fetch("logging", {}), logging_config: specializations.fetch("logging", {}),
context: "servers/#{name}/logging" context: "servers/#{name}/logging"
initialize_specialized_proxy
end end
def primary_host def primary_host
@@ -65,6 +67,14 @@ class Kamal::Configuration::Role
@logging ||= config.logging.merge(specialized_logging) @logging ||= config.logging.merge(specialized_logging)
end end
def proxy
@proxy ||= config.proxy.merge(specialized_proxy) if running_proxy?
end
def running_proxy?
@running_proxy
end
def stop_args def stop_args
# When deploying with the proxy, kamal-proxy will drain request before returning so we don't need to wait. # When deploying with the proxy, kamal-proxy will drain request before returning so we don't need to wait.
timeout = running_proxy? ? nil : config.drain_timeout timeout = running_proxy? ? nil : config.drain_timeout
@@ -98,16 +108,8 @@ class Kamal::Configuration::Role
end end
def running_proxy?
if specializations["proxy"].nil?
primary?
else
specializations["proxy"]
end
end
def primary? def primary?
self == @config.primary_role name == @config.primary_role_name
end end
@@ -144,6 +146,27 @@ class Kamal::Configuration::Role
end end
private private
def initialize_specialized_proxy
proxy_specializations = specializations["proxy"]
if primary?
# only false means no proxy for non-primary roles
@running_proxy = proxy_specializations != false
else
# false and nil both mean no proxy for non-primary roles
@running_proxy = !!proxy_specializations
end
if running_proxy?
proxy_config = proxy_specializations == true || proxy_specializations.nil? ? {} : proxy_specializations
@specialized_proxy = Kamal::Configuration::Proxy.new \
config: config,
proxy_config: proxy_config,
context: "servers/#{name}/proxy"
end
end
def tagged_hosts def tagged_hosts
{}.tap do |tagged_hosts| {}.tap do |tagged_hosts|
extract_hosts_from_config.map do |host_config| extract_hosts_from_config.map do |host_config|

View File

@@ -24,7 +24,9 @@ class Kamal::Configuration::Validator
example_value = example[key] example_value = example[key]
if example_value == "..." if example_value == "..."
validate_type! value, *(Array if key == :servers), Hash unless key.to_s == "proxy" && boolean?(value.class)
validate_type! value, *(Array if key == :servers), Hash
end
elsif key == "hosts" elsif key == "hosts"
validate_servers! value validate_servers! value
elsif example_value.is_a?(Array) elsif example_value.is_a?(Array)

View File

@@ -1,9 +1,11 @@
class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
def validate! def validate!
super unless config.nil?
super
if config["host"].blank? && config["ssl"] if config["host"].blank? && config["ssl"]
error "Must set a host to enable automatic SSL" error "Must set a host to enable automatic SSL"
end
end end
end end
end end

View File

@@ -388,6 +388,15 @@ class CliAppTest < CliTestCase
end end
end end
test "boot proxy with role specific config" do
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("123") # old version
run_command("boot", config: :with_proxy_roles, host: nil).tap do |output|
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target \"123:80\" --deploy-timeout \"6s\" --drain-timeout \"30s\" --target-timeout \"10s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", output
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web2 --target \"123:80\" --deploy-timeout \"6s\" --drain-timeout \"30s\" --target-timeout \"15s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", output
end
end
private private
def run_command(*command, config: :with_accessories, host: "1.1.1.1", allow_execute_error: false) def run_command(*command, config: :with_accessories, host: "1.1.1.1", allow_execute_error: false)
stdouted do stdouted do

View File

@@ -4,7 +4,7 @@ class CliProxyTest < CliTestCase
test "boot" do test "boot" do
run_command("boot").tap do |output| run_command("boot").tap do |output|
assert_match "docker login", output assert_match "docker login", output
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", output assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}", output
end end
end end
@@ -18,11 +18,11 @@ class CliProxyTest < CliTestCase
exception = assert_raises do exception = assert_raises do
run_command("boot").tap do |output| run_command("boot").tap do |output|
assert_match "docker login", output assert_match "docker login", output
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", output assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}", output
end end
end end
assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::Proxy::MINIMUM_VERSION}" assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
ensure ensure
Thread.report_on_exception = false Thread.report_on_exception = false
end end
@@ -31,12 +31,12 @@ class CliProxyTest < CliTestCase
Thread.report_on_exception = false Thread.report_on_exception = false
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2") .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
.returns(Kamal::Configuration::Proxy::MINIMUM_VERSION) .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
.at_least_once .at_least_once
run_command("boot").tap do |output| run_command("boot").tap do |output|
assert_match "docker login", output assert_match "docker login", output
assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", output assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}", output
end end
ensure ensure
Thread.report_on_exception = false Thread.report_on_exception = false
@@ -57,13 +57,13 @@ class CliProxyTest < CliTestCase
assert_match "docker container stop kamal-proxy on 1.1.1.1", output assert_match "docker container stop kamal-proxy on 1.1.1.1", output
assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.1", output assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik 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 "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE} on 1.1.1.1", output assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image} 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 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 stop kamal-proxy on 1.1.1.2", output
assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.2", output assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik 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 "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.2", output
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE} on 1.1.1.2", output assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image} 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 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
end end
@@ -189,7 +189,7 @@ class CliProxyTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2") .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
.returns(Kamal::Configuration::Proxy::MINIMUM_VERSION) .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) 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}}'") .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}}'")
@@ -205,7 +205,7 @@ class CliProxyTest < CliTestCase
assert_match "/usr/bin/env mkdir -p .kamal", output assert_match "/usr/bin/env mkdir -p .kamal", output
assert_match "docker network create kamal", output assert_match "docker network create kamal", output
assert_match "docker login -u [REDACTED] -p [REDACTED]", output assert_match "docker login -u [REDACTED] -p [REDACTED]", output
assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" basecamp/kamal-proxy:#{Kamal::Configuration::Proxy::MINIMUM_VERSION}", output assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}", output
assert_match "/usr/bin/env mkdir -p .kamal", 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 %r{docker rename app-web-latest app-web-latest_replaced_.*}, output
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/env/roles", output assert_match "/usr/bin/env mkdir -p .kamal/apps/app/env/roles", output
@@ -228,7 +228,7 @@ class CliProxyTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2") .with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
.returns(Kamal::Configuration::Proxy::MINIMUM_VERSION) .returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) 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}}'") .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}}'")

View File

@@ -113,6 +113,19 @@ class CommandsAppTest < ActiveSupport::TestCase
new_command.info.join(" ") new_command.info.join(" ")
end end
test "deploy" do
assert_equal \
"docker exec kamal-proxy kamal-proxy deploy app-web --target \"172.1.0.2:80\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"",
new_command.deploy(target: "172.1.0.2").join(" ")
end
test "remove" do
assert_equal \
"docker exec kamal-proxy kamal-proxy remove app-web --target \"172.1.0.2:80\"",
new_command.remove(target: "172.1.0.2").join(" ")
end
test "logs" do test "logs" do
assert_equal \ assert_equal \

View File

@@ -15,13 +15,13 @@ class CommandsProxyTest < ActiveSupport::TestCase
test "run" do test "run" do
assert_equal \ assert_equal \
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}",
new_command.run.join(" ") new_command.run.join(" ")
end end
test "run with ports configured" do test "run with ports configured" do
assert_equal \ assert_equal \
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}",
new_command.run.join(" ") new_command.run.join(" ")
end end
@@ -29,7 +29,7 @@ class CommandsProxyTest < ActiveSupport::TestCase
@config.delete(:proxy) @config.delete(:proxy)
assert_equal \ assert_equal \
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-opt max-size=\"10m\" #{KAMAL.config.proxy_image}",
new_command.run.join(" ") new_command.run.join(" ")
end end
@@ -37,7 +37,7 @@ class CommandsProxyTest < ActiveSupport::TestCase
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } } @config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
assert_equal \ assert_equal \
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" #{Kamal::Configuration::Proxy::DEFAULT_IMAGE}", "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --publish 80:80 --publish 443:443 --volume /var/run/docker.sock:/var/run/docker.sock --volume $(pwd)/.kamal/proxy/config:/home/kamal-proxy/.config/kamal-proxy --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" #{KAMAL.config.proxy_image}",
new_command.run.join(" ") new_command.run.join(" ")
end end
@@ -107,18 +107,6 @@ class CommandsProxyTest < ActiveSupport::TestCase
new_command.follow_logs(host: @config[:servers].first, grep: "hello!") new_command.follow_logs(host: @config[:servers].first, grep: "hello!")
end end
test "deploy" do
assert_equal \
"docker exec kamal-proxy kamal-proxy deploy service --target \"172.1.0.2:80\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"",
new_command.deploy("service", target: "172.1.0.2").join(" ")
end
test "remove" do
assert_equal \
"docker exec kamal-proxy kamal-proxy remove service --target \"172.1.0.2:80\"",
new_command.remove("service", target: "172.1.0.2").join(" ")
end
test "version" do test "version" do
assert_equal \ assert_equal \
"docker inspect kamal-proxy --format '{{.Config.Image}}' | cut -d: -f2", "docker inspect kamal-proxy --format '{{.Config.Image}}' | cut -d: -f2",

View File

@@ -250,6 +250,14 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
assert_equal [ "-t", 30 ], config_with_roles.role(:workers).stop_args assert_equal [ "-t", 30 ], config_with_roles.role(:workers).stop_args
end end
test "role specific proxy config" do
@deploy_with_roles[:proxy] = { "response_timeout" => 15 }
@deploy_with_roles[:servers]["workers"]["proxy"] = { "response_timeout" => 18 }
assert_equal "15s", config_with_roles.role(:web).proxy.deploy_options[:"target-timeout"]
assert_equal "18s", config_with_roles.role(:workers).proxy.deploy_options[:"target-timeout"]
end
private private
def config def config
Kamal::Configuration.new(@deploy) Kamal::Configuration.new(@deploy)

View File

@@ -2,12 +2,12 @@ service: app
image: dhh/app image: dhh/app
servers: servers:
web_chicago: web_chicago:
proxy: true proxy: {}
hosts: hosts:
- 1.1.1.1 - 1.1.1.1
- 1.1.1.2 - 1.1.1.2
web_tokyo: web_tokyo:
proxy: true proxy: {}
hosts: hosts:
- 1.1.1.3 - 1.1.1.3
- 1.1.1.4 - 1.1.1.4

View File

@@ -1,6 +1,6 @@
x-web: &web x-web: &web
proxy: true proxy: {}
service: app service: app
image: dhh/app image: dhh/app

View File

@@ -0,0 +1,46 @@
service: app
image: dhh/app
servers:
web:
hosts:
- "1.1.1.1"
- "1.1.1.2"
web2:
hosts:
- "1.1.1.3"
- "1.1.1.4"
proxy:
response_timeout: 15
registry:
username: user
password: pw
builder:
arch: amd64
proxy:
response_timeout: 10
accessories:
mysql:
image: mysql:5.7
host: 1.1.1.3
port: 3306
env:
clear:
MYSQL_ROOT_HOST: '%'
secret:
- MYSQL_ROOT_PASSWORD
files:
- test/fixtures/files/my.cnf:/etc/mysql/my.cnf
directories:
- data:/var/lib/mysql
redis:
image: redis:latest
roles:
- web
port: 6379
directories:
- data:/data
readiness_delay: 0
deploy_timeout: 6