Skip to content
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
16 changes: 11 additions & 5 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2113,7 +2113,7 @@ tests/:
parametric/:
test_feature_flag_exposure/:
test_feature_flag_exposure.py:
Test_Feature_Flag_Exposure: missing_feature
Test_Feature_Flag_Exposure: v1.56.0
test_128_bit_traceids.py:
Test_128_Bit_Traceids: v1.12.0
test_config_consistency.py:
Expand Down Expand Up @@ -2150,7 +2150,7 @@ tests/:
Test_Parametric_DDTrace_Baggage: incomplete_test_app (baggage endpoints are not implemented)
Test_Parametric_DDTrace_Crash: bug (APMAPI-778) # The crash endpoint does not kill the application
Test_Parametric_DDTrace_Current_Span: bug (APMAPI-778) # Fails to retreive the current span after a span has finished
Test_Parametric_FFE_Start: missing_feature
Test_Parametric_FFE_Start: v1.56.0
Test_Parametric_Otel_Baggage: missing_feature (otel baggage is not supported)
Test_Parametric_Otel_Current_Span: bug (APMAPI-778) # Current span endpoint does not return DataDog spans created by the otel api
Test_Parametric_Write_Log: missing_feature
Expand Down Expand Up @@ -2331,9 +2331,15 @@ tests/:
Test_Span_Links_From_Conflicting_Contexts: v1.43.0
Test_Span_Links_Omit_Tracestate_From_Conflicting_Contexts: missing_feature (implementation specs have not been determined)
test_feature_flag_exposures.py:
Test_FFE_Exposure_Events: missing_feature
Test_FFE_Exposure_Events_Empty: missing_feature
Test_FFE_Exposure_Events_Errors: missing_feature
Test_FFE_Exposure_Events:
'*': irrelevant
spring-boot: v1.56.0
Test_FFE_Exposure_Events_Empty:
'*': irrelevant
spring-boot: v1.56.0
Test_FFE_Exposure_Events_Errors:
'*': irrelevant
spring-boot: v1.56.0
test_graphql.py:
Test_GraphQLOperationErrorReporting:
'*': missing_feature
Expand Down
3 changes: 3 additions & 0 deletions utils/build/docker/java/install_ddtrace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ install_custom_jar() {
# Look for custom dd-trace-api jar in custom binaries folder
install_custom_jar "dd-trace-api*.jar" "dd-trace-api" "$MVN_OPTS"

# Look for custom dd-openfeature jar in custom binaries folder
install_custom_jar "dd-openfeature*.jar" "dd-openfeature" "$MVN_OPTS"

# Look for custom dd-trace-java jar in custom binaries folder
if [ $(ls /binaries/dd-java-agent*.jar | wc -l) = 0 ]; then
BUILD_URL="https://github.com/DataDog/dd-trace-java/releases/latest/download/dd-java-agent.jar"
Expand Down
3 changes: 3 additions & 0 deletions utils/build/docker/java/parametric/install_ddtrace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ configure_custom_jar() {
# Look for custom dd-trace-api jar in custom binaries folder
configure_custom_jar "dd-trace-api*.jar" "dd-trace-api" "customDdTraceApi"

# Look for custom dd-openfeature jar in custom binaries folder
configure_custom_jar "dd-openfeature*.jar" "dd-openfeature" "customDdOpenfeature"

# Look for custom dd-java-agent jar in custom binaries folder
CUSTOM_DD_JAVA_AGENT_COUNT=$(find /binaries/dd-java-agent*.jar 2>/dev/null | wc -l)
if [ "$CUSTOM_DD_JAVA_AGENT_COUNT" = 0 ]; then
Expand Down
29 changes: 29 additions & 0 deletions utils/build/docker/java/parametric/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<opentelemetry.version>1.45.0</opentelemetry.version>
<!-- Latest version of public tracer APP to match latest pulled agent -->
<dd-trace-api.version>[1.47.0,)</dd-trace-api.version>
<dd-openfeature.version>[1.56.0,)</dd-openfeature.version>
</properties>

<!--Enable when oss.sonatype.org is available again-->
Expand Down Expand Up @@ -69,6 +70,12 @@
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- OpenFeature API -->
<dependency>
<groupId>com.datadoghq</groupId>
<artifactId>dd-openfeature</artifactId>
<version>${dd-openfeature.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand All @@ -89,6 +96,11 @@
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.18.2</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -122,5 +134,22 @@
</dependency>
</dependencies>
</profile>
<profile>
<id>custom-dd-openfeature</id>
<activation>
<property>
<name>customDdOpenfeature</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>com.datadoghq</groupId>
<artifactId>dd-openfeature</artifactId>
<version>dev</version>
<scope>system</scope>
<systemPath>${customDdOpenfeature}</systemPath>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package com.datadoghq.trace.controller;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import com.fasterxml.jackson.annotation.JsonAlias;
import datadog.trace.api.openfeature.Provider;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.NoOpProvider;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.ProviderState;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/ffe")
public class FeatureFlagEvaluatorController {

private static final Logger LOGGER = LoggerFactory.getLogger(FeatureFlagEvaluatorController.class);

@Configuration
public static class FeatureFlagEvaluatorConfig {

@Lazy
@Bean
public Client client() {
final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
final String envProperty = System.getenv("DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED");
final FeatureProvider provider;
if (Boolean.parseBoolean(envProperty)) {
provider = new Provider();
} else {
provider = new NoOpProvider() {
@Override
public ProviderState getState() {
return ProviderState.READY;
}
};
}
api.setProviderAndWait(provider);
return api.getClient();
}
}

@Autowired
@Lazy
private Client client;

@PostMapping(value = "/start")
public ResponseEntity<Boolean> start() {
final ProviderState state = client.getProviderState();
if (state == ProviderState.READY) {
return ResponseEntity.ok(true);
} else {
return ResponseEntity.internalServerError().body(false);
}
}

@PostMapping(value = "/evaluate", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> evaluate(@RequestBody final EvaluateRequest request) {
Object value;
String reason;
final EvaluationContext context = context(request);
try {
value = switch (request.getVariationType()) {
case "BOOLEAN" ->
client.getBooleanValue(request.getFlag(), (Boolean) request.getDefaultValue(), context);
case "STRING" -> client.getStringValue(request.getFlag(), (String) request.getDefaultValue(), context);
case "INTEGER" -> {
final Number integerEval = (Number) request.getDefaultValue();
yield client.getIntegerValue(request.getFlag(), integerEval.intValue(), context);
}
case "NUMERIC" -> {
final Number doubleEval = (Number) request.getDefaultValue();
yield client.getDoubleValue(request.getFlag(), doubleEval.doubleValue(), context);
}
case "JSON" -> {
final Value objectValue = client.getObjectValue(request.getFlag(), Value.objectToValue(request.getDefaultValue()), context);
yield context.convertValue(objectValue);
}
default -> request.getDefaultValue();
};

reason = "DEFAULT";
} catch (Throwable e) {
LOGGER.error("Error on resolution", e);
value = request.getDefaultValue();
reason = "ERROR";
}
final Map<String, Object> result = new HashMap<>();
result.put("reason", reason);
result.put("value", value);
return ResponseEntity.ok(result);
}

private static EvaluationContext context(final EvaluateRequest request) {
final MutableContext context = new MutableContext();
context.setTargetingKey(request.getTargetingKey());
request.attributes.forEach((key, value) -> {
if (value instanceof Boolean) {
context.add(key, (Boolean) value);
} else if (value instanceof Integer) {
context.add(key, (Integer) value);
} else if (value instanceof Double) {
context.add(key, (Double) value);
} else if (value instanceof String) {
context.add(key, (String) value);
} else if (value instanceof Map) {
context.add(key, Value.objectToValue(value).asStructure());
} else if (value instanceof List) {
context.add(key, Value.objectToValue(value).asList());
} else {
context.add(key, (Structure) null);
}
});
return context;
}

public static class EvaluateRequest {
private String flag;
@JsonAlias("variation_type")
private String variationType;
@JsonAlias("default_value")
private Object defaultValue;
@JsonAlias("targeting_key")
private String targetingKey;
private Map<String, Object> attributes;

public Map<String, Object> getAttributes() {
return attributes;
}

public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}

public Object getDefaultValue() {
return defaultValue;
}

public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}

public String getFlag() {
return flag;
}

public void setFlag(String flag) {
this.flag = flag;
}

public String getTargetingKey() {
return targetingKey;
}

public void setTargetingKey(String targetingKey) {
this.targetingKey = targetingKey;
}

public String getVariationType() {
return variationType;
}

public void setVariationType(String variationType) {
this.variationType = variationType;
}
}
}
11 changes: 11 additions & 0 deletions utils/build/docker/java/spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
<artifactId>dd-trace-api</artifactId>
<version>${dd-trace-api.version}</version>
</dependency>
<dependency>
<groupId>com.datadoghq</groupId>
<artifactId>dd-openfeature</artifactId>
<version>${dd-openfeature.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
Expand Down Expand Up @@ -224,6 +229,11 @@
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.18.2</version>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down Expand Up @@ -492,6 +502,7 @@
<grpc.version>1.65.1</grpc.version>
<packaging.type>jar</packaging.type>
<dd-trace-api.version>[1.47.0,)</dd-trace-api.version>
<dd-openfeature.version>[1.56.0,)</dd-openfeature.version>
</properties>
<!--Enable when oss.sonatype.org is available again-->
<!-- <repositories>-->
Expand Down
Loading
Loading