From 04568dea2f4f252e43451497dace72cbea2dae9a Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Tue, 22 Apr 2025 09:00:22 +0100 Subject: [PATCH] Inherit locks We'll set the KAMAL_LOCK environment when calling run hooks. If set to true we have the lock and the hook will not need to acquire it again if it runs kamal commands. Fixes: https://github.com/basecamp/kamal/issues/1517 --- lib/kamal/cli/base.rb | 8 +++++++- lib/kamal/commander.rb | 2 +- test/cli/main_test.rb | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) 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/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/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