package core.aws.task.ec2;
import com.amazonaws.services.ec2.model.CreateImageRequest;
import com.amazonaws.services.ec2.model.CreateImageResult;
import com.amazonaws.services.ec2.model.CreateSecurityGroupRequest;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeImagesResult;
import com.amazonaws.services.ec2.model.InstanceType;
import com.amazonaws.services.ec2.model.IpPermission;
import com.amazonaws.services.ec2.model.IpRange;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.Subnet;
import core.aws.client.AWS;
import core.aws.env.Context;
import core.aws.env.Environment;
import core.aws.resource.ec2.Instance;
import core.aws.resource.ec2.InstanceState;
import core.aws.resource.ec2.KeyPair;
import core.aws.resource.image.Image;
import core.aws.task.linux.AnsibleProvisioner;
import core.aws.util.Asserts;
import core.aws.util.Lists;
import core.aws.util.Threads;
import core.aws.workflow.Action;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author neo
*/
@Action("bake")
public class BakeAMITask extends core.aws.workflow.Task<Image> {
private final Logger logger = LoggerFactory.getLogger(BakeAMITask.class);
private final Instance resumeBakeInstance;
private String resourceId;
private KeyPairHelper keyPairHelper;
private EC2TagHelper tagHelper;
private Subnet bakeSubnet;
public BakeAMITask(Image image, Instance resumeBakeInstance) {
super(image);
this.resumeBakeInstance = resumeBakeInstance;
}
@Override
public void execute(Context context) throws Exception {
tagHelper = new EC2TagHelper(context.env);
keyPairHelper = new KeyPairHelper(context.env);
String bakeSubnetId = context.env.bakeSubnetId;
if (bakeSubnetId != null) {
bakeSubnet = AWS.vpc.describeSubnets(Lists.newArrayList(bakeSubnetId)).get(0);
if (!bakeSubnet.isMapPublicIpOnLaunch())
throw new Error("bake subnet does not auto assign public ip, please check the subnet setting, subnetId=" + bakeSubnetId);
}
resourceId = "ami-" + resource.id + LocalDateTime.now().format(DateTimeFormatter.ofPattern("-yyyyMMdd-HHmm"));
com.amazonaws.services.ec2.model.Instance instance;
if (resumeBakeInstance != null) {
instance = resumeBakeInstance.remoteInstances.get(0);
logger.info("resume bake with {}, instanceId={}", resumeBakeInstance.id, instance.getInstanceId());
Asserts.isTrue(InstanceState.RUNNING.equalsTo(instance.getState()), "resume bake instance must be running, instanceId={}, currentState={}", instance.getInstanceId(), instance.getState().getName());
} else {
KeyPair keyPair = createKeyPair(context.env);
String sgId = createSG(context.env);
instance = createInstance(keyPair, sgId);
}
AnsibleProvisioner provisioner = new AnsibleProvisioner(context.env, instance, resource.playbook, resource.packageDir);
provisioner.additionalVariables.put("tomcat_service_state", "stopped");
provisioner.additionalVariables.put("supervisor_service_state", "stopped");
provisioner.additionalVariables.put("nginx_service_state", "stopped");
provisioner.additionalVariables.put("elasticsearch_heap_size", "1g");
provisioner.provision();
String imageId = createAMI(context, instance.getInstanceId());
AWS.ec2.terminateInstances(Lists.newArrayList(instance.getInstanceId()));
keyPairHelper.deleteKeyPair(instance.getKeyName());
AWS.ec2.deleteSecurityGroup(instance.getSecurityGroups().get(0).getGroupId());
waitUntilAMIFinished(imageId);
}
private void waitUntilAMIFinished(String imageId) throws InterruptedException {
logger.info("wait until AMI finished, imageId={}", imageId);
while (true) {
DescribeImagesResult result = AWS.ec2.ec2.describeImages(new DescribeImagesRequest().withImageIds(imageId));
String state = result.getImages().get(0).getState();
logger.info("AMI state {} => {}", imageId, state);
if ("available".equals(state)) {
break;
} else if ("failed".equals(state)) {
throw new IllegalStateException("AMI failed to create, please check AWS console for more details");
}
Threads.sleepRoughly(Duration.ofSeconds(30));
}
}
private String createAMI(Context context, String instanceId) throws Exception {
AWS.ec2.stopInstances(Lists.newArrayList(instanceId));
logger.info("create AMI, instanceId={}, imageName={}", instanceId, resource.name());
CreateImageResult result = AWS.ec2.ec2.createImage(new CreateImageRequest(instanceId, resource.name()));
String imageId = result.getImageId();
AWS.ec2.createTags(new CreateTagsRequest()
.withResources(imageId)
.withTags(tagHelper.env(),
tagHelper.resourceId(resource.id),
tagHelper.version(resource.nextVersion()),
tagHelper.name(resource.id + ":" + resource.nextVersion())));
String key = "ami/" + resource.id;
context.output(key, String.format("imageId=%s", imageId));
logger.info("result imageId => {}", imageId);
return imageId;
}
private com.amazonaws.services.ec2.model.Instance createInstance(KeyPair keyPair, String sgId) throws Exception {
RunInstancesRequest request = new RunInstancesRequest()
.withKeyName(keyPair.remoteKeyPair.getKeyName())
.withInstanceType(InstanceType.M4Large)
.withImageId(resource.baseAMI.imageId())
.withMinCount(1)
.withMaxCount(1)
.withSecurityGroupIds(sgId);
if (bakeSubnet != null) request.withSubnetId(bakeSubnet.getSubnetId());
return AWS.ec2.runInstances(request,
tagHelper.name(resourceId),
tagHelper.env(),
tagHelper.resourceId(resourceId),
tagHelper.type("ami"),
tagHelper.amiImageId(resource.id())).get(0);
}
private KeyPair createKeyPair(Environment env) throws IOException {
KeyPair keyPair = new KeyPair(resourceId, env.name + ":" + resourceId);
if (!AWS.ec2.keyPairExists(keyPair.name)) { // for resume previous failed baking, key pair may exist
keyPairHelper.createKeyPair(keyPair);
}
return keyPair;
}
private String createSG(Environment env) throws Exception {
String sgName = env.name + ":" + resourceId;
CreateSecurityGroupRequest request = new CreateSecurityGroupRequest(sgName, sgName);
if (bakeSubnet != null) request.setVpcId(bakeSubnet.getVpcId());
String sgId = AWS.ec2.createSecurityGroup(request).getGroupId();
AWS.ec2.createSGIngressRules(sgId, Lists.newArrayList(new IpPermission()
.withIpv4Ranges(new IpRange().withCidrIp("0.0.0.0/0"))
.withFromPort(22)
.withToPort(22)
.withIpProtocol("tcp")));
AWS.ec2.createTags(new CreateTagsRequest()
.withResources(sgId)
.withTags(tagHelper.name(resourceId), tagHelper.env(), tagHelper.resourceId(resourceId)));
return sgId;
}
}