Merge branch 'basecamp:main' into buildpacks

This commit is contained in:
Nick Hammond
2025-05-16 15:21:49 -07:00
committed by GitHub
13 changed files with 67 additions and 48 deletions

View File

@@ -26,16 +26,12 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
ruby-version: ruby-version:
- "3.1"
- "3.2" - "3.2"
- "3.3" - "3.3"
- "3.4" - "3.4"
gemfile: gemfile:
- Gemfile - Gemfile
- gemfiles/rails_edge.gemfile - gemfiles/rails_edge.gemfile
exclude:
- ruby-version: "3.1"
gemfile: gemfiles/rails_edge.gemfile
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }} name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

View File

@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
kamal (2.6.0) kamal (2.6.1)
activesupport (>= 7.0) activesupport (>= 7.0)
base64 (~> 0.2) base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)

View File

@@ -14,6 +14,10 @@ class Kamal::Cli::Build < Kamal::Cli::Base
def push def push
cli = self cli = self
# Ensure pre-connect hooks run before the build, they may needed for a remote builder
# or the pre-build hooks.
pre_connect_if_required
ensure_docker_installed ensure_docker_installed
login_to_registry_locally login_to_registry_locally

View File

@@ -120,18 +120,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
execute *KAMAL.proxy.ensure_apps_config_directory execute *KAMAL.proxy.ensure_apps_config_directory
execute *KAMAL.proxy.run execute *KAMAL.proxy.run
KAMAL.roles_on(host).select(&:running_proxy?).each do |role|
app = KAMAL.app(role: role, host: host)
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
if endpoint.present?
info "Deploying #{endpoint} for role `#{role}` on #{host}..."
execute *app.deploy(target: endpoint)
end
end
end end
run_hook "post-proxy-reboot", hosts: host_list run_hook "post-proxy-reboot", hosts: host_list
end end

View File

@@ -11,7 +11,7 @@ class Kamal::Commander::Specifics
@primary_role = primary_or_first_role(roles_on(primary_host)) @primary_role = primary_or_first_role(roles_on(primary_host))
stable_sort!(roles) { |role| role == primary_role ? 0 : 1 } stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 } sort_primary_role_hosts_first!(hosts)
end end
def roles_on(host) def roles_on(host)
@@ -19,7 +19,7 @@ class Kamal::Commander::Specifics
end end
def app_hosts def app_hosts
config.app_hosts & specified_hosts @app_hosts ||= sort_primary_role_hosts_first!(config.app_hosts & specified_hosts)
end end
def proxy_hosts def proxy_hosts
@@ -55,4 +55,8 @@ class Kamal::Commander::Specifics
specified_hosts specified_hosts
end end
end end
def sort_primary_role_hosts_first!(hosts)
stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
end
end end

View File

@@ -63,7 +63,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_config: @raw_config.key?(:proxy) ? @raw_config.proxy : {}) @proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy)
@proxy_boot = Proxy::Boot.new(config: self) @proxy_boot = Proxy::Boot.new(config: self)
@ssh = Ssh.new(config: self) @ssh = Ssh.new(config: self)
@sshkit = Sshkit.new(config: self) @sshkit = Sshkit.new(config: self)

View File

@@ -10,11 +10,6 @@
# They are application-specific, so they are not shared when multiple applications # They are application-specific, so they 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:
# Hosts # Hosts
@@ -113,3 +108,30 @@ proxy:
response_headers: response_headers:
- X-Request-ID - X-Request-ID
- X-Request-Start - X-Request-Start
# Enabling/disabling the proxy on roles
#
# The proxy is enabled by default on the primary role but can be disabled by
# setting `proxy: false` in the primary role's configuration.
#
# ```yaml
# servers:
# web:
# hosts:
# - ...
# proxy: false
# ```
#
# It is disabled by default on all other roles but can be enabled by setting
# `proxy: true` or providing a proxy configuration for that role.
#
# ```yaml
# servers:
# web:
# hosts:
# - ...
# web2:
# hosts:
# - ...
# proxy: true
# ```

View File

@@ -11,6 +11,7 @@ class Kamal::Configuration::Proxy
def initialize(config:, proxy_config:, context: "proxy") def initialize(config:, proxy_config:, context: "proxy")
@config = config @config = config
@proxy_config = proxy_config @proxy_config = proxy_config
@proxy_config = {} if @proxy_config.nil?
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
end end

View File

@@ -1,3 +1,3 @@
module Kamal module Kamal
VERSION = "2.6.0" VERSION = "2.6.1"
end end

View File

@@ -21,6 +21,7 @@ class CliBuildTest < CliTestCase
.returns("") .returns("")
run_command("push", "--verbose").tap do |output| run_command("push", "--verbose").tap do |output|
assert_hook_ran "pre-connect", output
assert_hook_ran "pre-build", output assert_hook_ran "pre-build", output
assert_match /Cloning repo into build directory/, output assert_match /Cloning repo into build directory/, output
assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output

View File

@@ -44,42 +44,20 @@ class CliProxyTest < CliTestCase
end end
test "reboot" do test "reboot" do
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet")
.returns("abcdefabcdef")
.at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with { |*args| args[0..1] == [ :sh, "-c" ] }
.returns("123")
.at_least_once
run_command("reboot", "-y").tap do |output| run_command("reboot", "-y").tap do |output|
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 "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 "mkdir -p .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 "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 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 "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.2", output
assert_match "mkdir -p .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.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::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
end end
test "reboot --rolling" do test "reboot --rolling" do
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet")
.returns("abcdefabcdef")
.at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with { |*args| args[0..1] == [ :sh, "-c" ] }
.returns("123")
.at_least_once
run_command("reboot", "--rolling", "-y").tap do |output| run_command("reboot", "--rolling", "-y").tap do |output|
assert_match "Running docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output assert_match "Running docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output
end end

View File

@@ -149,6 +149,12 @@ class CommanderTest < ActiveSupport::TestCase
assert_equal [], @kamal.accessory_hosts assert_equal [], @kamal.accessory_hosts
end end
test "primary role hosts are first" do
configure_with(:deploy_with_roles_workers_primary)
assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @kamal.hosts
assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @kamal.app_hosts
end
private private
def configure_with(variant) def configure_with(variant)
@kamal = Kamal::Commander.new.tap do |kamal| @kamal = Kamal::Commander.new.tap do |kamal|

View File

@@ -0,0 +1,19 @@
service: app
image: dhh/app
servers:
workers:
- 1.1.1.1
- 1.1.1.2
web:
- 1.1.1.3
- 1.1.1.4
env:
REDIS_URL: redis://x/y
registry:
server: registry.digitalocean.com
username: user
password: pw
builder:
arch: amd64
deploy_timeout: 1
primary_role: workers