Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import jdk.jfr.Experimental;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
* FlagdOptions is a builder to build flagd provider options.
*/
@Builder(toBuilder = true)
@Getter
@Slf4j
@SuppressWarnings("PMD.TooManyStaticImports")
public class FlagdOptions {

Expand Down Expand Up @@ -314,9 +316,44 @@ void prebuild() {
String defaultPort = determineDefaultPortForResolver();
String fromPortEnv = fallBackToEnvOrDefault(Config.PORT_ENV_VAR_NAME, defaultPort);

String portValue = resolverType == Config.Resolver.IN_PROCESS
? fallBackToEnvOrDefault(Config.SYNC_PORT_ENV_VAR_NAME, fromPortEnv)
: fromPortEnv;
String portValue = fromPortEnv;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this alias?

if (resolverType == Config.Resolver.IN_PROCESS) {
String syncPortValue = fallBackToEnvOrDefault(Config.SYNC_PORT_ENV_VAR_NAME, null);
if (syncPortValue != null) {
if (isValidPort(syncPortValue)) {
portValue = syncPortValue;
} else {
// FLAGD_SYNC_PORT is unset by the user but populated with a non-numeric
// value — most commonly Kubernetes service-link env injection from a
// Service named `flagd-sync` in the pod's namespace, which produces
// `FLAGD_SYNC_PORT=tcp://<clusterIP>:<port>`. Fall back to FLAGD_PORT
// rather than failing at parse time.
log.warn(
"Ignoring {} value '{}' (not a valid port); falling back to {} ('{}'). "
+ "This commonly indicates Kubernetes service-link environment "
+ "variable injection from a Service named 'flagd-sync' in the "
+ "pod's namespace; set enableServiceLinks: false on the pod "
+ "template or rename the Service to avoid the collision.",
Config.SYNC_PORT_ENV_VAR_NAME,
syncPortValue,
Config.PORT_ENV_VAR_NAME,
fromPortEnv);
}
}
}

if (!isValidPort(portValue)) {
// Last-line-of-defence: FLAGD_PORT itself can be polluted by Kubernetes
// service-link injection too if a Service named `flagd` exists in the
// pod's namespace (FLAGD_PORT=tcp://<clusterIP>:8013), which would
// affect RPC-mode consumers identically. Fall back to the resolver's
// default port rather than throwing at parse time.
log.warn(
"Configured port value '{}' is not a valid port; falling back to default '{}'.",
portValue,
defaultPort);
portValue = defaultPort;
}

port = Integer.parseInt(portValue);
Comment thread
jabenedicic marked this conversation as resolved.
}
Expand All @@ -329,4 +366,16 @@ private String determineDefaultPortForResolver() {
return Config.DEFAULT_IN_PROCESS_PORT;
}
}

private static boolean isValidPort(String value) {
if (value == null) {
return false;
}
try {
int parsed = Integer.parseInt(value);
return parsed > 0 && parsed <= 65535;
} catch (NumberFormatException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,64 @@ void testInProcessProvider_syncPortTakesPrecedenceOverFlagdPort() {
assertThat(flagdOptions.getPort()).isEqualTo(9999);
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_IN_PROCESS)
@SetEnvironmentVariable(key = PORT_ENV_VAR_NAME, value = "8888")
@SetEnvironmentVariable(key = SYNC_PORT_ENV_VAR_NAME, value = "tcp://10.0.0.1:8015")
void testInProcessProvider_invalidSyncPortFallsBackToFlagdPort() {
// Kubernetes service-link injection populates FLAGD_SYNC_PORT with a URL like
// tcp://<clusterIP>:<port> when a Service named flagd-sync shares the pod's
// namespace. The SDK must not fail on this; it should fall back to FLAGD_PORT.
FlagdOptions flagdOptions = FlagdOptions.builder().build();

assertThat(flagdOptions.getResolverType()).isEqualTo(Resolver.IN_PROCESS);
assertThat(flagdOptions.getPort()).isEqualTo(8888);
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_IN_PROCESS)
@SetEnvironmentVariable(key = SYNC_PORT_ENV_VAR_NAME, value = "tcp://10.0.0.1:8015")
void testInProcessProvider_invalidSyncPortWithNoFlagdPortUsesDefault() {
FlagdOptions flagdOptions = FlagdOptions.builder().build();

assertThat(flagdOptions.getResolverType()).isEqualTo(Resolver.IN_PROCESS);
assertThat(flagdOptions.getPort()).isEqualTo(Integer.parseInt(DEFAULT_IN_PROCESS_PORT));
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_IN_PROCESS)
@SetEnvironmentVariable(key = PORT_ENV_VAR_NAME, value = "8888")
@SetEnvironmentVariable(key = SYNC_PORT_ENV_VAR_NAME, value = "99999")
void testInProcessProvider_outOfRangeSyncPortFallsBackToFlagdPort() {
FlagdOptions flagdOptions = FlagdOptions.builder().build();

assertThat(flagdOptions.getResolverType()).isEqualTo(Resolver.IN_PROCESS);
assertThat(flagdOptions.getPort()).isEqualTo(8888);
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_RPC)
@SetEnvironmentVariable(key = PORT_ENV_VAR_NAME, value = "tcp://10.0.0.1:8013")
void testRpcProvider_invalidFlagdPortFallsBackToDefault() {
// RPC-mode equivalent of the in-process collision: if a Service named `flagd`
// shares the pod's namespace, kubelet injects FLAGD_PORT=tcp://<clusterIP>:8013.
FlagdOptions flagdOptions = FlagdOptions.builder().build();

assertThat(flagdOptions.getResolverType()).isEqualTo(Resolver.RPC);
assertThat(flagdOptions.getPort()).isEqualTo(Integer.parseInt(DEFAULT_RPC_PORT));
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_IN_PROCESS)
@SetEnvironmentVariable(key = PORT_ENV_VAR_NAME, value = "tcp://10.0.0.1:8013")
@SetEnvironmentVariable(key = SYNC_PORT_ENV_VAR_NAME, value = "tcp://10.0.0.1:8015")
void testInProcessProvider_bothPortEnvsInvalidFallsBackToDefault() {
FlagdOptions flagdOptions = FlagdOptions.builder().build();

assertThat(flagdOptions.getResolverType()).isEqualTo(Resolver.IN_PROCESS);
assertThat(flagdOptions.getPort()).isEqualTo(Integer.parseInt(DEFAULT_IN_PROCESS_PORT));
}

@Test
@SetEnvironmentVariable(key = RESOLVER_ENV_VAR, value = RESOLVER_RPC)
void testRpcProviderFromEnv_noPortConfigured_defaultsToCorrectPort() {
Expand Down
Loading