Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add a mechanism to set multiple envs per configuration #3679

Merged
merged 4 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading