diff --git a/providers/flagd/docker-compose.yml b/providers/flagd/docker-compose.yml deleted file mode 100644 index 454edb7d1..000000000 --- a/providers/flagd/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - flagd: - build: - context: ./test-harness - dockerfile: flagd/Dockerfile diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java index 86fbb8056..e54c938cf 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; /** * Resolves flag values using @@ -204,7 +205,7 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC // check variant existence Object value = flag.getVariants().get(resolvedVariant); if (value == null) { - if (resolvedVariant.isEmpty() && flag.getDefaultVariant().isEmpty()) { + if (StringUtils.isEmpty(resolvedVariant) && StringUtils.isEmpty(flag.getDefaultVariant())) { return ProviderEvaluation.builder() .reason(Reason.ERROR.toString()) .errorCode(ErrorCode.FLAG_NOT_FOUND) diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/ContainerUtil.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/ContainerUtil.java new file mode 100644 index 000000000..b63967223 --- /dev/null +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/ContainerUtil.java @@ -0,0 +1,32 @@ +package dev.openfeature.contrib.providers.flagd.e2e; + +import dev.openfeature.contrib.providers.flagd.Config; +import java.util.Optional; +import org.testcontainers.containers.ComposeContainer; +import org.testcontainers.containers.ContainerState; + +public class ContainerUtil { + public static int getPort(ComposeContainer container, Config.Resolver resolver) { + Optional flagd = container.getContainerByServiceName("flagd"); + + return flagd.map(containerState -> { + switch (resolver) { + case RPC: + return containerState.getMappedPort(8013); + case IN_PROCESS: + return containerState.getMappedPort(8015); + default: + return 0; + } + }) + .orElseThrow(() -> new RuntimeException("Could not map port")); + } + + public static String getLaunchpadUrl(ComposeContainer container) { + Optional flagd = container.getContainerByServiceName("flagd"); + return flagd.map(containerState -> { + return containerState.getHost() + ":" + containerState.getMappedPort(8080); + }) + .orElseThrow(() -> new RuntimeException("Could not find launchpad url")); + } +} diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/FlagdContainer.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/FlagdContainer.java deleted file mode 100644 index 8fb83833a..000000000 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/FlagdContainer.java +++ /dev/null @@ -1,66 +0,0 @@ -package dev.openfeature.contrib.providers.flagd.e2e; - -import dev.openfeature.contrib.providers.flagd.Config; -import java.io.File; -import java.nio.file.Files; -import java.util.List; -import org.jetbrains.annotations.NotNull; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.Network; -import org.testcontainers.utility.DockerImageName; -import org.testcontainers.utility.MountableFile; - -public class FlagdContainer extends GenericContainer { - private static final String version; - private static final Network network = Network.newNetwork(); - - static { - String path = "test-harness/version.txt"; - File file = new File(path); - try { - List lines = Files.readAllLines(file.toPath()); - version = lines.get(0); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public FlagdContainer() { - super(generateContainerName()); - this.addExposedPorts(8013, 8014, 8015, 8080); - } - - public int getPort(Config.Resolver resolver) { - switch (resolver) { - case RPC: - return getMappedPort(8013); - case IN_PROCESS: - return getMappedPort(8015); - default: - return 0; - } - } - - public String getLaunchpadUrl() { - return this.getHost() + ":" + this.getMappedPort(8080); - } - /** - * @return a {@link org.testcontainers.containers.GenericContainer} instance of envoy container using - * flagd sync service as backend expose on port 9211 - */ - public static GenericContainer envoy() { - final String container = "envoyproxy/envoy:v1.31.0"; - return new GenericContainer(DockerImageName.parse(container)) - .withCopyFileToContainer( - MountableFile.forClasspathResource("/envoy-config/envoy-custom.yaml"), "/etc/envoy/envoy.yaml") - .withExposedPorts(9211) - .withNetwork(network) - .withNetworkAliases("envoy"); - } - - public static @NotNull String generateContainerName() { - String container = "ghcr.io/open-feature/flagd-testbed"; - container += ":v" + version; - return container; - } -} diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java index 3a1c33f03..acb060065 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java @@ -29,7 +29,7 @@ @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") @IncludeTags("in-process") -@ExcludeTags({"unixsocket", "targetURI"}) +@ExcludeTags({"unixsocket"}) @Testcontainers @Isolated public class RunInProcessTest { diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java index bc649ddeb..f0b0765fc 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java @@ -28,7 +28,7 @@ @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") @IncludeTags({"rpc"}) -@ExcludeTags({"targetURI", "unixsocket"}) +@ExcludeTags({"unixsocket"}) @Testcontainers public class RunRpcTest { diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java index 47da566ba..e8760909c 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java @@ -5,13 +5,12 @@ import dev.openfeature.contrib.providers.flagd.Config; import dev.openfeature.contrib.providers.flagd.FlagdOptions; import dev.openfeature.contrib.providers.flagd.FlagdProvider; -import dev.openfeature.contrib.providers.flagd.e2e.FlagdContainer; +import dev.openfeature.contrib.providers.flagd.e2e.ContainerUtil; import dev.openfeature.contrib.providers.flagd.e2e.State; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.OpenFeatureAPI; import io.cucumber.java.After; import io.cucumber.java.AfterAll; -import io.cucumber.java.Before; import io.cucumber.java.BeforeAll; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; @@ -20,10 +19,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.parallel.Isolated; -import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.ComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; @Isolated() @@ -31,7 +33,7 @@ public class ProviderSteps extends AbstractSteps { public static final int UNAVAILABLE_PORT = 9999; - static FlagdContainer container; + static ComposeContainer container; static Path sharedTempDir; @@ -43,8 +45,14 @@ public ProviderSteps(State state) { public static void beforeAll() throws IOException { sharedTempDir = Files.createDirectories( Paths.get("tmp/" + RandomStringUtils.randomAlphanumeric(8).toLowerCase() + "/")); - container = new FlagdContainer() - .withFileSystemBind(sharedTempDir.toAbsolutePath().toString(), "/flags", BindMode.READ_WRITE); + container = new ComposeContainer(new File("test-harness/docker-compose.yaml")) + .withEnv("FLAGS_DIR", sharedTempDir.toAbsolutePath().toString()) + .withExposedService("flagd", 8013, Wait.forListeningPort()) + .withExposedService("flagd", 8015, Wait.forListeningPort()) + .withExposedService("flagd", 8080, Wait.forListeningPort()) + .withExposedService("envoy", 9211, Wait.forListeningPort()) + .withStartupTimeout(Duration.ofSeconds(45)); + container.start(); } @AfterAll @@ -53,17 +61,10 @@ public static void afterAll() throws IOException { FileUtils.deleteDirectory(sharedTempDir.toFile()); } - @Before - public void before() { - if (!container.isRunning()) { - container.start(); - } - } - @After public void tearDown() { if (state.client != null) { - when().post("http://" + container.getLaunchpadUrl() + "/stop") + when().post("http://" + ContainerUtil.getLaunchpadUrl(container) + "/stop") .then() .statusCode(200); } @@ -100,7 +101,7 @@ public void setupProvider(String providerType) throws InterruptedException { String absolutePath = file.getAbsolutePath(); this.state.providerType = ProviderType.SSL; state.builder - .port(container.getPort(State.resolverType)) + .port(ContainerUtil.getPort(container, State.resolverType)) .tls(true) .certPath(absolutePath); flagdConfig = "ssl"; @@ -117,12 +118,12 @@ public void setupProvider(String providerType) throws InterruptedException { .port(UNAVAILABLE_PORT) .offlineFlagSourcePath(new File("test-harness/flags/" + replace).getAbsolutePath()); } else { - state.builder.port(container.getPort(State.resolverType)); + state.builder.port(ContainerUtil.getPort(container, State.resolverType)); } break; case "syncpayload": flagdConfig = "sync-payload"; - state.builder.port(container.getPort(State.resolverType)); + state.builder.port(ContainerUtil.getPort(container, State.resolverType)); break; case "stable": this.state.providerType = ProviderType.DEFAULT; @@ -135,13 +136,22 @@ public void setupProvider(String providerType) throws InterruptedException { .toAbsolutePath() .toString()); } else { - state.builder.port(container.getPort(State.resolverType)); + state.builder.port(ContainerUtil.getPort(container, State.resolverType)); } break; default: throw new IllegalStateException(); } - when().post("http://" + container.getLaunchpadUrl() + "/start?config={config}", flagdConfig) + + // Setting TargetUri if this setting is set + FlagdOptions tempBuild = state.builder.build(); + if (!StringUtils.isEmpty(tempBuild.getTargetUri())) { + String replace = tempBuild.getTargetUri().replace("", "" + container.getServicePort("envoy", 9211)); + state.builder.targetUri(replace); + state.builder.port(UNAVAILABLE_PORT); + } + + when().post("http://" + ContainerUtil.getLaunchpadUrl(container) + "/start?config={config}", flagdConfig) .then() .statusCode(200); @@ -162,18 +172,22 @@ public void setupProvider(String providerType) throws InterruptedException { @When("the connection is lost") public void the_connection_is_lost() { - when().post("http://" + container.getLaunchpadUrl() + "/stop").then().statusCode(200); + when().post("http://" + ContainerUtil.getLaunchpadUrl(container) + "/stop") + .then() + .statusCode(200); } @When("the connection is lost for {int}s") public void the_connection_is_lost_for(int seconds) { - when().post("http://" + container.getLaunchpadUrl() + "/restart?seconds={seconds}", seconds) + when().post("http://" + ContainerUtil.getLaunchpadUrl(container) + "/restart?seconds={seconds}", seconds) .then() .statusCode(200); } @When("the flag was modified") public void the_flag_was_modded() { - when().post("http://" + container.getLaunchpadUrl() + "/change").then().statusCode(200); + when().post("http://" + ContainerUtil.getLaunchpadUrl(container) + "/change") + .then() + .statusCode(200); } } diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/config/ConfigSteps.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/config/ConfigSteps.java index c33960f31..25a6cdc7d 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/config/ConfigSteps.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/config/ConfigSteps.java @@ -71,6 +71,7 @@ public void we_have_an_option_of_type_with_value(String option, String type, Str .filter(method1 -> method1.getName().equals(mapOptionNames(option))) .findFirst() .orElseThrow(RuntimeException::new); + method.invoke(state.builder, converted); } diff --git a/providers/flagd/src/test/resources/envoy-config/envoy-custom.yaml b/providers/flagd/src/test/resources/envoy-config/envoy-custom.yaml deleted file mode 100644 index fb945e956..000000000 --- a/providers/flagd/src/test/resources/envoy-config/envoy-custom.yaml +++ /dev/null @@ -1,49 +0,0 @@ -static_resources: - listeners: - - name: local-envoy - address: - socket_address: - address: 0.0.0.0 - port_value: 9211 - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress_http - access_log: - - name: envoy.access_loggers.stdout - typed_config: - "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - route_config: - name: local_route - virtual_hosts: - - name: local_service - domains: - - "flagd-sync.service" - routes: - - match: - prefix: "/" - grpc: {} - route: - cluster: local-sync-service - - clusters: - - name: local-sync-service - type: LOGICAL_DNS - # Comment out the following line to test on v6 networks - dns_lookup_family: V4_ONLY - http2_protocol_options: {} - load_assignment: - cluster_name: local-sync-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: sync-service - port_value: 8015 diff --git a/providers/flagd/test-harness b/providers/flagd/test-harness index fe68e0310..fdce98780 160000 --- a/providers/flagd/test-harness +++ b/providers/flagd/test-harness @@ -1 +1 @@ -Subproject commit fe68e0310fd817a8f9bc1e2559f2277fed3aed34 +Subproject commit fdce98780f5811bd4672fb7f2b56a6be05fc46d2