Compare commits
1 Commits
18f1bbbeac
...
more-robus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
559bb3667b |
@@ -4,9 +4,36 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
||||
::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub }
|
||||
end
|
||||
|
||||
# Improved version of Dotenv::Substitutions::Command's INTERPOLATED_SHELL_COMMAND
|
||||
# Handles:
|
||||
# $(echo 'foo)')
|
||||
# $(echo "foo)")
|
||||
# $(echo foo\))
|
||||
# $(echo "foo\")")
|
||||
# $(echo foo\\)
|
||||
# $(echo 'foo'"'"')')
|
||||
INTERPOLATED_SHELL_COMMAND = /
|
||||
(?<backslash>\\)? # (1) Optional backslash (escaped '$')
|
||||
\$ # (2) Match a literal '$' (start of command)
|
||||
(?<cmd> # (3) Capture the command within '$()' as 'cmd'
|
||||
\( # (4) Require an opening parenthesis '('
|
||||
(?: # (5) Match either:
|
||||
[^()\\'"]+ # - Any non-parens, non-escape, non-quotes (normal chars)
|
||||
| \\ (?!\)) . # - Escaped character (e.g., `\(`, `\'`, `\"`), but **not** `\)` alone
|
||||
| \\\\ \) # - Special case: Match `\\)` as a literal `\)`
|
||||
| '(?:[^'\\]* (?:\\.[^'\\]*)*)' # - Single-quoted strings with escaped quotes (`\'`)
|
||||
| "(?:[^"\\]* (?:\\.[^"\\]*)*)" # - Double-quoted strings with escaped quotes (`\"`)
|
||||
| '(?:[^']*)' (?:"[^"]*")* # - Single-quoted, followed by optional mixed double-quoted parts
|
||||
| "(?:[^"]*)" (?:'[^']*')* # - Double-quoted, followed by optional mixed single-quoted parts
|
||||
| \g<cmd> # - Nested `$()` expressions (recursive call)
|
||||
)* # (6) Repeat to allow full parsing
|
||||
\) # (7) Require a closing parenthesis ')'
|
||||
)
|
||||
/x
|
||||
|
||||
def call(value, _env, overwrite: false)
|
||||
# Process interpolated shell commands
|
||||
value.gsub(Dotenv::Substitutions::Command.singleton_class::INTERPOLATED_SHELL_COMMAND) do |*|
|
||||
value.gsub(INTERPOLATED_SHELL_COMMAND) do |*|
|
||||
# Eliminate opening and closing parentheses
|
||||
command = $LAST_MATCH_INFO[:cmd][1..-2]
|
||||
|
||||
|
||||
@@ -34,6 +34,30 @@ class SecretsTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "secret with open bracket" do
|
||||
with_test_secrets("secrets" => "SECRET1=$(echo 'foo)')") do
|
||||
assert_equal "foo)", Kamal::Secrets.new["SECRET1"]
|
||||
end
|
||||
end
|
||||
|
||||
test "secret with close bracket" do
|
||||
with_test_secrets("secrets" => "SECRET1=$(echo 'foo(')") do
|
||||
assert_equal "foo(", Kamal::Secrets.new["SECRET1"]
|
||||
end
|
||||
end
|
||||
|
||||
test "secret with escaped quote" do
|
||||
with_test_secrets("secrets" => "SECRET1=$(echo \"foo\\\")") do
|
||||
assert_equal "foo", Kamal::Secrets.new["SECRET1"]
|
||||
end
|
||||
end
|
||||
|
||||
test "secret with escaped single quote" do
|
||||
with_test_secrets("secrets" => "SECRET1= $(echo 'foo'\"'\"'bar')") do
|
||||
assert_equal "foo'bar", Kamal::Secrets.new["SECRET1"]
|
||||
end
|
||||
end
|
||||
|
||||
test "destinations" do
|
||||
with_test_secrets("secrets.dest" => "SECRET=DEF", "secrets" => "SECRET=ABC", "secrets-common" => "SECRET=GHI\nSECRET2=JKL") do
|
||||
assert_equal "ABC", Kamal::Secrets.new["SECRET"]
|
||||
|
||||
Reference in New Issue
Block a user