/* * Copyright (c) 2016 ingenieux Labs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package br.com.ingenieux.mojo.cloudformation; import com.amazonaws.services.cloudformation.model.DescribeStacksRequest; import com.amazonaws.services.cloudformation.model.DescribeStacksResult; import com.amazonaws.services.cloudformation.model.Output; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.io.IOUtils; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang.StringUtils.isEmpty; /** * Load CloudFormation Stack Output Variables into the Maven Build */ @Mojo(name = "load-stack-outputs", defaultPhase = LifecyclePhase.VALIDATE) public class LoadStackOutputsMojo extends AbstractCloudformationMojo { public enum OutputFormat { PROPERTIES, JSON } /** * If set to true, ignores/skips in case of a missing active stack found */ @Parameter(property = "cloudformation.failIfMissing", defaultValue = "false") Boolean failIfMissing; /** * When declaring this, it allows you to properly override a variable */ @Parameter(property = "cloudformation.outputMapping") Map<String, String> outputMapping = new LinkedHashMap<>(); public void setOutputMapping(String outputMapping) { List<String> nvPairs = Arrays.asList(outputMapping.split(",")); nvPairs .stream() .map(this::extractNVPair) .forEach( e -> { if (isEmpty(e.getValue())) { this.outputMapping.remove(e.getKey()); } else { this.outputMapping.put(e.getKey(), e.getValue()); } }); } /** * Optional: If Defined, will redirect writing to this file as well. */ @Parameter(property = "cloudformation.outputFile") File outputFile; /** * <p>Optional: If defined as "JSON", will write into outputFile as JSON</p> * * <p>Value values are:</p> * * <ul> * <li>PROPERTIES</li> * <li>JSON</li> * </ul> * * */ @Parameter(property = "cloudformation.outputFormat", defaultValue = "PROPERTIES") OutputFormat outputFormat = OutputFormat.PROPERTIES; @Override protected Object executeInternal() throws Exception { if (shouldFailIfMissingStack(failIfMissing)) { return null; } ; Map<String, String> result = new LinkedHashMap<>(); final Collection<Output> variables = listOutputs(); for (Output o : variables) { String propertyName = resolvePropertyName(o); getLog().info(" * Found output: " + o); getLog().info(" * Setting as:"); getLog().info(" * key: " + propertyName); getLog().info(" * value: " + o.getOutputValue()); result.put(propertyName, o.getOutputValue()); } Properties p = null; if (null != outputFile) { p = new Properties(); } for (Map.Entry<String, String> e : result.entrySet()) { session.getSystemProperties().setProperty(e.getKey(), e.getValue()); if (null != p) p.setProperty(e.getKey(), e.getValue()); } if (null != p) { if (OutputFormat.PROPERTIES.equals(outputFormat)) { p.store(new FileOutputStream(outputFile), "Output from cloudformation-maven-plugin for stackId " + this.stackSummary.getStackId()); } else if (OutputFormat.JSON.equals(outputFormat)) { ObjectNode resultNode = objectMapper.createObjectNode(); for (Map.Entry<String, String> e : result.entrySet()) { resultNode.put(e.getKey(), e.getValue()); } objectMapper.writeValue(outputFile, resultNode); } } return result; } private String resolvePropertyName(Output o) { // TODO Handle Globs + Eventual Replacements if (outputMapping.containsKey(o.getOutputKey())) { final String replacementKey = outputMapping.get(o.getOutputKey()); getLog().info("There's a <outputMapping/> entry for '" + o.getOutputKey() + "' (set to '" + replacementKey + "') declared. Using it instead."); return replacementKey; } return "cloudformation.stack." + o.getOutputKey(); } private Collection<Output> listOutputs() { if (isEmpty(stackId)) { return Collections.emptyList(); } String nextToken = null; final DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackId); List<Output> result = new ArrayList<>(); do { request.setNextToken(nextToken); final DescribeStacksResult response = getService().describeStacks(request); result.addAll(response.getStacks().stream().flatMap(stack -> stack.getOutputs().stream()).collect(Collectors.toList())); nextToken = response.getNextToken(); } while (null != nextToken); return result; } }