Skip to content

Commit 0b11534

Browse files
committed
feat(flagd): migrate file to own provider type
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 23c5e69 commit 0b11534

File tree

14 files changed

+118
-38
lines changed

14 files changed

+118
-38
lines changed

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/Config.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public final class Config {
4040

4141
static final String RESOLVER_RPC = "rpc";
4242
static final String RESOLVER_IN_PROCESS = "in-process";
43+
static final String RESOLVER_FILE = "file";
4344

4445
public static final String STATIC_REASON = "STATIC";
4546
public static final String CACHED_REASON = "CACHED";
@@ -87,6 +88,8 @@ static Resolver fromValueProvider(Function<String, String> provider) {
8788
return Resolver.IN_PROCESS;
8889
case "rpc":
8990
return Resolver.RPC;
91+
case "file":
92+
return Resolver.FILE;
9093
default:
9194
log.warn("Unsupported resolver variable: {}", resolverVar);
9295
return DEFAULT_RESOLVER_TYPE;
@@ -143,6 +146,11 @@ public String asString() {
143146
public String asString() {
144147
return RESOLVER_IN_PROCESS;
145148
}
149+
},
150+
FILE {
151+
public String asString() {
152+
return RESOLVER_FILE;
153+
}
146154
}
147155
}
148156
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.function.Function;
1313
import lombok.Builder;
1414
import lombok.Getter;
15+
import org.apache.commons.lang3.StringUtils;
1516

1617
/**
1718
* FlagdOptions is a builder to build flagd provider options.
@@ -119,8 +120,7 @@ public class FlagdOptions {
119120
* File source of flags to be used by offline mode.
120121
* Setting this enables the offline mode of the in-process provider.
121122
*/
122-
@Builder.Default
123-
private String offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null);
123+
private String offlineFlagSourcePath;
124124

125125
/**
126126
* gRPC custom target string.
@@ -193,7 +193,20 @@ void prebuild() {
193193
resolverType = fromValueProvider(System::getenv);
194194
}
195195

196-
if (port == 0) {
196+
if (StringUtils.isEmpty(offlineFlagSourcePath)) {
197+
offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null);
198+
}
199+
200+
if (!StringUtils.isEmpty(offlineFlagSourcePath) && resolverType == Config.Resolver.IN_PROCESS) {
201+
resolverType = Config.Resolver.FILE;
202+
}
203+
204+
// We need a file path for FILE Provider
205+
if (StringUtils.isEmpty(offlineFlagSourcePath) && resolverType == Config.Resolver.FILE) {
206+
throw new IllegalArgumentException("Resolver Type 'FILE' requires a offlineFlagSourcePath");
207+
}
208+
209+
if (port == 0 && resolverType != Config.Resolver.FILE) {
197210
port = Integer.parseInt(
198211
fallBackToEnvOrDefault(Config.PORT_ENV_VAR_NAME, determineDefaultPortForResolver()));
199212
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public FlagdProvider() {
8080
*/
8181
public FlagdProvider(final FlagdOptions options) {
8282
switch (options.getResolverType().asString()) {
83+
case Config.RESOLVER_FILE:
8384
case Config.RESOLVER_IN_PROCESS:
8485
this.flagResolver = new InProcessResolver(options, this::onProviderEvent);
8586
break;

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ void TestBuilderOptions() {
6161
.cacheType("lru")
6262
.maxCacheSize(100)
6363
.selector("app=weatherApp")
64-
.offlineFlagSourcePath("some-path")
6564
.openTelemetry(openTelemetry)
6665
.customConnector(connector)
6766
.resolverType(Resolver.IN_PROCESS)
@@ -76,7 +75,6 @@ void TestBuilderOptions() {
7675
assertEquals("lru", flagdOptions.getCacheType());
7776
assertEquals(100, flagdOptions.getMaxCacheSize());
7877
assertEquals("app=weatherApp", flagdOptions.getSelector());
79-
assertEquals("some-path", flagdOptions.getOfflineFlagSourcePath());
8078
assertEquals(openTelemetry, flagdOptions.getOpenTelemetry());
8179
assertEquals(connector, flagdOptions.getCustomConnector());
8280
assertEquals(Resolver.IN_PROCESS, flagdOptions.getResolverType());
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.openfeature.contrib.providers.flagd.e2e;
1+
package dev.openfeature.contrib.providers.flagd;
22

33
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
44
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e;
2+
3+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
4+
import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME;
5+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
6+
7+
import dev.openfeature.contrib.providers.flagd.Config;
8+
import org.apache.logging.log4j.core.config.Order;
9+
import org.junit.platform.suite.api.BeforeSuite;
10+
import org.junit.platform.suite.api.ConfigurationParameter;
11+
import org.junit.platform.suite.api.ExcludeTags;
12+
import org.junit.platform.suite.api.IncludeEngines;
13+
import org.junit.platform.suite.api.IncludeTags;
14+
import org.junit.platform.suite.api.SelectDirectories;
15+
import org.junit.platform.suite.api.Suite;
16+
import org.testcontainers.junit.jupiter.Testcontainers;
17+
18+
/**
19+
* Class for running the reconnection tests for the RPC provider
20+
*/
21+
@Order(value = Integer.MAX_VALUE)
22+
@Suite
23+
@IncludeEngines("cucumber")
24+
@SelectDirectories("test-harness/gherkin")
25+
// if you want to run just one feature file, use the following line instead of @SelectDirectories
26+
// @SelectFile("test-harness/gherkin/connection.feature")
27+
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
28+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps")
29+
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
30+
@IncludeTags("file")
31+
@ExcludeTags({"unixsocket", "targetURI", "reconnect", "customCert", "events"})
32+
@Testcontainers
33+
public class RunFileTest {
34+
35+
@BeforeSuite
36+
public static void before() {
37+
State.resolverType = Config.Resolver.FILE;
38+
}
39+
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/State.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ public class State {
2323
public FlagdOptions options;
2424
public FlagdOptions.FlagdOptionsBuilder builder = FlagdOptions.builder();
2525
public static Config.Resolver resolverType;
26+
public boolean hasError;
2627
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/AbstractSteps.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import dev.openfeature.contrib.providers.flagd.e2e.State;
44

5-
abstract class AbstractSteps {
6-
State state;
5+
public abstract class AbstractSteps {
6+
protected State state;
77

8-
public AbstractSteps(State state) {
8+
protected AbstractSteps(State state) {
99
this.state = state;
1010
}
1111
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ConfigSteps.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ public ConfigSteps(State state) {
3434

3535
@When("a config was initialized")
3636
public void we_initialize_a_config() {
37-
state.options = state.builder.build();
37+
try {
38+
state.options = state.builder.build();
39+
} catch (IllegalArgumentException e) {
40+
state.options = null;
41+
state.hasError = true;
42+
}
3843
}
3944

4045
@When("a config was initialized for {string}")
@@ -87,19 +92,25 @@ public void the_option_of_type_should_have_the_value(String option, String type,
8792
}
8893

8994
option = mapOptionNames(option);
90-
91-
assertThat(state.options).hasFieldOrPropertyWithValue(option, convert);
92-
93-
// Resetting env vars
94-
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
95-
if (envVar.getValue() == null) {
96-
EnvironmentVariableUtils.clear(envVar.getKey());
97-
} else {
98-
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
95+
try {
96+
assertThat(state.options).hasFieldOrPropertyWithValue(option, convert);
97+
} finally {
98+
// Resetting env vars
99+
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
100+
if (envVar.getValue() == null) {
101+
EnvironmentVariableUtils.clear(envVar.getKey());
102+
} else {
103+
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
104+
}
99105
}
100106
}
101107
}
102108

109+
@Then("we should have an error")
110+
public void we_should_have_an_error() {
111+
assertThat(state.hasError).isTrue();
112+
}
113+
103114
private static String mapOptionNames(String option) {
104115
Map<String, String> propertyMapper = new HashMap<>();
105116
propertyMapper.put("resolver", "resolverType");

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/EnvironmentVariableUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* This class modifies the internals of the environment variables map with reflection. Warning: If
1515
* your {@link SecurityManager} does not allow modifications, it fails.
1616
*/
17-
class EnvironmentVariableUtils {
17+
public class EnvironmentVariableUtils {
1818

1919
private EnvironmentVariableUtils() {
2020
// private constructor to prevent instantiation of utility class

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ public int getPort(Config.Resolver resolver, ProviderType providerType) {
134134
case SSL:
135135
return toxiproxy.getMappedPort(8669);
136136
}
137+
case FILE:
138+
return 0;
137139
default:
138140
throw new IllegalArgumentException("Unsupported resolver: " + resolver);
139141
}
@@ -143,10 +145,23 @@ public int getPort(Config.Resolver resolver, ProviderType providerType) {
143145
public void setupProvider(String providerType) throws IOException {
144146
state.builder.deadline(500).keepAlive(0).retryGracePeriod(3);
145147
boolean wait = true;
148+
File flags = new File("test-harness/flags");
149+
ObjectMapper objectMapper = new ObjectMapper();
150+
Object merged = new Object();
151+
for (File listFile : Objects.requireNonNull(flags.listFiles())) {
152+
ObjectReader updater = objectMapper.readerForUpdating(merged);
153+
merged = updater.readValue(listFile, Object.class);
154+
}
155+
Path offlinePath = Files.createTempFile("flags", ".json");
156+
objectMapper.writeValue(offlinePath.toFile(), merged);
146157
switch (providerType) {
147158
case "unavailable":
148159
this.state.providerType = ProviderType.SOCKET;
149160
state.builder.port(UNAVAILABLE_PORT);
161+
if (State.resolverType == Config.Resolver.FILE) {
162+
163+
state.builder.offlineFlagSourcePath("not-existing");
164+
}
150165
wait = false;
151166
break;
152167
case "socket":
@@ -167,25 +182,17 @@ public void setupProvider(String providerType) throws IOException {
167182
.tls(true)
168183
.certPath(absolutePath);
169184
break;
170-
case "offline":
171-
File flags = new File("test-harness/flags");
172-
ObjectMapper objectMapper = new ObjectMapper();
173-
Object merged = new Object();
174-
for (File listFile : Objects.requireNonNull(flags.listFiles())) {
175-
ObjectReader updater = objectMapper.readerForUpdating(merged);
176-
merged = updater.readValue(listFile, Object.class);
177-
}
178-
Path offlinePath = Files.createTempFile("flags", ".json");
179-
objectMapper.writeValue(offlinePath.toFile(), merged);
180-
181-
state.builder
182-
.port(UNAVAILABLE_PORT)
183-
.offlineFlagSourcePath(offlinePath.toAbsolutePath().toString());
184-
break;
185185

186186
default:
187187
this.state.providerType = ProviderType.DEFAULT;
188-
state.builder.port(getPort(State.resolverType, state.providerType));
188+
if (State.resolverType == Config.Resolver.FILE) {
189+
190+
state.builder
191+
.port(UNAVAILABLE_PORT)
192+
.offlineFlagSourcePath(offlinePath.toAbsolutePath().toString());
193+
} else {
194+
state.builder.port(getPort(State.resolverType, state.providerType));
195+
}
189196
break;
190197
}
191198
FeatureProvider provider =

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public static Object convert(String value, String type) throws ClassNotFoundExce
3030
return Config.Resolver.IN_PROCESS;
3131
case "rpc":
3232
return Config.Resolver.RPC;
33+
case "file":
34+
return Config.Resolver.FILE;
3335
default:
3436
throw new RuntimeException("Unknown resolver type: " + value);
3537
}

providers/flagd/test-harness

0 commit comments

Comments
 (0)