ApplicationSpec.java
package com.cloudforge.core.interfaces;
import com.cloudforge.core.annotation.ApplicationPlugin;
import java.util.List;
/**
* Application specification interface defining application-specific configuration.
* This enables CloudForge to deploy any application (Jenkins, GitLab, Vault, etc.)
* using the same infrastructure patterns.
*
* <p>Implementations provide configuration for both container (Fargate) and
* EC2 deployments, supporting both EFS and EBS storage strategies.</p>
*
* <p>CloudForge 3.0.0: Universal Application Support</p>
*
* <p>Example implementations:</p>
* <ul>
* <li>JenkinsApplicationSpec: Jenkins CI/CD automation server</li>
* <li>GitLabApplicationSpec: GitLab DevOps platform</li>
* <li>GrafanaApplicationSpec: Grafana metrics visualization</li>
* <li>PostgreSQLApplicationSpec: PostgreSQL database</li>
* <li>VaultApplicationSpec: HashiCorp Vault secrets management</li>
* <li>+ 9 more built-in applications</li>
* </ul>
*
* <h2>Plugin Metadata:</h2>
* <p>Implementations should be annotated with {@link ApplicationPlugin} for auto-discovery
* and metadata support:</p>
* <pre>{@code
* @ApplicationPlugin(
* value = "jenkins",
* category = "cicd",
* displayName = "Jenkins",
* description = "Open-source automation server for CI/CD"
* )
* public class JenkinsApplicationSpec implements ApplicationSpec {
* // ...
* }
* }</pre>
*/
public interface ApplicationSpec {
// ========== Application Identity ==========
/**
* Returns a unique identifier for this application.
* Used for logging, metrics, and resource naming.
*
* @return application identifier (e.g., "jenkins", "gitlab", "vault")
*/
String applicationId();
// ========== Container Configuration ==========
/**
* Returns the default container image for this application.
* Can be overridden by deployment context configuration.
*
* @return container image string (e.g., "jenkins/jenkins:lts")
*/
String defaultContainerImage();
/**
* Returns the primary application port.
* This is the port the application listens on inside the container.
*
* @return application port (e.g., 8080 for Jenkins)
*/
int applicationPort();
/**
* Returns the container path where application data is stored.
* This is where the volume will be mounted inside the container.
*
* @return container mount path (e.g., "/var/jenkins_home")
*/
String containerDataPath();
/**
* Returns the EFS path for this application's data.
* This is the path within the EFS filesystem.
*
* @return EFS path (e.g., "/jenkins")
*/
String efsDataPath();
/**
* Returns the volume name for this application.
* Used to reference the volume in task definitions.
*
* @return volume name (e.g., "jenkinsHome")
*/
String volumeName();
/**
* Returns the container user (UID:GID) to run as.
* Important for file permissions when using EFS.
*
* @return user in format "UID:GID" (e.g., "1000:1000")
*/
String containerUser();
/**
* Returns the EFS permissions for the access point.
*
* @return permissions string (e.g., "750")
*/
String efsPermissions();
/**
* Returns the CPU architecture this application's container image is built for, so a Fargate
* task definition requests matching compute rather than silently defaulting to X86_64.
*
* <p>Defaults to {@code X86_64} -- almost every built-in application image (Jenkins, GitLab,
* Grafana, etc.) is x86_64-only or x86_64-primary today. Override only when the image is
* confirmed to actually support the declared architecture; a mismatch fails the task at
* startup with no fallback, since Fargate provisions the underlying compute from this value
* before it ever pulls the image.</p>
*
* @return the target CPU architecture, defaulting to X86_64
*/
default com.cloudforge.core.enums.CpuArchitecture cpuArchitecture() {
return com.cloudforge.core.enums.CpuArchitecture.X86_64;
}
/**
* Configures application-specific environment variables for the container.
*
* <p>Applications can override this to provide custom environment variables
* based on deployment configuration (FQDN, SSL, authMode, etc.). The infrastructure
* passes the FQDN, SSL settings, and authentication mode for applications that need
* reverse proxy configuration or authentication-specific setup.</p>
*
* <p>Example use cases:</p>
* <ul>
* <li>Jenkins: JAVA_OPTS, JENKINS_OPTS for reverse proxy configuration, skip setup wizard for application-oidc</li>
* <li>GitLab: GITLAB_OMNIBUS_CONFIG for external URL configuration and OIDC setup</li>
* <li>Vault: VAULT_ADDR for API endpoint configuration</li>
* </ul>
*
* @param fqdn The fully qualified domain name (may be null)
* @param sslEnabled Whether SSL is enabled
* @param authMode The authentication mode (may be null, e.g., "none", "alb-oidc", "application-oidc")
* @return Map of environment variable key-value pairs (never null, may be empty)
*/
default java.util.Map<String, String> containerEnvironmentVariables(String fqdn, boolean sslEnabled, String authMode) {
return java.util.Collections.emptyMap();
}
/**
* Returns the health check path for ALB/ELB health checks.
*
* <p>Different applications expose health endpoints at different paths:</p>
* <ul>
* <li>Jenkins: /login</li>
* <li>GitLab: /users/sign_in</li>
* <li>Grafana: /api/health</li>
* <li>Metabase: /api/health</li>
* </ul>
*
* @return health check path (e.g., "/login", "/api/health")
*/
default String healthCheckPath() {
return "/";
}
// ========== EC2 Configuration ==========
/**
* Returns the EBS device name for EC2 instances when not using EFS.
* This is the device that will be formatted and mounted for application data.
*
* @return EBS device path (e.g., "/dev/xvdh")
*/
String ebsDeviceName();
/**
* Returns the EC2 data path where application stores persistent data.
* This may differ from containerDataPath depending on application packaging.
*
* @return EC2 mount path (e.g., "/var/lib/jenkins")
*/
String ec2DataPath();
/**
* Returns CloudWatch log file paths for EC2 monitoring.
* These files will be streamed to CloudWatch Logs for centralized logging.
*
* @return list of absolute log file paths (e.g., ["/var/log/jenkins/jenkins.log"])
*/
List<String> ec2LogPaths();
/**
* Configure EC2 UserData script for application installation and setup.
*
* <p>The implementation should use the UserDataBuilder to add application-specific
* installation commands while leveraging infrastructure helpers for storage mounting
* and CloudWatch configuration.</p>
*
* <p>The infrastructure handles:</p>
* <ul>
* <li>System updates</li>
* <li>EFS vs EBS storage mounting (based on availability)</li>
* <li>CloudWatch Agent installation and configuration</li>
* <li>File permissions and ownership</li>
* </ul>
*
* <p>The application provides:</p>
* <ul>
* <li>Application installation commands (yum/dnf install, etc.)</li>
* <li>Application configuration</li>
* <li>Service startup commands</li>
* </ul>
*
* @param builder The UserDataBuilder providing infrastructure helpers
* @param context The Ec2Context providing runtime information
*/
void configureUserData(UserDataBuilder builder, Ec2Context context);
// ========== OIDC Integration (Optional) ==========
/**
* Returns whether this application supports OIDC integration.
*
* <p>Applications with built-in OIDC support (GitLab, Grafana, SonarQube) or
* plugin support (Jenkins) should return true.</p>
*
* @return true if application can integrate with OIDC providers
*/
default boolean supportsOidcIntegration() {
return false;
}
/**
* Returns the OIDC integration handler for this application.
*
* <p>This provides application-specific configuration for integrating with
* Cognito or IAM Identity Center OIDC.</p>
*
* @return OIDC integration handler, or null if not supported
*/
default OidcIntegration getOidcIntegration() {
return null;
}
/**
* Returns the list of supported authentication modes for this application.
*
* <p>CloudForge supports three authentication modes:</p>
* <ul>
* <li><b>application-oidc</b>: OIDC authentication integrated within the application (requires getOidcIntegration() != null)</li>
* <li><b>alb-oidc</b>: OIDC authentication at ALB level (works for all applications)</li>
* <li><b>none</b>: No authentication (public access or manually configured)</li>
* </ul>
*
* <p>The list is ordered by preference. The first mode is the recommended default.</p>
*
* <p>Default behavior:</p>
* <ul>
* <li>If application has OIDC integration → ["application-oidc", "alb-oidc", "none"]</li>
* <li>If application claims OIDC support but lacks integration → ["alb-oidc", "none"]</li>
* <li>If application doesn't support OIDC → ["none"]</li>
* </ul>
*
* @return List of supported auth modes in order of preference (never null, never empty)
*/
default List<String> getSupportedAuthModes() {
if (getOidcIntegration() != null) {
// Application has working OIDC integration - prefer application-oidc
return List.of("application-oidc", "alb-oidc", "none");
} else if (supportsOidcIntegration()) {
// Application claims OIDC support but doesn't implement it - use alb-oidc
return List.of("alb-oidc", "none");
} else {
// No OIDC support at all
return List.of("none");
}
}
/**
* Auth modes allowed for a deployment target ({@code aws}, {@code ministack}, {@code localstack}).
*
* <p>Default: same as {@link #getSupportedAuthModes()}. Specs may restrict Cognito/OIDC
* on MiniStack while keeping it optional on LocalStack/AWS.</p>
*
* @param deploymentTarget target id, or null/blank for the default list
* @return non-empty list of auth mode strings
*/
default List<String> getSupportedAuthModes(String deploymentTarget) {
return getSupportedAuthModes();
}
/**
* Returns the recommended (default) authentication mode for this application.
*
* <p>This is the first mode from {@link #getSupportedAuthModes()}.</p>
*
* @return the recommended auth mode (e.g., "application-oidc", "alb-oidc", "none")
*/
default String getRecommendedAuthMode() {
return getSupportedAuthModes().get(0);
}
/**
* Recommended auth mode for a deployment target.
*
* @param deploymentTarget target id ({@code aws}, {@code ministack}, {@code localstack})
* @return first entry from {@link #getSupportedAuthModes(String)}
*/
default String getRecommendedAuthMode(String deploymentTarget) {
return getSupportedAuthModes(deploymentTarget).get(0);
}
// ========== Path-Based Authentication ==========
/**
* Returns paths that require authentication when using ALB-level OIDC.
*
* <p>When this list is non-empty and authMode is "alb-oidc", the ALB will:
* <ul>
* <li>Require OIDC authentication for requests matching these paths</li>
* <li>Allow unauthenticated access to all other paths</li>
* </ul>
*
* <p>When this list is empty (default), ALL paths require authentication.</p>
*
* <p>Path patterns support ALB path-pattern syntax:</p>
* <ul>
* <li>Exact: "/admin"</li>
* <li>Prefix wildcard: "/admin/*"</li>
* <li>Extension: "*.php"</li>
* </ul>
*
* <p>Example for phpBB (protect admin and installer):</p>
* <pre>{@code
* @Override
* public List<String> protectedPaths() {
* return List.of("/adm/*", "/install/*");
* }
* }</pre>
*
* <p>Example for WordPress (protect wp-admin):</p>
* <pre>{@code
* @Override
* public List<String> protectedPaths() {
* return List.of("/wp-admin/*", "/wp-login.php");
* }
* }</pre>
*
* <p>Users can override these defaults via DeploymentContext:</p>
* <ul>
* <li>protectedPaths: Override/replace the application defaults</li>
* <li>additionalProtectedPaths: Add to the application defaults</li>
* <li>publicPaths: Explicitly mark paths as public (overrides protected)</li>
* </ul>
*
* @return list of path patterns requiring authentication (empty = protect everything)
* @see #publicPaths()
*/
default List<String> protectedPaths() {
return List.of(); // Default: protect everything when auth is enabled
}
/**
* Returns paths that should always be public (no authentication required).
*
* <p>These paths are excluded from authentication even when they would
* otherwise be protected. Useful for health checks, public APIs, etc.</p>
*
* <p>Common use cases:</p>
* <ul>
* <li>Health check endpoints: "/health", "/api/health"</li>
* <li>Public API endpoints: "/api/public/*"</li>
* <li>Static assets: "/static/*", "/assets/*"</li>
* </ul>
*
* @return list of path patterns that should be public (empty by default)
*/
default List<String> publicPaths() {
return List.of(); // Default: no explicit public paths
}
// ========== Optional Ports (Security-Conscious) ==========
/**
* Optional service port that can be enabled via deployment configuration.
*
* <p>Ports are NOT exposed by default - must be explicitly enabled via the configKey
* in deployment configuration. This follows the principle of least privilege.</p>
*
* @param port The port number
* @param protocol The protocol ("tcp" or "udp")
* @param configKey The DeploymentContext key to enable this port (e.g., "enableSmtp")
* @param service Human-readable service name for logging/prompts
* @param inbound true if port accepts inbound connections (requires security group rule),
* false if outbound only (container connects out, no SG rule needed)
*/
record OptionalPort(int port, String protocol, String configKey, String service, boolean inbound) {
/**
* Convenience constructor for inbound TCP ports.
*
* @param port the port number
* @param configKey the deployment config key to enable this port
* @param service the service name using this port
* @return an OptionalPort configured for inbound TCP
*/
public static OptionalPort inboundTcp(int port, String configKey, String service) {
return new OptionalPort(port, "tcp", configKey, service, true);
}
/**
* Convenience constructor for outbound TCP ports (no security group rule needed).
*
* @param port the port number
* @param configKey the deployment config key to enable this port
* @param service the service name using this port
* @return an OptionalPort configured for outbound TCP
*/
public static OptionalPort outboundTcp(int port, String configKey, String service) {
return new OptionalPort(port, "tcp", configKey, service, false);
}
}
/**
* Returns optional ports that can be enabled via deployment configuration.
*
* <p>These ports are NOT exposed by default. Users must set the corresponding
* configKey to true in their deployment configuration to enable each port.</p>
*
* <p>Example implementation for Mattermost:</p>
* <pre>{@code
* @Override
* public List<OptionalPort> optionalPorts() {
* return List.of(
* OptionalPort.outboundTcp(587, "enableSmtp", "SMTP Email"),
* OptionalPort.inboundTcp(8074, "enableClustering", "Cluster Gossip")
* );
* }
* }</pre>
*
* <p>User enables in deployment-context.json:</p>
* <pre>{@code
* {
* "enableSmtp": true,
* "enableClustering": true
* }
* }</pre>
*
* @return list of optional ports (empty by default - most apps only need primary port)
*/
default List<OptionalPort> optionalPorts() {
return List.of();
}
/**
* Declares an always-running, same-Fargate-task companion container: {@code ContainerFactory}
* adds it to the same task definition as this app's own container, wires the main container
* to wait for it (a startup dependency on {@code HEALTHY}, not {@code SUCCESS} — unlike the
* SAML certificate init container this deliberately isn't modeled after, a sidecar never
* exits), and gives it its own CloudWatch log stream. No IAM task-role grants and no inbound
* security-group rule are added for it — a sidecar reachable only over the task's own loopback
* interface (the way every container in one Fargate/{@code awsvpc} task already shares one ENI)
* needs neither.
*
* @param containerName unique name within the task definition (also the log stream prefix)
* @param image container image reference (registry/repo:tag)
* @param containerPort port the sidecar listens on inside the task's shared network namespace
* @param healthCheckCommand ECS container health check command, e.g.
* {@code List.of("CMD-SHELL", "curl -f http://localhost:8090/v1/healthz || exit 1")} —
* required, since the main container's startup dependency needs a {@code HEALTHY} signal
* to wait on. The port in that example is the {@code cloudforge-synth-service} sidecar's
* own default — see {@link com.cloudforge.core.manager.ManagerEnvKeys#SYNTH_SERVICE_DEFAULT_PORT}
* for the one place that port is actually declared
* @param environment environment variables for the sidecar container
*/
record SidecarContainer(String containerName, String image, int containerPort,
List<String> healthCheckCommand, java.util.Map<String, String> environment) {
}
/**
* Returns same-task sidecar containers this application needs alongside its own container.
*
* <p>Empty for every application by default — this exists for
* {@code cloudforge-manager}'s {@code cloudforge-synth-service} sidecar (see that spec's own
* override), not a general-purpose extension point most applications need.</p>
*
* @return list of sidecar containers (empty by default)
*/
default List<SidecarContainer> sidecarContainers() {
return List.of();
}
// ========== Plugin Metadata Methods ==========
/**
* Get the application category from the {@link ApplicationPlugin} annotation.
*
* @return the category (e.g., "cicd", "monitoring", "database")
*/
default String category() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return "unknown";
}
return annotation.category();
}
/**
* Get the human-readable display name for this application.
*
* @return the display name, defaulting to capitalized {@link #applicationId()} if not specified
*/
default String displayName() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
String id = applicationId();
return id.substring(0, 1).toUpperCase() + id.substring(1);
}
String displayName = annotation.displayName();
if (displayName.isEmpty()) {
String id = applicationId();
return id.substring(0, 1).toUpperCase() + id.substring(1);
}
return displayName;
}
/**
* Get the application description.
*
* @return the application description
*/
default String description() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return "";
}
return annotation.description();
}
/**
* Get the default Fargate CPU units.
*
* @return the default CPU units (256, 512, 1024, 2048, 4096)
*/
default int defaultCpu() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return 1024; // Default 1 vCPU
}
return annotation.defaultCpu();
}
/**
* Get the default Fargate memory in MB.
*
* @return the default memory in MB
*/
default int defaultMemory() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return 2048; // Default 2GB
}
return annotation.defaultMemory();
}
/**
* Get the default EC2 instance type.
*
* @return the default instance type (e.g., "t3.small")
*/
default String defaultInstanceType() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return "t3.small";
}
return annotation.defaultInstanceType();
}
/**
* Check if this application supports Fargate deployment.
*
* @return true if Fargate is supported
*/
default boolean supportsFargate() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return true; // Default to supported
}
return annotation.supportsFargate();
}
/**
* Check if this application supports EC2 deployment.
*
* @return true if EC2 is supported
*/
default boolean supportsEc2() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return true; // Default to supported
}
return annotation.supportsEc2();
}
/**
* Whether this application requires a managed database (RDS) in the canonical template.
*
* <p>Reads {@link ApplicationPlugin#requiresDatabase()} when present; defaults to false.</p>
*/
default boolean requiresDatabase() {
ApplicationPlugin annotation = getClass().getAnnotation(ApplicationPlugin.class);
if (annotation == null) {
return false;
}
return annotation.requiresDatabase();
}
/**
* Get the recommended health check grace period for this application.
*
* <p>The grace period is how long ECS/ALB waits before starting health checks
* after a container starts. Applications with longer initialization times
* (like GitLab) need longer grace periods.</p>
*
* <p>Default values:</p>
* <ul>
* <li>Most applications: 300 seconds (5 minutes)</li>
* <li>GitLab: 600 seconds (10 minutes) - due to database migrations and initialization</li>
* <li>Other database-heavy apps may also need longer periods</li>
* </ul>
*
* @return recommended health check grace period in seconds
*/
default int defaultHealthCheckGracePeriod() {
return 300; // Default 5 minutes for most applications
}
/**
* The container environment variable name a generated initial-admin-password secret should
* be bound to, or {@code null} (the default) if this application has no such variable.
*
* <p>Mirrors {@code CmsSpec.databaseEnvVars}'s existing pattern for the database password
* specifically: a database connection's host/port/name/user travel as plain strings through
* {@link #containerEnvironmentVariables}, but the password itself is never a plaintext value
* an app spec method returns — it's delivered as a Secrets Manager-backed ECS Secret, which
* needs a CDK construct only {@code cloudforge-api} can build. This method is the same shape,
* generalized to any application whose official image supports a fully non-interactive
* first-run install once its site name/admin account/admin password are all present (Joomla's
* own {@code docker-entrypoint.sh} is the first example — see {@code JoomlaApplicationSpec}),
* instead of a per-application name check hardcoded into {@code ApplicationFactory}/
* {@code ContainerFactory}.</p>
*
* @return the env var name (e.g. {@code "JOOMLA_ADMIN_PASSWORD"}), or {@code null}
*/
default String autoAdminPasswordEnvVar() {
return null;
}
/**
* Whether this application needs a Redis-backed session store (ElastiCache), provisioned by
* {@code ApplicationFactory} and delivered as {@code redisSessionStoreEndpoint}/
* {@code redisSessionStorePort} in {@code SystemContext}. Same generalization shape as
* {@link #autoAdminPasswordEnvVar()} — an application declares the need through this contract
* rather than {@code ApplicationFactory} recognizing it by {@link #applicationId()}.
*
* @return true if a Redis session store should be provisioned
*/
default boolean requiresSessionStore() {
return false;
}
/**
* The container environment variable name a generated AES cipher-key secret should be bound
* to, or {@code null} (the default) if this application has no such need. Same shape as
* {@link #autoAdminPasswordEnvVar()}: {@code ApplicationFactory} provisions the Secrets
* Manager entry and {@code ContainerFactory} binds it as an ECS Secret under this name,
* without either needing to recognize a specific {@link #applicationId()}.
*
* @return the env var name (e.g. {@code "CFC_MANAGER_ACCOUNT_SECRET_KEY"}), or {@code null}
*/
default String cipherKeySecretEnvVar() {
return null;
}
/**
* The container environment variable name a deploy-time-supplied license key should be bound
* to, or {@code null} (the default) if this application has no license key concept. Same
* shape as {@link #cipherKeySecretEnvVar()} — the value itself still comes from whatever
* deployment-context field the application's own deployment contract declares; this only
* names the delivery env var.
*
* @return the env var name (e.g. {@code "CFC_MANAGER_LICENSESEAT_LICENSE_KEY"}), or {@code null}
*/
default String licenseKeySecretEnvVar() {
return null;
}
/**
* Whether this application's persistence can't safely tolerate the brief two-task overlap a
* normal zero-downtime rolling ECS deployment creates (new task starts before the old one
* stops) — e.g. a single-writer embedded-file database with no real concurrent-access support.
* Only takes effect when this application also has no managed database connection provisioned
* (see {@code DatabaseSpec}) — once one is, a real database safely handles that overlap and
* this no longer applies regardless of what this method returns.
*
* @return true if this application needs a stop-then-start deployment replacement instead of
* ECS's own rolling default, when running without a managed database
*/
default boolean requiresSequentialDeploymentWithoutDatabase() {
return false;
}
/**
* The container environment variable name the ALB's own ARN should be bound to when this
* application is deployed with {@code authMode=alb-oidc}, or {@code null} (the default) if
* this application has no need to know it (e.g. to validate an ALB-signed OIDC token itself).
*
* @return the env var name, or {@code null}
*/
default String albSignerArnEnvVar() {
return null;
}
/**
* The container environment variable name a "was this deployment's public endpoint reachable
* over a publicly-trusted TLS certificate" boolean signal should be bound to, or {@code null}
* (the default) if this application has no such need.
*
* @return the env var name, or {@code null}
* @see com.cloudforge.core.interfaces.OidcIntegration
*/
default String publicTlsTrustedEnvVar() {
return null;
}
/**
* The container environment variable name the resolved deployment target
* ({@code aws}/{@code localstack}/{@code ministack}) should be bound to, or {@code null} (the
* default) if this application has no need to distinguish targets at runtime.
*
* @return the env var name, or {@code null}
*/
default String deploymentTargetEnvVar() {
return null;
}
/**
* The four container environment variable names a provisioned Redis session-store connection
* should be bound to (mode/host/port/TLS-enabled, in that order), or {@code null} (the
* default) if this application has no session-store concept — same gate
* {@link #requiresSessionStore()} uses for whether to provision the cluster at all; this only
* names where its connection details land once provisioned. The TLS-enabled var is always
* told {@code "false"} — the cluster this provisions has no TLS listener.
*
* @return {@code [modeEnvVar, hostEnvVar, portEnvVar, tlsEnabledEnvVar]}, or {@code null}
*/
default String[] sessionStoreEnvVars() {
return null;
}
/**
* The container environment variable name a provisioned database's password secret should be
* bound to, overriding {@code ContainerFactory}'s own per-{@link #applicationId()} switch
* (and its {@code DATABASE_PASSWORD} fallback for any application not in it) when non-null.
* Most built-in applications are covered by that switch already and don't need this — it
* exists for an application declared outside {@code cloudforge-api} entirely, which the
* switch can't have a case for.
*
* @return the env var name, or {@code null} to use the switch/fallback instead
*/
default String databasePasswordEnvVar() {
return null;
}
/**
* The container environment variable name an application-OIDC client secret should be bound
* to, overriding {@code ContainerFactory}'s own per-{@link #applicationId()} switch (and its
* {@code <APP>_OIDC_CLIENT_SECRET} fallback naming for any application not in it) when
* non-null. Same reasoning as {@link #databasePasswordEnvVar()} — exists for an application
* declared outside {@code cloudforge-api} entirely, whose expected env var name doesn't match
* that generic {@code <APP>_} naming convention.
*
* @return the env var name, or {@code null} to use the switch/fallback instead
*/
default String oidcClientSecretEnvVar() {
return null;
}
/**
* Whether this application writes a role edit through to its Cognito user pool's real group
* membership at runtime (e.g. {@code AdminAddUserToGroup}) rather than only updating its own
* local cache — {@code CognitoAuthenticationFactory} grants the corresponding IAM actions on
* the pool's task role only when this is true, since most applications never call those APIs
* at all and shouldn't carry the grant.
*
* @return true if the Cognito admin group-management IAM actions should be granted
*/
default boolean requiresCognitoGroupManagementIam() {
return false;
}
/**
* Default admin/developer group names for an external-OIDC-provider deployment, used when the
* deployment context doesn't override them ({@code cognitoAdminGroupName}/
* {@code cognitoUserGroupName}).
*
* @return {@code [adminGroupName, developerGroupName]}, defaulting to {@code ["Admins",
* "Developers"]}
*/
default String[] defaultOidcGroupNames() {
return new String[] {"Admins", "Developers"};
}
}