ManagerEndpointSupport.java
package com.cloudforge.core.manager;
import com.cloudforge.core.local.DeploymentTarget;
/**
* Resolves LocalStack / MiniStack gateway URLs from env or system properties.
* Defaults to {@code http://localhost:4566} when unset.
*/
public final class ManagerEndpointSupport {
public static final String DEFAULT_LOCAL_GATEWAY = "http://localhost:4566";
private ManagerEndpointSupport() {
}
/**
* The one authoritative, target-gated answer to "should this call be redirected to a local
* emulator instead of real AWS" — {@code null} immediately for {@link DeploymentTarget#AWS}
* OR an unresolved/{@code null} target, without even reading the env vars, otherwise the same
* {@code LOCALSTACK_ENDPOINT} then {@code AWS_ENDPOINT_URL} fallback chain {@link
* #resolveLocalStackEndpoint} already used. {@code null} target fails closed to "real AWS"
* rather than falling through to the env-var chain — an installation that never explicitly
* chose a local target shouldn't have its calls silently redirected just because some env var
* happens to be present, same fail-closed shape as this codebase's other target-based gates
* (see {@code StripeConfiguration}).
*
* <p>Exists because, before this method, several call sites across cloudforge-api and
* cloudforge-manager each independently re-derived "are we local" from env-var presence
* alone, with no target check at all — so a real {@code target=aws} production install would
* happily route Cognito/STS/Service Catalog SDK calls to whatever {@code AWS_ENDPOINT_URL}
* happened to be set to, an SSRF-adjacent risk on the customer's own AWS credentials.
* {@code target} should always be the caller's own already-known, validated {@link
* DeploymentTarget} (the per-request target for a deploy, or the installation's own {@code
* ManagerRuntimeConfiguration.Target} for Manager's own dev/test-loop calls) — never
* re-derived from these same env vars, or this gate would just move one level up instead of
* closing.
*
* <p>{@code MINISTACK} and {@code LOCALSTACK} deliberately check different dedicated env vars
* first ({@code MINISTACK_ENDPOINT} / {@code LOCALSTACK_ENDPOINT} respectively) before the
* shared {@code AWS_ENDPOINT_URL} fallback — an installation running both emulators side by
* side needs a {@code MINISTACK}-targeted call to reach MiniStack, not silently fall through
* to whichever one {@code LOCALSTACK_ENDPOINT} happens to point at.
*/
public static String resolveLocalEmulatorEndpoint(DeploymentTarget target) {
if (target == null || target == DeploymentTarget.AWS) {
return null;
}
if (target == DeploymentTarget.MINISTACK) {
String ministack = first(ManagerEnvKeys.MINISTACK_ENDPOINT);
if (ministack != null) {
return ministack;
}
return first(ManagerEnvKeys.AWS_ENDPOINT_URL);
}
String localstack = first(ManagerEnvKeys.LOCALSTACK_ENDPOINT);
if (localstack != null) {
return localstack;
}
return first(ManagerEnvKeys.AWS_ENDPOINT_URL);
}
public static String resolveLocalStackEndpoint() {
String localstack = first(ManagerEnvKeys.LOCALSTACK_ENDPOINT);
if (localstack != null) {
return localstack;
}
String aws = first(ManagerEnvKeys.AWS_ENDPOINT_URL);
return aws != null ? aws : DEFAULT_LOCAL_GATEWAY;
}
public static String resolveMiniStackEndpoint() {
String dedicated = first(ManagerEnvKeys.MINISTACK_ENDPOINT);
if (dedicated != null) {
return dedicated;
}
String aws = first(ManagerEnvKeys.AWS_ENDPOINT_URL);
return aws != null ? aws : DEFAULT_LOCAL_GATEWAY;
}
public static String resolveRegion() {
String region = first(ManagerEnvKeys.AWS_DEFAULT_REGION);
return region != null ? region : "us-east-1";
}
private static String first(String key) {
String env = System.getenv(key);
if (env != null && !env.isBlank()) {
return env.trim();
}
String prop = System.getProperty(key);
return prop == null || prop.isBlank() ? null : prop.trim();
}
}