Pass secrets to pre/post deploy hooks
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
Reference in New Issue
Block a user