Pass secrets to pre/post deploy hooks

This commit is contained in:
Donal McBreen
2024-09-04 16:24:10 +01:00
parent a68294c384
commit 1522d94ac9
6 changed files with 57 additions and 29 deletions

View File

@@ -33,7 +33,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
with_lock do with_lock do
run_hook "pre-deploy" run_hook "pre-deploy", secrets: true
say "Ensure Traefik is running...", :magenta say "Ensure Traefik is running...", :magenta
invoke "kamal:cli:traefik:boot", [], invoke_options invoke "kamal:cli:traefik:boot", [], invoke_options
@@ -48,7 +48,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
end end
run_hook "post-deploy", runtime: runtime.round run_hook "post-deploy", secrets: true, runtime: runtime.round
end end
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login" desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
@@ -66,7 +66,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
with_lock do with_lock do
run_hook "pre-deploy" run_hook "pre-deploy", secrets: true
say "Detect stale containers...", :magenta say "Detect stale containers...", :magenta
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true) invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
@@ -75,7 +75,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
end end
run_hook "post-deploy", runtime: runtime.round run_hook "post-deploy", secrets: true, runtime: runtime.round
end end
desc "rollback [VERSION]", "Rollback app to VERSION" desc "rollback [VERSION]", "Rollback app to VERSION"
@@ -89,7 +89,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
old_version = nil old_version = nil
if container_available?(version) if container_available?(version)
run_hook "pre-deploy" run_hook "pre-deploy", secrets: true
invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version) invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
rolled_back = true rolled_back = true
@@ -99,7 +99,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
end end
run_hook "post-deploy", runtime: runtime.round if rolled_back run_hook "post-deploy", secrets: true, runtime: runtime.round if rolled_back
end end
desc "details", "Show details about all containers" desc "details", "Show details about all containers"

View File

@@ -1,6 +1,9 @@
class Kamal::Commands::Hook < Kamal::Commands::Base class Kamal::Commands::Hook < Kamal::Commands::Base
def run(hook, **details) def run(hook, secrets: false, **details)
[ hook_file(hook), env: tags(**details).env ] env = tags(**details).env
env.merge!(config.secrets.to_h) if secrets
[ hook_file(hook), env: env ]
end end
def hook_exists?(hook) def hook_exists?(hook)

View File

@@ -6,8 +6,7 @@ class Kamal::Secrets
end end
def [](key) def [](key)
@secrets ||= parse_secrets secrets.fetch(key)
@secrets.fetch(key)
rescue KeyError rescue KeyError
if secrets_file if secrets_file
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_file}" raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_file}"
@@ -16,7 +15,15 @@ class Kamal::Secrets
end end
end end
def to_h
secrets
end
private private
def secrets
@secrets ||= parse_secrets
end
def parse_secrets def parse_secrets
if secrets_file if secrets_file
interrupting_parent_on_error { Dotenv.parse(secrets_file) } interrupting_parent_on_error { Dotenv.parse(secrets_file) }

View File

@@ -40,7 +40,7 @@ class CliTestCase < ActiveSupport::TestCase
.with(:docker, :buildx, :inspect, "kamal-local-docker-container") .with(:docker, :buildx, :inspect, "kamal-local-docker-container")
end end
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false) def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false, secrets: false)
whoami = `whoami`.chomp whoami = `whoami`.chomp
performer = Kamal::Git.email.presence || whoami performer = Kamal::Git.email.presence || whoami
service = service_version.split("@").first service = service_version.split("@").first
@@ -58,6 +58,7 @@ class CliTestCase < ActiveSupport::TestCase
KAMAL_COMMAND=\"#{command}\"\s KAMAL_COMMAND=\"#{command}\"\s
#{"KAMAL_SUBCOMMAND=\\\"#{subcommand}\\\"\\s" if subcommand} #{"KAMAL_SUBCOMMAND=\\\"#{subcommand}\\\"\\s" if subcommand}
#{"KAMAL_RUNTIME=\\\"\\d+\\\"\\s" if runtime} #{"KAMAL_RUNTIME=\\\"\\d+\\\"\\s" if runtime}
#{"DB_PASSWORD=\"secret\"\\s" if secrets}
;\s/usr/bin/env\s\.kamal/hooks/#{hook} }x ;\s/usr/bin/env\s\.kamal/hooks/#{hook} }x
assert_match expected, output assert_match expected, output

View File

@@ -43,27 +43,29 @@ class CliMainTest < CliTestCase
end end
test "deploy" do test "deploy" do
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true } 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:registry:login", [], invoke_options.merge(skip_local: false)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: 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:build:deliver", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik: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: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:app:boot", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], 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) Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" } hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" }
run_command("deploy", "--verbose").tap do |output| run_command("deploy", "--verbose").tap do |output|
assert_hook_ran "pre-connect", output, **hook_variables assert_hook_ran "pre-connect", output, **hook_variables
assert_match /Log into image registry/, output assert_match /Log into image registry/, output
assert_match /Build and push app image/, output assert_match /Build and push app image/, output
assert_hook_ran "pre-deploy", output, **hook_variables assert_hook_ran "pre-deploy", output, **hook_variables, secrets: true
assert_match /Ensure Traefik is running/, output assert_match /Ensure Traefik is running/, output
assert_match /Detect stale containers/, output assert_match /Detect stale containers/, output
assert_match /Prune old containers and images/, output assert_match /Prune old containers and images/, output
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true assert_hook_ran "post-deploy", output, **hook_variables, runtime: true, secrets: true
end
end end
end end

View File

@@ -39,6 +39,21 @@ class CommandsHookTest < ActiveSupport::TestCase
], new_command(hooks_path: "custom/hooks/path").run("foo") ], new_command(hooks_path: "custom/hooks/path").run("foo")
end end
test "hook with secrets" do
with_test_secrets("secrets" => "DB_PASSWORD=secret") do
assert_equal [
".kamal/hooks/foo",
{ env: {
"KAMAL_RECORDED_AT" => @recorded_at,
"KAMAL_PERFORMER" => @performer,
"KAMAL_VERSION" => "123",
"KAMAL_SERVICE_VERSION" => "app@123",
"KAMAL_SERVICE" => "app",
"DB_PASSWORD" => "secret" } }
], new_command(env: { "secret" => [ "DB_PASSWORD" ] }).run("foo", secrets: true)
end
end
private private
def new_command(**extra_config) def new_command(**extra_config)
Kamal::Commands::Hook.new(Kamal::Configuration.new(@config.merge(**extra_config), version: "123")) Kamal::Commands::Hook.new(Kamal::Configuration.new(@config.merge(**extra_config), version: "123"))