diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8da7d39c..4e0358b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,16 +26,12 @@ jobs: fail-fast: false matrix: ruby-version: - - "3.1" - "3.2" - "3.3" - "3.4" gemfile: - Gemfile - gemfiles/rails_edge.gemfile - exclude: - - ruby-version: "3.1" - gemfile: gemfiles/rails_edge.gemfile name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }} runs-on: ubuntu-latest env: diff --git a/Gemfile.lock b/Gemfile.lock index bcc38f0a..3fb94ec5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - kamal (2.6.0) + kamal (2.6.1) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) diff --git a/lib/kamal/cli/build.rb b/lib/kamal/cli/build.rb index b9b6a5d5..2327943e 100644 --- a/lib/kamal/cli/build.rb +++ b/lib/kamal/cli/build.rb @@ -14,6 +14,10 @@ class Kamal::Cli::Build < Kamal::Cli::Base def push 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 login_to_registry_locally diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 73707572..0e00af6c 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -120,18 +120,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base execute *KAMAL.proxy.ensure_apps_config_directory 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 run_hook "post-proxy-reboot", hosts: host_list end diff --git a/lib/kamal/commander/specifics.rb b/lib/kamal/commander/specifics.rb index e609baba..d61f6b28 100644 --- a/lib/kamal/commander/specifics.rb +++ b/lib/kamal/commander/specifics.rb @@ -11,7 +11,7 @@ class Kamal::Commander::Specifics @primary_role = primary_or_first_role(roles_on(primary_host)) 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 def roles_on(host) @@ -19,7 +19,7 @@ class Kamal::Commander::Specifics end def app_hosts - config.app_hosts & specified_hosts + @app_hosts ||= sort_primary_role_hosts_first!(config.app_hosts & specified_hosts) end def proxy_hosts @@ -55,4 +55,8 @@ class Kamal::Commander::Specifics specified_hosts 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 diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 26450170..6435fa57 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -63,7 +63,7 @@ class Kamal::Configuration @env = Env.new(config: @raw_config.env || {}, secrets: secrets) @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) @ssh = Ssh.new(config: self) @sshkit = Sshkit.new(config: self) diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 49c11ac8..a127bfea 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -10,11 +10,6 @@ # They are application-specific, so they are not shared when multiple applications # 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: # Hosts @@ -113,3 +108,30 @@ proxy: response_headers: - X-Request-ID - 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 +# ``` diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index b5afbaae..ccb4ac42 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -11,6 +11,7 @@ class Kamal::Configuration::Proxy def initialize(config:, proxy_config:, context: "proxy") @config = config @proxy_config = proxy_config + @proxy_config = {} if @proxy_config.nil? validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context end diff --git a/lib/kamal/version.rb b/lib/kamal/version.rb index 9525c872..d9029c54 100644 --- a/lib/kamal/version.rb +++ b/lib/kamal/version.rb @@ -1,3 +1,3 @@ module Kamal - VERSION = "2.6.0" + VERSION = "2.6.1" end diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index c0a236ad..0bdd6b65 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -21,6 +21,7 @@ class CliBuildTest < CliTestCase .returns("") run_command("push", "--verbose").tap do |output| + assert_hook_ran "pre-connect", output assert_hook_ran "pre-build", output assert_match /Cloning repo into build directory/, output assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index fd131984..e6b0eaea 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -44,42 +44,20 @@ class CliProxyTest < CliTestCase end 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| 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 "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 "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 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| assert_match "Running docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output end diff --git a/test/commander_test.rb b/test/commander_test.rb index 4a3aa2e2..84115f3a 100644 --- a/test/commander_test.rb +++ b/test/commander_test.rb @@ -149,6 +149,12 @@ class CommanderTest < ActiveSupport::TestCase assert_equal [], @kamal.accessory_hosts 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 def configure_with(variant) @kamal = Kamal::Commander.new.tap do |kamal| diff --git a/test/fixtures/deploy_with_roles_workers_primary.yml b/test/fixtures/deploy_with_roles_workers_primary.yml new file mode 100644 index 00000000..4e208c6b --- /dev/null +++ b/test/fixtures/deploy_with_roles_workers_primary.yml @@ -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