Skip to content

Commit 814dfbf

Browse files
committed
Add custom endpoint for agentless feature flags
1 parent a207eb9 commit 814dfbf

6 files changed

Lines changed: 85 additions & 4 deletions

File tree

dd-trace-api/src/main/java/datadog/trace/api/config/FeatureFlaggingConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public class FeatureFlaggingConfig {
66

77
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE =
88
"feature.flags.configuration.source";
9+
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL =
10+
"feature.flags.configuration.source.agentless.base.url";
911
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS =
1012
"feature.flags.configuration.source.agentless.poll.interval.seconds";
1113
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS =

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@
365365
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_INCLUDES;
366366
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_SHADING_IDENTIFIERS;
367367
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
368+
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL;
368369
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS;
369370
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS;
370371
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_LEVEL;
@@ -1220,6 +1221,7 @@ public static String getHostName() {
12201221
private final int remoteConfigMaxExtraServices;
12211222

12221223
private final String featureFlaggingConfigurationSource;
1224+
private final String featureFlaggingConfigurationSourceAgentlessBaseUrl;
12231225
private final double featureFlaggingConfigurationSourcePollIntervalSeconds;
12241226
private final double featureFlaggingConfigurationSourceRequestTimeoutSeconds;
12251227

@@ -2849,6 +2851,9 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment())
28492851
normalizeFeatureFlaggingConfigurationSource(
28502852
configProvider.getString(
28512853
FEATURE_FLAGS_CONFIGURATION_SOURCE, DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE));
2854+
featureFlaggingConfigurationSourceAgentlessBaseUrl =
2855+
configProvider.getStringNotEmpty(
2856+
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL, null);
28522857
featureFlaggingConfigurationSourcePollIntervalSeconds =
28532858
configProvider.getDouble(
28542859
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS,
@@ -4689,6 +4694,10 @@ public String getFeatureFlaggingConfigurationSource() {
46894694
return featureFlaggingConfigurationSource;
46904695
}
46914696

4697+
public String getFeatureFlaggingConfigurationSourceAgentlessBaseUrl() {
4698+
return featureFlaggingConfigurationSourceAgentlessBaseUrl;
4699+
}
4700+
46924701
public double getFeatureFlaggingConfigurationSourcePollIntervalSeconds() {
46934702
return featureFlaggingConfigurationSourcePollIntervalSeconds;
46944703
}
@@ -6531,6 +6540,8 @@ public String toString() {
65316540
+ remoteConfigIntegrityCheckEnabled
65326541
+ ", featureFlaggingConfigurationSource="
65336542
+ featureFlaggingConfigurationSource
6543+
+ ", featureFlaggingConfigurationSourceAgentlessBaseUrl="
6544+
+ featureFlaggingConfigurationSourceAgentlessBaseUrl
65346545
+ ", featureFlaggingConfigurationSourcePollIntervalSeconds="
65356546
+ featureFlaggingConfigurationSourcePollIntervalSeconds
65366547
+ ", featureFlaggingConfigurationSourceRequestTimeoutSeconds="

metadata/supported-configurations.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,14 @@
15051505
"aliases": []
15061506
}
15071507
],
1508+
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL": [
1509+
{
1510+
"version": "A",
1511+
"type": "string",
1512+
"default": null,
1513+
"aliases": []
1514+
}
1515+
],
15081516
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS": [
15091517
{
15101518
"version": "A",

products/feature-flagging/feature-flagging-api/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ OTEL_EXPORTER_OTLP_PROTOCOL=grpc
8686

8787
- Java 11+
8888
- `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=agentless` uses the Datadog agentless
89-
backend. `remote_config` uses the existing Agent Remote Configuration path.
90-
`offline` is reserved for startup-provided UFC bytes; until those bytes are
91-
implemented, no network source starts and evaluations use defaults.
89+
backend. Set `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL` to a
90+
different HTTP backend while keeping agentless delivery semantics. A bare
91+
host uses the standard server-distribution path; a URL with a path is used as
92+
the exact UFC endpoint. `remote_config` uses the existing Agent Remote
93+
Configuration path. `offline` is reserved for startup-provided UFC bytes;
94+
until those bytes are implemented, no network source starts and evaluations
95+
use defaults.

products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessConfigurationSource.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,11 @@ private void updateEtag(final String nextEtag) {
170170
}
171171

172172
static HttpUrl endpoint(final Config config) {
173-
final String endpoint = datadogApiServerDistributionEndpoint(config);
173+
final String configuredBaseUrl = config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl();
174+
final String endpoint =
175+
configuredBaseUrl == null
176+
? datadogApiServerDistributionEndpoint(config)
177+
: endpointFromConfiguredBaseUrl(configuredBaseUrl);
174178
final HttpUrl parsed = HttpUrl.parse(endpoint);
175179
if (parsed == null) {
176180
throw new IllegalArgumentException(
@@ -179,6 +183,22 @@ static HttpUrl endpoint(final Config config) {
179183
return parsed;
180184
}
181185

186+
private static String endpointFromConfiguredBaseUrl(final String configuredBaseUrl) {
187+
final HttpUrl parsed = HttpUrl.parse(configuredBaseUrl.trim());
188+
if (parsed == null) {
189+
throw new IllegalArgumentException(
190+
"Invalid Feature Flagging HTTP configuration source URL: " + configuredBaseUrl);
191+
}
192+
if ("/".equals(parsed.encodedPath()) || parsed.encodedPath().isEmpty()) {
193+
return parsed
194+
.newBuilder()
195+
.addPathSegments(DATADOG_API_SERVER_DISTRIBUTION_PATH.substring(1))
196+
.build()
197+
.toString();
198+
}
199+
return parsed.toString();
200+
}
201+
182202
private static String datadogApiServerDistributionEndpoint(final Config config) {
183203
final StringBuilder endpoint =
184204
new StringBuilder("https://api.")

products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/AgentlessConfigurationSourceTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,41 @@ void derivesDatadogApiServerDistributionEndpointWithoutEnv() {
7070
AgentlessConfigurationSource.endpoint(config("datadoghq.com", null)).toString());
7171
}
7272

73+
@Test
74+
void appendsServerDistributionPathToConfiguredAgentlessBaseUrl() {
75+
final Config config = config();
76+
lenient()
77+
.when(config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl())
78+
.thenReturn("http://mock-backend:8080");
79+
80+
assertEquals(
81+
"http://mock-backend:8080/api/v2/feature-flagging/config/server-distribution",
82+
AgentlessConfigurationSource.endpoint(config).toString());
83+
}
84+
85+
@Test
86+
void usesConfiguredAgentlessEndpointWithPathUnchanged() {
87+
final Config config = config();
88+
lenient()
89+
.when(config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl())
90+
.thenReturn("http://mock-backend:8080/custom/ufc?tenant=test");
91+
92+
assertEquals(
93+
"http://mock-backend:8080/custom/ufc?tenant=test",
94+
AgentlessConfigurationSource.endpoint(config).toString());
95+
}
96+
97+
@Test
98+
void rejectsInvalidConfiguredAgentlessBaseUrl() {
99+
final Config config = config();
100+
lenient()
101+
.when(config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl())
102+
.thenReturn("not a URL");
103+
104+
assertThrows(
105+
IllegalArgumentException.class, () -> AgentlessConfigurationSource.endpoint(config));
106+
}
107+
73108
@Test
74109
void rejectsInvalidDatadogApiServerDistributionEndpoint() {
75110
assertThrows(
@@ -398,6 +433,7 @@ private static Config config(final String site, final String env) {
398433
lenient()
399434
.when(config.getFeatureFlaggingConfigurationSourceRequestTimeoutSeconds())
400435
.thenReturn(2.0D);
436+
lenient().when(config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl()).thenReturn(null);
401437
lenient().when(config.getApiKey()).thenReturn("test-api-key");
402438
lenient().when(config.getSite()).thenReturn(site);
403439
lenient().when(config.getEnv()).thenReturn(env);

0 commit comments

Comments
 (0)