Skip to content

Commit a658f9a

Browse files
committed
feat: new provider
Signed-off-by: Simon Schrottner <[email protected]>
1 parent a14931f commit a658f9a

File tree

11 files changed

+117
-37
lines changed

11 files changed

+117
-37
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: 17 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,21 @@ void prebuild() {
193193
resolverType = fromValueProvider(System::getenv);
194194
}
195195

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

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
@@ -82,6 +82,7 @@ public FlagdProvider() {
8282
*/
8383
public FlagdProvider(final FlagdOptions options) {
8484
switch (options.getResolverType().asString()) {
85+
case Config.RESOLVER_FILE:
8586
case Config.RESOLVER_IN_PROCESS:
8687
this.flagResolver = new InProcessResolver(options, this::onProviderEvent);
8788
break;

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,9 @@ void TestBuilderOptions() {
6060
.certPath("etc/cert/ca.crt")
6161
.cacheType("lru")
6262
.maxCacheSize(100)
63-
.selector("app=weatherApp")
64-
.offlineFlagSourcePath("some-path")
65-
.openTelemetry(openTelemetry)
66-
.customConnector(connector)
67-
.resolverType(Resolver.IN_PROCESS)
63+
.resolverType(Resolver.RPC)
6864
.targetUri("dns:///localhost:8016")
65+
.customConnector(connector)
6966
.keepAlive(1000)
7067
.build();
7168

@@ -76,7 +73,6 @@ void TestBuilderOptions() {
7673
assertEquals("lru", flagdOptions.getCacheType());
7774
assertEquals(100, flagdOptions.getMaxCacheSize());
7875
assertEquals("app=weatherApp", flagdOptions.getSelector());
79-
assertEquals("some-path", flagdOptions.getOfflineFlagSourcePath());
8076
assertEquals(openTelemetry, flagdOptions.getOpenTelemetry());
8177
assertEquals(connector, flagdOptions.getCustomConnector());
8278
assertEquals(Resolver.IN_PROCESS, flagdOptions.getResolverType());
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 dev.openfeature.contrib.providers.flagd.Config;
4+
import org.apache.logging.log4j.core.config.Order;
5+
import org.junit.platform.suite.api.BeforeSuite;
6+
import org.junit.platform.suite.api.ConfigurationParameter;
7+
import org.junit.platform.suite.api.ExcludeTags;
8+
import org.junit.platform.suite.api.IncludeEngines;
9+
import org.junit.platform.suite.api.IncludeTags;
10+
import org.junit.platform.suite.api.SelectDirectories;
11+
import org.junit.platform.suite.api.Suite;
12+
import org.testcontainers.junit.jupiter.Testcontainers;
13+
14+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
15+
import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME;
16+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
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/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: 24 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,24 @@ 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
164+
.offlineFlagSourcePath("not-existing");
165+
}
150166
wait = false;
151167
break;
152168
case "socket":
@@ -167,25 +183,17 @@ public void setupProvider(String providerType) throws IOException {
167183
.tls(true)
168184
.certPath(absolutePath);
169185
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;
185186

186187
default:
187188
this.state.providerType = ProviderType.DEFAULT;
188-
state.builder.port(getPort(State.resolverType, state.providerType));
189+
if (State.resolverType == Config.Resolver.FILE) {
190+
191+
state.builder
192+
.port(UNAVAILABLE_PORT)
193+
.offlineFlagSourcePath(offlinePath.toAbsolutePath().toString());
194+
} else {
195+
state.builder.port(getPort(State.resolverType, state.providerType));
196+
}
189197
break;
190198
}
191199
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
}

0 commit comments

Comments
 (0)