diff --git a/pom.xml b/pom.xml
index 39316d7d2..9e4be63fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,8 @@
spring-cloud-aws-integration-test
docs
spring-cloud-aws-samples
+ spring-cloud-aws-cloudmap
+ spring-cloud-starter-aws-cloudmap
diff --git a/spring-cloud-aws-cloudmap/pom.xml b/spring-cloud-aws-cloudmap/pom.xml
new file mode 100644
index 000000000..97f783afc
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+ io.awspring.cloud
+ spring-cloud-aws
+ 2.3.1
+
+
+ spring-cloud-aws-cloudmap
+ Spring Cloud Cloud Map
+ Spring Cloud AWS Cloud Map
+
+
+
+ org.springframework
+ spring-context
+
+
+
+ org.springframework.cloud
+ spring-cloud-commons
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.springframework.cloud
+ spring-cloud-context
+
+
+
+ com.amazonaws
+ aws-java-sdk-servicediscovery
+
+
+
+ com.amazonaws
+ aws-java-sdk-ec2
+
+
+
+ org.springframework
+ spring-web
+
+
+
+ org.springframework
+ spring-webflux
+
+
+
+ io.projectreactor.netty
+ reactor-netty-http
+
+
+
+
+
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/CloudMapUtils.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/CloudMapUtils.java
new file mode 100644
index 000000000..80ba64264
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/CloudMapUtils.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright 2013-2021 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
+ *
+ * https://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.
+ */
+
+// @checkstyle:off
+package org.springframework.cloud.aws.cloudmap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
+import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
+import com.amazonaws.services.ec2.model.Filter;
+import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
+import com.amazonaws.services.servicediscovery.model.CreatePrivateDnsNamespaceRequest;
+import com.amazonaws.services.servicediscovery.model.CreateServiceRequest;
+import com.amazonaws.services.servicediscovery.model.DeregisterInstanceRequest;
+import com.amazonaws.services.servicediscovery.model.DiscoverInstancesRequest;
+import com.amazonaws.services.servicediscovery.model.DnsConfig;
+import com.amazonaws.services.servicediscovery.model.DnsRecord;
+import com.amazonaws.services.servicediscovery.model.DuplicateRequestException;
+import com.amazonaws.services.servicediscovery.model.GetOperationRequest;
+import com.amazonaws.services.servicediscovery.model.HttpInstanceSummary;
+import com.amazonaws.services.servicediscovery.model.InvalidInputException;
+import com.amazonaws.services.servicediscovery.model.ListNamespacesRequest;
+import com.amazonaws.services.servicediscovery.model.ListNamespacesResult;
+import com.amazonaws.services.servicediscovery.model.ListServicesRequest;
+import com.amazonaws.services.servicediscovery.model.ListServicesResult;
+import com.amazonaws.services.servicediscovery.model.NamespaceAlreadyExistsException;
+import com.amazonaws.services.servicediscovery.model.NamespaceSummary;
+import com.amazonaws.services.servicediscovery.model.Operation;
+import com.amazonaws.services.servicediscovery.model.RecordType;
+import com.amazonaws.services.servicediscovery.model.RegisterInstanceRequest;
+import com.amazonaws.services.servicediscovery.model.ResourceLimitExceededException;
+import com.amazonaws.services.servicediscovery.model.ServiceAlreadyExistsException;
+import com.amazonaws.services.servicediscovery.model.ServiceFilter;
+import com.amazonaws.services.servicediscovery.model.ServiceSummary;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.cloud.aws.cloudmap.discovery.CloudMapServiceInstance;
+import org.springframework.cloud.aws.cloudmap.exceptions.CreateNameSpaceException;
+import org.springframework.cloud.aws.cloudmap.exceptions.CreateServiceException;
+import org.springframework.cloud.aws.cloudmap.exceptions.MaxRetryExceededException;
+import org.springframework.cloud.aws.cloudmap.model.discovery.CloudMapDiscoveryProperties;
+import org.springframework.cloud.aws.cloudmap.model.registration.CloudMapRegistryProperties;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Uses Fargate Metadata URL to retrieve IPv4 address and VPC ID to register instances to
+ * cloudmap.
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapUtils {
+
+ /*
+ * Singleton instance
+ */
+ private static CloudMapUtils cloudMapUtils = null;
+
+ public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data";
+ /*
+ * AWS VPC ID
+ */
+ private static final String VPC_ID = "VPC_ID";
+ /*
+ * Local IP address
+ */
+ private static final String AWS_INSTANCE_IPV_4 = "AWS_INSTANCE_IPV4";
+ /*
+ * AWS Region
+ */
+ private static final String REGION = "REGION";
+ /*
+ * Logger
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(CloudMapUtils.class);
+ /*
+ * Namespace status - SUBMITTED
+ */
+ private static final String SUBMITTED = "SUBMITTED";
+ /*
+ * Namespace status - PENDING
+ */
+ private static final String PENDING = "PENDING";
+ /*
+ * Maximum number of polling before returning error
+ */
+ private static final int MAX_POLL = 30;
+ /*
+ * Deployment platform environment variable
+ */
+ public final String DEPLOYMENT_PLATFORM = "DEPLOYMENT_PLATFORM";
+
+ /*
+ * Deployment platform type ECS
+ */
+ public final String EKS = "EKS";
+
+ /*
+ * Metadata URL
+ */
+ public final String ECS_CONTAINER_METADATA_URI_V_4 = "ECS_CONTAINER_METADATA_URI_V4";
+
+ /*
+ * Request attributes - NamespaceID
+ */
+ public final String NAMESPACE_ID = "NAMESPACE_ID";
+
+ /*
+ * Request attributes - ServiceID
+ */
+ public final String SERVICE_ID = "SERVICE_ID";
+
+ /*
+ * Request attributes - ServiceInstanceID
+ */
+ public final String SERVICE_INSTANCE_ID = "SERVICE_INSTANCE_ID";
+
+ /*
+ * Request attributes - IP address
+ */
+ public final String IPV_4_ADDRESS = "IPV4_ADDRESS";
+
+ final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+ private final WebClient WEB_CLIENT = WebClient.create();
+
+ private AmazonEC2 ec2Client;
+
+ /**
+ * Uses metadata URL to fetch all the required details around IP address and VpcID to
+ * register instances to cloudmap service. If Deployment platform is not passed in then we consider it
+ * as classic EC2 or ECS based deployment platform
+ * @return map containing ip address and vpcid
+ */
+ public Map getRegistrationAttributes() {
+ String deploymentPlatform = System.getenv(DEPLOYMENT_PLATFORM);
+ LOGGER.info("Deployment platform passed in {} ", deploymentPlatform);
+ if (StringUtils.hasText(deploymentPlatform) && EKS.equalsIgnoreCase(deploymentPlatform.trim()))
+ return getEksRegistrationAttributes();
+ return getEcsRegistrationAttributes();
+ }
+
+ /**
+ * Get Cloudmap namespaceID based on name
+ * @param serviceDiscovery AWS Service discovery
+ * @param nameSpace cloudmap namespace
+ * @return namespaceID
+ */
+ public String getNameSpaceId(final AWSServiceDiscovery serviceDiscovery, final String nameSpace) {
+ String token;
+ do {
+ ListNamespacesRequest namespacesRequest = new ListNamespacesRequest();
+ ListNamespacesResult namespacesResult = serviceDiscovery.listNamespaces(namespacesRequest);
+ token = namespacesRequest.getNextToken();
+
+ List namespaceSummaries = namespacesResult.getNamespaces();
+ if (namespaceSummaries != null) {
+ Optional namespaceId = namespaceSummaries.stream().filter(n -> n.getName().equals(nameSpace))
+ .map(NamespaceSummary::getId).findFirst();
+ if (namespaceId.isPresent())
+ return namespaceId.get();
+ }
+ else
+ LOGGER.warn("Namespace {} not available", nameSpace);
+ }
+ while (StringUtils.hasText(token));
+
+ return null;
+ }
+
+ /**
+ * List services based on namespace and filter them based on name
+ * @param serviceDiscovery AWS service discovery
+ * @param discoveryProperties discovery properties (includes namespace and service
+ * name)
+ * @return list of cloudmap services
+ */
+ public List listServices(final AWSServiceDiscovery serviceDiscovery,
+ List discoveryProperties) {
+ final List serviceList = new ArrayList<>();
+
+ if (discoveryProperties != null && !discoveryProperties.isEmpty()) {
+ for (CloudMapDiscoveryProperties d : discoveryProperties) {
+ final String serviceName = d.getService();
+ final String nameSpace = d.getNameSpace();
+ String token = null;
+
+ do {
+ // Get namespaceID
+ final String nameSpaceId = getNameSpaceId(serviceDiscovery, nameSpace);
+ if (StringUtils.hasText(nameSpaceId)) {
+ // Filter cloudmap services
+ final ServiceFilter serviceFilter = new ServiceFilter().withName(NAMESPACE_ID)
+ .withCondition("EQ").withValues(nameSpaceId);
+ final ListServicesRequest servicesRequest = new ListServicesRequest()
+ .withFilters(serviceFilter);
+ Optional.ofNullable(token).ifPresent(servicesRequest::withNextToken);
+ final ListServicesResult result = serviceDiscovery.listServices(servicesRequest);
+ token = result.getNextToken();
+
+ if (StringUtils.hasText(serviceName)) {
+ serviceList.addAll(result.getServices().stream()
+ .filter(r -> r.getName().equals(d.getService()))
+ .map(r -> generateServiceId(r.getName(), nameSpace)).collect(Collectors.toList()));
+ if (serviceList.size() == discoveryProperties.size())
+ return serviceList;
+ }
+ else
+ serviceList.addAll(result.getServices().stream()
+ .map(r -> generateServiceId(r.getName(), nameSpace)).collect(Collectors.toList()));
+ }
+ else
+ LOGGER.warn("Namespace is empty");
+ }
+ while (StringUtils.hasText(token));
+ }
+ }
+
+ return serviceList;
+ }
+
+ /**
+ * List cloudmap instances based on service name and namespace
+ * @param serviceDiscovery AWS Service discovery
+ * @param namespace cloudmap namespace
+ * @param serviceName cloudmap service name
+ * @return list of http instances
+ */
+ public List listInstances(final AWSServiceDiscovery serviceDiscovery, final String namespace,
+ String serviceName) {
+ final DiscoverInstancesRequest dRequest = new DiscoverInstancesRequest().withNamespaceName(namespace)
+ .withServiceName(serviceName);
+
+ return serviceDiscovery.discoverInstances(dRequest).getInstances();
+ }
+
+ /**
+ * Get service instance from http instance summary
+ * @param instanceSummary HTTP instance summary - Cloudmap object
+ * @return Service instance - Spring object
+ */
+ public ServiceInstance getServiceInstance(HttpInstanceSummary instanceSummary) {
+ return new CloudMapServiceInstance(instanceSummary);
+ }
+
+ /**
+ * Register with cloudmap, the method takes care of the following: 1. Create
+ * namespace, if not exists 2. Create service, if not exists 3. Register the instance
+ * with the created namespace and service
+ * @param serviceDiscovery AWS Service discovery service
+ * @param properties Cloud map registry properties
+ * @param environment Spring environment
+ * @return map of registration properties
+ */
+ public Map registerInstance(final AWSServiceDiscovery serviceDiscovery,
+ final CloudMapRegistryProperties properties, final Environment environment) {
+ if (properties != null && StringUtils.hasText(properties.getNameSpace())
+ && StringUtils.hasText(properties.getService())) {
+
+ String nameSpace = properties.getNameSpace();
+ if (!StringUtils.hasText(nameSpace))
+ nameSpace = "default-namespace";
+
+ String service = properties.getService();
+ if (!StringUtils.hasText(service))
+ service = environment.getProperty("spring.application.name");
+
+ if (!StringUtils.hasText(service))
+ service = "default-service";
+
+ final String serviceInstanceId = UUID.randomUUID().toString();
+
+ LOGGER.info("Registration details namespace {} - service {} - serviceInstance {}", nameSpace, service,
+ serviceInstanceId);
+ Map registrationDetails = getRegistrationAttributes();
+ String nameSpaceId = getNameSpaceId(serviceDiscovery, properties.getNameSpace());
+ try {
+ // Create namespace if not exists
+ if (!StringUtils.hasText(nameSpaceId)) {
+ LOGGER.debug("Namespace " + nameSpace + "not available so creating");
+ nameSpaceId = createNameSpace(serviceDiscovery, properties, registrationDetails.get(VPC_ID));
+ }
+
+ // Create service if not exists
+ String serviceId = getServiceId(serviceDiscovery, nameSpaceId, service);
+ if (!StringUtils.hasText(serviceId)) {
+ LOGGER.debug("Service " + service + " doesnt exist so creating new one");
+ serviceId = createService(serviceDiscovery, nameSpaceId, service);
+ }
+
+ Map attributes = new HashMap<>();
+ attributes.put(AWS_INSTANCE_IPV_4, registrationDetails.get(IPV_4_ADDRESS));
+ attributes.put(REGION, System.getenv("AWS_REGION"));
+ attributes.put(NAMESPACE_ID, nameSpaceId);
+ attributes.put(SERVICE_ID, serviceId);
+ attributes.put(SERVICE_INSTANCE_ID, serviceInstanceId);
+
+ // Register instance
+ final String operationId = serviceDiscovery.registerInstance(new RegisterInstanceRequest()
+ .withInstanceId(serviceInstanceId).withServiceId(serviceId).withAttributes(attributes))
+ .getOperationId();
+ LOGGER.debug("Register instance initiated, polling for completion {}", operationId);
+
+ // Poll for completion
+ pollForCompletion(serviceDiscovery, operationId);
+
+ return attributes;
+ }
+ catch (InvalidInputException e) {
+ LOGGER.error("Invalid input passed into the service {} - {}", nameSpaceId, e.getMessage(), e);
+ }
+ catch (CreateNameSpaceException e) {
+ LOGGER.error("Error while creating namespace {} - {}", nameSpace, e.getMessage());
+ }
+ catch (InterruptedException e) {
+ LOGGER.error("Error while polling for status update {} with error {}", nameSpace, e.getMessage());
+ }
+ catch (CreateServiceException e) {
+ LOGGER.error("Error while creating service {} with {} - {}", service, nameSpace, e.getMessage());
+ }
+ catch (MaxRetryExceededException e) {
+ LOGGER.error("Maximum number of retry exceeded for registering instance with {} for {}", nameSpace,
+ service, e);
+ }
+ }
+ else {
+ LOGGER.info("Service registration skipped");
+ }
+
+ return null;
+ }
+
+ /**
+ * Create Cloudmap namespace.
+ * @param serviceDiscovery AWS Service discovery
+ * @param properties cloudmap properties
+ * @param vpcId VPC ID
+ * @return NamespaceID
+ * @throws CreateNameSpaceException thrown in case of runtime exception
+ */
+ private String createNameSpace(AWSServiceDiscovery serviceDiscovery, CloudMapRegistryProperties properties,
+ String vpcId) throws CreateNameSpaceException {
+ final String nameSpace = properties.getNameSpace();
+ try {
+ // Create namespace
+ final String operationId = serviceDiscovery.createPrivateDnsNamespace(new CreatePrivateDnsNamespaceRequest()
+ .withName(nameSpace).withVpc(vpcId).withDescription(properties.getDescription())).getOperationId();
+ LOGGER.info("Creating namespace {} with operationId {}", nameSpace, operationId);
+
+ // Wait till completion
+ pollForCompletion(serviceDiscovery, operationId);
+
+ return getNameSpaceId(serviceDiscovery, nameSpace);
+ }
+ catch (NamespaceAlreadyExistsException e) {
+ LOGGER.warn("Namespace {} already exists", nameSpace);
+ return getNameSpaceId(serviceDiscovery, nameSpace);
+ }
+ catch (InvalidInputException | ResourceLimitExceededException | DuplicateRequestException e) {
+ LOGGER.error("Error while registering with cloudmap {} with error {}", nameSpace, e.getMessage(), e);
+ throw new CreateNameSpaceException(e);
+ }
+ catch (InterruptedException e) {
+ LOGGER.error("Error while polling for status update {} with error {}", nameSpace, e.getMessage(), e);
+ throw new CreateNameSpaceException(e);
+ }
+ catch (MaxRetryExceededException e) {
+ LOGGER.error("Maximum number of retry exceeded for namespace {}", nameSpace, e);
+ throw new CreateNameSpaceException(e);
+ }
+ }
+
+ /**
+ * Create service.
+ * @param serviceDiscovery AWS Service Discovery
+ * @param nameSpaceId CloudMap Namespace ID
+ * @param service Service name
+ * @return Service ID
+ * @throws CreateServiceException thrown in case of runtime exception
+ */
+ private String createService(AWSServiceDiscovery serviceDiscovery, String nameSpaceId, String service)
+ throws CreateServiceException {
+ try {
+ CreateServiceRequest serviceRequest = new CreateServiceRequest().withName(service)
+ .withNamespaceId(nameSpaceId).withDnsConfig(
+ new DnsConfig().withDnsRecords(new DnsRecord().withType(RecordType.A).withTTL(300L)));
+
+ final String serviceId = serviceDiscovery.createService(serviceRequest).getService().getId();
+ LOGGER.info("Service ID create {} for {} with namespace {}", serviceId, service, nameSpaceId);
+ return serviceId;
+ }
+ catch (ServiceAlreadyExistsException e) {
+ LOGGER.warn("Service {} already exists", service);
+ return getServiceId(serviceDiscovery, service, nameSpaceId);
+ }
+ catch (InvalidInputException | ResourceLimitExceededException e) {
+ LOGGER.error("Error while creating service {} with namespace {}", service, nameSpaceId);
+ throw new CreateServiceException(e);
+ }
+ }
+
+ public String generateServiceId(final String namespace, final String serviceName) {
+ return String.format("%s@%s", namespace, serviceName);
+ }
+
+ /**
+ * Automatically deregister the instance when the container is stopped.
+ * @param serviceDiscovery AWS Service Discovery Service
+ * @param attributeMap Service discovery attributes
+ */
+ public void deregisterInstance(final AWSServiceDiscovery serviceDiscovery, final Map attributeMap) {
+ try {
+ final String serviceInstanceId = attributeMap.get(SERVICE_INSTANCE_ID);
+ final String serviceId = attributeMap.get(SERVICE_ID);
+ LOGGER.info("Initiating de-registration process {} - {}", serviceInstanceId, serviceId);
+
+ // Deregister instance
+ String operationId = serviceDiscovery
+ .deregisterInstance(
+ new DeregisterInstanceRequest().withInstanceId(serviceInstanceId).withServiceId(serviceId))
+ .getOperationId();
+
+ // Wait till completion
+ pollForCompletion(serviceDiscovery, operationId);
+ }
+ catch (InterruptedException e) {
+ LOGGER.error("Error while polling for status while de-registering instance {}", e.getMessage(), e);
+ }
+ catch (MaxRetryExceededException e) {
+ LOGGER.error("Maximum number of retry exceeded {}", e.getMessage(), e);
+ }
+ }
+
+ public static CloudMapUtils getInstance() {
+ if (cloudMapUtils == null){
+ cloudMapUtils = new CloudMapUtils();
+ }
+ return cloudMapUtils;
+ }
+
+ /**
+ * Get service ID based on service name and namespace ID.
+ * @param serviceDiscovery AWS Service discovery
+ * @param nameSpaceId Namespace ID
+ * @param serviceName name of the cloudmap service
+ * @return Cloudmap service ID
+ */
+ private String getServiceId(AWSServiceDiscovery serviceDiscovery, String nameSpaceId, String serviceName) {
+ ServiceFilter filter = new ServiceFilter();
+ filter.setName(NAMESPACE_ID);
+ filter.setValues(Collections.singletonList(nameSpaceId));
+ Optional serviceSummary = serviceDiscovery
+ .listServices(new ListServicesRequest().withFilters(filter)).getServices().stream()
+ .filter(s -> serviceName.equals(s.getName())).findFirst();
+ return serviceSummary.map(ServiceSummary::getId).orElse(null);
+ }
+
+ /**
+ * Poll for completion.
+ * @param serviceDiscovery AWS Service discovery
+ * @param operationId cloudmap operationID
+ * @throws InterruptedException thrown in case of thread.sleep() exception
+ * @throws MaxRetryExceededException thrown if maximum polling duration has exceeded
+ */
+ private void pollForCompletion(AWSServiceDiscovery serviceDiscovery, String operationId)
+ throws InterruptedException, MaxRetryExceededException {
+ Operation operation = serviceDiscovery.getOperation(new GetOperationRequest().withOperationId(operationId))
+ .getOperation();
+ int counter = 0;
+ LOGGER.info("Operation ID {} will be polled", operationId);
+ while ((SUBMITTED.equalsIgnoreCase(operation.getStatus()) || PENDING.equalsIgnoreCase(operation.getStatus()))
+ && counter < MAX_POLL) {
+ operation = serviceDiscovery.getOperation(new GetOperationRequest().withOperationId(operationId))
+ .getOperation();
+ Thread.sleep(2000);
+ counter++;
+ }
+
+ if (counter > MAX_POLL) {
+ throw new MaxRetryExceededException("Maximum of retry exceeded for " + operationId);
+ }
+ }
+
+ /**
+ * Get CloudMap attributes for EKS platform
+ * @return map of cloud map attributes with Ipaddress and vpcid
+ */
+ private Map getEksRegistrationAttributes() {
+ try {
+ String ipAddress = getUrlResponse(String.format("%s/local-ipv4", EC2_METADATA_URL));
+ final String macId = getUrlResponse(String.format("%s/network/interfaces/macs", EC2_METADATA_URL));
+ if (StringUtils.hasText(macId) && macId.contains("/")) {
+ final String macAddress = macId.split("/")[0];
+ final String vpcUrl = String.format("%s/network/interfaces/macs/%s/vpc-id", EC2_METADATA_URL, macAddress);
+ final String vpcId = getUrlResponse(vpcUrl);
+ LOGGER.info("Meta data details IP Address {}, macAddress {} - VPCId {}", ipAddress, macAddress, vpcId);
+ return getCloudMapAttributes(ipAddress, vpcId);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.error("Error while getting registration details {}", e.getMessage(), e);
+ }
+ return getCloudMapAttributes(generateIP(), "vpc-13c33d6e");
+ }
+
+ /**
+ * Get CloudMap attributes for ECS platform
+ * @return map of cloud map attributes with Ipaddress and vpcid
+ */
+ private Map getEcsRegistrationAttributes() {
+ try {
+ String metaDataUrl = System.getenv(ECS_CONTAINER_METADATA_URI_V_4);
+ if (!StringUtils.hasText(metaDataUrl))
+ metaDataUrl = EC2_METADATA_URL;
+ final String responseBody = getUrlResponse(metaDataUrl + "/task");
+ JsonNode root = JSON_MAPPER.readTree(responseBody);
+ JsonNode jsonNode = root.get("Containers").get(0).get("Networks").get(0);
+ final String ipv4Address = jsonNode.get("IPv4Addresses").get(0).asText();
+ final String cidrBlock = jsonNode.get("IPv4SubnetCIDRBlock").asText();
+ final String vpcId = getEc2Client()
+ .describeSubnets(new DescribeSubnetsRequest()
+ .withFilters(new Filter().withName("cidr-block").withValues(cidrBlock)))
+ .getSubnets().get(0).getVpcId();
+ LOGGER.info("IPv4Address {} - CIDR Block {} - VPC ID {}", ipv4Address, cidrBlock, vpcId);
+
+// String ipv4Address = "10.20.10.111";
+// String vpcId = "vpc-0294915e2f02ac742";
+ return getCloudMapAttributes(ipv4Address, vpcId);
+ }
+ catch (Exception e) {
+ LOGGER.error("Error while fetching network details - {}", e.getMessage(), e);
+ return getCloudMapAttributes(generateIP(), "vpc-13c33d6e");
+ }
+ }
+
+ /**
+ * Helper method to fetch contents of URL as string
+ * @param url URL to fetch from
+ * @return response as string
+ */
+ private String getUrlResponse(String url) {
+ return WEB_CLIENT.get().uri(url).retrieve().bodyToMono(String.class).block();
+ }
+
+ /**
+ * Returns hash map of cloudmap attributes
+ * @param ipv4Address IP Address of the instance
+ * @param vpcId VPC ID in which the instance is hosted
+ * @return hash map of cloudmap attributes
+ */
+ private Map getCloudMapAttributes(String ipv4Address, String vpcId) {
+ Map attributes = new HashMap<>();
+ attributes.put(IPV_4_ADDRESS, ipv4Address);
+ attributes.put(VPC_ID, vpcId);
+ return attributes;
+ }
+
+ // TODO: Will be removed after testing
+ private String generateIP() {
+ Random r = new Random();
+ return r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256);
+ }
+
+ /**
+ * Get Ec2 client
+ * @return ec2 client object
+ */
+ AmazonEC2 getEc2Client() {
+ if (ec2Client == null) {
+ ec2Client = AmazonEC2ClientBuilder.standard().withRegion(System.getenv("AWS_REGION"))
+ .withCredentials(new DefaultAWSCredentialsProviderChain()).build();
+ }
+ return ec2Client;
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapDiscoveryClient.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapDiscoveryClient.java
new file mode 100644
index 000000000..c923a22ce
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapDiscoveryClient.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.discovery;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
+
+import org.springframework.cloud.aws.cloudmap.CloudMapUtils;
+import org.springframework.cloud.aws.cloudmap.model.CloudMapProperties;
+import org.springframework.cloud.aws.cloudmap.model.discovery.CloudMapDiscoveryProperties;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+
+// @checkstyle: off
+public class CloudMapDiscoveryClient implements DiscoveryClient {
+
+ /**
+ * Description of the service.
+ */
+ public static final String DESCRIPTION = "AWS CloudMap Discovery Client";
+
+ private static final CloudMapUtils UTILS = CloudMapUtils.getInstance();
+
+ private final AWSServiceDiscovery serviceDiscovery;
+
+ private final CloudMapProperties properties;
+
+ public CloudMapDiscoveryClient(AWSServiceDiscovery serviceDiscovery, CloudMapProperties properties) {
+ this.serviceDiscovery = serviceDiscovery;
+ this.properties = properties;
+ }
+
+ @Override
+ public int getOrder() {
+ return DiscoveryClient.super.getOrder();
+ }
+
+ @Override
+ public String description() {
+ return DESCRIPTION;
+ }
+
+ @Override
+ public List getServices() {
+ final List discoveryProperties = properties.getDiscovery().getDiscoveryList();
+ if (discoveryProperties != null && !discoveryProperties.isEmpty()) {
+ return UTILS.listServices(serviceDiscovery, discoveryProperties);
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getInstances(String serviceId) {
+ // Service ID maintained as _
+ String[] split = serviceId.split("@");
+ if (split.length == 2) {
+ return UTILS.listInstances(serviceDiscovery, split[0], split[1]).stream().map(UTILS::getServiceInstance)
+ .collect(Collectors.toList());
+ }
+
+ return Collections.emptyList();
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapServiceInstance.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapServiceInstance.java
new file mode 100644
index 000000000..e6b66ddba
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/discovery/CloudMapServiceInstance.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.
+ */
+
+// @checkstyle:off
+package org.springframework.cloud.aws.cloudmap.discovery;
+
+import com.amazonaws.services.servicediscovery.model.HttpInstanceSummary;
+import org.springframework.cloud.aws.cloudmap.CloudMapUtils;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.util.StringUtils;
+
+import java.net.URI;
+import java.util.Map;
+
+public class CloudMapServiceInstance implements ServiceInstance {
+
+ private final CloudMapUtils UTILS = CloudMapUtils.getInstance();
+
+ HttpInstanceSummary instanceSummary;
+
+ public CloudMapServiceInstance(HttpInstanceSummary httpInstanceSummary) {
+ this.instanceSummary = httpInstanceSummary;
+ }
+
+ @Override
+ public String getInstanceId() {
+ return instanceSummary.getInstanceId();
+ }
+
+ @Override
+ public String getScheme() {
+ return getUri().getScheme();
+ }
+
+ @Override
+ public String getServiceId() {
+ return UTILS.generateServiceId(instanceSummary.getNamespaceName(), instanceSummary.getServiceName());
+ }
+
+ @Override
+ public String getHost() {
+ return instanceSummary.getAttributes().get("AWS_INSTANCE_IPV4");
+ }
+
+ @Override
+ public int getPort() {
+ String port = instanceSummary.getAttributes().get("AWS_INSTANCE_PORT");
+ return StringUtils.hasText(port) ? Integer.parseInt(port) : 0;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public URI getUri() {
+ return URI.create(String.format("http://%s:%s", this.getHost(), this.getPort()));
+ }
+
+ @Override
+ public Map getMetadata() {
+ return instanceSummary.getAttributes();
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateNameSpaceException.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateNameSpaceException.java
new file mode 100644
index 000000000..22385d38a
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateNameSpaceException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.exceptions;
+
+// Thrown in case of namespace exception.
+public class CreateNameSpaceException extends RuntimeException {
+
+ public CreateNameSpaceException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateServiceException.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateServiceException.java
new file mode 100644
index 000000000..6e57d6285
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateServiceException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.exceptions;
+
+// Throw in case of cloudmap service exception.
+public class CreateServiceException extends RuntimeException {
+
+ public CreateServiceException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/MaxRetryExceededException.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/MaxRetryExceededException.java
new file mode 100644
index 000000000..2eeaaf9a2
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/MaxRetryExceededException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.exceptions;
+
+// Thrown in case maximum retry for polling has exceeded.
+public class MaxRetryExceededException extends RuntimeException {
+
+ public MaxRetryExceededException(String message) {
+ super(message);
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/CloudMapProperties.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/CloudMapProperties.java
new file mode 100644
index 000000000..54b698d7d
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/CloudMapProperties.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.model;
+
+import java.net.URI;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.aws.cloudmap.model.discovery.CloudMapDiscovery;
+import org.springframework.cloud.aws.cloudmap.model.registration.CloudMapRegistryProperties;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.core.env.Environment;
+
+/**
+ * POJO to capture all cloudmap integration parameters (both registry and discovery).
+ *
+ * @author Hari Ohm Prasath Rajagopal
+ * @since 2.3.2
+ */
+@ConfigurationProperties(CloudMapProperties.CONFIG_PREFIX)
+public class CloudMapProperties implements EnvironmentAware {
+
+ /**
+ * Default cloudmap prefix.
+ */
+ public static final String CONFIG_PREFIX = "aws.cloudmap";
+
+ private CloudMapRegistryProperties registry;
+
+ private CloudMapDiscovery discovery;
+
+ private String region;
+
+ /**
+ * Overrides the default endpoint.
+ */
+ private URI endpoint;
+
+ private boolean enabled;
+
+ private String annotationBasePackage;
+
+ private Environment environment;
+
+ public String getAnnotationBasePackage() {
+ return this.annotationBasePackage;
+ }
+
+ public void setAnnotationBasePackage(String annotationBasePackage) {
+ this.annotationBasePackage = annotationBasePackage;
+ }
+
+ public String getRegion() {
+ return this.region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public CloudMapRegistryProperties getRegistry() {
+ return this.registry;
+ }
+
+ public void setRegistry(CloudMapRegistryProperties registry) {
+ this.registry = registry;
+ }
+
+ public CloudMapDiscovery getDiscovery() {
+ return this.discovery;
+ }
+
+ public void setDiscovery(CloudMapDiscovery discovery) {
+ this.discovery = discovery;
+ }
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ public URI getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(URI endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ @Override
+ public String toString() {
+ return "AwsCloudMapProperties{" + "registry=" + registry + ", discovery=" + discovery + ", region='" + region
+ + '\'' + ", enabled=" + enabled + ", annotationBasePackage='" + annotationBasePackage + '\'' + '}';
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscovery.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscovery.java
new file mode 100644
index 000000000..6bb86a446
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscovery.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.model.discovery;
+
+import java.util.List;
+
+/**
+ * Pojo class to capture all the discovery parameters.
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapDiscovery {
+
+ // Default to fail if discovery has failed
+ private boolean failFast = true;
+
+ private List discoveryList;
+
+ public boolean isFailFast() {
+ return this.failFast;
+ }
+
+ public void setFailFast(boolean failFast) {
+ this.failFast = failFast;
+ }
+
+ public List getDiscoveryList() {
+ return this.discoveryList;
+ }
+
+ public void setDiscoveryList(List discoveryList) {
+ this.discoveryList = discoveryList;
+ }
+
+ @Override
+ public String toString() {
+ return "AwsCloudMapDiscovery{" + "failFast=" + failFast + ", discoveryList=" + discoveryList.toString() + '}';
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscoveryProperties.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscoveryProperties.java
new file mode 100644
index 000000000..94eb57f0d
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/discovery/CloudMapDiscoveryProperties.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.model.discovery;
+
+import java.util.Map;
+
+/**
+ * POJO class to capture cloudmap discovery attributes.
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapDiscoveryProperties {
+
+ private String nameSpace;
+
+ private String service;
+
+ private Map filterAttributes;
+
+ public String getNameSpace() {
+ return this.nameSpace;
+ }
+
+ public void setNameSpace(String nameSpace) {
+ this.nameSpace = nameSpace;
+ }
+
+ public String getService() {
+ return this.service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public Map getFilterAttributes() {
+ return this.filterAttributes;
+ }
+
+ public void setFilterAttributes(Map filterAttributes) {
+ this.filterAttributes = filterAttributes;
+ }
+
+ @Override
+ public String toString() {
+ String data = "AwsCloudMapDiscoveryProperties{" + "serviceNameSpace=" + nameSpace + ", service=" + service;
+ if (filterAttributes != null) {
+ data += filterAttributes.keySet().stream().map(f -> "key = " + f + ":" + filterAttributes.get(f))
+ .reduce((a, b) -> a + "," + b).get();
+ }
+ data += "}";
+ return data;
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/CloudMapRegistryProperties.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/CloudMapRegistryProperties.java
new file mode 100644
index 000000000..151db7de2
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/CloudMapRegistryProperties.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.model.registration;
+
+/**
+ * POJO class to capture cloudmap registration parameters.
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapRegistryProperties {
+
+ private String nameSpace;
+
+ private String service;
+
+ private String description;
+
+ public String getNameSpace() {
+ return nameSpace;
+ }
+
+ public void setNameSpace(String nameSpace) {
+ this.nameSpace = nameSpace;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/ServiceRegistration.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/ServiceRegistration.java
new file mode 100644
index 000000000..1e4a0ccd1
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/model/registration/ServiceRegistration.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.cloudmap.model.registration;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.UUID;
+
+import org.springframework.cloud.aws.cloudmap.CloudMapUtils;
+import org.springframework.cloud.client.serviceregistry.Registration;
+
+public class ServiceRegistration implements Registration {
+
+ private final CloudMapRegistryProperties properties;
+
+ private final Map registrationDetails;
+
+ private final CloudMapUtils UTILS = CloudMapUtils.getInstance();
+
+ public ServiceRegistration(CloudMapRegistryProperties properties) {
+ registrationDetails = UTILS.getRegistrationAttributes();
+ this.properties = properties;
+ }
+
+ @Override
+ public String getInstanceId() {
+ return UUID.randomUUID().toString();
+ }
+
+ @Override
+ public String getScheme() {
+ return Registration.super.getScheme();
+ }
+
+ @Override
+ public String getServiceId() {
+ return UTILS.generateServiceId(properties.getNameSpace(), properties.getService());
+ }
+
+ @Override
+ public String getHost() {
+ return registrationDetails.get(UTILS.IPV_4_ADDRESS);
+ }
+
+ @Override
+ public int getPort() {
+ return 0;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
+ @Override
+ public Map getMetadata() {
+ return registrationDetails;
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/registration/CloudMapAutoRegistration.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/registration/CloudMapAutoRegistration.java
new file mode 100644
index 000000000..6b5470225
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/registration/CloudMapAutoRegistration.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013-2022 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
+ *
+ * https://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.
+ */
+
+// @checkstyle:off
+package org.springframework.cloud.aws.cloudmap.registration;
+
+import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
+import org.springframework.cloud.aws.cloudmap.CloudMapUtils;
+import org.springframework.cloud.aws.cloudmap.model.registration.CloudMapRegistryProperties;
+import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.SmartLifecycle;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.SmartApplicationListener;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.Environment;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CloudMapAutoRegistration
+ implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener, EnvironmentAware {
+
+ private final AWSServiceDiscovery serviceDiscovery;
+
+ private final CloudMapRegistryProperties properties;
+
+ private final ApplicationContext context;
+
+ private final AtomicBoolean running = new AtomicBoolean(false);
+
+ private final CloudMapUtils UTILS = CloudMapUtils.getInstance();
+
+ private Environment environment;
+
+ private Map attributesMap;
+
+ public CloudMapAutoRegistration(ApplicationContext context, AWSServiceDiscovery serviceDiscovery,
+ CloudMapRegistryProperties properties) {
+ this.context = context;
+ this.serviceDiscovery = serviceDiscovery;
+ this.properties = properties;
+ }
+
+ @Override
+ public boolean isAutoStartup() {
+ return true;
+ }
+
+ @Override
+ public void stop(Runnable callback) {
+ stop();
+ callback.run();
+ }
+
+ @Override
+ public int getPhase() {
+ return 0;
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationEvent event) {
+ if (event instanceof ContextClosedEvent) {
+ onApplicationEvent((ContextClosedEvent) event);
+ }
+ }
+
+ @Override
+ public void start() {
+ if (!this.running.get()) {
+ final Map attributesMap = UTILS.registerInstance(serviceDiscovery, properties, environment);
+ if (attributesMap != null && attributesMap.containsKey(UTILS.SERVICE_INSTANCE_ID)) {
+ this.attributesMap = attributesMap;
+ this.context.publishEvent(new InstanceRegisteredEvent<>(this, attributesMap));
+ this.running.set(true);
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (this.running.get() && attributesMap != null && attributesMap.containsKey(UTILS.SERVICE_INSTANCE_ID)) {
+ UTILS.deregisterInstance(serviceDiscovery, attributesMap);
+ this.running.set(false);
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return this.running.get();
+ }
+
+ @Override
+ public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
+ return true;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ public void onApplicationEvent(ContextClosedEvent event) {
+ stop();
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapRegisterServiceTest.java b/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapRegisterServiceTest.java
new file mode 100644
index 000000000..0585a2fff
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapRegisterServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2013-2021 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
+ *
+ * https://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.cloudmap;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
+import com.amazonaws.services.servicediscovery.model.CreatePrivateDnsNamespaceRequest;
+import com.amazonaws.services.servicediscovery.model.CreatePrivateDnsNamespaceResult;
+import com.amazonaws.services.servicediscovery.model.CreateServiceRequest;
+import com.amazonaws.services.servicediscovery.model.CreateServiceResult;
+import com.amazonaws.services.servicediscovery.model.DeregisterInstanceRequest;
+import com.amazonaws.services.servicediscovery.model.DeregisterInstanceResult;
+import com.amazonaws.services.servicediscovery.model.GetOperationRequest;
+import com.amazonaws.services.servicediscovery.model.GetOperationResult;
+import com.amazonaws.services.servicediscovery.model.ListNamespacesRequest;
+import com.amazonaws.services.servicediscovery.model.ListNamespacesResult;
+import com.amazonaws.services.servicediscovery.model.ListServicesRequest;
+import com.amazonaws.services.servicediscovery.model.ListServicesResult;
+import com.amazonaws.services.servicediscovery.model.NamespaceSummary;
+import com.amazonaws.services.servicediscovery.model.Operation;
+import com.amazonaws.services.servicediscovery.model.RegisterInstanceRequest;
+import com.amazonaws.services.servicediscovery.model.RegisterInstanceResult;
+import com.amazonaws.services.servicediscovery.model.Service;
+import com.amazonaws.services.servicediscovery.model.ServiceSummary;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.cloud.aws.cloudmap.model.registration.CloudMapRegistryProperties;
+import org.springframework.cloud.aws.cloudmap.model.registration.ServiceRegistration;
+import org.springframework.core.env.Environment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit testcase for {@link ServiceRegistration}
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapRegisterServiceTest {
+
+ private final AWSServiceDiscovery serviceDiscovery = mock(AWSServiceDiscovery.class);
+
+ private final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance();
+
+ private final Environment environment = mock(Environment.class);
+
+ @Test
+ public void cloudMapRegisterInstancesNameSpaceAndServiceExists() {
+ final ListNamespacesResult result = getListNamespacesResult();
+ final ListServicesResult listServicesResult = getListServicesResult();
+ final RegisterInstanceResult registerInstanceRequest = getRegisterInstanceResult();
+ final GetOperationResult operationResult = getOperationResult();
+
+ when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn(result);
+ when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResult);
+ when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class))))
+ .thenReturn(registerInstanceRequest);
+ when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResult);
+
+ when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResult);
+ assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty();
+ }
+
+ @Test
+ public void cloudMapRegisterInstanceWithNoNameSpace() {
+ final ListNamespacesResult namespacesResult = getListNamespacesResult();
+ final ListServicesResult listServicesResult = getListServicesResult();
+ final RegisterInstanceResult registerInstanceRequest = getRegisterInstanceResult();
+ final GetOperationResult operationResult = getOperationResult();
+ final CreatePrivateDnsNamespaceResult nameSpaceResult = getCreatePrivateDnsNamespaceResult();
+
+ when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class)))
+ .thenReturn(new ListNamespacesResult().withNamespaces(Collections.emptyList()), namespacesResult);
+ when(serviceDiscovery.createPrivateDnsNamespace(any(CreatePrivateDnsNamespaceRequest.class)))
+ .thenReturn(nameSpaceResult);
+ when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResult);
+
+ when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResult);
+ when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class))))
+ .thenReturn(registerInstanceRequest);
+ when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResult);
+
+ when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResult);
+ assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty();
+ }
+
+ @Test
+ public void cloudMapRegisterInstancesWithNoService() {
+ final ListNamespacesResult result = getListNamespacesResult();
+ final ListServicesResult listServicesResult = getListServicesResult();
+ final RegisterInstanceResult registerInstanceRequest = getRegisterInstanceResult();
+ final GetOperationResult operationResult = getOperationResult();
+ final CreateServiceResult createServiceResult = new CreateServiceResult();
+ createServiceResult
+ .setService(new Service().withName(CloudMapTestUtils.SERVICE).withId(CloudMapTestUtils.SERVICE));
+
+ when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn(result);
+
+ when(serviceDiscovery.listServices(any(ListServicesRequest.class)))
+ .thenReturn(new ListServicesResult().withServices(Collections.emptyList()), listServicesResult);
+ when(serviceDiscovery.createService(any(CreateServiceRequest.class))).thenReturn(createServiceResult);
+
+ when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class))))
+ .thenReturn(registerInstanceRequest);
+ when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResult);
+
+ assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty();
+ }
+
+ @Test
+ public void deRegisterInstances() {
+ try {
+ final Map attributeMap = new HashMap<>();
+ attributeMap.put("SERVICE_INSTANCE_ID", "SERVICE_INSTANCE_ID");
+ attributeMap.put("SERVICE_ID", "SERVICE_ID");
+
+ DeregisterInstanceResult result = new DeregisterInstanceResult();
+ result.setOperationId(CloudMapTestUtils.OPERATION_ID);
+ when(serviceDiscovery.deregisterInstance(any(DeregisterInstanceRequest.class))).thenReturn(result);
+
+ final GetOperationResult waitingResult = getOperationResult();
+ waitingResult.setOperation(new Operation().withStatus("PENDING"));
+
+ final GetOperationResult successResult = getOperationResult();
+ when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(waitingResult,
+ successResult);
+ cloudMapUtils.deregisterInstance(serviceDiscovery, attributeMap);
+ }
+ catch (Exception e) {
+ Assertions.fail();
+ }
+ }
+
+ private CreatePrivateDnsNamespaceResult getCreatePrivateDnsNamespaceResult() {
+ CreatePrivateDnsNamespaceResult createPrivateDnsNamespaceResult = new CreatePrivateDnsNamespaceResult();
+ createPrivateDnsNamespaceResult.setOperationId(CloudMapTestUtils.OPERATION_ID);
+ return createPrivateDnsNamespaceResult;
+ }
+
+ private GetOperationResult getOperationResult() {
+ GetOperationResult operationResult = new GetOperationResult();
+ operationResult.setOperation(new Operation().withStatus("SUCCESS"));
+ return operationResult;
+ }
+
+ private RegisterInstanceResult getRegisterInstanceResult() {
+ RegisterInstanceResult registerInstanceRequest = new RegisterInstanceResult();
+ registerInstanceRequest.setOperationId(CloudMapTestUtils.OPERATION_ID);
+ return registerInstanceRequest;
+ }
+
+ private ListServicesResult getListServicesResult() {
+ ServiceSummary serviceSummary = new ServiceSummary();
+ serviceSummary.setId(CloudMapTestUtils.SERVICE);
+ serviceSummary.setName(CloudMapTestUtils.SERVICE);
+ ListServicesResult listServicesResult = new ListServicesResult();
+ listServicesResult.setServices(Collections.singletonList(serviceSummary));
+ return listServicesResult;
+ }
+
+ private ListNamespacesResult getListNamespacesResult() {
+ NamespaceSummary summary = new NamespaceSummary();
+ summary.setId(CloudMapTestUtils.NAMESPACE);
+ summary.setName(CloudMapTestUtils.NAMESPACE);
+ ListNamespacesResult result = new ListNamespacesResult();
+ result.setNamespaces(Collections.singleton(summary));
+ return result;
+ }
+
+ private Map getAttributesMap() {
+ Map attributeMap = new HashMap<>();
+ attributeMap.put(cloudMapUtils.IPV_4_ADDRESS, "10.1.1.23");
+ return attributeMap;
+ }
+
+ private CloudMapRegistryProperties getProperties() {
+ CloudMapRegistryProperties properties = new CloudMapRegistryProperties();
+ properties.setService(CloudMapTestUtils.SERVICE);
+ properties.setNameSpace(CloudMapTestUtils.NAMESPACE);
+ properties.setDescription("DESCRIPTION");
+ return properties;
+ }
+
+}
diff --git a/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapTestUtils.java b/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapTestUtils.java
new file mode 100644
index 000000000..e5165c5f1
--- /dev/null
+++ b/spring-cloud-aws-cloudmap/src/test/java/org/springframework/cloud/aws/cloudmap/CloudMapTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013-2021 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
+ *
+ * https://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.cloudmap;
+
+/**
+ * Unit testcase for {@link CloudMapUtils}
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+public class CloudMapTestUtils {
+
+ public static final String NAMESPACE = "NAMESPACE";
+
+ public static final String SERVICE = "SERVICE";
+
+ public static final String OPERATION_ID = "OPERATION_ID";
+
+}
diff --git a/spring-cloud-aws-samples/pom.xml b/spring-cloud-aws-samples/pom.xml
index 69b1b76b3..0f2727595 100644
--- a/spring-cloud-aws-samples/pom.xml
+++ b/spring-cloud-aws-samples/pom.xml
@@ -16,6 +16,7 @@
spring-cloud-aws-sns-sample
spring-cloud-aws-parameter-store-sample
spring-cloud-aws-secrets-manager-sample
+ spring-cloud-aws-cloud-map-sample
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/Dockerfile b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/Dockerfile
new file mode 100644
index 000000000..105b1cfcf
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/Dockerfile
@@ -0,0 +1,5 @@
+FROM amazoncorretto:11
+ARG JAR_FILE=target/spring-cloud-aws-cloud-map-sample-2.3.1.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
+EXPOSE 8080
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/pom.xml b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/pom.xml
new file mode 100644
index 000000000..a1daa7a90
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ spring-cloud-aws-samples
+ io.awspring.cloud
+ 2.3.1
+
+ 4.0.0
+
+ spring-cloud-aws-cloud-map-sample
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+ io.awspring.cloud
+ spring-cloud-starter-aws-cloudmap
+ 2.3.1
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ io.awspring.cloud.cloudmap.sample.SpringCloudAwsCloudMapSample
+
+
+
+
+
+
+
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/run.sh b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/run.sh
new file mode 100755
index 000000000..01328bab2
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/run.sh
@@ -0,0 +1,4 @@
+mvn clean install -DskipTests=true
+docker build -t aws-samples-cloudmap .
+docker tag aws-samples-cloudmap:latest 775492342640.dkr.ecr.us-east-1.amazonaws.com/aws-samples-cloudmap:latest
+docker push 775492342640.dkr.ecr.us-east-1.amazonaws.com/aws-samples-cloudmap:latest
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/java/io/awspring/cloud/cloudmap/sample/SpringCloudAwsCloudMapSample.java b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/java/io/awspring/cloud/cloudmap/sample/SpringCloudAwsCloudMapSample.java
new file mode 100644
index 000000000..6f0b37781
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/java/io/awspring/cloud/cloudmap/sample/SpringCloudAwsCloudMapSample.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013-2021 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
+ *
+ * https://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 io.awspring.cloud.cloudmap.sample;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+public class SpringCloudAwsCloudMapSample implements ApplicationRunner {
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringCloudAwsCloudMapSample.class, args);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) {
+ this.discoveryClient.getServices();
+ }
+
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/application.properties b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/application.properties
new file mode 100644
index 000000000..15693f6b4
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+# importing cloudmap configuration files
+spring.config.import=bootstrap.properties
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/bootstrap.properties b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/bootstrap.properties
new file mode 100644
index 000000000..6a5b67b49
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/bootstrap.properties
@@ -0,0 +1,14 @@
+aws.cloudmap.region=us-east-1
+aws.cloudmap.enabled=true
+spring.application.name=cloudmap-namespace-here
+
+# Discover existing cloudmap instances
+aws.cloudmap.discovery.failFast=false
+aws.cloudmap.discovery.discoveryList[0].service=TestService
+aws.cloudmap.discovery.discoveryList[0].nameSpace=ECS-CloudMap
+
+# Register new instance
+aws.cloudmap.registry.description=Namespace for sample cloudmap registry service
+aws.cloudmap.registry.port=80
+aws.cloudmap.registry.service=a-service
+aws.cloudmap.registry.nameSpace=a-namespace
diff --git a/spring-cloud-starter-aws-cloudmap/pom.xml b/spring-cloud-starter-aws-cloudmap/pom.xml
new file mode 100644
index 000000000..05009a316
--- /dev/null
+++ b/spring-cloud-starter-aws-cloudmap/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ io.awspring.cloud
+ spring-cloud-aws
+ 2.3.1
+
+
+ spring-cloud-starter-aws-cloudmap
+ Spring Cloud AWS Cloud Map Starter
+ Spring Cloud AWS Cloud Map Starter
+ https://projects.spring.io/spring-cloud
+
+ Pivotal Software, Inc.
+ https://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-cloudmap
+ 2.3.1
+
+
+ io.awspring.cloud
+ spring-cloud-aws-core
+
+
+
+
diff --git a/spring-cloud-starter-aws-cloudmap/src/main/java/org/springframework/cloud/aws/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java b/spring-cloud-starter-aws-cloudmap/src/main/java/org/springframework/cloud/aws/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java
new file mode 100644
index 000000000..313adff72
--- /dev/null
+++ b/spring-cloud-starter-aws-cloudmap/src/main/java/org/springframework/cloud/aws/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013-2021 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
+ *
+ * https://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.autoconfigure.cloudmap;
+
+import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
+import com.amazonaws.services.servicediscovery.AWSServiceDiscoveryClientBuilder;
+import com.amazonaws.util.StringUtils;
+import io.awspring.cloud.core.SpringCloudClientConfiguration;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.aws.cloudmap.discovery.CloudMapDiscoveryClient;
+import org.springframework.cloud.aws.cloudmap.model.CloudMapProperties;
+import org.springframework.cloud.aws.cloudmap.model.registration.ServiceRegistration;
+import org.springframework.cloud.aws.cloudmap.registration.CloudMapAutoRegistration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Cloudmap BootstrapConfiguration configuration class to create the required beans.
+ *
+ * @author Hari Ohm Prasath
+ * @since 2.3.2
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(CloudMapProperties.class)
+@ConditionalOnClass({ AWSServiceDiscovery.class, ServiceRegistration.class, CloudMapAutoRegistration.class })
+@ConditionalOnProperty(prefix = CloudMapProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
+public class CloudMapBootstrapConfiguration {
+
+ private final ApplicationContext context;
+
+ private final AWSServiceDiscovery serviceDiscovery;
+
+ private final CloudMapProperties properties;
+
+ public CloudMapBootstrapConfiguration(CloudMapProperties properties, ApplicationContext context) {
+ AWSServiceDiscoveryClientBuilder builder = AWSServiceDiscoveryClientBuilder.standard()
+ .withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration())
+ .withCredentials(new DefaultAWSCredentialsProviderChain());
+
+ if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
+ builder.withRegion(properties.getRegion());
+ }
+
+ if (properties.getEndpoint() != null) {
+ AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
+ properties.getEndpoint().toString(), null);
+ builder.withEndpointConfiguration(endpointConfiguration);
+ }
+
+ this.serviceDiscovery = builder.build();
+ this.properties = properties;
+ this.context = context;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ CloudMapAutoRegistration createAutoRegistration() {
+ return new CloudMapAutoRegistration(context, serviceDiscovery, properties.getRegistry());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public CloudMapDiscoveryClient discoveryClient() {
+ return new CloudMapDiscoveryClient(serviceDiscovery, properties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ServiceRegistration serviceRegistration() {
+ return new ServiceRegistration(properties.getRegistry());
+ }
+
+}
diff --git a/spring-cloud-starter-aws-cloudmap/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-aws-cloudmap/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..7a201cb2b
--- /dev/null
+++ b/spring-cloud-starter-aws-cloudmap/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.cloud.bootstrap.BootstrapConfiguration=\
+org.springframework.cloud.aws.autoconfigure.cloudmap.CloudMapBootstrapConfiguration