/* * 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.ec2; import com.amazonaws.AmazonClientException; import com.amazonaws.util.EC2MetadataUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; /** * @author Agim Emruli */ public class AmazonEc2InstanceDataPropertySource extends PropertySource<Object> { private static final Logger LOGGER = LoggerFactory.getLogger(AmazonEc2InstanceDataPropertySource.class); private static final String EC2_METADATA_ROOT = "/latest/meta-data"; private static final String DEFAULT_USER_DATA_ATTRIBUTE_SEPARATOR = ";"; private static final String DEFAULT_KNOWN_PROPERTIES_PATH = AmazonEc2InstanceDataPropertySource.class.getSimpleName() + ".properties"; private static final Properties KNOWN_PROPERTY_NAMES; private String userDataAttributeSeparator = DEFAULT_USER_DATA_ATTRIBUTE_SEPARATOR; private String userDataValueSeparator = PlaceholderConfigurerSupport.DEFAULT_VALUE_SEPARATOR; static { // Load all known properties from the classpath. This is not meant // to be changed by external developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_KNOWN_PROPERTIES_PATH, AmazonEc2InstanceDataPropertySource.class); KNOWN_PROPERTY_NAMES = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_KNOWN_PROPERTIES_PATH + "': " + ex.getMessage()); } } private volatile Map<String, String> cachedUserData; public AmazonEc2InstanceDataPropertySource(String name) { super(name, new Object()); } public void setUserDataAttributeSeparator(String userDataAttributeSeparator) { this.userDataAttributeSeparator = userDataAttributeSeparator; } public void setUserDataValueSeparator(String userDataValueSeparator) { this.userDataValueSeparator = userDataValueSeparator; } @Override public Object getProperty(String name) { Map<String, String> userData = getUserData(); if (userData.containsKey(name)) { return userData.get(name); } if (!KNOWN_PROPERTY_NAMES.containsKey(getRootPropertyName(name))) { return null; } try { return EC2MetadataUtils.getData(EC2_METADATA_ROOT + "/" + name); } catch (AmazonClientException e) { //Suppress exception if we are not able to contact the service, //because that is quite often the case if we run in unit tests outside the environment. LOGGER.warn("Error getting instance meta-data with name '{}' error message is '{}'", name, e.getMessage()); return null; } } private static String getRootPropertyName(String propertyName) { String[] propertyTokens = StringUtils.split(propertyName, "/"); return propertyTokens != null ? propertyTokens[0] : propertyName; } private Map<String, String> getUserData() { if (this.cachedUserData == null) { Map<String, String> userDataMap = new LinkedHashMap<>(); String userData = null; try { userData = EC2MetadataUtils.getUserData(); } catch (AmazonClientException e) { //Suppress exception if we are not able to contact the service, //because that is quite often the case if we run in unit tests outside the environment. LOGGER.warn("Error getting instance user-data error message is '{}'", e.getMessage()); } if (StringUtils.hasText(userData)) { String[] userDataAttributes = userData.split(this.userDataAttributeSeparator); for (String userDataAttribute : userDataAttributes) { String[] userDataAttributesParts = StringUtils.split(userDataAttribute, this.userDataValueSeparator); if (userDataAttributesParts != null && userDataAttributesParts.length > 0) { String key = userDataAttributesParts[0]; String value = null; if (userDataAttributesParts.length > 1) { value = userDataAttributesParts[1]; } userDataMap.put(key, value); } } } this.cachedUserData = Collections.unmodifiableMap(userDataMap); } return this.cachedUserData; } }