Skip to content

Commit d19bf60

Browse files
Implement Config Inversion with Default Strictness of Warning (#9539)
* Refactored EnvironmentVariables to be testable. * adding supported-configurations.json file * removing extra supported-configurations.json * migrating config-utils tests and ConfigInversionMetric telemetry * config inversion init * migrating config-utils tests * undo move of test files that rely on inject*config * adding deprecation handling * updating tests * spotless * responding to PR comments * updating class coverage exclude * updating ConfigHelper to be a singleton * refactoring ConfigHelper and ConfigurationSources to simplify code readability and hide supported-configuration details * responding to PR comments and refactoring ConfigHelperTest to utilize ControllableEnvironmentVariables * updating PR comments * cleanup and code coverage * bugfix * responding to PR comments * fixing issue with EmptyMap * manually handling rebase issues --------- Co-authored-by: Alexey Kuznetsov <[email protected]>
1 parent ef27983 commit d19bf60

File tree

5 files changed

+429
-2
lines changed

5 files changed

+429
-2
lines changed

utils/config-utils/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@ val excludedClassesCoverage by extra(
3030
"datadog.trace.bootstrap.config.provider.stableconfig.Selector",
3131
// tested in internal-api
3232
"datadog.trace.bootstrap.config.provider.StableConfigParser",
33-
"datadog.trace.bootstrap.config.provider.SystemPropertiesConfigSource",
33+
"datadog.trace.bootstrap.config.provider.SystemPropertiesConfigSource"
3434
)
3535
)
3636

3737
val excludedClassesBranchCoverage by extra(
3838
listOf(
3939
"datadog.trace.bootstrap.config.provider.AgentArgsInjector",
40+
// Enum
41+
"datadog.trace.config.inversion.ConfigHelper.StrictnessPolicy",
4042
"datadog.trace.util.ConfigStrings"
4143
)
4244
)
4345

4446
val excludedClassesInstructionCoverage by extra(
4547
listOf(
46-
"datadog.trace.config.inversion.GeneratedSupportedConfigurations"
48+
"datadog.trace.config.inversion.GeneratedSupportedConfigurations",
49+
"datadog.trace.config.inversion.SupportedConfigurationSource"
4750
)
4851
)
4952

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package datadog.trace.config.inversion;
2+
3+
import datadog.environment.EnvironmentVariables;
4+
import datadog.trace.api.telemetry.ConfigInversionMetricCollectorProvider;
5+
import java.util.Collections;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Locale;
9+
import java.util.Map;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
public class ConfigHelper {
14+
15+
/** Config Inversion strictness policy for enforcement of undocumented environment variables */
16+
public enum StrictnessPolicy {
17+
STRICT,
18+
WARNING,
19+
TEST;
20+
21+
private String displayName;
22+
23+
StrictnessPolicy() {
24+
this.displayName = name().toLowerCase(Locale.ROOT);
25+
}
26+
27+
@Override
28+
public String toString() {
29+
if (displayName == null) {
30+
displayName = name().toLowerCase(Locale.ROOT);
31+
}
32+
return displayName;
33+
}
34+
}
35+
36+
private static final Logger log = LoggerFactory.getLogger(ConfigHelper.class);
37+
38+
private static final ConfigHelper INSTANCE = new ConfigHelper();
39+
40+
private StrictnessPolicy configInversionStrict = StrictnessPolicy.WARNING;
41+
42+
private static final String DD_PREFIX = "DD_";
43+
private static final String OTEL_PREFIX = "OTEL_";
44+
45+
// Cache for configs, init value is EmptyMap
46+
private Map<String, String> configs = Collections.emptyMap();
47+
48+
// Default to production source
49+
private SupportedConfigurationSource configSource = new SupportedConfigurationSource();
50+
51+
public static ConfigHelper get() {
52+
return INSTANCE;
53+
}
54+
55+
public void setConfigInversionStrict(StrictnessPolicy configInversionStrict) {
56+
this.configInversionStrict = configInversionStrict;
57+
}
58+
59+
public StrictnessPolicy configInversionStrictFlag() {
60+
return configInversionStrict;
61+
}
62+
63+
// Used only for testing purposes
64+
void setConfigurationSource(SupportedConfigurationSource testSource) {
65+
configSource = testSource;
66+
}
67+
68+
/** Resetting config cache. Useful for cleaning up after tests. */
69+
void resetCache() {
70+
configs = Collections.emptyMap();
71+
}
72+
73+
/** Reset all configuration data to the generated defaults. Useful for cleaning up after tests. */
74+
void resetToDefaults() {
75+
configSource = new SupportedConfigurationSource();
76+
this.configInversionStrict = StrictnessPolicy.WARNING;
77+
resetCache();
78+
}
79+
80+
public Map<String, String> getEnvironmentVariables() {
81+
if (!configs.isEmpty()) {
82+
return configs;
83+
}
84+
85+
// Initial value is EmptyMap
86+
configs = new HashMap<>();
87+
88+
Map<String, String> env = EnvironmentVariables.getAll();
89+
for (Map.Entry<String, String> entry : env.entrySet()) {
90+
String key = entry.getKey();
91+
String value = entry.getValue();
92+
String primaryEnv = configSource.primaryEnvFromAlias(key);
93+
if (key.startsWith(DD_PREFIX) || key.startsWith(OTEL_PREFIX) || primaryEnv != null) {
94+
if (configSource.supported(key)) {
95+
configs.put(key, value);
96+
// If this environment variable is the alias of another, and we haven't processed the
97+
// original environment variable yet, handle it here.
98+
} else if (primaryEnv != null && !configs.containsKey(primaryEnv)) {
99+
List<String> aliases = configSource.getAliases(primaryEnv);
100+
for (String alias : aliases) {
101+
if (env.containsKey(alias)) {
102+
configs.put(primaryEnv, env.get(alias));
103+
break;
104+
}
105+
}
106+
}
107+
String envFromDeprecated = configSource.primaryEnvFromDeprecated(key);
108+
if (envFromDeprecated != null) {
109+
String warning =
110+
"Environment variable "
111+
+ key
112+
+ " is deprecated. Please use "
113+
+ (primaryEnv != null ? primaryEnv : envFromDeprecated)
114+
+ " instead.";
115+
log.warn(warning);
116+
}
117+
} else {
118+
configs.put(key, value);
119+
}
120+
}
121+
return configs;
122+
}
123+
124+
public String getEnvironmentVariable(String name) {
125+
if (configs.containsKey(name)) {
126+
return configs.get(name);
127+
}
128+
129+
if ((name.startsWith(DD_PREFIX) || name.startsWith(OTEL_PREFIX))
130+
&& configSource.primaryEnvFromAlias(name) == null
131+
&& !configSource.supported(name)) {
132+
if (configInversionStrict != StrictnessPolicy.TEST) {
133+
ConfigInversionMetricCollectorProvider.get().setUndocumentedEnvVarMetric(name);
134+
}
135+
136+
if (configInversionStrict == StrictnessPolicy.STRICT) {
137+
return null; // If strict mode is enabled, return null for unsupported configs
138+
}
139+
}
140+
141+
String config = EnvironmentVariables.get(name);
142+
List<String> aliases;
143+
if (config == null && (aliases = configSource.getAliases(name)) != null) {
144+
for (String alias : aliases) {
145+
String aliasValue = EnvironmentVariables.get(alias);
146+
if (aliasValue != null) {
147+
return aliasValue;
148+
}
149+
}
150+
}
151+
return config;
152+
}
153+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package datadog.trace.config.inversion;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
6+
/**
7+
* This class uses {@link #GeneratedSupportedConfigurations} for handling supported configurations
8+
* for Config Inversion Can be extended for testing with custom configuration data.
9+
*/
10+
class SupportedConfigurationSource {
11+
12+
/** @return Set of supported environment variable keys */
13+
public boolean supported(String env) {
14+
return GeneratedSupportedConfigurations.SUPPORTED.contains(env);
15+
}
16+
17+
/** @return List of aliases for an environment variable */
18+
public List<String> getAliases(String env) {
19+
return GeneratedSupportedConfigurations.ALIASES.getOrDefault(env, Collections.emptyList());
20+
}
21+
22+
/** @return Primary environment variable for a queried alias */
23+
public String primaryEnvFromAlias(String alias) {
24+
return GeneratedSupportedConfigurations.ALIAS_MAPPING.getOrDefault(alias, null);
25+
}
26+
27+
/** @return Map of deprecated configurations */
28+
public String primaryEnvFromDeprecated(String deprecated) {
29+
return GeneratedSupportedConfigurations.DEPRECATED.getOrDefault(deprecated, null);
30+
}
31+
}

0 commit comments

Comments
 (0)