Skip to content

Commit 1e41f36

Browse files
committed
config inversion init
1 parent 6a1274c commit 1e41f36

File tree

5 files changed

+436
-0
lines changed

5 files changed

+436
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package datadog.trace.config.inversion;
2+
3+
import datadog.environment.EnvironmentVariables;
4+
import datadog.trace.api.telemetry.ConfigInversionMetricCollectorProvider;
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public class ConfigHelper {
10+
private static ConfigInversionStrictStyle configInversionStrict;
11+
12+
// Default to production source
13+
private static SupportedConfigurationSource configSource = new SupportedConfigurationSource();
14+
15+
public static void setConfigInversionStrict(ConfigInversionStrictStyle configInversionStrict) {
16+
ConfigHelper.configInversionStrict = configInversionStrict;
17+
}
18+
19+
public static ConfigInversionStrictStyle configInversionStrictFlag() {
20+
return configInversionStrict;
21+
}
22+
23+
// Used only for testing purposes
24+
static void setConfigurationSource(SupportedConfigurationSource testSource) {
25+
configSource = testSource;
26+
}
27+
28+
/** Reset all configuration data to the generated defaults. Useful for cleaning up after tests. */
29+
static void resetToDefaults() {
30+
configSource = new SupportedConfigurationSource();
31+
configInversionStrict = ConfigInversionStrictStyle.WARNING;
32+
}
33+
34+
public static Map<String, String> getEnvironmentVariables() {
35+
Map<String, String> env = EnvironmentVariables.getAll();
36+
Map<String, String> configs = new LinkedHashMap<>();
37+
for (Map.Entry<String, String> entry : env.entrySet()) {
38+
String key = entry.getKey();
39+
String value = entry.getValue();
40+
if (key.startsWith("DD_")
41+
|| key.startsWith("OTEL_")
42+
|| configSource.getAliasMapping().containsKey(key)) {
43+
if (configSource.getSupportedConfigurations().contains(key)) {
44+
configs.put(key, value);
45+
// If this environment variable is the alias of another, and we haven't processed the
46+
// original environment variable yet, handle it here.
47+
} else if (configSource.getAliasMapping().containsKey(key)
48+
&& !configs.containsKey(configSource.getAliasMapping().get(key))) {
49+
List<String> aliasList =
50+
configSource.getAliases().get(configSource.getAliasMapping().get(key));
51+
for (String alias : aliasList) {
52+
if (env.containsKey(alias)) {
53+
configs.put(configSource.getAliasMapping().get(key), env.get(alias));
54+
break;
55+
}
56+
}
57+
}
58+
59+
// TODO: Follow-up - Add deprecation handling
60+
// if (configSource.getDeprecatedConfigurations().containsKey(key)) {
61+
// String warning = "Environment variable " + key + " is deprecated. " +
62+
// (configSource.getAliasMapping().containsKey(key)
63+
// ? "Please use " + configSource.getAliasMapping().get(key) + " instead."
64+
// : configSource.getDeprecatedConfigurations().get(key));
65+
// System.err.println(warning);
66+
// }
67+
} else {
68+
configs.put(key, value);
69+
}
70+
}
71+
return configs;
72+
}
73+
74+
public static String getEnvironmentVariable(String name) {
75+
if ((name.startsWith("DD_") || name.startsWith("OTEL_"))
76+
&& !configSource.getAliasMapping().containsKey(name)
77+
&& !configSource.getSupportedConfigurations().contains(name)) {
78+
if (configInversionStrict != ConfigInversionStrictStyle.TEST) {
79+
ConfigInversionMetricCollectorProvider.get().setUndocumentedEnvVarMetric(name);
80+
}
81+
82+
if (configInversionStrict == ConfigInversionStrictStyle.STRICT) {
83+
return null; // If strict mode is enabled, return null for unsupported configs
84+
}
85+
}
86+
87+
String config = EnvironmentVariables.get(name);
88+
if (config == null && configSource.getAliases().containsKey(name)) {
89+
for (String alias : configSource.getAliases().get(name)) {
90+
String aliasValue = EnvironmentVariables.get(alias);
91+
if (aliasValue != null) {
92+
return aliasValue;
93+
}
94+
}
95+
}
96+
return config;
97+
}
98+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package datadog.trace.config.inversion;
2+
3+
import java.util.Locale;
4+
5+
/** Trace propagation styles for injecting and extracting trace propagation headers. */
6+
public enum ConfigInversionStrictStyle {
7+
STRICT,
8+
WARNING,
9+
TEST;
10+
11+
private String displayName;
12+
13+
ConfigInversionStrictStyle() {
14+
this.displayName = name().toLowerCase(Locale.ROOT);
15+
}
16+
17+
@Override
18+
public String toString() {
19+
if (displayName == null) {
20+
displayName = name().toLowerCase(Locale.ROOT);
21+
}
22+
return displayName;
23+
}
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package datadog.trace.config.inversion;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Set;
6+
7+
/**
8+
* This class uses {@link #GeneratedSupportedConfigurations} for handling supported configurations
9+
* for Config Inversion Can be extended for testing with custom configuration data.
10+
*/
11+
class SupportedConfigurationSource {
12+
13+
/** @return Set of supported configuration keys */
14+
public Set<String> getSupportedConfigurations() {
15+
return GeneratedSupportedConfigurations.SUPPORTED;
16+
}
17+
18+
/** @return Map of configuration keys to their aliases */
19+
public Map<String, List<String>> getAliases() {
20+
return GeneratedSupportedConfigurations.ALIASES;
21+
}
22+
23+
/** @return Map of alias keys to their primary configuration keys */
24+
public Map<String, String> getAliasMapping() {
25+
return GeneratedSupportedConfigurations.ALIAS_MAPPING;
26+
}
27+
28+
/** @return Map of deprecated configurations */
29+
public Map<String, String> getDeprecatedConfigurations() {
30+
return GeneratedSupportedConfigurations.DEPRECATED;
31+
}
32+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package datadog.trace.config.inversion;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
7+
import java.lang.reflect.Field;
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
import java.util.HashMap;
11+
import java.util.HashSet;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Set;
15+
import org.junit.jupiter.api.AfterAll;
16+
import org.junit.jupiter.api.BeforeAll;
17+
import org.junit.jupiter.api.Test;
18+
19+
public class ConfigHelperTest {
20+
// Test environment variables
21+
private static final String TEST_DD_VAR = "DD_TEST_CONFIG";
22+
private static final String TEST_DD_VAR_VAL = "test_dd_var";
23+
private static final String TEST_OTEL_VAR = "OTEL_TEST_CONFIG";
24+
private static final String TEST_OTEL_VAR_VAL = "test_otel_var";
25+
private static final String TEST_REGULAR_VAR = "REGULAR_TEST_CONFIG";
26+
private static final String TEST_REGULAR_VAR_VAL = "test_regular_var";
27+
private static final String UNSUPPORTED_DD_VAR = "DD_UNSUPPORTED_CONFIG";
28+
29+
private static final String ALIAS_DD_VAR = "DD_TEST_CONFIG_ALIAS";
30+
private static final String ALIAS_DD_VAL = "test_alias_val";
31+
private static final String NON_DD_ALIAS_VAR = "TEST_CONFIG_ALIAS";
32+
private static final String NON_DD_ALIAS_VAL = "test_alias_val_non_dd";
33+
34+
private static final String NEW_ALIAS_TARGET = "DD_NEW_ALIAS_TARGET";
35+
private static final String NEW_ALIAS_KEY_1 = "DD_NEW_ALIAS_KEY_1";
36+
private static final String NEW_ALIAS_KEY_2 = "DD_NEW_ALIAS_KEY_2";
37+
38+
private static ConfigInversionStrictStyle strictness;
39+
private static TestSupportedConfigurationSource testSource;
40+
41+
@BeforeAll
42+
static void setUp() {
43+
// Set up test configurations using SupportedConfigurationSource
44+
Set<String> testSupported =
45+
new HashSet<>(Arrays.asList(TEST_DD_VAR, TEST_OTEL_VAR, TEST_REGULAR_VAR));
46+
47+
Map<String, List<String>> testAliases = new HashMap<>();
48+
testAliases.put(TEST_DD_VAR, Arrays.asList(ALIAS_DD_VAR, NON_DD_ALIAS_VAR));
49+
testAliases.put(NEW_ALIAS_TARGET, Arrays.asList(NEW_ALIAS_KEY_1));
50+
51+
Map<String, String> testAliasMapping = new HashMap<>();
52+
testAliasMapping.put(ALIAS_DD_VAR, TEST_DD_VAR);
53+
testAliasMapping.put(NON_DD_ALIAS_VAR, TEST_DD_VAR);
54+
testAliasMapping.put(NEW_ALIAS_KEY_2, NEW_ALIAS_TARGET);
55+
56+
// Create and set test configuration source
57+
testSource =
58+
new TestSupportedConfigurationSource(
59+
testSupported, testAliases, testAliasMapping, new HashMap<>());
60+
ConfigHelper.setConfigurationSource(testSource);
61+
strictness = ConfigHelper.configInversionStrictFlag();
62+
ConfigHelper.setConfigInversionStrict(ConfigInversionStrictStyle.STRICT);
63+
}
64+
65+
@AfterAll
66+
static void tearDown() {
67+
ConfigHelper.resetToDefaults();
68+
ConfigHelper.setConfigInversionStrict(strictness);
69+
}
70+
71+
@Test
72+
void testBasicConfigHelper() {
73+
setEnvVar(TEST_DD_VAR, TEST_DD_VAR_VAL);
74+
setEnvVar(TEST_OTEL_VAR, TEST_OTEL_VAR_VAL);
75+
setEnvVar(TEST_REGULAR_VAR, TEST_REGULAR_VAR_VAL);
76+
77+
assertEquals(TEST_DD_VAR_VAL, ConfigHelper.getEnvironmentVariable(TEST_DD_VAR));
78+
assertEquals(TEST_OTEL_VAR_VAL, ConfigHelper.getEnvironmentVariable(TEST_OTEL_VAR));
79+
assertEquals(TEST_REGULAR_VAR_VAL, ConfigHelper.getEnvironmentVariable(TEST_REGULAR_VAR));
80+
81+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
82+
assertEquals(TEST_DD_VAR_VAL, result.get(TEST_DD_VAR));
83+
assertEquals(TEST_OTEL_VAR_VAL, result.get(TEST_OTEL_VAR));
84+
assertEquals(TEST_REGULAR_VAR_VAL, result.get(TEST_REGULAR_VAR));
85+
86+
// Cleanup
87+
setEnvVar(TEST_DD_VAR, null);
88+
setEnvVar(TEST_OTEL_VAR, null);
89+
setEnvVar(TEST_REGULAR_VAR, null);
90+
}
91+
92+
@Test
93+
void testAliasSupport() {
94+
setEnvVar(ALIAS_DD_VAR, ALIAS_DD_VAL);
95+
96+
assertEquals(ALIAS_DD_VAL, ConfigHelper.getEnvironmentVariable(TEST_DD_VAR));
97+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
98+
assertEquals(ALIAS_DD_VAL, result.get(TEST_DD_VAR));
99+
assertFalse(result.containsKey(ALIAS_DD_VAR));
100+
101+
// Cleanup
102+
setEnvVar(ALIAS_DD_VAR, null);
103+
}
104+
105+
@Test
106+
void testMainConfigPrecedence() {
107+
// When both main variable and alias are set, main should take precedence
108+
setEnvVar(TEST_DD_VAR, TEST_DD_VAR_VAL);
109+
setEnvVar(ALIAS_DD_VAR, ALIAS_DD_VAL);
110+
111+
assertEquals(TEST_DD_VAR_VAL, ConfigHelper.getEnvironmentVariable(TEST_DD_VAR));
112+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
113+
assertEquals(TEST_DD_VAR_VAL, result.get(TEST_DD_VAR));
114+
assertFalse(result.containsKey(ALIAS_DD_VAR));
115+
116+
// Cleanup
117+
setEnvVar(TEST_DD_VAR, null);
118+
setEnvVar(ALIAS_DD_VAR, null);
119+
}
120+
121+
@Test
122+
void testUnsupportedDDEnvironmentVariable() {
123+
setEnvVar(UNSUPPORTED_DD_VAR, "");
124+
125+
assertNull(ConfigHelper.getEnvironmentVariable(UNSUPPORTED_DD_VAR));
126+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
127+
assertFalse(result.containsKey(UNSUPPORTED_DD_VAR));
128+
129+
// Cleanup
130+
setEnvVar(UNSUPPORTED_DD_VAR, null);
131+
}
132+
133+
@Test
134+
void testNonDDAliases() {
135+
setEnvVar(NON_DD_ALIAS_VAR, NON_DD_ALIAS_VAL);
136+
137+
assertEquals(NON_DD_ALIAS_VAL, ConfigHelper.getEnvironmentVariable(TEST_DD_VAR));
138+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
139+
assertEquals(NON_DD_ALIAS_VAL, result.get(TEST_DD_VAR));
140+
assertFalse(result.containsKey(NON_DD_ALIAS_VAR));
141+
142+
// Cleanup
143+
setEnvVar(NON_DD_ALIAS_VAR, null);
144+
}
145+
146+
@Test
147+
void testAliasesWithoutPresentAliases() {
148+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
149+
assertFalse(result.containsKey(ALIAS_DD_VAR));
150+
}
151+
152+
@Test
153+
void testUnsupportedButNotStrict() {
154+
ConfigHelper.setConfigInversionStrict(ConfigInversionStrictStyle.WARNING);
155+
setEnvVar(UNSUPPORTED_DD_VAR, "loose");
156+
157+
// Should fall through and return the env var even though it's unsupported
158+
assertEquals("loose", ConfigHelper.getEnvironmentVariable(UNSUPPORTED_DD_VAR));
159+
160+
// Cleanup
161+
ConfigHelper.setConfigInversionStrict(ConfigInversionStrictStyle.STRICT);
162+
setEnvVar(UNSUPPORTED_DD_VAR, null);
163+
}
164+
165+
@Test
166+
void testAliasWithEmptyList() {
167+
Map<String, List<String>> aliasMap = new HashMap<>();
168+
aliasMap.put("EMPTY_ALIAS_CONFIG", new ArrayList<>());
169+
170+
ConfigHelper.setConfigurationSource(
171+
new TestSupportedConfigurationSource(
172+
new HashSet<>(), aliasMap, new HashMap<>(), new HashMap<>()));
173+
174+
assertNull(ConfigHelper.getEnvironmentVariable("EMPTY_ALIAS_CONFIG"));
175+
176+
// Cleanup
177+
ConfigHelper.setConfigurationSource(testSource);
178+
}
179+
180+
@Test
181+
void testAliasSkippedWhenBaseAlreadyPresent() {
182+
setEnvVar(TEST_DD_VAR, TEST_DD_VAR_VAL);
183+
setEnvVar(NON_DD_ALIAS_VAR, NON_DD_ALIAS_VAL);
184+
185+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
186+
assertEquals(TEST_DD_VAR_VAL, result.get(TEST_DD_VAR));
187+
assertFalse(result.containsKey(NON_DD_ALIAS_VAR));
188+
189+
// Cleanup
190+
setEnvVar(TEST_DD_VAR, null);
191+
setEnvVar(NON_DD_ALIAS_VAR, null);
192+
}
193+
194+
@Test
195+
void testInconsistentAliasesAndAliasMapping() {
196+
setEnvVar(NEW_ALIAS_KEY_2, "some_value");
197+
198+
Map<String, String> result = ConfigHelper.getEnvironmentVariables();
199+
200+
assertFalse(result.containsKey(NEW_ALIAS_KEY_2));
201+
assertFalse(result.containsKey(NEW_ALIAS_TARGET));
202+
203+
// Cleanup
204+
setEnvVar(NEW_ALIAS_KEY_2, null);
205+
}
206+
207+
// TODO: Update to verify telemetry when implemented
208+
@Test
209+
void testUnsupportedEnvWarningNotInTestMode() {
210+
ConfigHelper.setConfigInversionStrict(ConfigInversionStrictStyle.TEST);
211+
212+
setEnvVar("DD_FAKE_VAR", "banana");
213+
214+
// Should allow unsupported variable in TEST mode
215+
assertEquals("banana", ConfigHelper.getEnvironmentVariable("DD_FAKE_VAR"));
216+
217+
// Cleanup
218+
setEnvVar("DD_FAKE_VAR", null);
219+
ConfigHelper.setConfigInversionStrict(ConfigInversionStrictStyle.STRICT);
220+
}
221+
222+
// Copied from utils.TestHelper
223+
@SuppressWarnings("unchecked")
224+
private static void setEnvVar(String envName, String envValue) {
225+
try {
226+
Class<?> classOfMap = System.getenv().getClass();
227+
Field field = classOfMap.getDeclaredField("m");
228+
field.setAccessible(true);
229+
if (envValue == null) {
230+
((Map<String, String>) field.get(System.getenv())).remove(envName);
231+
} else {
232+
((Map<String, String>) field.get(System.getenv())).put(envName, envValue);
233+
}
234+
} catch (Exception ex) {
235+
ex.printStackTrace();
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)