ApplicationFactory.java
package com.cloudforgeci.api.compute;
import com.cloudforgeci.api.core.*;
import com.cloudforgeci.api.core.annotation.BaseFactory;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.enums.IAMProfile;
import com.cloudforge.core.iam.IAMProfileMapper;
import com.cloudforge.core.interfaces.ApplicationSpec;
import com.cloudforge.core.interfaces.DatabaseSpec;
import com.cloudforge.core.interfaces.DatabaseSpec.DatabaseRequirement;
import com.cloudforge.core.interfaces.DatabaseSpec.DatabaseConnection;
import com.cloudforgeci.api.network.DomainFactory;
import com.cloudforgeci.api.network.VpcFactory;
import com.cloudforgeci.api.ingress.AlbFactory;
import com.cloudforgeci.api.observability.FlowLogFactory;
import com.cloudforgeci.api.storage.EfsFactory;
import com.cloudforgeci.api.observability.AlarmFactory;
import com.cloudforgeci.api.database.RdsFactory;
import com.cloudforgeci.api.storage.BackupFactory;
import software.constructs.Construct;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Universal factory class for deploying any application using ApplicationSpec.
*
* <p>This factory supports deployment of any application that implements the ApplicationSpec
* interface, including:</p>
* <ul>
* <li>CI/CD: Jenkins, GitLab, Drone, Gitea</li>
* <li>Monitoring: Grafana, Prometheus</li>
* <li>Databases: PostgreSQL, Redis</li>
* <li>Secrets Management: HashiCorp Vault</li>
* <li>Artifact Registry: Nexus, Harbor</li>
* <li>Collaboration: Mattermost</li>
* <li>Analytics: Metabase, Apache Superset</li>
* </ul>
*
* <p>The factory automatically configures:</p>
* <ul>
* <li>VPC and networking (respects networkMode: public-no-nat, private-with-nat)</li>
* <li>Application Load Balancer (ALB) with SSL/TLS termination</li>
* <li>Security groups and IAM roles</li>
* <li>Storage (EFS or EBS based on application needs)</li>
* <li>Observability (CloudWatch logs, flow logs, alarms)</li>
* <li>Auto-scaling (for service deployments)</li>
* </ul>
*
* @author CloudForgeCI
* @since 3.0.0
* @see ApplicationSpec
* @see DeploymentContext
* @see SystemContext
*/
public class ApplicationFactory extends BaseFactory {
private static final Logger LOG = Logger.getLogger(ApplicationFactory.class.getName());
private final RuntimeType runtime;
private final ApplicationSpec applicationSpec;
private SystemContext.InfrastructureFactories infrastructure;
@com.cloudforge.core.annotation.DeploymentContext("domain")
private String domain;
@com.cloudforge.core.annotation.DeploymentContext("subdomain")
private String subdomain;
@com.cloudforge.core.annotation.DeploymentContext("fqdn")
private String fqdn;
@com.cloudforge.core.annotation.DeploymentContext("maxInstanceCapacity")
private Integer maxInstanceCapacity;
@com.cloudforge.core.annotation.DeploymentContext("provisionDatabase")
private Boolean provisionDatabase;
@com.cloudforge.core.annotation.DeploymentContext("databaseEngine")
private String databaseEngine;
@com.cloudforge.core.annotation.DeploymentContext("databaseVersion")
private String databaseVersion;
@com.cloudforge.core.annotation.DeploymentContext("databaseInstanceClass")
private String databaseInstanceClass;
@com.cloudforge.core.annotation.DeploymentContext("databaseAllocatedStorageGB")
private Integer databaseAllocatedStorageGB;
@com.cloudforge.core.annotation.DeploymentContext("databaseName")
private String databaseName;
@com.cloudforge.core.annotation.DeploymentContext("databaseBackupRetentionDays")
private Integer databaseBackupRetentionDays;
@com.cloudforge.core.annotation.DeploymentContext("databaseMultiAz")
private Boolean databaseMultiAz;
@com.cloudforge.core.annotation.DeploymentContext("enableEncryption")
private Boolean enableEncryption;
@com.cloudforge.core.annotation.DeploymentContext("enableAutoScaling")
private Boolean enableAutoScaling;
public ApplicationFactory(Construct scope, String id, RuntimeType runtime, ApplicationSpec applicationSpec) {
super(scope, id);
this.runtime = runtime;
this.applicationSpec = applicationSpec;
}
/**
* Gets the infrastructure factories created during deployment.
* @return Infrastructure factories containing VPC, ALB, EFS, and logging components
*/
public SystemContext.InfrastructureFactories getInfrastructure() {
return infrastructure;
}
/**
* Container for application system components created by the factory.
*
* @param vpc the VPC factory that created the network infrastructure
* @param alb the ALB factory that created the load balancer
* @param efs the EFS factory that created the shared file system
*/
public record ApplicationSystem(
VpcFactory vpc,
AlbFactory alb,
EfsFactory efs
) {}
/**
* Creates the application deployment infrastructure.
* This method is called after SystemContext has been started and ApplicationFactory has been instantiated.
*/
@Override
public void create() {
try {
LOG.info("Creating " + runtime + " deployment for application: " + applicationSpec.applicationId());
// Set the ApplicationSpec for this deployment (must be done before createInfrastructureFactories)
ctx.applicationSpec.set(applicationSpec);
// Set DNS configuration early for SSL certificate creation
ctx.domain.set(domain);
ctx.subdomain.set(subdomain);
ctx.fqdn.set(fqdn);
// Get the unique ID for this factory (without the "Application" prefix to avoid duplication)
String id = getNode().getId().replace("Application", "");
// Create FlowLogFactory early and configure flow logs
try {
FlowLogFactory flowLogFactory = new FlowLogFactory(this, id + "Flowlog");
flowLogFactory.create();
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in FlowLogFactory ***", e);
throw e;
}
// Create domain factory if domain is provided (must run before security factories for cert validation)
if (domain != null && !domain.isBlank()) {
DomainFactory domainFactory;
try {
domainFactory = new DomainFactory(this, id + "Domain");
domainFactory.create();
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in DomainFactory ***", e);
throw e;
}
}
// Create infrastructure factories FIRST (VPC, ALB, EFS, Logging)
// This ensures the ALB exists before security factories try to reference it
// for OIDC callback URLs (CognitoAuthenticationFactory needs ALB DNS name)
infrastructure = ctx.createInfrastructureFactories(this, id);
// Create security factories AFTER infrastructure factories
// CognitoAuthenticationFactory needs the ALB to be created first so it can
// use the ALB DNS name in callback URLs when no custom domain is configured
try {
ctx.createSecurityFactories(this, id);
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in createSecurityFactories() ***", e);
throw e;
}
// Provision database if required by the application
// MUST run AFTER createInfrastructureFactories() so VPC is available
if (applicationSpec instanceof DatabaseSpec) {
DatabaseSpec dbSpec = (DatabaseSpec) applicationSpec;
DatabaseRequirement dbReq = dbSpec.databaseRequirement();
boolean shouldProvisionDatabase = false;
// Determine if we should provision the database
if (dbReq.type() == DatabaseRequirement.RequirementType.REQUIRED) {
// Application MUST have a database
shouldProvisionDatabase = true;
LOG.info("Database is REQUIRED for " + applicationSpec.applicationId());
} else if (dbReq.type() == DatabaseRequirement.RequirementType.OPTIONAL) {
// Application CAN use database if provisionDatabase flag is set
shouldProvisionDatabase = Boolean.TRUE.equals(provisionDatabase);
LOG.info("Database is OPTIONAL for " + applicationSpec.applicationId() +
" (provisionDatabase=" + provisionDatabase + ")");
}
if (shouldProvisionDatabase) {
try {
LOG.info("Provisioning RDS database for " + applicationSpec.applicationId());
// Get VPC from context (created by FlowLogFactory -> VpcFactory)
software.amazon.awscdk.services.ec2.IVpc vpc = ctx.vpc.get()
.orElseThrow(() -> new IllegalStateException("VPC not available for database provisioning"));
// Merge DeploymentConfig values with ApplicationSpec defaults
// Priority: DeploymentConfig > ApplicationSpec
DatabaseRequirement mergedReq = DatabaseRequirement.required(
databaseEngine != null ? databaseEngine : dbReq.engine(),
databaseVersion != null ? databaseVersion : dbReq.version()
)
.withInstanceClass(databaseInstanceClass != null ? databaseInstanceClass : dbReq.instanceClass())
.withStorage(databaseAllocatedStorageGB != null ? databaseAllocatedStorageGB : dbReq.allocatedStorageGB())
.withDatabaseName(databaseName != null ? databaseName : dbReq.databaseName());
LOG.info("Database configuration (DeploymentConfig overrides applied):");
LOG.info(" Engine: " + mergedReq.engine() + " " + mergedReq.version());
LOG.info(" Instance: " + mergedReq.instanceClass());
LOG.info(" Storage: " + mergedReq.allocatedStorageGB() + " GB");
LOG.info(" Database: " + mergedReq.databaseName());
// Create RDS database instance with merged configuration
DatabaseConnection dbConnection = RdsFactory.createDatabase(
ctx,
mergedReq,
vpc,
applicationSpec.applicationId() + "-db",
databaseBackupRetentionDays, // DeploymentConfig override
databaseMultiAz, // DeploymentConfig override
enableEncryption // DeploymentConfig override
);
// Store database connection in SystemContext for use by ContainerFactory
ctx.dbConnection.set(dbConnection);
LOG.info("Successfully provisioned RDS database: " + dbConnection.endpoint());
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception provisioning database for " +
applicationSpec.applicationId() + " ***", e);
throw e;
}
} else {
LOG.info("No database provisioning for " + applicationSpec.applicationId());
}
}
// NOTE: WAF is created by security profile configurations (ProductionSecurityConfiguration, StagingSecurityConfiguration)
// to ensure proper integration with security profile settings and avoid duplicates
// Create compute resources based on runtime type
if (runtime == RuntimeType.FARGATE) {
FargateFactory fargate;
try {
fargate = new FargateFactory(this, id + "Fargate");
fargate.create();
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in FargateFactory ***", e);
throw e;
}
} else if (runtime == RuntimeType.EC2) {
// Create EC2 compute resources
if (maxInstanceCapacity != null && maxInstanceCapacity > 1) {
Ec2Factory ec2 = new Ec2Factory(this, id + "Ec2");
ec2.create();
} else {
// Single instance deployment
// TODO: Implement createSingleEc2Instance for universal applications
LOG.warning("Single instance EC2 deployment not yet implemented for universal applications");
}
}
// Create AWS Backup infrastructure (after EFS and RDS are available)
try {
BackupFactory backupFactory = new BackupFactory(this, id + "Backup");
backupFactory.create();
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in BackupFactory ***", e);
throw e;
}
try {
new AlarmFactory(this, id + "Alarms", null);
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in AlarmFactory ***", e);
throw e;
}
// Execute deferred actions
try {
ctx.executeDeferredActions();
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in executeDeferredActions() ***", e);
throw e;
}
LOG.info("Successfully created " + runtime + " deployment for: " + applicationSpec.applicationId());
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in ApplicationFactory.create() for " +
applicationSpec.applicationId() + " ***", e);
throw e;
}
}
/**
* Static helper method for creating a Fargate-based application deployment.
* This method ensures SystemContext is started before creating the ApplicationFactory.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createFargate(Construct scope, String id, DeploymentContext cfc, ApplicationSpec applicationSpec) {
return createFargate(scope, id, cfc, cfc.securityProfile(), applicationSpec);
}
/**
* Static helper method for creating a Fargate-based application deployment with specific security profile.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param security Security profile determining security hardening level
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createFargate(Construct scope, String id, DeploymentContext cfc,
SecurityProfile security, ApplicationSpec applicationSpec) {
IAMProfile iamProfile = IAMProfileMapper.mapFromSecurity(security);
return createFargate(scope, id, cfc, security, iamProfile, applicationSpec);
}
/**
* Static helper method for creating a Fargate-based application deployment with explicit IAM profile.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param security Security profile determining security hardening level
* @param iamProfile IAM profile for access control
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createFargate(Construct scope, String id, DeploymentContext cfc,
SecurityProfile security, IAMProfile iamProfile,
ApplicationSpec applicationSpec) {
try {
// Use APPLICATION_SERVICE topology for universal application deployment
TopologyType topology = TopologyType.APPLICATION_SERVICE;
// Start SystemContext (or get existing one)
SystemContext ctx = SystemContext.start(scope, topology, RuntimeType.FARGATE, security, iamProfile, cfc);
// Create ApplicationFactory and invoke create()
ApplicationFactory factory = new ApplicationFactory(scope, id + "Application", RuntimeType.FARGATE, applicationSpec);
factory.create();
// Return the infrastructure components from the factory
SystemContext.InfrastructureFactories infra = factory.getInfrastructure();
return new ApplicationSystem(infra.vpc(), infra.alb(), infra.efs());
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in createFargate() for " +
applicationSpec.applicationId() + " ***", e);
throw e;
}
}
/**
* Creates an EC2-based application deployment.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createEc2(Construct scope, String id, DeploymentContext cfc, ApplicationSpec applicationSpec) {
return createEc2(scope, id, cfc, cfc.securityProfile(), applicationSpec);
}
/**
* Creates an EC2-based application deployment with specific security profile.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param security Security profile determining security hardening level
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createEc2(Construct scope, String id, DeploymentContext cfc,
SecurityProfile security, ApplicationSpec applicationSpec) {
IAMProfile iamProfile = IAMProfileMapper.mapFromSecurity(security);
return createEc2(scope, id, cfc, security, iamProfile, applicationSpec);
}
/**
* Static helper method for creating an EC2-based application deployment with explicit IAM profile.
*
* @param scope The CDK construct scope
* @param id Unique identifier for the deployment
* @param cfc Deployment context containing configuration parameters
* @param security Security profile determining security hardening level
* @param iamProfile IAM profile for access control
* @param applicationSpec The ApplicationSpec defining the application
* @return ApplicationSystem containing references to created infrastructure components
*/
public static ApplicationSystem createEc2(Construct scope, String id, DeploymentContext cfc,
SecurityProfile security, IAMProfile iamProfile,
ApplicationSpec applicationSpec) {
try {
// Use APPLICATION_SERVICE topology for universal application deployment
TopologyType topology = TopologyType.APPLICATION_SERVICE;
// Start SystemContext (or get existing one)
SystemContext.start(scope, topology, RuntimeType.EC2, security, iamProfile, cfc);
// Create ApplicationFactory and invoke create()
ApplicationFactory factory = new ApplicationFactory(scope, id + "Application", RuntimeType.EC2, applicationSpec);
factory.create();
// Return the infrastructure components from the factory
SystemContext.InfrastructureFactories infra = factory.getInfrastructure();
return new ApplicationSystem(infra.vpc(), infra.alb(), infra.efs());
} catch (Exception e) {
LOG.log(Level.SEVERE, "*** CRITICAL: Exception in createEc2() for " +
applicationSpec.applicationId() + " ***", e);
throw e;
}
}
}