Cantara / ConfigService-SDK

Common core library for ConfigService and ConfigService clients
Apache License 2.0
2 stars 2 forks source link

Support fetching configuration from AWS parameter store #6

Open sherriff opened 7 years ago

sherriff commented 7 years ago

DownloadUtil.java currently supports fetching files from http and s3. Adding support for AWS parameter store looks interesting.

Suggest using NamedPropertiesStore.java over DownLoadItem, but it might be a good idea to create an interface or extend NamedPropertiesStore to support

  1. "how to make the properties available": i.e. write to file or send as system properties: java -Dtest="true" -jar myApplication.jar
  2. Make NamedPropertiesStore hold metadata required to fetch properties from AWS parameter store.

Impl notes: use describeParameters to fetch list of all keys and use this list of keys as input to getParameters. It is necessay to fetch all keys and filter client side due to some bugs.

https://aws.amazon.com/ec2/systems-manager/parameter-store/ https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-ssm

sindremehus commented 7 years ago

Code for downloading parameters from SSM:

package no.embriq.aws.util.ssm;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
import com.amazonaws.services.simplesystemsmanagement.model.DescribeParametersRequest;
import com.amazonaws.services.simplesystemsmanagement.model.DescribeParametersResult;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersRequest;
import com.amazonaws.services.simplesystemsmanagement.model.Parameter;
import com.amazonaws.services.simplesystemsmanagement.model.ParameterMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Looks up parameters from the <a href="http://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html">Amazon System Manager Parameter Store</a>.
 *
 * @author Sindre Mehus
 */
public class ParameterStore {

    private static final Logger LOG = LoggerFactory.getLogger(ParameterStore.class);
    private static final int LIMIT_DESCRIBE_PARAMS = 50;
    private static final int LIMIT_GET_PARAMS = 10;

    private final AWSSimpleSystemsManagement ssm;

    public ParameterStore(Regions region) {
        ssm = AWSSimpleSystemsManagementClientBuilder.standard().withRegion(region).build();
    }

    /**
     * Returns parameters from the AWS SSM Parameter Store.
     *
     * @param prefixes Only return parameters with the given parameter name prefixes (e.g., "elwinadapter-"). If a parameter
     *                 is present with more than one prefix, the latter prefix takes presendence.
     * @return The parameters as a (key, value) map.
     */
    public Map<String, String> getParameters(String... prefixes) {
        List<String> allParameterNames = getAllParameterNames();

        Map<String, String> result = new LinkedHashMap<>();
        for (String prefix : prefixes) {
            result.putAll(getParametersByName(getParameterNamesByPrefix(allParameterNames, prefix), prefix));
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Found parameters: {}", result.keySet().stream().collect(Collectors.joining(", ")));
        }
        return result;
    }

    private List<String> getAllParameterNames() {
        List<ParameterMetadata> parameterMetadata = new ArrayList<>();
        String nextToken = null;

        do {
            DescribeParametersResult result = ssm.describeParameters(new DescribeParametersRequest().withNextToken(nextToken).withMaxResults(LIMIT_DESCRIBE_PARAMS));
            nextToken = result.getNextToken();
            parameterMetadata.addAll(result.getParameters());
        } while (nextToken != null);

        return parameterMetadata.stream()
                                .map(ParameterMetadata::getName)
                                .collect(Collectors.toList());

    }

    private List<String> getParameterNamesByPrefix(List<String> parameterNames, String prefix) {
        return parameterNames.stream()
                             .filter(name -> name.startsWith(prefix))
                             .collect(Collectors.toList());
    }

    private Map<String, String> getParametersByName(List<String> parameterNames, String prefix) {
        if (parameterNames.isEmpty()) {
            return Collections.emptyMap();
        }

        Map<String, String> result = new LinkedHashMap<>();
        partitionList(parameterNames, LIMIT_GET_PARAMS).forEach(paramNames -> result.putAll(getParameterValues(paramNames, prefix)));

        return result;
    }

    private Map<String, String> getParameterValues(List<String> parameterNames, String prefix) {
        List<Parameter> parameters = ssm.getParameters(new GetParametersRequest().withWithDecryption(true).withNames(parameterNames)).getParameters();
        return parameters.stream().collect(Collectors.toMap(parameter -> parameter.getName().replaceFirst("^" + prefix, ""),
                                                            Parameter::getValue));
    }

    private List<List<String>> partitionList(List<String> list, int size) {
        List<List<String>> partitions = new ArrayList<>();
        for (int i = 0; i < list.size(); i += size) {
            partitions.add(list.subList(i, Math.min(list.size(), i + size)));
        }
        return partitions;
    }
}