Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom proxy image repository #1202

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions lib/kamal/cli/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,35 @@ def boot

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::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}"
end
execute *KAMAL.proxy.start_or_run
end
end
end

desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
option :image, type: :string, default: Kamal::Configuration::Proxy::DEFAULT_IMAGE, desc: "Repository of the image"
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::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 :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
def boot_config(subcommand)
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])),
*options[:docker_options].map { |option| "--#{option}" }
*(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])),
*options[:docker_options].map { |option| "--#{option}" },
options[:image]
]

on(KAMAL.proxy_hosts) do |host|
execute(*KAMAL.proxy.ensure_proxy_directory)
upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file
upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy.options_file
end
when "get"
on(KAMAL.proxy_hosts) do |host|
Expand Down
1 change: 0 additions & 1 deletion lib/kamal/commands/accessory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions lib/kamal/commands/accessory/proxy.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Kamal::Commands::Accessory::Proxy
delegate :proxy_container_name, to: :config
delegate :proxy, to: :config

def deploy(target:)
proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
Expand All @@ -11,6 +11,6 @@ def remove

private
def proxy_exec(*command)
docker :exec, proxy_container_name, "kamal-proxy", *command
docker :exec, proxy.container_name, "kamal-proxy", *command
end
end
4 changes: 1 addition & 3 deletions lib/kamal/commands/app/proxy.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
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
Expand All @@ -11,6 +9,6 @@ def remove

private
def proxy_exec(*command)
docker :exec, proxy_container_name, "kamal-proxy", *command
docker :exec, config.proxy.container_name, "kamal-proxy", *command
end
end
13 changes: 6 additions & 7 deletions lib/kamal/commands/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ def run
"--detach",
"--restart", "unless-stopped",
"--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
"\$\(#{get_boot_options.join(" ")}\)",
config.proxy_image
"\$\(#{get_boot_options.join(" ")}\)"
end

def start
Expand Down Expand Up @@ -65,23 +64,23 @@ def cleanup_traefik
end

def ensure_proxy_directory
make_directory config.proxy_directory
make_directory config.proxy.directory
end

def remove_proxy_directory
remove_directory config.proxy_directory
remove_directory config.proxy.directory
end

def get_boot_options
combine [ :cat, config.proxy_options_file ], [ :echo, "\"#{config.proxy_options_default.join(" ")}\"" ], by: "||"
combine [ :cat, config.proxy.options_file ], [ :echo, "\"#{config.proxy.options_default.join(" ")}\"" ], by: "||"
end

def reset_boot_options
remove_file config.proxy_options_file
remove_file config.proxy.options_file
end

private
def container_name
config.proxy_container_name
config.proxy.container_name
end
end
59 changes: 0 additions & 59 deletions lib/kamal/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ class Kamal::Configuration

include Validation

PROXY_MINIMUM_VERSION = "v0.8.4"
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
Expand Down Expand Up @@ -241,42 +236,6 @@ def env_tag(name)
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_options_default
[ *proxy_publish_args(PROXY_HTTP_PORT, PROXY_HTTPS_PORT), *proxy_logging_args(PROXY_LOG_MAX_SIZE) ]
end

def proxy_image
"basecamp/kamal-proxy:#{PROXY_MINIMUM_VERSION}"
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 to_h
{
roles: role_names,
Expand Down Expand Up @@ -343,15 +302,6 @@ def ensure_valid_kamal_version
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

Expand Down Expand Up @@ -383,15 +333,6 @@ def ensure_unique_hosts_for_ssl_roles
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
Expand Down
1 change: 0 additions & 1 deletion lib/kamal/configuration/docs/proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# It is disabled by default on all other roles but can be enabled by setting
# `proxy: true` or providing a proxy configuration.
proxy:

# Hosts
#
# The hosts that will be used to serve the app. The proxy will only route requests
Expand Down
57 changes: 56 additions & 1 deletion lib/kamal/configuration/proxy.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
class Kamal::Configuration::Proxy
include Kamal::Configuration::Validation

HTTP_PORT = 80
HTTPS_PORT = 443
LOG_MAX_SIZE = "10m"
MINIMUM_VERSION = "v0.8.4"
DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
CONTAINER_NAME = "kamal-proxy"
DEFAULT_CONTAINER_NAME = "kamal-proxy"
DEFAULT_IMAGE = "basecamp/kamal-proxy:#{MINIMUM_VERSION}"

delegate :argumentize, :optionize, to: Kamal::Utils

Expand All @@ -14,6 +19,10 @@ def initialize(config:, proxy_config:, context: "proxy")
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
end

def container_name
DEFAULT_CONTAINER_NAME
end

def app_port
proxy_config.fetch("app_port", 80)
end
Expand Down Expand Up @@ -47,6 +56,34 @@ def deploy_options
}.compact
end

def directory
File.join config.run_directory, "proxy"
end

def options_file
File.join directory, "options"
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, HTTP_PORT ].compact.join(":")
publish_https = [ bind_ip, https_port, 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 options_default
[ *publish_args(HTTP_PORT, HTTPS_PORT), *logging_args(LOG_MAX_SIZE), DEFAULT_IMAGE ]
end

def deploy_command_args(target:)
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
end
Expand All @@ -59,4 +96,22 @@ def merge(other)
def seconds_duration(value)
value ? "#{value}s" : nil
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 format_bind_ip(ip)
# Ensure IPv6 address inside square brackets - e.g. [::1]
if ip =~ Resolv::IPv6::Regex && ip !~ /\[.*\]/
"[#{ip}]"
else
ip
end
end
end
Loading