package tw.com.repository;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.cloudformation.model.*;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.elasticloadbalancing.model.Instance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tw.com.AwsFacade;
import tw.com.MonitorStackEvents;
import tw.com.StackCache;
import tw.com.entity.*;
import tw.com.exceptions.*;
import tw.com.providers.CloudFormationClient;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class CfnRepository implements CloudFormRepository {
private static final Logger logger = LoggerFactory.getLogger(CfnRepository.class);
private static final String AWS_EC2_INSTANCE_TYPE = "AWS::EC2::Instance";
private static final long STATUS_CHECK_INTERVAL_MILLIS = 1000;
private static final long MAX_CHECK_INTERVAL_MILLIS = 5000;
private final String project;
private CloudFormationClient formationClient;
private CloudRepository cloudRepository;
private StackCache stackCache;
public CfnRepository(CloudFormationClient formationClient, CloudRepository cloudRepository, String project) {
this.formationClient = formationClient;
this.cloudRepository = cloudRepository;
this.project = project;
stackCache = new StackCache(formationClient, project);
}
@Override
public List<StackEntry> getStacks(EnvironmentTag envTag) {
List<StackEntry> results = new LinkedList<>();
for(StackEntry entry : stackCache.getEntries()) {
if (entry.getEnvTag().equals(envTag)) {
results.add(entry);
}
}
return results;
}
public List<StackEntry> getStacksMatching(EnvironmentTag envTag, String name) {
logger.info(String.format("Find stacks matching env %s and name %s", envTag, name));
List<StackEntry> results = new LinkedList<>();
for(StackEntry entry : stackCache.getEntries()) {
logger.debug("Check if entry matches " + entry);
if (entry.getEnvTag().equals(envTag) && entry.getBaseName().equals(name)) {
results.add(entry);
}
}
return results;
}
@Override
public String findPhysicalIdByLogicalId(EnvironmentTag envTag, String logicalId) {
logger.info(String.format("Looking for resource matching logicalID: %s for %s",logicalId, envTag));
List<StackEntry> stacks = getStacks(envTag);
for (StackEntry stackEntry : stacks) {
String stackName = stackEntry.getStackName();
logger.debug(String.format("Checking stack %s for logical id %s", stackName, logicalId));
String maybeHaveId = findPhysicalIdByLogicalId(envTag, stackName, logicalId);
if (maybeHaveId != null) {
logger.info(String.format(
"Found physicalID: %s for logical ID: %s in stack %s", maybeHaveId, logicalId, stackName));
return maybeHaveId;
}
}
logger.warn("No match for logical ID was found");
return null;
}
private String findPhysicalIdByLogicalId(EnvironmentTag envTag, String stackName, String logicalId) {
logger.info(String.format(
"Check Env %s and stack %s for logical ID %s", envTag,
stackName, logicalId));
try {
List<StackResource> resources = stackCache.getResourcesForStack(stackName);
logger.debug(String.format("Found %s resources for stack %s",
resources.size(), stackName));
for (StackResource resource : resources) {
String candidateId = resource.getLogicalResourceId();
String physicalResourceId = resource.getPhysicalResourceId();
logger.debug(String
.format("Checking for match against resource phyId=%s logId=%s",
physicalResourceId,
resource.getLogicalResourceId()));
if (candidateId.equals(logicalId)) {
return physicalResourceId;
}
}
} catch (AmazonServiceException exception) {
logger.warn("Unable to check for resources, stack name: "
+ stackName, exception);
}
return null;
}
@Override
public String waitForStatusToChangeFrom(String stackName,
StackStatus currentStatus, List<String> aborts)
throws WrongNumberOfStacksException, InterruptedException {
long pause = STATUS_CHECK_INTERVAL_MILLIS;
logger.info(String.format("Waiting for stack %s to change FROM status %s", stackName, currentStatus));
String status = currentStatus.toString();
Stack stack = null;
while (status.equals(currentStatus.toString())) {
Thread.sleep(pause);
stack = formationClient.describeStack(stackName);
status = stack.getStackStatus();
logger.debug(String
.format("Waiting for status of stack %s, status was %s, pause was %s",
stackName, status, pause));
if (pause < MAX_CHECK_INTERVAL_MILLIS) {
pause = pause + STATUS_CHECK_INTERVAL_MILLIS;
}
if (aborts.contains(status)) {
logger.error("Matched an abort status");
break;
}
}
logger.info(String
.format("Stack status changed, status is now %s and reason (if any) was: '%s' ",
status, stack.getStackStatusReason()));
return status;
}
@Override
public List<StackEvent> getStackEvents(String stackName) {
return formationClient.describeStackEvents(stackName);
}
@Override
public boolean stackExists(String stackName) throws WrongNumberOfStacksException {
logger.info("Check if stack exists for " + stackName);
try { // throws if stack does not exist
formationClient.describeStack(stackName);
return true;
}
catch (AmazonServiceException exception) {
if (exception.getStatusCode()==400) {
return false;
}
throw exception;
} catch (WrongNumberOfStacksException wrongNumberException) {
if (wrongNumberException.getNumber()!=0) {
throw wrongNumberException;
} else
{
return false;
}
}
}
@Override
public String getStackStatus(String stackName) {
logger.info("Getting stack status for " + stackName);
for (StackEntry entry : stackCache.getEntries()) {
Stack stack = entry.getStack();
if (stack.getStackName().equals(stackName)) {
// get latest status
try {
return getStackCurrentStatus(stackName);
} catch (WrongNumberOfStacksException e) {
logger.warn("Mismatch on stack status", e);
return "";
} catch (AmazonServiceException e) {
logger.warn("Could not check status of stack " +stackName,e);
if (e.getStatusCode()==400) {
return ""; // stack does not exist, perhaps a delete was in progress
}
}
}
}
logger.warn("Failed to find stack status for :" + stackName);
return "";
}
private String getStackCurrentStatus(String stackName)
throws WrongNumberOfStacksException {
Stack stack = formationClient.describeStack(stackName);
String stackStatus = stack.getStackStatus();
logger.info(String.format("Got status %s for stack %s", stackStatus, stackName));
return stackStatus;
}
@Override
public StackNameAndId getStackNameAndId(String stackName)
throws WrongNumberOfStacksException {
Stack stack = formationClient.describeStack(stackName);
String id = stack.getStackId();
return new StackNameAndId(stackName, id);
}
@Override
public List<String> getAllInstancesFor(SearchCriteria criteria) throws CfnAssistException {
logger.info("Finding instances for " + criteria);
List<StackEntry> stacks = criteria.matches(stackCache.getEntries());
List<String> instanceIds = new LinkedList<>();
for (StackEntry entry : stacks) {
String stackName = entry.getStackName();
if (entry.isLive()) {
logger.info("Found stack :" + stackName);
instanceIds.addAll(getInstancesFor(stackName));
} else {
logger.info("Not adding stack :" + stackName);
}
}
return instanceIds;
}
public List<String> getInstancesFor(String stackname) {
List<String> instanceIds = new LinkedList<>();
List<StackResource> resources = stackCache.getResourcesForStack(stackname);
for (StackResource resource : resources) {
String type = resource.getResourceType();
if (type.equals(AWS_EC2_INSTANCE_TYPE)) {
logger.info("Matched instance: "+ resource.getPhysicalResourceId());
instanceIds.add(resource.getPhysicalResourceId());
}
}
return instanceIds;
}
@Override
public Stack getStack(String stackName) throws WrongNumberOfStacksException {
for(StackEntry entry : stackCache.getEntries()) {
if (entry.getStackName().equals(stackName)) {
return entry.getStack();
}
}
// TODO we can only get here if stack is not tagged by cfn assist managed, should we throw?
return formationClient.describeStack(stackName);
}
@Override
public StackEntry getStacknameByIndex(EnvironmentTag envTag, Integer index) throws WrongNumberOfStacksException {
SearchCriteria criteriaForIndex = new SearchCriteria(new ProjectAndEnv(project, envTag.getEnv())).withIndex(index);
List<StackEntry> stacks = criteriaForIndex.matches(stackCache.getEntries());
if (stacks.isEmpty()) {
SearchCriteria criteria = new SearchCriteria(new ProjectAndEnv(project, envTag.getEnv())).
withUpdateIndex(index);
stacks = criteria.matches(stackCache.getEntries());
}
if (stacks.size()!=1) {
throw new WrongNumberOfStacksException(1, stacks.size());
}
StackEntry stack = stacks.get(0);
logger.info(String.format("Found stack %s for %s and index %s", stack, envTag, index));
return stack;
}
@Override
public List<StackEntry> getStacks() {
return stackCache.getEntries();
}
public List<TemplateParameter> validateStackTemplate(String templateBody) {
return formationClient.validateTemplate(templateBody);
}
@Override
public void deleteStack(String stackName) {
formationClient.deleteStack(stackName);
}
@Override
public StackNameAndId createStack(ProjectAndEnv projAndEnv,
String contents, String stackName, Collection<Parameter> parameters, MonitorStackEvents monitor, Tagging tagging) throws CfnAssistException {
return formationClient.createStack(projAndEnv,contents, stackName, parameters, monitor, tagging);
}
@Override
public StackNameAndId updateStack(String contents,
Collection<Parameter> parameters, MonitorStackEvents monitor, String stackName) throws CfnAssistException {
return formationClient.updateStack(contents, parameters, monitor, stackName);
}
@Override
public List<Instance> getAllInstancesMatchingType(SearchCriteria criteria, String typeTag) throws CfnAssistException {
Collection<String> instancesIds = getAllInstancesFor(criteria);
List<Instance> instances = new LinkedList<Instance>();
for (String id : instancesIds) {
if (instanceHasCorrectType(typeTag, id)) {
logger.info(String.format("Adding instance %s as it matched %s %s",id, AwsFacade.TYPE_TAG, typeTag));
instances.add(new Instance(id));
} else {
logger.info(String.format("Not adding instance %s as did not match %s %s",id, AwsFacade.TYPE_TAG, typeTag));
}
}
logger.info(String.format("Found %s instances matching %s and type: %s", instances.size(), criteria, typeTag));
return instances;
}
private boolean instanceHasCorrectType(String type, String id) throws WrongNumberOfInstancesException {
List<Tag> tags = cloudRepository.getTagsForInstance(id);
for(Tag tag : tags) {
if (tag.getKey().equals(AwsFacade.TYPE_TAG)) {
return tag.getValue().equals(type);
}
}
return false;
}
private Stack updateRepositoryFor(StackNameAndId id) throws WrongNumberOfStacksException {
return stackCache.updateRepositoryFor(id);
}
@Override
public void createFail(StackNameAndId id) throws WrongNumberOfStacksException {
updateRepositoryFor(id);
}
@Override
public Stack createSuccess(StackNameAndId id) throws WrongNumberOfStacksException {
return updateRepositoryFor(id);
}
@Override
public void updateFail(StackNameAndId id) throws WrongNumberOfStacksException {
updateRepositoryFor(id);
}
@Override
public Stack updateSuccess(StackNameAndId id) throws WrongNumberOfStacksException {
return updateRepositoryFor(id);
}
}