AgentConfig.java
package io.github.baokhang83.mnemo.warden.agent;
import java.time.Duration;
import java.util.function.Function;
/**
* Immutable agent configuration, read from the environment with sane defaults.
*
* <p>Environment variables are the Kubernetes-native way to configure a sidecar, so no config
* file or YAML library is pulled in.
*
* <p>{@code podName} and {@code targetContainerName} are required, with no default (W-306):
* unlike the other fields here, guessing wrong means silently resizing nothing or resizing the
* wrong container, so this fails fast instead — the same posture {@code InClusterApiServer}
* already takes for {@code KUBERNETES_SERVICE_HOST}.
*
* @param healthPort TCP port the health endpoints listen on
* @param podName this pod's own name (Downward API {@code fieldRef: metadata.name}), so the
* agent can read back its own pod's intent annotations
* @param targetContainerName the sibling container {@code ShrinkSequence}/{@code GrowSequence}
* resize
* @param gcTimeout how long a shrink's deep-GC-and-uncommit step waits before giving up
* @param resizeTimeout how long a resize PATCH waits for kubelet confirmation
* @param intentPollInterval how often the agent re-reads its own pod's intent annotations
*/
public record AgentConfig(
int healthPort,
String podName,
String targetContainerName,
Duration gcTimeout,
Duration resizeTimeout,
Duration intentPollInterval) {
/** Default health-probe port. */
public static final int DEFAULT_HEALTH_PORT = 8080;
public static final Duration DEFAULT_GC_TIMEOUT = Duration.ofSeconds(30);
public static final Duration DEFAULT_RESIZE_TIMEOUT = Duration.ofSeconds(30);
public static final Duration DEFAULT_INTENT_POLL_INTERVAL = Duration.ofSeconds(5);
public static final String ENV_HEALTH_PORT = "WARDEN_HEALTH_PORT";
public static final String ENV_POD_NAME = "WARDEN_POD_NAME";
public static final String ENV_TARGET_CONTAINER_NAME = "WARDEN_TARGET_CONTAINER_NAME";
public static final String ENV_GC_TIMEOUT_SECONDS = "WARDEN_GC_TIMEOUT_SECONDS";
public static final String ENV_RESIZE_TIMEOUT_SECONDS = "WARDEN_RESIZE_TIMEOUT_SECONDS";
public static final String ENV_INTENT_POLL_INTERVAL_SECONDS = "WARDEN_INTENT_POLL_INTERVAL_SECONDS";
/** Reads configuration from the process environment, falling back to defaults. */
public static AgentConfig fromEnv() {
return fromEnv(System::getenv);
}
/**
* Reads configuration from an arbitrary environment lookup. Package-private seam so tests can
* supply a fake environment without mutating the real process environment.
*/
static AgentConfig fromEnv(Function<String, String> env) {
return new AgentConfig(
parsePort(env.apply(ENV_HEALTH_PORT)),
requireEnv(env, ENV_POD_NAME),
requireEnv(env, ENV_TARGET_CONTAINER_NAME),
parseSeconds(env.apply(ENV_GC_TIMEOUT_SECONDS), DEFAULT_GC_TIMEOUT, ENV_GC_TIMEOUT_SECONDS),
parseSeconds(env.apply(ENV_RESIZE_TIMEOUT_SECONDS), DEFAULT_RESIZE_TIMEOUT, ENV_RESIZE_TIMEOUT_SECONDS),
parseSeconds(
env.apply(ENV_INTENT_POLL_INTERVAL_SECONDS),
DEFAULT_INTENT_POLL_INTERVAL,
ENV_INTENT_POLL_INTERVAL_SECONDS));
}
private static String requireEnv(Function<String, String> env, String name) {
String value = env.apply(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " is not set; is this running as a Warden sidecar?");
}
return value.trim();
}
private static int parsePort(String raw) {
if (raw == null || raw.isBlank()) {
return DEFAULT_HEALTH_PORT;
}
final int port;
try {
port = Integer.parseInt(raw.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ENV_HEALTH_PORT + " must be an integer, got: " + raw, e);
}
if (port < 1 || port > 65535) {
throw new IllegalArgumentException(ENV_HEALTH_PORT + " must be in 1..65535, got: " + port);
}
return port;
}
private static Duration parseSeconds(String raw, Duration fallback, String envName) {
if (raw == null || raw.isBlank()) {
return fallback;
}
try {
return Duration.ofSeconds(Long.parseLong(raw.trim()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(envName + " must be an integer number of seconds, got: " + raw, e);
}
}
}