Inline dotenv kamal secrets calls
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
require "thor"
|
require "thor"
|
||||||
require "dotenv"
|
|
||||||
require "kamal/sshkit_with_ext"
|
require "kamal/sshkit_with_ext"
|
||||||
|
|
||||||
module Kamal::Cli
|
module Kamal::Cli
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
require "dotenv"
|
||||||
|
|
||||||
class Kamal::Secrets
|
class Kamal::Secrets
|
||||||
attr_reader :secrets_file
|
attr_reader :secrets_file
|
||||||
|
|
||||||
|
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
||||||
|
|
||||||
def initialize(destination: nil)
|
def initialize(destination: nil)
|
||||||
@secrets_file = [ *(".kamal/secrets.#{destination}" if destination), ".kamal/secrets" ].find { |f| File.exist?(f) }
|
@secrets_file = [ *(".kamal/secrets.#{destination}" if destination), ".kamal/secrets" ].find { |f| File.exist?(f) }
|
||||||
end
|
end
|
||||||
@@ -26,7 +30,7 @@ class Kamal::Secrets
|
|||||||
|
|
||||||
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) }
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|||||||
37
lib/kamal/secrets/dotenv/inline_command_substitution.rb
Normal file
37
lib/kamal/secrets/dotenv/inline_command_substitution.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
||||||
|
class << self
|
||||||
|
def install!
|
||||||
|
::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub }
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(value, _env, overwrite: false)
|
||||||
|
# Process interpolated shell commands
|
||||||
|
value.gsub(Dotenv::Substitutions::Command.singleton_class::INTERPOLATED_SHELL_COMMAND) do |*|
|
||||||
|
# Eliminate opening and closing parentheses
|
||||||
|
command = $LAST_MATCH_INFO[:cmd][1..-2]
|
||||||
|
|
||||||
|
if $LAST_MATCH_INFO[:backslash]
|
||||||
|
# Command is escaped, don't replace it.
|
||||||
|
$LAST_MATCH_INFO[0][1..]
|
||||||
|
else
|
||||||
|
if command =~ /\A\s*kamal\s*secrets\s+/
|
||||||
|
# Inline the command
|
||||||
|
capture_stdout { Kamal::Cli::Main.start(command.shellsplit[1..]) }.chomp
|
||||||
|
else
|
||||||
|
# Execute the command and return the value
|
||||||
|
`#{command}`.chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def capture_stdout
|
||||||
|
old_stdout = $stdout
|
||||||
|
$stdout = StringIO.new
|
||||||
|
yield
|
||||||
|
$stdout.string
|
||||||
|
ensure
|
||||||
|
$stdout = old_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
15
test/secrets/dotenv_inline_command_substitution_test.rb
Normal file
15
test/secrets/dotenv_inline_command_substitution_test.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class SecretsInlineCommandSubstitution < SecretAdapterTestCase
|
||||||
|
test "inlines kamal secrets commands" do
|
||||||
|
Kamal::Cli::Main.expects(:start).with { |command| puts "results"; command == [ "secrets", "fetch", "..." ] }
|
||||||
|
substituted = Kamal::Secrets::Dotenv::InlineCommandSubstitution.call("FOO=$(kamal secrets fetch ...)", nil, overwrite: false)
|
||||||
|
assert_equal "FOO=results", substituted
|
||||||
|
end
|
||||||
|
|
||||||
|
test "executes other commands" do
|
||||||
|
Kamal::Secrets::Dotenv::InlineCommandSubstitution.stubs(:`).with("blah").returns("results")
|
||||||
|
substituted = Kamal::Secrets::Dotenv::InlineCommandSubstitution.call("FOO=$(blah)", nil, overwrite: false)
|
||||||
|
assert_equal "FOO=results", substituted
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user