From cf492224311d4c0144b2a5084d5d46d45fd70351 Mon Sep 17 00:00:00 2001 From: Philipp Fehre Date: Mon, 4 Aug 2025 22:18:43 +0200 Subject: [PATCH 1/3] wip: start test for go-feature-flag support --- ...eatureBundleGoFeatureFlagProviderTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java diff --git a/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java new file mode 100644 index 0000000..0e2e64d --- /dev/null +++ b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java @@ -0,0 +1,43 @@ +package io.github.sideshowcoder.dropwizard_openfeature; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.codahale.metrics.health.HealthCheck; + +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Client; +import io.dropwizard.testing.ResourceHelpers; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import io.github.sideshowcoder.dropwizard_openfeature.helpers.App; +import io.github.sideshowcoder.dropwizard_openfeature.helpers.Config; + +@ExtendWith(DropwizardExtensionsSupport.class) +public class OpenFeatureBundleGoFeatureFlagProviderTest { + + // TODO start the gofeatureflag/go-feature-flag:latest docker image which is the relay proxy to interact with the provider https://gofeatureflag.org/docs/relay-proxy/getting_started + // TODO create class to parse the configuration options see https://gofeatureflag.org/docs/relay-proxy/getting_started for available options + // TODO how can I make this work on github? Can I start docker containers there? + + private static final DropwizardAppExtension APP = new DropwizardAppExtension<>( + App.class, + ResourceHelpers.resourceFilePath("go-feature-flag-provider-config.yml") + ); + + @Test + public void initializesHealthCheck() throws Exception { + HealthCheck.Result healthcheckResult = APP.getEnvironment().healthChecks().runHealthCheck("openfeature-health-check"); + assertTrue(healthcheckResult.isHealthy()); + } + + @Test + public void providesFeatureFlagsViaInMemoryProvider() throws Exception { + // See flagd-test-flags.json for flag definitions used! + Client client = OpenFeatureAPI.getInstance().getClient("go-feature-flag-client"); + assertEquals("red", client.getStringValue("static-string-flag", "not-expected-value")); + } +} From 6f12d80afbbfe0653c3ece6467cd573662fdc05b Mon Sep 17 00:00:00 2001 From: Philipp Fehre Date: Fri, 12 Sep 2025 11:07:25 +0200 Subject: [PATCH 2/3] WIP go-feature-flag --- pom.xml | 39 ++++++++++++++++--- .../GoFeatureFlagConfiguration.java | 23 +++++++++++ .../OpenFeatureBundle.java | 9 +++++ .../OpenFeatureConfiguration.java | 12 ++++++ .../dropwizard_openfeature/ProviderType.java | 1 + ...eatureBundleGoFeatureFlagProviderTest.java | 21 ++++++++-- src/test/resources/basic-flagd-config.yml | 2 +- .../go-feature-flag-provider-config.yml | 4 ++ .../resources/go-feature-flags-flags.yaml | 7 ++++ src/test/resources/goff-proxy.yaml | 3 ++ 10 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java create mode 100644 src/test/resources/go-feature-flag-provider-config.yml create mode 100644 src/test/resources/go-feature-flags-flags.yaml create mode 100644 src/test/resources/goff-proxy.yaml diff --git a/pom.xml b/pom.xml index 96ac6dd..3e2d365 100644 --- a/pom.xml +++ b/pom.xml @@ -15,13 +15,14 @@ OpenFeature bundle for Dropwizard - - 8 + + 11 UTF-8 UTF-8 5.0.0 1.18.2 0.11.17 + 1.0.1 0.8.14 3.14.1 3.2.8 @@ -31,6 +32,7 @@ 3.5.4 3.5.4 5.20.0 + 1.21.3 0.9.0 @@ -75,8 +77,15 @@ mockito-bom ${mockito.version} pom - test + import + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + @@ -112,6 +121,12 @@ flagd ${openfeature-contrib-providers-flagd.version} + + dev.openfeature.contrib.providers + go-feature-flag + ${openfeature-contrib-providers-go-feature-flag.version} + + io.dropwizard dropwizard-testing @@ -127,6 +142,21 @@ mockito-junit-jupiter test + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + testcontainers + test + @@ -165,8 +195,7 @@ maven-compiler-plugin ${maven-compiler-plugin.version} - ${java.version} - ${java.version} + ${maven.compiler.release} diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java new file mode 100644 index 0000000..fa18253 --- /dev/null +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java @@ -0,0 +1,23 @@ +package io.github.sideshowcoder.dropwizard_openfeature; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions; +import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions.GoFeatureFlagProviderOptionsBuilder; +import jakarta.validation.constraints.NotNull; + +public class GoFeatureFlagConfiguration { + + @JsonProperty + @NotNull + private String endpoint; + + public GoFeatureFlagProviderOptions getGoFeatureFlagProviderOptions() { + GoFeatureFlagProviderOptionsBuilder builder = GoFeatureFlagProviderOptions.builder(); + + builder.endpoint(endpoint); + + return builder.build(); + } + +} diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java index 6177a93..5b6b650 100644 --- a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java @@ -1,6 +1,8 @@ package io.github.sideshowcoder.dropwizard_openfeature; import dev.openfeature.contrib.providers.flagd.FlagdProvider; +import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProvider; +import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.OpenFeatureAPI; @@ -44,6 +46,13 @@ private synchronized void initializeFeatureProvider(OpenFeatureConfiguration con case FLAGD: featureProvider = new FlagdProvider(config.getFlagd().getFlagdOptions()); break; + case GOFEATUREFLAG: + try { + featureProvider = new GoFeatureFlagProvider(config.getGoFeatureFlag().getGoFeatureFlagProviderOptions()); + } catch(InvalidOptions e) { + new RuntimeException(e); + } + break; } } } diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureConfiguration.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureConfiguration.java index 1ec8b69..6a4d48d 100644 --- a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureConfiguration.java +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureConfiguration.java @@ -11,6 +11,10 @@ public class OpenFeatureConfiguration { @JsonProperty private FlagdConfiguration flagd = new FlagdConfiguration(); + @Valid + @JsonProperty + private GoFeatureFlagConfiguration gofeatureflag = new GoFeatureFlagConfiguration(); + @Valid @JsonProperty private OpenFeatureHealthCheckConfiguration healthcheck = new OpenFeatureHealthCheckConfiguration(); @@ -28,6 +32,14 @@ public void setFlagd(FlagdConfiguration flagd) { this.flagd = flagd; } + public GoFeatureFlagConfiguration getGoFeatureFlag() { + return gofeatureflag; + } + + public void setGoFeatureFlag(GoFeatureFlagConfiguration gofeatureflag) { + this.gofeatureflag = gofeatureflag; + } + public OpenFeatureHealthCheckConfiguration getHealthcheck() { return healthcheck; } diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/ProviderType.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/ProviderType.java index 3e46a69..7f846df 100644 --- a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/ProviderType.java +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/ProviderType.java @@ -2,5 +2,6 @@ public enum ProviderType { FLAGD, + GOFEATUREFLAG, INMEMORY; } diff --git a/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java index 0e2e64d..affcf20 100644 --- a/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java +++ b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java @@ -5,21 +5,37 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; import com.codahale.metrics.health.HealthCheck; -import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.OpenFeatureAPI; import io.dropwizard.testing.ResourceHelpers; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.github.sideshowcoder.dropwizard_openfeature.helpers.App; import io.github.sideshowcoder.dropwizard_openfeature.helpers.Config; +@Testcontainers @ExtendWith(DropwizardExtensionsSupport.class) public class OpenFeatureBundleGoFeatureFlagProviderTest { + @Container + public GenericContainer goFeatureFlagRelay = new GenericContainer<>(DockerImageName.parse("gofeatureflag/go-feature-flag:latest")) + .withExposedPorts(1031) + .withCopyFileToContainer(MountableFile.forClasspathResource("goff-proxy.yaml"), "/goff/goff-proxy.yaml") + .withCopyFileToContainer(MountableFile.forClasspathResource("go-feature-flags-flags.yaml"), "/goff/flags.yaml") + .waitingFor(Wait.forHttp("/health")); + + // TODO need to start container before dropwizard app! Need to bundle it in some kind of @BeforeAll or something to start the dropwizard app. // TODO start the gofeatureflag/go-feature-flag:latest docker image which is the relay proxy to interact with the provider https://gofeatureflag.org/docs/relay-proxy/getting_started + // TODO set endpoint to the endpoint provider via the docker container // TODO create class to parse the configuration options see https://gofeatureflag.org/docs/relay-proxy/getting_started for available options // TODO how can I make this work on github? Can I start docker containers there? @@ -36,8 +52,7 @@ public void initializesHealthCheck() throws Exception { @Test public void providesFeatureFlagsViaInMemoryProvider() throws Exception { - // See flagd-test-flags.json for flag definitions used! Client client = OpenFeatureAPI.getInstance().getClient("go-feature-flag-client"); - assertEquals("red", client.getStringValue("static-string-flag", "not-expected-value")); + assertEquals("red", client.getStringValue("staticstringflag", "not-expected-value")); } } diff --git a/src/test/resources/basic-flagd-config.yml b/src/test/resources/basic-flagd-config.yml index 04995ee..e4403d4 100644 --- a/src/test/resources/basic-flagd-config.yml +++ b/src/test/resources/basic-flagd-config.yml @@ -1,4 +1,4 @@ host: flagd port: 8082 resolver: rpc -cacheType: disabled \ No newline at end of file +cacheType: disabled diff --git a/src/test/resources/go-feature-flag-provider-config.yml b/src/test/resources/go-feature-flag-provider-config.yml new file mode 100644 index 0000000..b51c2ce --- /dev/null +++ b/src/test/resources/go-feature-flag-provider-config.yml @@ -0,0 +1,4 @@ +openfeature: + provider: gofeatureflag + gofeatureflag: + endpoint: "http://localhost:1031/" diff --git a/src/test/resources/go-feature-flags-flags.yaml b/src/test/resources/go-feature-flags-flags.yaml new file mode 100644 index 0000000..538b374 --- /dev/null +++ b/src/test/resources/go-feature-flags-flags.yaml @@ -0,0 +1,7 @@ +staticstringflag: + variations: + red: "red" + green: "green" + blue: "blue" + defaultRule: + variation: red diff --git a/src/test/resources/goff-proxy.yaml b/src/test/resources/goff-proxy.yaml new file mode 100644 index 0000000..4a373a3 --- /dev/null +++ b/src/test/resources/goff-proxy.yaml @@ -0,0 +1,3 @@ +retrievers: + - kind: file + path: /goff/flags.yaml From c20d433cb7b3de8b8b1e0dc5a813b4ef4237f5ee Mon Sep 17 00:00:00 2001 From: Philipp Fehre Date: Fri, 24 Oct 2025 23:21:42 +0200 Subject: [PATCH 3/3] feat: GoFeatureFlagProvider support currently only supporting configuring the endpoint. Using test containers to spin up instance to test against --- .../GoFeatureFlagConfiguration.java | 7 +++-- .../OpenFeatureBundle.java | 1 + ...eatureBundleGoFeatureFlagProviderTest.java | 31 ++++++++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java index fa18253..864b8c2 100644 --- a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/GoFeatureFlagConfiguration.java @@ -4,6 +4,7 @@ import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions; import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions.GoFeatureFlagProviderOptionsBuilder; +import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions; import jakarta.validation.constraints.NotNull; public class GoFeatureFlagConfiguration { @@ -12,12 +13,14 @@ public class GoFeatureFlagConfiguration { @NotNull private String endpoint; - public GoFeatureFlagProviderOptions getGoFeatureFlagProviderOptions() { + public GoFeatureFlagProviderOptions getGoFeatureFlagProviderOptions() throws InvalidOptions { GoFeatureFlagProviderOptionsBuilder builder = GoFeatureFlagProviderOptions.builder(); builder.endpoint(endpoint); - return builder.build(); + GoFeatureFlagProviderOptions options = builder.build(); + options.validate(); + return options; } } diff --git a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java index 5b6b650..9aa31d9 100644 --- a/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java +++ b/src/main/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundle.java @@ -2,6 +2,7 @@ import dev.openfeature.contrib.providers.flagd.FlagdProvider; import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProvider; +import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions; import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.FeatureProvider; diff --git a/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java index affcf20..8a5d281 100644 --- a/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java +++ b/src/test/java/io/github/sideshowcoder/dropwizard_openfeature/OpenFeatureBundleGoFeatureFlagProviderTest.java @@ -3,6 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.containers.GenericContainer; @@ -15,6 +17,8 @@ import com.codahale.metrics.health.HealthCheck; import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.ImmutableContext; import dev.openfeature.sdk.OpenFeatureAPI; import io.dropwizard.testing.ResourceHelpers; import io.dropwizard.testing.junit5.DropwizardAppExtension; @@ -27,17 +31,18 @@ public class OpenFeatureBundleGoFeatureFlagProviderTest { @Container - public GenericContainer goFeatureFlagRelay = new GenericContainer<>(DockerImageName.parse("gofeatureflag/go-feature-flag:latest")) - .withExposedPorts(1031) - .withCopyFileToContainer(MountableFile.forClasspathResource("goff-proxy.yaml"), "/goff/goff-proxy.yaml") - .withCopyFileToContainer(MountableFile.forClasspathResource("go-feature-flags-flags.yaml"), "/goff/flags.yaml") - .waitingFor(Wait.forHttp("/health")); - - // TODO need to start container before dropwizard app! Need to bundle it in some kind of @BeforeAll or something to start the dropwizard app. - // TODO start the gofeatureflag/go-feature-flag:latest docker image which is the relay proxy to interact with the provider https://gofeatureflag.org/docs/relay-proxy/getting_started - // TODO set endpoint to the endpoint provider via the docker container - // TODO create class to parse the configuration options see https://gofeatureflag.org/docs/relay-proxy/getting_started for available options - // TODO how can I make this work on github? Can I start docker containers there? + private static GenericContainer goFeatureFlagRelay = createGoFeatureFlagContainer(); + + private static GenericContainer createGoFeatureFlagContainer() { + GenericContainer container = new GenericContainer<>(DockerImageName.parse("gofeatureflag/go-feature-flag:latest")) + .withCopyFileToContainer(MountableFile.forClasspathResource("goff-proxy.yaml"), "/goff/goff-proxy.yaml") + .withCopyFileToContainer(MountableFile.forClasspathResource("go-feature-flags-flags.yaml"), "/goff/flags.yaml"); + + container.setPortBindings(List.of("1031:1031")); + container.waitingFor(Wait.forHttp("/health")); + + return container; + } private static final DropwizardAppExtension APP = new DropwizardAppExtension<>( App.class, @@ -53,6 +58,8 @@ public void initializesHealthCheck() throws Exception { @Test public void providesFeatureFlagsViaInMemoryProvider() throws Exception { Client client = OpenFeatureAPI.getInstance().getClient("go-feature-flag-client"); - assertEquals("red", client.getStringValue("staticstringflag", "not-expected-value")); + // GoFeatureFlags requires a target key for all queries! + EvaluationContext ctx = new ImmutableContext("target"); + assertEquals("red", client.getStringValue("staticstringflag", "not-expected-value", ctx)); } }