Skip to content

Commit

Permalink
chore: add a mechanism to set multiple envs per configuration (#3679)
Browse files Browse the repository at this point in the history
  • Loading branch information
mabdinur committed Jun 7, 2024
1 parent 56d933f commit 70e7fc4
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 15 deletions.
35 changes: 21 additions & 14 deletions lib/datadog/core/configuration/option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Option
# @!attribute [r] precedence_set
# When this option was last set, what was the value precedence used?
# @return [Precedence::Value]
attr_reader :definition, :precedence_set
attr_reader :definition, :precedence_set, :resolved_env

# Option setting precedence.
module Precedence
Expand Down Expand Up @@ -50,6 +50,7 @@ def initialize(definition, context)
@context = context
@value = nil
@is_set = false
@resolved_env = nil

# One value is stored per precedence, to allow unsetting a higher
# precedence value and falling back to a lower precedence one.
Expand All @@ -65,7 +66,7 @@ def initialize(definition, context)
#
# @param value [Object] the new value to be associated with this option
# @param precedence [Precedence] from what precedence order this new value comes from
def set(value, precedence: Precedence::PROGRAMMATIC)
def set(value, precedence: Precedence::PROGRAMMATIC, resolved_env: nil)
# Is there a higher precedence value set?
if @precedence_set > precedence
# This should be uncommon, as higher precedence values tend to
Expand All @@ -84,7 +85,7 @@ def set(value, precedence: Precedence::PROGRAMMATIC)
return @value
end

internal_set(value, precedence)
internal_set(value, precedence, resolved_env)
end

def unset(precedence)
Expand All @@ -102,7 +103,7 @@ def unset(precedence)
# Look for value that is set.
# The hash `@value_per_precedence` has a custom default value of `UNSET`.
if (value = @value_per_precedence[p]) != UNSET
internal_set(value, p)
internal_set(value, p, nil)
return nil
end
end
Expand Down Expand Up @@ -260,11 +261,12 @@ def validate(type, value)
end

# Directly manipulates the current value and currently set precedence.
def internal_set(value, precedence)
def internal_set(value, precedence, resolved_env)
old_value = @value
(@value = context_exec(validate_type(value), old_value, &definition.setter)).tap do |v|
@is_set = true
@precedence_set = precedence
@resolved_env = resolved_env
# Store original value to ensure we can always safely call `#internal_set`
# when restoring a value from `@value_per_precedence`, and we are only running `definition.setter`
# on the original value, not on a valud that has already been processed by `definition.setter`.
Expand All @@ -284,16 +286,21 @@ def context_eval(&block)
def set_value_from_env_or_default
value = nil
precedence = nil
effective_env = nil
resolved_env = nil

if definition.env && ENV[definition.env]
effective_env = definition.env
value = coerce_env_variable(ENV[definition.env])
precedence = Precedence::PROGRAMMATIC
if definition.env
Array(definition.env).each do |env|
next if ENV[env].nil?

resolved_env = env
value = coerce_env_variable(ENV[env])
precedence = Precedence::PROGRAMMATIC
break
end
end

if value.nil? && definition.deprecated_env && ENV[definition.deprecated_env]
effective_env = definition.deprecated_env
resolved_env = definition.deprecated_env
value = coerce_env_variable(ENV[definition.deprecated_env])
precedence = Precedence::PROGRAMMATIC

Expand All @@ -304,11 +311,11 @@ def set_value_from_env_or_default

option_value = value.nil? ? default_value : value

set(option_value, precedence: precedence || Precedence::DEFAULT)
set(option_value, precedence: precedence || Precedence::DEFAULT, resolved_env: resolved_env)
rescue ArgumentError
raise ArgumentError,
"Expected environment variable #{effective_env} to be a #{@definition.type}, " \
"but '#{ENV[effective_env]}' was provided"
"Expected environment variable #{resolved_env} to be a #{@definition.type}, " \
"but '#{ENV[resolved_env]}' was provided"
end

# Anchor object that represents a value that is not set.
Expand Down
6 changes: 5 additions & 1 deletion lib/datadog/core/configuration/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def options
end

def set_option(name, value, precedence: Configuration::Option::Precedence::PROGRAMMATIC)
resolve_option(name).set(value, precedence: precedence)
resolve_option(name).set(value, precedence: precedence, resolved_env: resolved_env(name))
end

def unset_option(name, precedence: Configuration::Option::Precedence::PROGRAMMATIC)
Expand Down Expand Up @@ -116,6 +116,10 @@ def resolve_option(name)
options[name] = definition.build(self)
end

def resolved_env(name)
return options[name].resolved_env if options.key?(name)
end

def assert_valid_option!(name)
raise(InvalidOptionError, "#{self.class.name} doesn't define the option: #{name}") unless option_defined?(name)
end
Expand Down
29 changes: 29 additions & 0 deletions spec/datadog/core/configuration/option_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,35 @@
end
end

context 'when env is an Array' do
let(:env) { ['TEST_ENV_VAR', 'TEST_ENV_VAR2'] }
let(:setter) { proc { |value| value } }

around do |example|
ClimateControl.modify(set_envs) { example.run }
end

context 'and the first environmet variable is set' do
let(:set_envs) { { 'TEST_ENV_VAR' => 'val1' } }
it { is_expected.to eq('val1') }
end

context 'and the second environmet variable is set' do
let(:set_envs) { { 'TEST_ENV_VAR2' => 'val2' } }
it { is_expected.to eq('val2') }
end

context 'and both environmet variables are set' do
let(:set_envs) { { 'TEST_ENV_VAR' => 'val1', 'TEST_ENV_VAR2' => 'val2' } }
it { is_expected.to eq('val1') }
end

context 'and environmet variables are not set' do
let(:set_envs) { {} }
it { is_expected.to be(default) }
end
end

context 'when deprecated_env is defined' do
before do
allow(Datadog.logger).to receive(:warn) # For deprecation warnings
Expand Down

0 comments on commit 70e7fc4

Please sign in to comment.