feat(secrets): update doppler adapter to use --from option and DOPPLER_TOKEN env

This commit is contained in:
Ralf Schmitz Bongiolo
2024-11-04 19:00:38 -04:00
parent 77cd29f5ad
commit 3069552315
2 changed files with 130 additions and 19 deletions

View File

@@ -1,28 +1,53 @@
class Kamal::Secrets::Adapters::Doppler < Kamal::Secrets::Adapters::Base class Kamal::Secrets::Adapters::Doppler < Kamal::Secrets::Adapters::Base
def requires_account?
false
end
private private
def login(account) def login(*)
unless loggedin?(account) unless loggedin?
`doppler login -y` `doppler login -y`
raise RuntimeError, "Failed to login to Doppler" unless $?.success? raise RuntimeError, "Failed to login to Doppler" unless $?.success?
end end
end end
def loggedin?(account) def loggedin?
`doppler me --json 2> /dev/null` `doppler me --json 2> /dev/null`
$?.success? $?.success?
end end
def fetch_secrets(secrets, account:, session:) def fetch_secrets(secrets, **)
project, config = account.split("/") project_and_config_flags = ""
unless service_token_set?
project, config, _ = secrets.first.split("/")
raise RuntimeError, "Missing project or config from --acount=project/config option" unless project && config unless project && config
raise RuntimeError, "Using --from option or FOLDER/SECRET is not supported by Doppler" if secrets.any?(/\//) raise RuntimeError, "Missing project or config from '--from=project/config' option"
end
items = `doppler secrets get #{secrets.map(&:shellescape).join(" ")} --json -p #{project} -c #{config}` project_and_config_flags = "-p #{project.shellescape} -c #{config.shellescape}"
end
secret_names = secrets.collect { |s| s.split("/").last }
items = `doppler secrets get #{secret_names.map(&:shellescape).join(" ")} --json #{project_and_config_flags}`
raise RuntimeError, "Could not read #{secrets} from Doppler" unless $?.success? raise RuntimeError, "Could not read #{secrets} from Doppler" unless $?.success?
items = JSON.parse(items) items = JSON.parse(items)
items.transform_values { |value| value["computed"] } items.transform_values { |value| value["computed"] }
end end
def service_token_set?
ENV["DOPPLER_TOKEN"] && ENV["DOPPLER_TOKEN"][0, 5] == "dp.st"
end
def check_dependencies!
raise RuntimeError, "Doppler CLI is not installed" unless cli_installed?
end
def cli_installed?
`doppler --version 2> /dev/null`
$?.success?
end
end end

View File

@@ -6,6 +6,7 @@ class DopplerAdapterTest < SecretAdapterTestCase
end end
test "fetch" do test "fetch" do
stub_ticks_with("doppler --version 2> /dev/null", succeed: true)
stub_ticks.with("doppler me --json 2> /dev/null") stub_ticks.with("doppler me --json 2> /dev/null")
stub_ticks stub_ticks
@@ -30,7 +31,9 @@ class DopplerAdapterTest < SecretAdapterTestCase
} }
JSON JSON
json = JSON.parse(shellunescape(run_command("fetch", "SECRET1", "FSECRET1", "FSECRET2"))) json = JSON.parse(
shellunescape run_command("fetch", "--from", "my-project/prd", "SECRET1", "FSECRET1", "FSECRET2")
)
expected_json = { expected_json = {
"SECRET1"=>"secret1", "SECRET1"=>"secret1",
@@ -41,32 +44,106 @@ class DopplerAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json assert_equal expected_json, json
end end
test "fetch with from" do test "fetch having DOPPLER_TOKEN" do
ENV["DOPPLER_TOKEN"] = "dp.st.xxxxxxxxxxxxxxxxxxxxxx"
stub_ticks_with("doppler --version 2> /dev/null", succeed: true)
stub_ticks.with("doppler me --json 2> /dev/null") stub_ticks.with("doppler me --json 2> /dev/null")
error = assert_raises RuntimeError do stub_ticks
run_command("fetch", "--from", "FOLDER1", "FSECRET1", "FSECRET2") .with("doppler secrets get SECRET1 FSECRET1 FSECRET2 --json ")
end .returns(<<~JSON)
{
"SECRET1": {
"computed":"secret1",
"computedVisibility":"unmasked",
"note":""
},
"FSECRET1": {
"computed":"fsecret1",
"computedVisibility":"unmasked",
"note":""
},
"FSECRET2": {
"computed":"fsecret2",
"computedVisibility":"unmasked",
"note":""
}
}
JSON
assert_match(/Using --from option or FOLDER\/SECRET is not supported by Doppler/, error.message) json = JSON.parse(
shellunescape run_command("fetch", "SECRET1", "FSECRET1", "FSECRET2")
)
expected_json = {
"SECRET1"=>"secret1",
"FSECRET1"=>"fsecret1",
"FSECRET2"=>"fsecret2"
}
assert_equal expected_json, json
ENV.delete("DOPPLER_TOKEN")
end end
test "fetch with folder in secret" do test "fetch with folder in secret" do
stub_ticks_with("doppler --version 2> /dev/null", succeed: true)
stub_ticks.with("doppler me --json 2> /dev/null")
stub_ticks
.with("doppler secrets get SECRET1 FSECRET1 FSECRET2 --json -p my-project -c prd")
.returns(<<~JSON)
{
"SECRET1": {
"computed":"secret1",
"computedVisibility":"unmasked",
"note":""
},
"FSECRET1": {
"computed":"fsecret1",
"computedVisibility":"unmasked",
"note":""
},
"FSECRET2": {
"computed":"fsecret2",
"computedVisibility":"unmasked",
"note":""
}
}
JSON
json = JSON.parse(
shellunescape run_command("fetch", "my-project/prd/SECRET1", "my-project/prd/FSECRET1", "my-project/prd/FSECRET2")
)
expected_json = {
"SECRET1"=>"secret1",
"FSECRET1"=>"fsecret1",
"FSECRET2"=>"fsecret2"
}
assert_equal expected_json, json
end
test "fetch without --from" do
stub_ticks_with("doppler --version 2> /dev/null", succeed: true)
stub_ticks.with("doppler me --json 2> /dev/null") stub_ticks.with("doppler me --json 2> /dev/null")
error = assert_raises RuntimeError do error = assert_raises RuntimeError do
run_command("fetch", "FOLDER1/FSECRET1", "SECRET2") run_command("fetch", "FSECRET1", "FSECRET2")
end end
assert_match(/Using --from option or FOLDER\/SECRET is not supported by Doppler/, error.message) assert_equal "Missing project or config from '--from=project/config' option", error.message
end end
test "fetch with signin" do test "fetch with signin" do
stub_ticks_with("doppler --version 2> /dev/null", succeed: true)
stub_ticks_with("doppler me --json 2> /dev/null", succeed: false) stub_ticks_with("doppler me --json 2> /dev/null", succeed: false)
stub_ticks_with("doppler login -y", succeed: true).returns("") stub_ticks_with("doppler login -y", succeed: true).returns("")
stub_ticks.with("doppler secrets get SECRET1 --json -p my-project -c prd").returns(single_item_json) stub_ticks.with("doppler secrets get SECRET1 --json -p my-project -c prd").returns(single_item_json)
json = JSON.parse(shellunescape(run_command("fetch", "SECRET1"))) json = JSON.parse(shellunescape(run_command("fetch", "--from", "my-project/prd", "SECRET1")))
expected_json = { expected_json = {
"SECRET1"=>"secret1" "SECRET1"=>"secret1"
@@ -75,14 +152,23 @@ class DopplerAdapterTest < SecretAdapterTestCase
assert_equal expected_json, json assert_equal expected_json, json
end end
test "fetch without CLI installed" do
stub_ticks_with("doppler --version 2> /dev/null", succeed: false)
error = assert_raises RuntimeError do
JSON.parse(shellunescape(run_command("fetch", "HOST", "PORT")))
end
assert_equal "Doppler CLI is not installed", error.message
end
private private
def run_command(*command) def run_command(*command)
stdouted do stdouted do
Kamal::Cli::Secrets.start \ Kamal::Cli::Secrets.start \
[ *command, [ *command,
"-c", "test/fixtures/deploy_with_accessories.yml", "-c", "test/fixtures/deploy_with_accessories.yml",
"--adapter", "doppler", "--adapter", "doppler" ]
"--account", "my-project/prd" ]
end end
end end