UserDataBuilderImpl.java

package com.cloudforgeci.api.core;

import com.cloudforge.core.interfaces.UserDataBuilder;
import software.amazon.awscdk.services.ec2.UserData;

import java.util.List;

/**
 * Implementation of UserDataBuilder that generates bash commands for EC2 UserData scripts.
 *
 * <p>This class encapsulates the complex bash logic for common infrastructure tasks,
 * allowing applications to focus on their specific installation and configuration needs.</p>
 */
public class UserDataBuilderImpl implements UserDataBuilder {
    private final UserData userData;

    public UserDataBuilderImpl(UserData userData) {
        this.userData = userData;
    }

    @Override
    public void addSystemUpdate() {
        userData.addCommands(
            "command -v dnf >/dev/null && dnf -y update || yum -y update"
        );
    }

    @Override
    public void installCloudWatchAgent(String logGroupName, List<String> logFilePaths) {
        userData.addCommands(
            "# Install CloudWatch Agent",
            "echo 'Installing CloudWatch Agent...' >> /var/log/userdata.log",
            "wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm",
            "rpm -U ./amazon-cloudwatch-agent.rpm",
            "rm -f ./amazon-cloudwatch-agent.rpm",
            "",
            "# Configure CloudWatch Agent",
            "echo 'Configuring CloudWatch Agent...' >> /var/log/userdata.log",
            "mkdir -p /opt/aws/amazon-cloudwatch-agent/etc"
        );

        // Build the CloudWatch Agent configuration JSON
        StringBuilder configJson = new StringBuilder();
        configJson.append("cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << 'EOF'\n");
        configJson.append("{\n");
        configJson.append("  \"agent\": {\n");
        configJson.append("    \"metrics_collection_interval\": 60,\n");
        configJson.append("    \"run_as_user\": \"root\"\n");
        configJson.append("  },\n");
        configJson.append("  \"logs\": {\n");
        configJson.append("    \"logs_collected\": {\n");
        configJson.append("      \"files\": {\n");
        configJson.append("        \"collect_list\": [\n");

        for (int i = 0; i < logFilePaths.size(); i++) {
            String logPath = logFilePaths.get(i);
            String logStreamName = logPath.substring(logPath.lastIndexOf('/') + 1);

            configJson.append("          {\n");
            configJson.append("            \"file_path\": \"").append(logPath).append("\",\n");
            configJson.append("            \"log_group_name\": \"").append(logGroupName).append("\",\n");
            configJson.append("            \"log_stream_name\": \"{instance_id}/").append(logStreamName).append("\",\n");
            configJson.append("            \"timezone\": \"UTC\"\n");
            configJson.append("          }");

            if (i < logFilePaths.size() - 1) {
                configJson.append(",");
            }
            configJson.append("\n");
        }

        configJson.append("        ]\n");
        configJson.append("      }\n");
        configJson.append("    }\n");
        configJson.append("  }\n");
        configJson.append("}\n");
        configJson.append("EOF");

        userData.addCommands(
            configJson.toString(),
            "",
            "# Start CloudWatch Agent",
            "echo 'Starting CloudWatch Agent...' >> /var/log/userdata.log",
            "/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s"
        );
    }

    @Override
    public void mountEfs(String efsId, String accessPointId, String mountPath, String uid, String gid) {
        userData.addCommands(
            "# Mount EFS",
            "command -v dnf >/dev/null && dnf -y install amazon-efs-utils || yum -y install amazon-efs-utils",
            "mkdir -p " + mountPath,
            String.format("echo \"%s:/ %s efs _netdev,tls,iam,accesspoint=%s 0 0\" >> /etc/fstab",
                efsId, mountPath, accessPointId),
            "mount -a || (journalctl -xe; exit 1)",
            String.format("chown -R %s:%s %s || true", uid, gid, mountPath),
            "echo 'EFS mounted successfully' >> /var/log/userdata.log"
        );
    }

    @Override
    public void mountEbs(String deviceName, String mountPath, String uid, String gid) {
        userData.addCommands(
            "# Mount EBS",
            "DATA_DEV=\"" + deviceName + "\"",
            "if [ ! -b \"$DATA_DEV\" ]; then DATA_DEV=$(readlink -f /dev/nvme1n1 || true); fi",
            "mkfs -t xfs -f \"$DATA_DEV\" || true",
            "mkdir -p " + mountPath,
            String.format("echo \"$DATA_DEV %s xfs defaults,noatime 0 2\" >> /etc/fstab", mountPath),
            "mount -a",
            String.format("chown -R %s:%s %s || true", uid, gid, mountPath),
            "echo 'EBS mounted successfully' >> /var/log/userdata.log"
        );
    }

    @Override
    public void addCommands(String... commands) {
        userData.addCommands(commands);
    }

    @Override
    public void addCommand(String command) {
        userData.addCommands(command);
    }
}