package tw.com; import com.amazonaws.services.cloudformation.model.Stack; import com.amazonaws.services.cloudformation.model.StackResource; import com.amazonaws.services.cloudformation.model.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tw.com.entity.EnvironmentTag; import tw.com.entity.StackEntry; import tw.com.entity.StackNameAndId; import tw.com.entity.StackResources; import tw.com.exceptions.WrongNumberOfStacksException; import tw.com.providers.CloudFormationClient; import java.util.*; public class StackCache { private static final Logger logger = LoggerFactory.getLogger(StackCache.class); private List<StackEntry> theEntries; CloudFormationClient formationClient; private StackResources stackResources; private String project; public StackCache(CloudFormationClient formationClient, String project) { this.formationClient = formationClient; this.project = project; stackResources = new StackResources(); theEntries = new LinkedList<>(); } public List<StackEntry> getEntries() { getAllStacksForProject(); return theEntries; } private void getAllStacksForProject() { // TODO handle "next token"? if (theEntries.size() == 0) { logger.info("No cached stacks, loading all stacks"); List<Stack> stacks = formationClient.describeAllStacks(); populateEntriesIfProjectMatches(stacks); logger.info(String.format("Loaded %s stacks", theEntries.size())); } else { logger.info("Cache hit on stacks"); } } private void populateEntriesIfProjectMatches(List<Stack> stacks) { logger.info(String.format("Populating stack entries for %s stacks", stacks.size())); for(Stack stack : stacks) { logger.info(String.format("Checking stack %s for tag", stack.getStackName())); List<Tag> tags = stack.getTags(); Map<String, String> keyValues = convertToMap(tags); int count = 3; String env = ""; String proj = ""; Integer build = null; for(Tag tag : tags) { String key = tag.getKey(); String value = tag.getValue(); if (key.equals(AwsFacade.ENVIRONMENT_TAG)) { env = value; count--; } else if (key.equals(AwsFacade.PROJECT_TAG)) { proj = value; count--; } else if (key.equals(AwsFacade.BUILD_TAG)) { build = Integer.parseInt(value); count--; } if (count==0) break; // small optimisation } //String index = keyValues.get(AwsFacade.INDEX_TAG); addEntryIfProjectAndEnvMatches(stack, env, proj, build, keyValues); } } private HashMap<String, String> convertToMap(List<Tag> tags) { HashMap<String, String> result = new HashMap<>(); tags.forEach(tag -> result.put(tag.getKey(), tag.getValue())); return result; } private void addEntryIfProjectAndEnvMatches(Stack stack, String env, String proj, Integer build, Map<String, String> keyValues) { String stackName = stack.getStackName(); if (!proj.equals(project) || (env.isEmpty())) { logger.warn(String.format("Could not match expected tags (%s and %s) for project '%s' and stackname %s", AwsFacade.ENVIRONMENT_TAG, AwsFacade.PROJECT_TAG, proj, stackName)); return; } logger.info(String.format("Stack %s matched %s and %s", stackName, env, proj)); EnvironmentTag envTag = new EnvironmentTag(env); StackEntry entry = new StackEntry(proj, envTag, stack); if (build!=null) { logger.info(String.format("Saving associated build number (%s) into stack %s", build, stackName)); entry.setBuildNumber(build); } if (keyValues.containsKey(AwsFacade.INDEX_TAG)) { addIndexTag(keyValues, stackName, entry); } if (keyValues.containsKey(AwsFacade.UPDATE_INDEX_TAG)) { addUpdateIndexTag(keyValues, entry); } if (theEntries.contains(entry)) { theEntries.remove(entry); logger.info("Replacing or Removing entry for stack " + stackName); } String stackStatus = stack.getStackStatus(); theEntries.add(entry); stackResources.removeResources(stackName); logger.info(String.format("Added stack %s matched, environment is %s, status was %s", stackName, envTag, stackStatus)); } private void addUpdateIndexTag(Map<String, String> keyValues, StackEntry entry) { String raw = keyValues.get(AwsFacade.UPDATE_INDEX_TAG); String[] values = raw.split(","); Set<Integer> updateIndexs = new HashSet<>(); for (String value : values) { updateIndexs.add(Integer.parseInt(value)); } entry.setUpdateIndex(updateIndexs); } private void addIndexTag(Map<String, String> keyValues, String stackName, StackEntry entry) { String index = keyValues.get(AwsFacade.INDEX_TAG); int number = Integer.parseInt(index); logger.info(String.format("Saving associated index (%s) into stack %s", number, stackName)); entry.setIndex(number); } public Stack updateRepositoryFor(StackNameAndId id) throws WrongNumberOfStacksException { logger.info("Update stack repository for stack: " + id); Stack stack = formationClient.describeStack(id.getStackName()); populateEntriesIfProjectMatches(stack); return stack; } private void populateEntriesIfProjectMatches(Stack stack) { LinkedList<Stack> list = new LinkedList<>(); list.add(stack); this.populateEntriesIfProjectMatches(list); } public List<StackResource> getResourcesForStack(String stackName) { List<StackResource> resources = null; if (stackResources.containsStack(stackName)) { logger.info("Cache hit on stack resources for stack " + stackName); resources = stackResources.getStackResources(stackName); } else { logger.info("Cache miss, loading resources for stack " + stackName); resources = formationClient.describeStackResources(stackName); stackResources.addStackResources(stackName, resources); } return resources; } }