RedisApplicationSpec.java
package com.cloudforgeci.api.application.database;
import com.cloudforge.core.annotation.ApplicationPlugin;
import com.cloudforge.core.interfaces.ApplicationSpec;
import com.cloudforge.core.interfaces.Ec2Context;
import com.cloudforge.core.interfaces.UserDataBuilder;
import java.util.List;
/**
* Redis ApplicationSpec implementation.
*
* <p>Redis is an in-memory data structure store used as database, cache, and message broker.</p>
*
* <p><strong>Key Features:</strong></p>
* <ul>
* <li>In-memory key-value store</li>
* <li>Support for multiple data structures (strings, lists, sets, hashes)</li>
* <li>Pub/sub messaging</li>
* <li>Persistence options (RDB, AOF)</li>
* <li>High performance</li>
* </ul>
*
* @see <a href="https://redis.io/documentation">Redis Documentation</a>
*/
@ApplicationPlugin(
value = "redis",
category = "database",
displayName = "Redis",
description = "In-memory data store and cache",
defaultCpu = 512,
defaultMemory = 1024,
defaultInstanceType = "t3.micro",
supportsFargate = true,
supportsEc2 = true,
supportsOidc = false
)
public class RedisApplicationSpec implements ApplicationSpec {
private static final String APPLICATION_ID = "redis";
private static final String DEFAULT_IMAGE = "redis:7-alpine";
private static final int APPLICATION_PORT = 6379;
private static final String CONTAINER_DATA_PATH = "/data";
private static final String EFS_DATA_PATH = "/redis";
private static final String VOLUME_NAME = "redisData";
private static final String CONTAINER_USER = "999:999"; // redis user
private static final String EFS_PERMISSIONS = "755";
private static final String EBS_DEVICE_NAME = "/dev/xvdh";
private static final String EC2_DATA_PATH = "/var/lib/redis";
private static final List<String> EC2_LOG_PATHS = List.of(
"/var/log/redis/redis.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(
// Redis Cluster Bus - inbound for cluster node communication
OptionalPort.inboundTcp(16379, "enableCluster", "Cluster Bus"),
// Redis Sentinel - inbound for HA failover coordination
OptionalPort.inboundTcp(26379, "enableSentinel", "Sentinel")
);
}
@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 Redis container
// NOTE: REDIS_PASSWORD should be set via EC2 instance metadata or Secrets Manager
builder.addCommands(
"# Retrieve Redis password from Secrets Manager or use placeholder",
"REDIS_PASSWORD=$(aws secretsmanager get-secret-value --secret-id ${STACK_NAME:-redis}/redis-password --query SecretString --output text 2>/dev/null || echo 'REPLACE_WITH_SECURE_PASSWORD')",
"",
"# Run Redis container with persistence enabled",
"docker run -d \\",
" --name redis \\",
" -p 6379:6379 \\",
" -v " + ec2DataPath() + ":/data \\",
" " + DEFAULT_IMAGE + " \\",
" redis-server --appendonly yes --requirepass \"$REDIS_PASSWORD\"",
"echo 'Redis container started with AOF persistence' >> /var/log/userdata.log",
"",
"# Test Redis connection",
"sleep 5",
"docker exec redis redis-cli -a \"$REDIS_PASSWORD\" ping && \\",
" echo 'Redis is responding' >> /var/log/userdata.log || \\",
" echo 'Redis not responding yet' >> /var/log/userdata.log"
);
}
@Override
public String toString() {
return "RedisApplicationSpec{" +
"applicationId='" + APPLICATION_ID + '\'' +
", defaultImage='" + DEFAULT_IMAGE + '\'' +
", applicationPort=" + APPLICATION_PORT +
'}';
}
}