/* * Copyright 2013-2014 the original author or authors. * * 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 org.springframework.cloud.aws.core.env.stack.config; import com.amazonaws.services.cloudformation.AmazonCloudFormation; import com.amazonaws.services.cloudformation.model.ListStackResourcesRequest; import com.amazonaws.services.cloudformation.model.ListStackResourcesResult; import com.amazonaws.services.cloudformation.model.StackResourceSummary; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.cloud.aws.core.env.stack.ListableStackResourceFactory; import org.springframework.cloud.aws.core.env.stack.StackResource; import org.springframework.cloud.aws.core.support.documentation.RuntimeUse; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Exposes a fully populated {@link org.springframework.cloud.aws.core.env.stack.StackResourceRegistry} instance representing the resources of * the specified stack. * * @author Christian Stettler * @author Agim Emruli */ @RuntimeUse public class StackResourceRegistryFactoryBean extends AbstractFactoryBean<ListableStackResourceFactory> { private final AmazonCloudFormation amazonCloudFormationClient; private final StackNameProvider stackNameProvider; public StackResourceRegistryFactoryBean(AmazonCloudFormation amazonCloudFormationClient, StackNameProvider stackNameProvider) { this.amazonCloudFormationClient = amazonCloudFormationClient; this.stackNameProvider = stackNameProvider; } @Override public Class<?> getObjectType() { return ListableStackResourceFactory.class; } @Override protected ListableStackResourceFactory createInstance() throws Exception { String stackName = this.stackNameProvider.getStackName(); return new StaticStackResourceRegistry(stackName, getResourceMappings("", stackName)); } private Map<String, StackResource> getResourceMappings(String prefix, String stackName) { ListStackResourcesResult listStackResourcesResult = this.amazonCloudFormationClient.listStackResources(new ListStackResourcesRequest().withStackName(stackName)); List<StackResourceSummary> stackResourceSummaries = listStackResourcesResult.getStackResourceSummaries(); Map<String, StackResource> stackResourceMappings = new HashMap<>(); Map<String, StackResource> current = convertToStackResourceMappings(prefix, stackResourceSummaries); stackResourceMappings.putAll(current); for (Map.Entry<String, StackResource> e : current.entrySet()) { StackResource resource = e.getValue(); if ("AWS::CloudFormation::Stack".equals(resource.getType())) { stackResourceMappings.putAll(getResourceMappings(e.getKey(), resource.getPhysicalId())); } } return stackResourceMappings; } private Map<String, StackResource> convertToStackResourceMappings(String prefix, List<StackResourceSummary> stackResourceSummaries) { Map<String, StackResource> stackResourceMappings = new HashMap<>(); for (StackResourceSummary stackResourceSummary : stackResourceSummaries) { String logicalResourceId = toNestedResourceId(prefix, stackResourceSummary.getLogicalResourceId()); stackResourceMappings.put(logicalResourceId, new StackResource(logicalResourceId, stackResourceSummary.getPhysicalResourceId(), stackResourceSummary.getResourceType())); } return stackResourceMappings; } private String toNestedResourceId(String prefix, String logicalResourceId) { return StringUtils.isEmpty(prefix) ? logicalResourceId : prefix + "." + logicalResourceId; } /** * Stack resource registry containing a static mapping of logical resource ids to physical resource ids. */ private static class StaticStackResourceRegistry implements ListableStackResourceFactory { private final String stackName; private final Map<String, StackResource> stackResourceByLogicalId; private StaticStackResourceRegistry(String stackName, Map<String, StackResource> stackResourceByLogicalId) { this.stackName = stackName; this.stackResourceByLogicalId = stackResourceByLogicalId; } @Override public String getStackName() { return this.stackName; } @Override public String lookupPhysicalResourceId(String logicalResourceId) { if (this.stackResourceByLogicalId.containsKey(logicalResourceId)) { return this.stackResourceByLogicalId.get(logicalResourceId).getPhysicalId(); } else if (!logicalResourceId.contains(".")) { String prefix = "." + logicalResourceId; String physicalId = null; for (Map.Entry<String, StackResource> entry : this.stackResourceByLogicalId.entrySet()) { if (entry.getKey() != null && entry.getKey().endsWith(prefix)) { if (physicalId == null) { physicalId = entry.getValue().getPhysicalId(); } else { // unqualified resourceId is not unique return null; } } } return physicalId; } else { return null; } } @Override public Collection<StackResource> getAllResources() { return this.stackResourceByLogicalId.values(); } @Override public Collection<StackResource> resourcesByType(String type) { List<StackResource> result = new ArrayList<>(); for (StackResource stackResource : this.stackResourceByLogicalId.values()) { if (stackResource.getType().equals(type)) { result.add(stackResource); } } return result; } } }