Merge pull request #928 from basecamp/kamal-secrets-inline-aware

Make the secrets commands inline aware
This commit is contained in:
Donal McBreen
2024-09-10 11:08:10 +01:00
committed by GitHub
4 changed files with 26 additions and 32 deletions

View File

@@ -3,26 +3,26 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use" option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
option :account, type: :string, required: true, desc: "The account identifier or username" option :account, type: :string, required: true, desc: "The account identifier or username"
option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from" option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
option :inline, type: :boolean, required: false, hidden: true
def fetch(*secrets) def fetch(*secrets)
handle_output(inline: options[:inline]) do
results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys) results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
puts JSON.dump(results).shellescape JSON.dump(results).shellescape
rescue => e end
handle_error(e)
end end
desc "extract", "Extract a single secret from the results of a fetch call" desc "extract", "Extract a single secret from the results of a fetch call"
option :inline, type: :boolean, required: false, hidden: true
def extract(name, secrets) def extract(name, secrets)
handle_output(inline: options[:inline]) do
parsed_secrets = JSON.parse(secrets) parsed_secrets = JSON.parse(secrets)
if (value = parsed_secrets[name]).nil? value = parsed_secrets[name] || parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
value = parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
end
raise "Could not find secret #{name}" if value.nil? raise "Could not find secret #{name}" if value.nil?
puts value value
rescue => e end
handle_error(e)
end end
private private
@@ -30,11 +30,18 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
Kamal::Secrets::Adapters.lookup(adapter) Kamal::Secrets::Adapters.lookup(adapter)
end end
def handle_output(inline: nil)
yield.tap do |output|
puts output unless inline
end
rescue => e
handle_error(e)
end
def handle_error(e) def handle_error(e)
$stderr.puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m" $stderr.puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
$stderr.puts e.backtrace if ENV["VERBOSE"] $stderr.puts e.backtrace if ENV["VERBOSE"]
Process.kill("INT", Process.ppid) if ENV["KAMAL_SECRETS_INT_PARENT"]
exit 1 exit 1
end end
end end

View File

@@ -30,17 +30,9 @@ class Kamal::Secrets
def parse_secrets def parse_secrets
if secrets_file if secrets_file
interrupting_parent_on_error { ::Dotenv.parse(secrets_file) } ::Dotenv.parse(secrets_file)
else else
{} {}
end end
end end
def interrupting_parent_on_error
# Make any `kamal secrets` calls in dotenv interpolation interrupt this process if there are errors
ENV["KAMAL_SECRETS_INT_PARENT"] = "1"
yield
ensure
ENV.delete("KAMAL_SECRETS_INT_PARENT")
end
end end

View File

@@ -16,7 +16,7 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
else else
if command =~ /\A\s*kamal\s*secrets\s+/ if command =~ /\A\s*kamal\s*secrets\s+/
# Inline the command # Inline the command
capture_stdout { Kamal::Cli::Main.start(command.shellsplit[1..]) }.chomp inline_secrets_command(command)
else else
# Execute the command and return the value # Execute the command and return the value
`#{command}`.chomp `#{command}`.chomp
@@ -25,13 +25,8 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
end end
end end
def capture_stdout def inline_secrets_command(command)
old_stdout = $stdout Kamal::Cli::Main.start(command.shellsplit[1..] + [ "--inline" ]).chomp
$stdout = StringIO.new
yield
$stdout.string
ensure
$stdout = old_stdout
end end
end end
end end

View File

@@ -2,7 +2,7 @@ require "test_helper"
class SecretsInlineCommandSubstitution < SecretAdapterTestCase class SecretsInlineCommandSubstitution < SecretAdapterTestCase
test "inlines kamal secrets commands" do test "inlines kamal secrets commands" do
Kamal::Cli::Main.expects(:start).with { |command| puts "results"; command == [ "secrets", "fetch", "..." ] } Kamal::Cli::Main.expects(:start).with { |command| command == [ "secrets", "fetch", "...", "--inline" ] }.returns("results")
substituted = Kamal::Secrets::Dotenv::InlineCommandSubstitution.call("FOO=$(kamal secrets fetch ...)", nil, overwrite: false) substituted = Kamal::Secrets::Dotenv::InlineCommandSubstitution.call("FOO=$(kamal secrets fetch ...)", nil, overwrite: false)
assert_equal "FOO=results", substituted assert_equal "FOO=results", substituted
end end