Make the secrets commands inline aware
Rather than redirecting the global $stdout, which is not never clever in a threaded program, we'll make the secrets commands aware they are being inlined, so they return the value instead of printing it. Additionally we no longer need to interrupt the parent process on error as we've inlined the command - exit 1 is enough.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user