Skip to content

Commit

Permalink
Merge pull request #928 from basecamp/kamal-secrets-inline-aware
Browse files Browse the repository at this point in the history
Make the secrets commands inline aware
  • Loading branch information
djmb authored Sep 10, 2024
2 parents 5aa3d1a + 06f4caa commit 63d0b5d
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 31 deletions.
33 changes: 20 additions & 13 deletions lib/kamal/cli/secrets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,45 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
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 :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)
results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
puts JSON.dump(results).shellescape
rescue => e
handle_error(e)
handle_output(inline: options[:inline]) do
results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
JSON.dump(results).shellescape
end
end

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)
parsed_secrets = JSON.parse(secrets)
handle_output(inline: options[:inline]) do
parsed_secrets = JSON.parse(secrets)

if (value = parsed_secrets[name]).nil?
value = parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
end
value = parsed_secrets[name] || parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last

raise "Could not find secret #{name}" if value.nil?
raise "Could not find secret #{name}" if value.nil?

puts value
rescue => e
handle_error(e)
value
end
end

private
def adapter(adapter)
Kamal::Secrets::Adapters.lookup(adapter)
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)
$stderr.puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
$stderr.puts e.backtrace if ENV["VERBOSE"]

Process.kill("INT", Process.ppid) if ENV["KAMAL_SECRETS_INT_PARENT"]
exit 1
end
end
10 changes: 1 addition & 9 deletions lib/kamal/secrets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,9 @@ def secrets

def parse_secrets
if secrets_file
interrupting_parent_on_error { ::Dotenv.parse(secrets_file) }
::Dotenv.parse(secrets_file)
else
{}
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
11 changes: 3 additions & 8 deletions lib/kamal/secrets/dotenv/inline_command_substitution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def call(value, _env, overwrite: false)
else
if command =~ /\A\s*kamal\s*secrets\s+/
# Inline the command
capture_stdout { Kamal::Cli::Main.start(command.shellsplit[1..]) }.chomp
inline_secrets_command(command)
else
# Execute the command and return the value
`#{command}`.chomp
Expand All @@ -25,13 +25,8 @@ def call(value, _env, overwrite: false)
end
end

def capture_stdout
old_stdout = $stdout
$stdout = StringIO.new
yield
$stdout.string
ensure
$stdout = old_stdout
def inline_secrets_command(command)
Kamal::Cli::Main.start(command.shellsplit[1..] + [ "--inline" ]).chomp
end
end
end
2 changes: 1 addition & 1 deletion test/secrets/dotenv_inline_command_substitution_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class SecretsInlineCommandSubstitution < SecretAdapterTestCase
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)
assert_equal "FOO=results", substituted
end
Expand Down

0 comments on commit 63d0b5d

Please sign in to comment.