DeploymentContext.java
package com.cloudforgeci.api.core;
import com.cloudforge.core.config.DeploymentConfig;
import com.cloudforge.core.enums.AuthMode;
import com.cloudforge.core.enums.ComplianceMode;
import com.cloudforge.core.enums.LoadBalancerType;
import com.cloudforge.core.enums.NetworkMode;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.enums.TopologyType;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Stack;
import software.constructs.Construct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Typed configuration interface for CDK deployment context.
*
* <p>Loads configuration from cdk.json "cfc" block or CLI flags (-c key = value).
* Provides type-safe access with validation and sensible defaults.</p>
*
* <p><b>Quick Start Example (cdk.json):</b></p>
* <pre>
* {
* "app": "...",
* "context": {
* "cfc": {
* "runtime": "fargate",
* "topology": "jenkins-service",
* "env": "dev",
* "domain": "example.com",
* "subdomain": "jenkins",
* "enableSsl": true,
* "authMode": "alb-oidc",
* "cognitoAutoProvision": true,
* "cognitoDomainPrefix": "myapp-auth",
* "cognitoMfaEnabled": true
* }
* }
* }
* </pre>
*
* <p><b>Configuration Keys</b> (all optional unless noted):</p>
*
* <p><b>Core Settings:</b></p>
* <ul>
* <li>tier: "public" | "enterprise" (default: public)</li>
* <li>runtime: "ec2" | "fargate" (default: fargate)</li>
* <li>topology: "jenkins-single-node" | "jenkins-service" | "s3-website"</li>
* <li>env: "dev" | "stage" | "prod" (default: dev)</li>
* <li>securityProfile: "dev" | "staging" | "production" (default: dev)</li>
* <li>region: AWS region (default: us-east-1)</li>
* </ul>
*
* <p><b>DNS & SSL:</b></p>
* <ul>
* <li>domain: Base domain (e.g., "example.com")</li>
* <li>subdomain: Subdomain prefix (e.g., "jenkins")</li>
* <li>fqdn: Full domain (e.g., "jenkins.example.com") - overrides domain+subdomain</li>
* <li>enableSsl: Enable HTTPS with ACM certificate (default: false)</li>
* <li>createZone: Create Route53 hosted zone (default: false)</li>
* </ul>
*
* <p><b>Network & Security:</b></p>
* <ul>
* <li>networkMode: "public-no-nat" | "private-with-nat" (default: public-no-nat)</li>
* <li>wafEnabled: Enable AWS WAF (default: false)</li>
* <li>albAccessLogging: Enable ALB access logs to S3 (default: false)</li>
* <li>cloudfront: Enable CloudFront distribution (default: false)</li>
* <li>bastionCidr: CIDR for SSH bastion access (default: 10.0.1.0/24)</li>
* </ul>
*
* <p><b>Authentication:</b></p>
* <ul>
* <li>authMode: "none" | "alb-oidc" | "jenkins-oidc" | "application-oidc" (default: none)</li>
* </ul>
*
* <p><b>Cognito (Auto-provision User Pool):</b></p>
* <ul>
* <li>cognitoAutoProvision: Auto-create Cognito User Pool (default: false)</li>
* <li>cognitoDomainPrefix: Globally unique domain prefix (required if auto-provisioning)</li>
* <li>cognitoUserPoolName: User Pool name (optional)</li>
* <li>cognitoMfaEnabled: Enable MFA (default: false)</li>
* <li>cognitoMfaMethod: "totp" | "sms" | "both" (default: "both")</li>
* <li>cognitoCreateGroups: Create admin/user groups (default: true)</li>
* <li>cognitoAdminGroupName: Admin group name (default: "Jenkins-Admins")</li>
* <li>cognitoUserGroupName: User group name (default: "Jenkins-Users")</li>
* <li>cognitoUserPoolId: Existing User Pool ID (for reuse)</li>
* <li>cognitoAppClientId: Existing App Client ID (for reuse)</li>
* <li>cognitoInitialAdminEmail: Initial admin user email (optional)</li>
* <li>cognitoInitialAdminPhone: Initial admin user phone in E.164 format, e.g., +12025551234 (optional, required for SMS MFA)</li>
* </ul>
*
* <p><b>Manual OIDC (Identity Center, Okta, Auth0):</b></p>
* <ul>
* <li>oidcIssuer: OIDC issuer URL</li>
* <li>oidcAuthorizationEndpoint: Authorization endpoint</li>
* <li>oidcTokenEndpoint: Token endpoint</li>
* <li>oidcUserInfoEndpoint: UserInfo endpoint</li>
* <li>oidcClientId: OIDC client ID</li>
* <li>oidcClientSecretName: Secrets Manager secret name (default: "jenkins/oidc/client-secret")</li>
* </ul>
*
* <p><b>Legacy IAM Identity Center:</b></p>
* <ul>
* <li>ssoInstanceArn: IAM Identity Center instance ARN</li>
* <li>ssoGroupId: Group UUID</li>
* <li>ssoTargetAccountId: 12-digit account ID</li>
* <li>autoProvisionIdentityCenter: Auto-provision (default: false)</li>
* <li>identityCenterGroupName: Group name (default: "Jenkins-Users")</li>
* </ul>
*
* <p><b>Compute & Scaling:</b></p>
* <ul>
* <li>lbType: "alb" | "nlb" (default: alb)</li>
* <li>instanceType: EC2 type (default: t3.micro)</li>
* <li>cpu: Fargate vCPU units (default: 1024)</li>
* <li>memory: Fargate memory MiB (default: 2048)</li>
* <li>containerImage: Override container image tag, e.g., "v1.2.3" or "2024.1" (default: uses tag from ApplicationSpec)</li>
* <li>minInstanceCapacity: Min instances (default: 1)</li>
* <li>maxInstanceCapacity: Max instances (default: 1)</li>
* <li>cpuTargetUtilization: CPU target % (default: 60)</li>
* </ul>
*
* <p><b>Monitoring & Compliance:</b></p>
* <ul>
* <li>enableMonitoring: CloudWatch monitoring (default: true)</li>
* <li>enableEncryption: Encryption at rest (default: true)</li>
* <li>logRetentionDays: CloudWatch log retention (default: security profile default)</li>
* <li>awsConfigEnabled: AWS Config compliance (default: false)</li>
* <li>securityMonitoringEnabled: Enable security monitoring (default: false)</li>
* <li>efsEncryptionInTransitEnabled: Enable EFS encryption in transit (default: profile default)</li>
* <li>automatedBackupEnabled: Enable automated backups (default: profile default)</li>
* <li>crossRegionBackupEnabled: Enable cross-region backups (default: profile default)</li>
* <li>complianceMode: "enforce" | "advisory" (auto: enforce for PRODUCTION, advisory for DEV/STAGING)</li>
* <li>complianceFrameworks: "PCI-DSS,HIPAA,SOC2,GDPR" (comma-separated)</li>
* </ul>
*
* <p><b>Health Checks:</b></p>
* <ul>
* <li>healthCheckGracePeriod: Grace period seconds (default: 300)</li>
* <li>healthCheckInterval: Interval seconds (default: 30)</li>
* <li>healthCheckTimeout: Timeout seconds (default: 5)</li>
* <li>healthyThreshold: Healthy count (default: 2)</li>
* <li>unhealthyThreshold: Unhealthy count (default: 3)</li>
* </ul>
*
* <p><b>Storage:</b></p>
* <ul>
* <li>artifactsBucket: S3 bucket name (optional)</li>
* <li>artifactsPrefix: S3 prefix (default: "jenkins/job/${JOB_NAME}/${BUILD_NUMBER}")</li>
* <li>retainStorage: Retain EFS/EBS on deletion (default: false)</li>
* <li>existingFileSystemId: Reuse existing EFS (disaster recovery)</li>
* </ul>
*
* <p><b>Usage:</b></p>
* <pre>
* // In CDK app
* DeploymentContext ctx = DeploymentContext.from(app);
*
* // In any Construct
* DeploymentContext ctx = DeploymentContext.from(scope);
* </pre>
*/
public final class DeploymentContext {
// Backing configuration loaded via Jackson (type-safe)
private final DeploymentConfig config;
// Raw map snapshot (frozen) - kept for backward compatibility
private final Map<String, Object> raw;
// Computed/derived fields not in DeploymentConfig
private final String fqdn; // computed if not provided
// Legacy raw values (kept for compatibility & logging)
private final String runtimeRaw; // may be "ec2"/"fargate" or a legacy combo like "jenkins-fargate"
private final String topologyRaw; // if user provided an explicit string topology
// Fields that need special handling beyond DeploymentConfig
private final String tier; // public | enterprise (not in DeploymentConfig per user preference)
private final String env; // dev | stage | prod (mapped from environment)
private final String complianceFrameworks; // String form for backward compatibility
private final ComplianceMode complianceMode; // With profile-based default
protected DeploymentContext(Map<String, Object> raw) {
this.raw = Collections.unmodifiableMap(new LinkedHashMap<>(raw));
// Load all configuration via Jackson type-safe deserialization
this.config = DeploymentConfig.fromMap(raw);
// Fields not in DeploymentConfig (legacy compatibility)
this.tier = str("tier", "public");
this.env = str("env", "dev");
// Compute FQDN from domain+subdomain if not provided
String fqdnCtx = str("fqdn", null);
this.fqdn = (fqdnCtx != null) ? fqdnCtx : composeFqdn(config.subdomain, config.domain);
// Legacy raw values for compatibility
String runtimeAlias = str("runtime", "fargate");
this.runtimeRaw = runtimeAlias;
this.topologyRaw = str("topology", "service");
// Handle runtime/topology legacy combos (e.g., "jenkins-fargate")
if (config.runtime == null || config.topology == null) {
DeploymentConfigurations configurations = process(runtimeAlias, topologyRaw);
if (config.runtime == null) {
config.runtime = configurations.runtime;
}
if (config.topology == null) {
config.topology = configurations.topology;
}
}
// Compliance frameworks string form for backward compatibility
this.complianceFrameworks = config.getComplianceFrameworksAsString();
// ComplianceMode with profile-based default
this.complianceMode = config.complianceMode != null
? config.complianceMode
: ComplianceMode.defaultForProfile(config.securityProfile);
validateOrThrow();
}
/** Build from the 'cfc' context object on the App. */
public static DeploymentContext from(App app) {
return Util.extractDeploymentContext(app.getNode().tryGetContext("cfc"));
}
/** Build from the 'cfc' context object on any Construct scope. */
public static DeploymentContext from(Construct scope) {
return Util.extractDeploymentContext(scope.getNode().tryGetContext("cfc"));
}
// --------- Public getters (delegate to config) ---------
public String tier() { return tier; }
public String env() { return env; }
/**
* Gets the security profile enum.
*
* @return SecurityProfile enum value
*/
public SecurityProfile securityProfile() {
return config.securityProfile;
}
public String region() { return config.region; }
public Boolean gdprDataTransferApproved() { return config.gdprDataTransferApproved; }
public String domain() { return config.domain; }
public String subdomain() { return config.subdomain; }
public String fqdn() { return fqdn; }
public NetworkMode networkMode() { return config.networkMode; }
public Boolean wafEnabled() { return config.wafEnabled; }
public Boolean httpsStrictEnabled() { return config.httpsStrictEnabled; }
public Boolean albAccessLogging() { return config.albAccessLogging; }
public Boolean guardDutyEnabled() { return config.guardDutyEnabled; }
public Boolean createGuardDutyDetector() { return config.createGuardDutyDetector; }
public Boolean guardDutyAlertsConfigured() { return config.guardDutyAlertsConfigured; }
public Boolean certificateExpirationMonitoring() { return config.certificateExpirationMonitoring; }
// Advanced Monitoring & Threat Protection
public Boolean macieEnabled() { return config.macieEnabled; }
public Boolean macieAutomatedDiscoveryEnabled() { return config.macieAutomatedDiscovery; }
public Boolean securityHubEnabled() { return config.securityHubEnabled; }
public Boolean inspectorEnabled() { return config.inspectorEnabled; }
public Boolean antiMalwareEnabled() { return config.antiMalwareEnabled; }
public Boolean fileIntegrityMonitoringEnabled() { return config.fileIntegrityMonitoring; }
public Boolean containerRuntimeSecurityEnabled() { return config.containerRuntimeSecurity; }
public Boolean containerImageScanningEnabled() { return config.containerImageScanning; }
// Enhanced Compliance Controls
public Boolean cloudWatchLogsKmsEncryptionEnabled() { return config.cloudWatchLogsKmsEncryptionEnabled; }
public Boolean cloudTrailInsightsEnabled() { return config.cloudTrailInsightsEnabled; }
public Boolean route53QueryLoggingEnabled() { return config.route53QueryLoggingEnabled; }
public Boolean s3ObjectLockEnabled() { return config.s3ObjectLockEnabled; }
public Boolean cloudfrontEnabled() { return config.cloudfrontEnabled; }
public LoadBalancerType lbType() { return config.lbType; }
public Integer cpuTargetUtilization() { return config.cpuTargetUtilization; }
public Integer maxInstanceCapacity() { return config.maxInstanceCapacity; }
public Integer minInstanceCapacity() { return config.minInstanceCapacity; }
public Boolean enableFlowlogs() { return config.enableFlowlogs; }
public Boolean cloudTrailEnabled() { return config.cloudTrailEnabled; }
public Boolean securityMonitoringEnabled() { return config.securityMonitoringEnabled; }
public Boolean efsEncryptionInTransitEnabled() { return config.efsEncryptionInTransitEnabled; }
public Boolean restrictSecurityGroupEgress() { return config.restrictSecurityGroupEgress; }
public Boolean automatedBackupEnabled() { return config.automatedBackupEnabled; }
public Boolean crossRegionBackupEnabled() { return config.crossRegionBackupEnabled; }
// Security - SSH Access Control
public String bastionCidr() { return config.bastionCidr; }
// Storage Persistence Configuration
public Boolean retainStorage() { return config.retainStorage; }
public String existingFileSystemId() { return config.existingFileSystemId; }
// Advanced Configuration
public Boolean enableMonitoring() { return config.enableMonitoring; }
public Boolean enableEncryption() { return config.enableEncryption; }
public Boolean awsConfigEnabled() { return config.awsConfigEnabled; }
public Boolean createConfigInfrastructure() { return config.createConfigInfrastructure; }
public Boolean auditManagerEnabled() { return config.auditManagerEnabled; }
public String complianceFrameworks() { return complianceFrameworks; }
public ComplianceMode complianceMode() { return complianceMode; }
public Integer logRetentionDays() { return config.logRetentionDays != null ? Integer.parseInt(config.logRetentionDays) : null; }
public String instanceType() { return config.instanceType; }
public Boolean provisionDatabase() { return config.provisionDatabase; }
public Boolean enableS3VersioningRemediation() { return config.enableS3VersioningRemediation; }
public Boolean enableCloudTrailBucketAccessRemediation() { return config.enableCloudTrailBucketAccessRemediation; }
public Boolean enableRdsDeletionProtectionRemediation() { return config.enableRdsDeletionProtectionRemediation; }
public Boolean enableRdsAutoMinorVersionUpgradeRemediation() { return config.enableRdsAutoMinorVersionUpgradeRemediation; }
// Health Check Configuration
public Integer healthCheckGracePeriod() { return config.healthCheckGracePeriod; }
public Integer healthCheckInterval() { return config.healthCheckInterval; }
public Integer healthCheckTimeout() { return config.healthCheckTimeout; }
public Integer healthyThreshold() { return config.healthyThreshold; }
public Integer unhealthyThreshold() { return config.unhealthyThreshold; }
public AuthMode authMode() { return config.authMode; }
// Cognito Configuration
public Boolean cognitoAutoProvision() { return config.cognitoAutoProvision; }
public String cognitoDomainPrefix() { return config.cognitoDomainPrefix; }
public String cognitoUserPoolName() { return config.cognitoUserPoolName; }
public Boolean cognitoMfaEnabled() { return config.cognitoMfaEnabled; }
public String cognitoMfaMethod() { return config.cognitoMfaMethod; }
public Boolean cognitoCreateGroups() { return config.cognitoCreateGroups; }
public String cognitoAdminGroupName() { return config.cognitoAdminGroupName; }
public String cognitoUserGroupName() { return config.cognitoUserGroupName; }
public String cognitoUserPoolId() { return config.cognitoUserPoolId; }
public String cognitoAppClientId() { return config.cognitoAppClientId; }
public String cognitoInitialAdminEmail() { return config.cognitoInitialAdminEmail; }
public String cognitoInitialAdminPhone() { return config.cognitoInitialAdminPhone; }
// Manual OIDC Configuration
public String oidcIssuer() { return config.oidcIssuer; }
public String oidcAuthorizationEndpoint() { return config.oidcAuthorizationEndpoint; }
public String oidcTokenEndpoint() { return config.oidcTokenEndpoint; }
public String oidcUserInfoEndpoint() { return config.oidcUserInfoEndpoint; }
public String oidcClientId() { return config.oidcClientId; }
public String oidcClientSecretName() { return config.oidcClientSecretName; }
// Legacy IAM Identity Center Configuration
public String ssoInstanceArn() { return config.ssoInstanceArn; }
public String ssoGroupId() { return config.ssoGroupId; }
public String ssoTargetAccountId() { return config.ssoTargetAccountId; }
public Boolean autoProvisionIdentityCenter() { return config.autoProvisionIdentityCenter; }
public String identityCenterGroupName() { return config.identityCenterGroupName; }
// Additional deployment tracking fields - not in config yet, use raw
public String deploymentId() { return str("deploymentId", null); }
public String deploymentVersion() { return str("deploymentVersion", null); }
public String tags() { return str("tags", null); }
public String stackName() { return config.stackName; }
public String artifactsBucket() { return config.artifactsBucket; }
public String artifactsPrefix() { return config.artifactsPrefix; }
public int cpu() { return config.cpu; }
public int memory() { return config.memory; }
public String containerImage() { return config.containerImage; }
public boolean enableSsl() { return config.enableSsl != null && config.enableSsl; }
public boolean createZone() { return config.createZone != null && config.createZone; }
/** Raw immutable view of all context keys. */
public Map<String, Object> raw() { return raw; }
/** Canonical axes (preferred). */
public RuntimeType runtime() { return config.runtime; }
public TopologyType topology() { return config.topology; }
/** Legacy raw accessors (compat only). */
@Deprecated public String runtimeRaw() { return runtimeRaw; }
@Deprecated public String topologyRaw() { return topologyRaw; }
// --------- Helpers / derived behavior ---------
/** True if the service should run in private subnets without public IPs. */
public boolean isPrivateWithNat() { return config.networkMode == NetworkMode.PRIVATE_WITH_NAT; }
/** True if enterprise features should be enabled. */
public boolean isEnterprise() { return "enterprise".equalsIgnoreCase(tier); }
/** Get the runtime type. */
public RuntimeType getRuntime() { return config.runtime; }
/** Get the topology type. */
public TopologyType getTopology() { return config.topology; }
/** Get a context value by key with default. */
public String getContextValue(String key, String defaultValue) {
return str(key, defaultValue);
}
/**
* Export all deployment context fields to a Map for serialization.
* This produces the same format as InteractiveDeployer's buildCfcContext.
* Delegates to DeploymentConfig.toContextMap() for consistency.
*/
public Map<String, Object> toContextMap() {
return config.toContextMap();
}
/** Tag a stack so you can see the config in the console. */
public void tagStack(Stack stack) {
stack.getTags().setTag("cfc:tier", tier);
stack.getTags().setTag("cfc:runtime", config.runtime.name());
stack.getTags().setTag("cfc:topology", config.topology.name());
stack.getTags().setTag("cfc:env", env);
if (fqdn != null) stack.getTags().setTag("cfc:fqdn", fqdn);
stack.getTags().setTag("cfc:network", config.networkMode.getValue());
stack.getTags().setTag("cfc:auth", config.authMode.getValue());
}
private void validateOrThrow() {
List<String> errs = new ArrayList<>();
// SSL can be enabled without a domain - AWS Private CA will be used for the ALB DNS name
// No validation needed here - both custom domain SSL and private certificate SSL are valid
// OIDC modes require HTTPS (enableSsl=true)
// When no custom domain is configured, AWS Private CA is used for the ALB DNS name
boolean sslEnabled = config.enableSsl != null && config.enableSsl;
if (config.authMode == AuthMode.ALB_OIDC && !sslEnabled) {
errs.add("authMode=alb-oidc requires HTTPS; set enableSsl=true. " +
"A custom domain (fqdn/domain) is recommended but not required - " +
"without a domain, AWS Private CA will be used for the ALB DNS name.");
}
if (config.authMode == AuthMode.APPLICATION_OIDC && !sslEnabled) {
errs.add("authMode=application-oidc requires HTTPS; set enableSsl=true. " +
"A custom domain (fqdn/domain) is recommended but not required - " +
"without a domain, AWS Private CA will be used for the ALB DNS name.");
}
// Cross-axis sanity (context level; rules will also validate)
// JENKINS_SINGLE_NODE topology removed in 3.0.0 - use JENKINS_SERVICE instead
if (!errs.isEmpty()) {
throw new IllegalArgumentException("DeploymentContext validation failed:\n - "
+ String.join("\n - ", errs));
}
}
// ---- Normalization helpers ----
private static final class DeploymentConfigurations {
final RuntimeType runtime;
final TopologyType topology;
DeploymentConfigurations(RuntimeType r, TopologyType t) { this.runtime = r; this.topology = t; }
}
private static DeploymentConfigurations process(String runtimeAlias, String topologyAlias) {
RuntimeType runtime = RuntimeType.FARGATE; // default
TopologyType topology = TopologyType.JENKINS_SERVICE; // conservative default
// explicit topology string wins if present
if (topologyAlias != null) {
topology = parseTopology(topologyAlias);
}
if (runtimeAlias != null) {
String r = runtimeAlias.trim().toLowerCase(Locale.ROOT);
switch (r) {
case "ec2" -> { runtime = RuntimeType.EC2;}
case "fargate" -> { runtime = RuntimeType.FARGATE; }
case "jenkins-fargate" -> { runtime = RuntimeType.FARGATE; topology = TopologyType.JENKINS_SERVICE; }
case "jenkins-ec2" -> { runtime = RuntimeType.EC2; topology = TopologyType.JENKINS_SERVICE; }
case "cf-alb-s3" -> { runtime = RuntimeType.EC2; topology = TopologyType.S3_WEBSITE; }
case "cf-alb-proxy" -> { runtime = RuntimeType.EC2; topology = TopologyType.JENKINS_SERVICE; }
default -> { runtime = RuntimeType.FARGATE; topology = TopologyType.JENKINS_SERVICE; }
}
}
return new DeploymentConfigurations(runtime, topology);
}
private static TopologyType parseTopology(String val) {
String t = val.trim().toLowerCase(Locale.ROOT)
.replace('_', '-')
.replace(' ', '-');
return switch (t) {
case "jenkins-service", "jenkins_service", "service" -> TopologyType.JENKINS_SERVICE;
case "s3-website", "s3_website", "s3" -> TopologyType.S3_WEBSITE;
case "application-service", "application_service", "app-service", "application" -> TopologyType.APPLICATION_SERVICE;
// CloudForge 3.0.0: No default fallback - explicit topology required
default -> throw new IllegalArgumentException(
"Unknown topology '" + val + "'. Valid values: jenkins-service, s3-website, application-service. " +
"Note: JENKINS_SINGLE_NODE was removed in 3.0.0 - use jenkins-service instead."
);
};
}
private static String composeFqdn(String sub, String dom) {
if (dom == null || dom.isBlank()) return null;
if (sub == null || sub.isBlank()) return dom;
return sub + "." + dom;
}
private String str(String key, String def) {
Object v = raw.get(key);
return v == null ? def : String.valueOf(v);
}
@Override public String toString() {
return "DeploymentContext{" +
"runtimeKind = " + config.runtime +
", topologyKind = " + config.topology +
", env = '" + env + '\'' +
", fqdn = '" + fqdn + '\'' +
", cpu = " + config.cpu +
", memory = " + config.memory +
'}';
}
static DeploymentContext of(Map<String, Object> raw) {
return new DeploymentContext(raw);
}
}