package core.aws.resource.image; import core.aws.resource.Resource; import core.aws.resource.Resources; import core.aws.resource.ec2.Instance; import core.aws.task.ec2.BakeAMITask; import core.aws.task.ec2.DeleteImageTask; import core.aws.task.ec2.DeleteInstanceTask; import core.aws.task.ec2.DeleteKeyPairTask; import core.aws.task.ec2.DeleteSGTask; import core.aws.task.ec2.DescribeImageTask; import core.aws.util.Asserts; import core.aws.util.Lists; import core.aws.util.ToStringHelper; import core.aws.workflow.Tasks; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; import java.util.stream.Collectors; /** * @author neo */ public class Image extends Resource implements AMI { public final NavigableMap<Integer, com.amazonaws.services.ec2.model.Image> remoteImages = new TreeMap<>(); public final List<Instance> unfinishedBakeInstances = Lists.newArrayList(); public AMI baseAMI; public Path playbook; public Optional<Path> packageDir = Optional.empty(); public String namePrefix; public Image(String id) { super(id); } public int nextVersion() { return version().get() + 1; } @Override public String id() { return id; } @Override public String imageId() { if (remoteImages.isEmpty()) return baseAMI.imageId(); return remoteImages.lastEntry().getValue().getImageId(); } @Override public Optional<Integer> version() { if (remoteImages.isEmpty()) return Optional.of(0); return Optional.of(remoteImages.lastKey()); } public String name() { return namePrefix + "-v" + nextVersion(); } @Override public void validate(Resources resources) { int length = name().length(); Asserts.isTrue(length >= 3 && length <= 128, "AMI names must be between 3 and 128 characters long, and may contain letters, numbers, '(', ')', '.', '-', '/' and '_'"); Asserts.isTrue(Files.exists(playbook), "playbook does not exist, path={}", playbook); packageDir.ifPresent(path -> Asserts.isTrue(Files.exists(path), "packageDir does not exist, path={}", packageDir)); } @Override protected void describeTasks(Tasks tasks) { tasks.add(new DescribeImageTask(this)); } public void bakeTasks(Tasks tasks, boolean resume) { Instance resumeBakeInstance = null; if (resume && !unfinishedBakeInstances.isEmpty()) { resumeBakeInstance = unfinishedBakeInstances.remove(0); } for (Instance instance : unfinishedBakeInstances) { tasks.add(new DeleteInstanceTask(instance, instance.remoteInstances)); if (instance.securityGroup != null) tasks.add(new DeleteSGTask(instance.securityGroup)); if (instance.keyPair != null) tasks.add(new DeleteKeyPairTask(instance.keyPair)); } tasks.add(new BakeAMITask(this, resumeBakeInstance)); deleteAllFailedImages(tasks); while (remoteImages.size() >= 5) { Map.Entry<Integer, com.amazonaws.services.ec2.model.Image> entry = remoteImages.pollFirstEntry(); tasks.add(new DeleteImageTask(this, entry.getValue())); } } private void deleteAllFailedImages(Tasks tasks) { List<Integer> failedImageVersions = remoteImages.entrySet().stream() .filter(entry -> !"available".equals(entry.getValue().getState())) .map(Map.Entry::getKey) .collect(Collectors.toList()); for (Integer failedImageVersion : failedImageVersions) { com.amazonaws.services.ec2.model.Image image = remoteImages.remove(failedImageVersion); tasks.add(new DeleteImageTask(this, image)); } } @Override public String toString() { return new ToStringHelper(this) .add(id) .add("imageId", imageId()) .add("version", version().get()) .toString(); } }