NexusApplicationSpec.java

package com.cloudforgeci.api.application.artifactregistry;

import com.cloudforge.core.annotation.ApplicationPlugin;

import com.cloudforge.core.interfaces.ApplicationSpec;
import com.cloudforge.core.interfaces.Ec2Context;
import com.cloudforge.core.interfaces.OidcIntegration;
import com.cloudforge.core.interfaces.UserDataBuilder;

import java.util.List;

/**
 * Sonatype Nexus Repository Manager ApplicationSpec implementation.
 *
 * <p>Nexus is a universal artifact repository supporting Maven, npm, Docker,
 * PyPI, RubyGems, and more.</p>
 *
 * <p><strong>Key Features:</strong></p>
 * <ul>
 *   <li>Universal artifact repository (Maven, npm, Docker, PyPI)</li>
 *   <li>Proxy remote repositories</li>
 *   <li>Role-based access control</li>
 *   <li>Vulnerability scanning</li>
 *   <li>License analysis</li>
 * </ul>
 *
 * <p><strong>Compliance Use Cases:</strong></p>
 * <ul>
 *   <li>SOC2: Software bill of materials (SBOM) tracking</li>
 *   <li>PCI-DSS: Secure artifact storage for payment processing applications</li>
 *   <li>HIPAA: Audit trail for healthcare application deployments</li>
 * </ul>
 *
 * <p><strong>Fintech Applications:</strong></p>
 * <ul>
 *   <li>Store payment gateway SDK artifacts</li>
 *   <li>Host Docker images for financial services</li>
 *   <li>Proxy npm packages for fintech frontends</li>
 *   <li>Track artifact provenance and signatures</li>
 * </ul>
 *
 * <p><strong>Security Note:</strong></p>
 * <ul>
 *   <li>Change default admin password immediately</li>
 *   <li>Enable LDAP/SAML/OIDC for production</li>
 *   <li>Configure blob store encryption</li>
 *   <li>Enable audit logging</li>
 * </ul>
 *
 * @see <a href="https://help.sonatype.com/repomanager3">Nexus Documentation</a>
 */
@ApplicationPlugin(
    value = "nexus",
    category = "artifactregistry",
    displayName = "Nexus",
    description = "Universal artifact repository manager",
    defaultCpu = 2048,
    defaultMemory = 4096,
    defaultInstanceType = "t3.medium",
    supportsFargate = true,
    supportsEc2 = true,
    supportsOidc = false
)

public class NexusApplicationSpec implements ApplicationSpec {

    private static final String APPLICATION_ID = "nexus";
    private static final String DEFAULT_IMAGE = "sonatype/nexus3:latest";
    private static final int APPLICATION_PORT = 8081;
    private static final String CONTAINER_DATA_PATH = "/nexus-data";
    private static final String EFS_DATA_PATH = "/nexus";
    private static final String VOLUME_NAME = "nexusData";
    private static final String CONTAINER_USER = "200:200"; // nexus user
    private static final String EFS_PERMISSIONS = "755";
    private static final String EBS_DEVICE_NAME = "/dev/xvdh";
    private static final String EC2_DATA_PATH = "/opt/nexus-data";
    private static final List<String> EC2_LOG_PATHS = List.of(
        "/opt/nexus-data/log/nexus.log",
        "/opt/nexus-data/log/audit/audit.log",
        "/var/log/userdata.log"
    );

    @Override
    public String applicationId() {
        return APPLICATION_ID;
    }

    @Override
    public String defaultContainerImage() {
        return DEFAULT_IMAGE;
    }

    @Override
    public int applicationPort() {
        return APPLICATION_PORT;
    }

    @Override
    public java.util.List<OptionalPort> optionalPorts() {
        return java.util.List.of(
            // Docker Registry - inbound for Docker image hosting (configurable port range)
            OptionalPort.inboundTcp(5000, "enableDockerRegistry", "Docker Registry"),
            OptionalPort.inboundTcp(5001, "enableDockerRegistry", "Docker Registry (hosted)"),
            OptionalPort.inboundTcp(5002, "enableDockerRegistry", "Docker Registry (proxy)")
        );
    }

    @Override
    public String containerDataPath() {
        return CONTAINER_DATA_PATH;
    }

    @Override
    public String efsDataPath() {
        return EFS_DATA_PATH;
    }

    @Override
    public String volumeName() {
        return VOLUME_NAME;
    }

    @Override
    public String containerUser() {
        return CONTAINER_USER;
    }

    @Override
    public String efsPermissions() {
        return EFS_PERMISSIONS;
    }

    @Override
    public String ebsDeviceName() {
        return EBS_DEVICE_NAME;
    }

    @Override
    public String ec2DataPath() {
        return EC2_DATA_PATH;
    }

    @Override
    public List<String> ec2LogPaths() {
        return EC2_LOG_PATHS;
    }

    @Override
    public void configureUserData(UserDataBuilder builder, Ec2Context context) {
        builder.addSystemUpdate();

        // Install Docker
        builder.addCommands(
            "# Install Docker",
            "yum install -y docker",
            "systemctl enable docker",
            "systemctl start docker",
            "echo 'Docker installed' >> /var/log/userdata.log"
        );

        // Install CloudWatch Agent
        String logGroupName = String.format("/aws/%s/%s/%s",
            context.stackName(),
            context.runtimeType(),
            context.securityProfile());
        builder.installCloudWatchAgent(logGroupName, ec2LogPaths());

        // Mount storage
        String[] userParts = containerUser().split(":");
        String uid = userParts[0];
        String gid = userParts[1];

        if (context.hasEfs()) {
            builder.mountEfs(
                context.efsId().orElseThrow(),
                context.accessPointId().orElseThrow(),
                ec2DataPath(),
                uid,
                gid
            );
        } else {
            builder.mountEbs(
                ebsDeviceName(),
                ec2DataPath(),
                uid,
                gid
            );
        }

        // Run Nexus container
        builder.addCommands(
            "# Run Nexus Repository Manager container",
            "docker run -d \\",
            "  --name nexus \\",
            "  -p 8081:8081 \\",
            "  -v " + ec2DataPath() + ":/nexus-data \\",
            "  -e INSTALL4J_ADD_VM_PARAMS='-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m' \\",
            "  " + DEFAULT_IMAGE,
            "echo 'Nexus container started' >> /var/log/userdata.log",
            "",
            "# Wait for Nexus to start (can take 2-3 minutes)",
            "echo 'Waiting for Nexus to initialize...' >> /var/log/userdata.log",
            "sleep 120",
            "",
            "# Retrieve admin password",
            "if [ -f " + ec2DataPath() + "/admin.password ]; then",
            "  NEXUS_ADMIN_PASSWORD=$(cat " + ec2DataPath() + "/admin.password)",
            "  echo \"Nexus initial admin password: $NEXUS_ADMIN_PASSWORD\" >> /var/log/userdata.log",
            "  echo \"IMPORTANT: Change this password immediately!\" >> /var/log/userdata.log",
            "else",
            "  echo \"Nexus admin password file not found yet\" >> /var/log/userdata.log",
            "fi",
            "",
            "echo 'Nexus should be available on port 8081' >> /var/log/userdata.log",
            "echo 'Default credentials: admin / (see /nexus-data/admin.password)' >> /var/log/userdata.log"
        );
    }

    @Override
    public boolean supportsOidcIntegration() {
        // Nexus Pro supports OIDC/SAML but no OidcIntegration implementation yet
        // Return false until getOidcIntegration() returns a valid implementation
        return false;
    }

    @Override
    public OidcIntegration getOidcIntegration() {
        // Nexus Pro supports OIDC/SAML
        // Requires Nexus Pro license for OIDC/SAML support
        return null;
    }

    @Override
    public String toString() {
        return "NexusApplicationSpec{" +
                "applicationId='" + APPLICATION_ID + '\'' +
                ", defaultImage='" + DEFAULT_IMAGE + '\'' +
                ", applicationPort=" + APPLICATION_PORT +
                '}';
    }
}