From 9986358e1354ef589384a19cdbb56aa6dd468696 Mon Sep 17 00:00:00 2001 From: Hari Ohm Prasath Rajagopal Date: Sun, 4 Sep 2022 02:53:26 -0700 Subject: [PATCH 1/8] AWS Spring cloud map support Support for integrating with AWS Cloudmap --- spring-cloud-aws-autoconfigure/pom.xml | 6 + .../CloudMapBootstrapConfiguration.java | 82 +++ .../autoconfigure/cloudmap/CloudMapUtils.java | 592 ++++++++++++++++++ .../discovery/CloudMapDiscoveryClient.java | 82 +++ .../discovery/CloudMapServiceInstance.java | 76 +++ .../properties/CloudMapProperties.java | 120 ++++ .../discovery/CloudMapDiscovery.java | 54 ++ .../CloudMapDiscoveryProperties.java | 69 ++ .../CloudMapRegistryProperties.java | 56 ++ .../registration/ServiceRegistration.java | 77 +++ .../CloudMapAutoRegistration.java | 125 ++++ ...itional-spring-configuration-metadata.json | 6 + .../main/resources/META-INF/spring.factories | 3 +- .../cloudmap/CloudMapRegisterServiceTest.java | 190 ++++++ .../cloudmap/CloudMapTestUtils.java | 32 + spring-cloud-aws-cloudmap/pom.xml | 72 +++ .../exceptions/CreateNameSpaceException.java | 25 + .../exceptions/CreateServiceException.java | 25 + .../exceptions/MaxRetryExceededException.java | 25 + spring-cloud-aws-samples/pom.xml | 1 + .../spring-cloud-aws-cloud-map-sample/pom.xml | 37 ++ .../sample/SpringCloudAwsCloudMapSample.java | 45 ++ .../src/main/resources/application.properties | 2 + .../src/main/resources/bootstrap.properties | 15 + spring-cloud-aws-starters/pom.xml | 1 + .../spring-cloud-aws-starter/pom.xml | 1 + .../spring-cloud-starter-aws-cloudmap/pom.xml | 29 + 27 files changed, 1847 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapServiceInstance.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscoveryProperties.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/CloudMapRegistryProperties.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestUtils.java create mode 100644 spring-cloud-aws-cloudmap/pom.xml create mode 100644 spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateNameSpaceException.java create mode 100644 spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateServiceException.java create mode 100644 spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/MaxRetryExceededException.java create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/pom.xml create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/java/io/awspring/cloud/cloudmap/sample/SpringCloudAwsCloudMapSample.java create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/application.properties create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/bootstrap.properties create mode 100644 spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml diff --git a/spring-cloud-aws-autoconfigure/pom.xml b/spring-cloud-aws-autoconfigure/pom.xml index c7727901a..1a06db6ab 100644 --- a/spring-cloud-aws-autoconfigure/pom.xml +++ b/spring-cloud-aws-autoconfigure/pom.xml @@ -66,6 +66,12 @@ 3.0.0-SNAPSHOT true + + io.awspring.cloud + spring-cloud-aws-cloudmap + 3.0.0-SNAPSHOT + true + io.awspring.cloud spring-cloud-aws-dynamodb diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java new file mode 100644 index 000000000..75048fbb2 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java @@ -0,0 +1,82 @@ +/* + * 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.autoconfigure.cloudmap; + +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapDiscoveryClient; +import io.awspring.cloud.autoconfigure.cloudmap.properties.CloudMapProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.ServiceRegistration; +import io.awspring.cloud.autoconfigure.cloudmap.registration.CloudMapAutoRegistration; +import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; +import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClientBuilder; + +import org.springframework.beans.factory.ObjectProvider; +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.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 3.0 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(CloudMapProperties.class) +@ConditionalOnClass({ ServiceDiscoveryClient.class, ServiceRegistration.class, CloudMapAutoRegistration.class }) +@ConditionalOnProperty(prefix = CloudMapProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true) +public class CloudMapBootstrapConfiguration { + + private final ApplicationContext context; + private final CloudMapProperties properties; + + public CloudMapBootstrapConfiguration(CloudMapProperties properties, ApplicationContext context) { + this.properties = properties; + this.context = context; + } + + @ConditionalOnMissingBean + @Bean + public ServiceDiscoveryClient discoveryClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, + ObjectProvider> configurer) { + return awsClientBuilderConfigurer + .configure(ServiceDiscoveryClient.builder(), this.properties, configurer.getIfAvailable()).build(); + } + + @Bean + @ConditionalOnMissingBean + CloudMapAutoRegistration createAutoRegistration(ServiceDiscoveryClient serviceDiscovery) { + return new CloudMapAutoRegistration(context, serviceDiscovery, properties.getRegistry()); + } + + @Bean + @ConditionalOnMissingBean + CloudMapDiscoveryClient createDiscoveryClient(ServiceDiscoveryClient serviceDiscovery){ + return new CloudMapDiscoveryClient(serviceDiscovery, properties); + } + + @Bean + @ConditionalOnMissingBean + ServiceRegistration serviceRegistration() { + return new ServiceRegistration(properties.getRegistry()); + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java new file mode 100644 index 000000000..eeb264b5b --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java @@ -0,0 +1,592 @@ +/* + * 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.autoconfigure.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.UUID; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapServiceInstance; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.Filter; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; +import software.amazon.awssdk.services.servicediscovery.model.CreatePrivateDnsNamespaceRequest; +import software.amazon.awssdk.services.servicediscovery.model.CreateServiceRequest; +import software.amazon.awssdk.services.servicediscovery.model.DeregisterInstanceRequest; +import software.amazon.awssdk.services.servicediscovery.model.DiscoverInstancesRequest; +import software.amazon.awssdk.services.servicediscovery.model.DnsConfig; +import software.amazon.awssdk.services.servicediscovery.model.DnsRecord; +import software.amazon.awssdk.services.servicediscovery.model.DuplicateRequestException; +import software.amazon.awssdk.services.servicediscovery.model.GetOperationRequest; +import software.amazon.awssdk.services.servicediscovery.model.HttpInstanceSummary; +import software.amazon.awssdk.services.servicediscovery.model.InvalidInputException; +import software.amazon.awssdk.services.servicediscovery.model.ListNamespacesRequest; +import software.amazon.awssdk.services.servicediscovery.model.ListNamespacesResponse; +import software.amazon.awssdk.services.servicediscovery.model.ListServicesRequest; +import software.amazon.awssdk.services.servicediscovery.model.ListServicesResponse; +import software.amazon.awssdk.services.servicediscovery.model.NamespaceAlreadyExistsException; +import software.amazon.awssdk.services.servicediscovery.model.NamespaceSummary; +import software.amazon.awssdk.services.servicediscovery.model.Operation; +import software.amazon.awssdk.services.servicediscovery.model.RecordType; +import software.amazon.awssdk.services.servicediscovery.model.RegisterInstanceRequest; +import software.amazon.awssdk.services.servicediscovery.model.ResourceLimitExceededException; +import software.amazon.awssdk.services.servicediscovery.model.ServiceAlreadyExistsException; +import software.amazon.awssdk.services.servicediscovery.model.ServiceFilter; +import software.amazon.awssdk.services.servicediscovery.model.ServiceSummary; + +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.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 3.0 + */ +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 Ec2Client 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 ServiceDiscoveryClient serviceDiscovery, final String nameSpace) { + String token; + do { + ListNamespacesResponse nameSpaceResponse = serviceDiscovery + .listNamespaces(ListNamespacesRequest.builder().build()); + token = nameSpaceResponse.nextToken(); + + List namespaceSummaries = nameSpaceResponse.namespaces(); + if (namespaceSummaries != null) { + Optional namespaceId = namespaceSummaries.stream().filter(n -> n.name().equals(nameSpace)) + .map(NamespaceSummary::id).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 ServiceDiscoveryClient 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 = ServiceFilter.builder().name(NAMESPACE_ID).condition("EQ") + .values(nameSpaceId).build(); + final ListServicesRequest servicesRequest = ListServicesRequest.builder().filters(serviceFilter) + .build(); + final ListServicesResponse response = serviceDiscovery.listServices(servicesRequest); + if (StringUtils.hasText(response.nextToken())) + token = response.nextToken(); + + if (StringUtils.hasText(serviceName)) { + serviceList.addAll(response.services().stream().filter(r -> r.name().equals(d.getService())) + .map(r -> generateServiceId(nameSpace, r.name())).collect(Collectors.toList())); + if (serviceList.size() == discoveryProperties.size()) + return serviceList; + } + else + serviceList.addAll(response.services().stream() + .map(r -> generateServiceId(nameSpace, r.name())).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 ServiceDiscoveryClient serviceDiscovery, + final String namespace, String serviceName) { + final DiscoverInstancesRequest dRequest = DiscoverInstancesRequest.builder().namespaceName(namespace) + .serviceName(serviceName).build(); + + return serviceDiscovery.discoverInstances(dRequest).instances(); + } + + /** + * 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 ServiceDiscoveryClient 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(RegisterInstanceRequest.builder() + .instanceId(serviceInstanceId).serviceId(serviceId).attributes(attributes).build()) + .operationId(); + 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(ServiceDiscoveryClient serviceDiscovery, CloudMapRegistryProperties properties, + String vpcId) throws CreateNameSpaceException { + final String nameSpace = properties.getNameSpace(); + try { + // Create namespace + final String operationId = serviceDiscovery.createPrivateDnsNamespace(CreatePrivateDnsNamespaceRequest + .builder().name(nameSpace).vpc(vpcId).description(properties.getDescription()).build()) + .operationId(); + 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(ServiceDiscoveryClient serviceDiscovery, String nameSpaceId, String service) + throws CreateServiceException { + try { + CreateServiceRequest serviceRequest = CreateServiceRequest.builder().name(service).namespaceId(nameSpaceId) + .dnsConfig(DnsConfig.builder().dnsRecords(DnsRecord.builder().type(RecordType.A).ttl(300L).build()) + .build()) + .build(); + + final String serviceId = serviceDiscovery.createService(serviceRequest).service().id(); + 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 ServiceDiscoveryClient 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( + DeregisterInstanceRequest.builder().instanceId(serviceInstanceId).serviceId(serviceId).build()) + .operationId(); + + // 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(ServiceDiscoveryClient serviceDiscovery, String nameSpaceId, String serviceName) { + ServiceFilter filter = ServiceFilter.builder().name(NAMESPACE_ID).values(Collections.singletonList(nameSpaceId)) + .build(); + Optional serviceSummary = serviceDiscovery + .listServices(ListServicesRequest.builder().filters(filter).build()).services().stream() + .filter(s -> serviceName.equals(s.name())).findFirst(); + return serviceSummary.map(ServiceSummary::id).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(ServiceDiscoveryClient serviceDiscovery, String operationId) + throws InterruptedException, MaxRetryExceededException { + Operation operation = serviceDiscovery + .getOperation(GetOperationRequest.builder().operationId(operationId).build()).operation(); + int counter = 0; + LOGGER.info("Operation ID {} will be polled", operationId); + while ((SUBMITTED.equalsIgnoreCase(operation.statusAsString()) + || PENDING.equalsIgnoreCase(operation.statusAsString())) && counter < MAX_POLL) { + operation = serviceDiscovery.getOperation(GetOperationRequest.builder().operationId(operationId).build()) + .operation(); + 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("Metadata 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 new HashMap<>(); + } + + /** + * 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(DescribeSubnetsRequest.builder() + .filters(Filter.builder().name("cidr-block").values(cidrBlock).build()).build()) + .subnets().get(0).vpcId(); + LOGGER.info("IPv4Address {} - CIDR Block {} - VPC ID {}", ipv4Address, cidrBlock, vpcId); + return getCloudMapAttributes(ipv4Address, vpcId); + } + catch (Exception e) { + LOGGER.error("Error while fetching network details - {}", e.getMessage(), e); + } + return new HashMap<>(); + } + + /** + * 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; + } + + /** + * Get Ec2 client + * @return ec2 client object + */ + Ec2Client getEc2Client() { + if (ec2Client == null) { + ec2Client = Ec2Client.builder().region(Region.of(System.getenv("AWS_REGION"))) + .credentialsProvider(DefaultCredentialsProvider.builder().build()).build(); + } + return ec2Client; + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java new file mode 100644 index 000000000..53deb3d78 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/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 io.awspring.cloud.autoconfigure.cloudmap.discovery; + +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import io.awspring.cloud.autoconfigure.cloudmap.properties.CloudMapProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Service; + +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; + +// @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 ServiceDiscoveryClient serviceDiscovery; + private final CloudMapProperties properties; + + public CloudMapDiscoveryClient(ServiceDiscoveryClient 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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapServiceInstance.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapServiceInstance.java new file mode 100644 index 000000000..0b1705382 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapServiceInstance.java @@ -0,0 +1,76 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.discovery; + +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import java.net.URI; +import java.util.Map; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.util.StringUtils; +import software.amazon.awssdk.services.servicediscovery.model.HttpInstanceSummary; + +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.instanceId(); + } + + @Override + public String getScheme() { + return getUri().getScheme(); + } + + @Override + public String getServiceId() { + return UTILS.generateServiceId(instanceSummary.namespaceName(), instanceSummary.serviceName()); + } + + @Override + public String getHost() { + return instanceSummary.attributes().get("AWS_INSTANCE_IPV4"); + } + + @Override + public int getPort() { + String port = instanceSummary.attributes().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.attributes(); + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java new file mode 100644 index 000000000..9f245dd97 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java @@ -0,0 +1,120 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.properties; + +import io.awspring.cloud.autoconfigure.AwsClientProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscovery; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; +import java.net.URI; +import org.springframework.boot.context.properties.ConfigurationProperties; +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 3.0 + */ +@ConfigurationProperties(CloudMapProperties.CONFIG_PREFIX) +public class CloudMapProperties extends AwsClientProperties implements EnvironmentAware { + + /** + * Default cloudmap prefix. + */ + public static final String CONFIG_PREFIX = "spring.cloud.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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java new file mode 100644 index 000000000..02e8e2780 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java @@ -0,0 +1,54 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.properties.discovery; + +import java.util.List; + +/** + * Pojo class to capture all the discovery parameters. + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscoveryProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscoveryProperties.java new file mode 100644 index 000000000..00121ea37 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscoveryProperties.java @@ -0,0 +1,69 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.properties.discovery; + +import java.util.Map; + +/** + * POJO class to capture cloudmap discovery attributes. + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/CloudMapRegistryProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/CloudMapRegistryProperties.java new file mode 100644 index 000000000..c0ac97fa3 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/CloudMapRegistryProperties.java @@ -0,0 +1,56 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.properties.registration; + +/** + * POJO class to capture cloudmap registration parameters. + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java new file mode 100644 index 000000000..4eb845b0c --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java @@ -0,0 +1,77 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.properties.registration; + +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import java.net.URI; +import java.util.Map; +import java.util.UUID; +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-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java new file mode 100644 index 000000000..a5c6017e9 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java @@ -0,0 +1,125 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap.registration; + +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +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 software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; + +public class CloudMapAutoRegistration + implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener, EnvironmentAware { + + private final ServiceDiscoveryClient 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, ServiceDiscoveryClient 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 eventType) { + return true; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public void onApplicationEvent(ContextClosedEvent event) { + stop(); + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 72dd78c62..b525e0afa 100644 --- a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -23,6 +23,12 @@ "name": "spring.cloud.aws.dynamodb.enabled", "description": "Enables DynamoDb integration.", "type": "java.lang.Boolean" + }, + { + "defaultValue": true, + "name": "spring.cloud.aws.cloudmap.enabled", + "description": "Enables CloudMap integration.", + "type": "java.lang.Boolean" } ] } diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories index 8cc2c8970..7c17e2757 100644 --- a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories @@ -8,7 +8,8 @@ io.awspring.cloud.autoconfigure.s3.S3TransferManagerAutoConfiguration,\ io.awspring.cloud.autoconfigure.s3.S3AutoConfiguration,\ io.awspring.cloud.autoconfigure.sns.SnsAutoConfiguration,\ io.awspring.cloud.autoconfigure.sqs.SqsAutoConfiguration,\ -io.awspring.cloud.autoconfigure.dynamodb.DynamoDbAutoConfiguration +io.awspring.cloud.autoconfigure.dynamodb.DynamoDbAutoConfiguration,\ +io.awspring.cloud.autoconfigure.cloudmap.CloudMapBootstrapConfiguration # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java new file mode 100644 index 000000000..5eb964d58 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java @@ -0,0 +1,190 @@ +/* + * 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.autoconfigure.cloudmap; + +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; + +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.ServiceRegistration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.core.env.Environment; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; +import software.amazon.awssdk.services.servicediscovery.model.CreatePrivateDnsNamespaceRequest; +import software.amazon.awssdk.services.servicediscovery.model.CreatePrivateDnsNamespaceResponse; +import software.amazon.awssdk.services.servicediscovery.model.CreateServiceRequest; +import software.amazon.awssdk.services.servicediscovery.model.CreateServiceResponse; +import software.amazon.awssdk.services.servicediscovery.model.DeregisterInstanceRequest; +import software.amazon.awssdk.services.servicediscovery.model.DeregisterInstanceResponse; +import software.amazon.awssdk.services.servicediscovery.model.GetOperationRequest; +import software.amazon.awssdk.services.servicediscovery.model.GetOperationResponse; +import software.amazon.awssdk.services.servicediscovery.model.ListNamespacesRequest; +import software.amazon.awssdk.services.servicediscovery.model.ListNamespacesResponse; +import software.amazon.awssdk.services.servicediscovery.model.ListServicesRequest; +import software.amazon.awssdk.services.servicediscovery.model.ListServicesResponse; +import software.amazon.awssdk.services.servicediscovery.model.NamespaceSummary; +import software.amazon.awssdk.services.servicediscovery.model.Operation; +import software.amazon.awssdk.services.servicediscovery.model.OperationStatus; +import software.amazon.awssdk.services.servicediscovery.model.RegisterInstanceRequest; +import software.amazon.awssdk.services.servicediscovery.model.RegisterInstanceResponse; +import software.amazon.awssdk.services.servicediscovery.model.Service; +import software.amazon.awssdk.services.servicediscovery.model.ServiceSummary; + +/** + * Unit testcase for {@link ServiceRegistration} + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +public class CloudMapRegisterServiceTest { + + private final ServiceDiscoveryClient serviceDiscovery = mock(ServiceDiscoveryClient.class); + + private final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + + private final Environment environment = mock(Environment.class); + + @Test + public void cloudMapRegisterInstancesNameSpaceAndServiceExists() { + final ListNamespacesResponse response = getListNamespacesResponse(); + final ListServicesResponse listServicesResponse = getListServicesResponse(); + final RegisterInstanceResponse registerInstanceRequest = getRegisterInstanceResponse(); + final GetOperationResponse operationResponse = getOperationResponse(); + + when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn(response); + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class)))) + .thenReturn(registerInstanceRequest); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + } + + @Test + public void cloudMapRegisterInstanceWithNoNameSpace() { + final ListNamespacesResponse listNamespacesResponse = getListNamespacesResponse(); + final ListServicesResponse listServicesResponse = getListServicesResponse(); + final RegisterInstanceResponse registerInstanceRequest = getRegisterInstanceResponse(); + final GetOperationResponse operationResponse = getOperationResponse(); + final CreatePrivateDnsNamespaceResponse createPrivateDnsNamespaceResponse = getCreatePrivateDnsNamespaceResponse(); + + when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn( + ListNamespacesResponse.builder().namespaces(Collections.emptyList()).build(), listNamespacesResponse); + when(serviceDiscovery.createPrivateDnsNamespace(any(CreatePrivateDnsNamespaceRequest.class))) + .thenReturn(createPrivateDnsNamespaceResponse); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class)))) + .thenReturn(registerInstanceRequest); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + } + + @Test + public void cloudMapRegisterInstancesWithNoService() { + final ListNamespacesResponse listNamespacesResponse = getListNamespacesResponse(); + final ListServicesResponse listServicesResponse = getListServicesResponse(); + final RegisterInstanceResponse registerInstanceResponse = getRegisterInstanceResponse(); + final GetOperationResponse operationResponse = getOperationResponse(); + final CreateServiceResponse createServiceResponse = CreateServiceResponse.builder() + .service(Service.builder().name(CloudMapTestUtils.SERVICE).id(CloudMapTestUtils.SERVICE).build()) + .build(); + + when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn(listNamespacesResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn( + ListServicesResponse.builder().services(Collections.emptyList()).build(), listServicesResponse); + when(serviceDiscovery.createService(any(CreateServiceRequest.class))).thenReturn(createServiceResponse); + + when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class)))) + .thenReturn(registerInstanceResponse); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + 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"); + + DeregisterInstanceResponse deregisterInstanceResponse = DeregisterInstanceResponse.builder() + .operationId(CloudMapTestUtils.OPERATION_ID).build(); + when(serviceDiscovery.deregisterInstance(any(DeregisterInstanceRequest.class))) + .thenReturn(deregisterInstanceResponse); + + final GetOperationResponse operationResponse = getOperationResponse(); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse, + operationResponse); + cloudMapUtils.deregisterInstance(serviceDiscovery, attributeMap); + } + catch (Exception e) { + Assertions.fail(); + } + } + + private CreatePrivateDnsNamespaceResponse getCreatePrivateDnsNamespaceResponse() { + return CreatePrivateDnsNamespaceResponse.builder().operationId(CloudMapTestUtils.OPERATION_ID).build(); + } + + private GetOperationResponse getOperationResponse() { + return GetOperationResponse.builder().operation(Operation.builder().status(OperationStatus.SUCCESS).build()) + .build(); + } + + private RegisterInstanceResponse getRegisterInstanceResponse() { + return RegisterInstanceResponse.builder().operationId(CloudMapTestUtils.OPERATION_ID).build(); + } + + private ListServicesResponse getListServicesResponse() { + ServiceSummary serviceSummary = ServiceSummary.builder().id(CloudMapTestUtils.SERVICE) + .name(CloudMapTestUtils.SERVICE).build(); + return ListServicesResponse.builder().services(Collections.singletonList(serviceSummary)).build(); + } + + private ListNamespacesResponse getListNamespacesResponse() { + NamespaceSummary summary = NamespaceSummary.builder().id(CloudMapTestUtils.NAMESPACE) + .name(CloudMapTestUtils.NAMESPACE).build(); + return ListNamespacesResponse.builder().namespaces(Collections.singleton(summary)).build(); + } + + 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-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestUtils.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestUtils.java new file mode 100644 index 000000000..e12c72c22 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestUtils.java @@ -0,0 +1,32 @@ +/* + * 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.autoconfigure.cloudmap; + +/** + * Unit testcase for {@link CloudMapUtils} + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +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-cloudmap/pom.xml b/spring-cloud-aws-cloudmap/pom.xml new file mode 100644 index 000000000..d9a955898 --- /dev/null +++ b/spring-cloud-aws-cloudmap/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + io.awspring.cloud + spring-cloud-aws + 3.0.0-SNAPSHOT + + + 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 + + + + software.amazon.awssdk + servicediscovery + + + + software.amazon.awssdk + ec2 + + + + org.springframework + spring-web + + + + org.springframework + spring-webflux + + + + io.projectreactor.netty + reactor-netty-http + + + + + com.fasterxml.jackson.core + jackson-databind + 2.13.3 + + + + + + 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..862a9f0c8 --- /dev/null +++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateNameSpaceException.java @@ -0,0 +1,25 @@ +/* + * 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..30b03f3c4 --- /dev/null +++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/CreateServiceException.java @@ -0,0 +1,25 @@ +/* + * 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..af959958e --- /dev/null +++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/MaxRetryExceededException.java @@ -0,0 +1,25 @@ +/* + * 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-samples/pom.xml b/spring-cloud-aws-samples/pom.xml index 08dc7366a..95579cb1d 100644 --- a/spring-cloud-aws-samples/pom.xml +++ b/spring-cloud-aws-samples/pom.xml @@ -21,6 +21,7 @@ spring-cloud-aws-secrets-manager-sample spring-cloud-aws-ses-sample spring-cloud-aws-sns-sample + spring-cloud-aws-cloud-map-sample 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..c8651642a --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/pom.xml @@ -0,0 +1,37 @@ + + + + io.awspring.cloud + spring-cloud-aws-samples + 3.0.0-SNAPSHOT + + 4.0.0 + spring-cloud-aws-cloud-map-sample + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + io.awspring.cloud + spring-cloud-starter-aws-cloudmap + 3.0.0-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + 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..f5f38f4e3 --- /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,45 @@ +/* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +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; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +@EnableDiscoveryClient +public class SpringCloudAwsCloudMapSample { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringCloudAwsCloudMapSample.class); + @Autowired + private DiscoveryClient discoveryClient; + + public static void main(String[] args) { + SpringApplication.run(SpringCloudAwsCloudMapSample.class, args); + } + + @Bean + ApplicationRunner applicationRunner() { + return args -> LOGGER.info("Total instances: {}", discoveryClient.getServices().size()); + } +} 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..8c5a60d85 --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/src/main/resources/bootstrap.properties @@ -0,0 +1,15 @@ +spring.cloud.aws.cloudmap.region=us-east-1 +spring.cloud.aws.cloudmap.enabled=true +spring.application.name=cloudmap-namespace-here + +# Discover existing cloudmap instances +spring.cloud.aws.cloudmap.discovery.failFast=false +spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=a-service +spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=a-namespace + +# Register new instance + +spring.cloud.aws.cloudmap.registry.description=Namespace for sample cloudmap registry service +spring.cloud.aws.cloudmap.registry.port=80 +spring.cloud.aws.cloudmap.registry.service=a-service +spring.cloud.aws.cloudmap.registry.nameSpace=a-namespace diff --git a/spring-cloud-aws-starters/pom.xml b/spring-cloud-aws-starters/pom.xml index 15193d28d..fc7f84d37 100644 --- a/spring-cloud-aws-starters/pom.xml +++ b/spring-cloud-aws-starters/pom.xml @@ -22,6 +22,7 @@ spring-cloud-aws-starter-sns spring-cloud-aws-starter-sqs spring-cloud-aws-starter-dynamodb + spring-cloud-starter-aws-cloudmap diff --git a/spring-cloud-aws-starters/spring-cloud-aws-starter/pom.xml b/spring-cloud-aws-starters/spring-cloud-aws-starter/pom.xml index 6434dd6b1..52e5d982b 100644 --- a/spring-cloud-aws-starters/spring-cloud-aws-starter/pom.xml +++ b/spring-cloud-aws-starters/spring-cloud-aws-starter/pom.xml @@ -16,6 +16,7 @@ io.awspring.cloud spring-cloud-aws-autoconfigure + 3.0.0-SNAPSHOT io.awspring.cloud diff --git a/spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml b/spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml new file mode 100644 index 000000000..d8f17e154 --- /dev/null +++ b/spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + spring-cloud-aws-starters + io.awspring.cloud + 3.0.0-SNAPSHOT + + + spring-cloud-starter-aws-cloudmap + Spring Cloud AWS Cloud Map Starter + Spring Cloud AWS Cloud Map Starter + + + + io.awspring.cloud + spring-cloud-aws-cloudmap + 3.0.0-SNAPSHOT + + + io.awspring.cloud + spring-cloud-aws-starter + 3.0.0-SNAPSHOT + + + + From 31f4c42894d85a3cee661fba38911a7e2fb843ec Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 6 Sep 2022 00:10:45 +0200 Subject: [PATCH 2/8] Fix CloudMap modules in `pom.xml`, reformat code. --- pom.xml | 1 + .../CloudMapBootstrapConfiguration.java | 7 +++-- .../autoconfigure/cloudmap/CloudMapUtils.java | 26 +++++++++---------- .../discovery/CloudMapDiscoveryClient.java | 7 +---- spring-cloud-aws-dependencies/pom.xml | 12 +++++++++ .../spring-cloud-aws-cloud-map-sample/pom.xml | 3 +-- .../sample/SpringCloudAwsCloudMapSample.java | 1 - spring-cloud-aws-starters/pom.xml | 2 +- .../pom.xml | 4 +-- 9 files changed, 32 insertions(+), 31 deletions(-) rename spring-cloud-aws-starters/{spring-cloud-starter-aws-cloudmap => spring-cloud-aws-starter-cloudmap}/pom.xml (86%) diff --git a/pom.xml b/pom.xml index d28726697..54f023adb 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ spring-cloud-aws-core + spring-cloud-aws-cloudmap spring-cloud-aws-autoconfigure spring-cloud-aws-dependencies spring-cloud-aws-parameter-store diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java index 75048fbb2..bb37b6084 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java @@ -21,9 +21,6 @@ import io.awspring.cloud.autoconfigure.cloudmap.registration.CloudMapAutoRegistration; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; -import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; -import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClientBuilder; - import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -32,6 +29,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClientBuilder; /** * Cloudmap BootstrapConfiguration configuration class to create the required beans. @@ -69,7 +68,7 @@ CloudMapAutoRegistration createAutoRegistration(ServiceDiscoveryClient serviceDi @Bean @ConditionalOnMissingBean - CloudMapDiscoveryClient createDiscoveryClient(ServiceDiscoveryClient serviceDiscovery){ + CloudMapDiscoveryClient createDiscoveryClient(ServiceDiscoveryClient serviceDiscovery) { return new CloudMapDiscoveryClient(serviceDiscovery, properties); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java index eeb264b5b..d30b6adec 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java @@ -15,6 +15,11 @@ */ package io.awspring.cloud.autoconfigure.cloudmap; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapServiceInstance; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -23,14 +28,15 @@ import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapServiceInstance; -import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; -import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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.client.ServiceInstance; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.client.WebClient; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ec2.Ec2Client; @@ -61,14 +67,6 @@ import software.amazon.awssdk.services.servicediscovery.model.ServiceFilter; import software.amazon.awssdk.services.servicediscovery.model.ServiceSummary; -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.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. * diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java index 53deb3d78..34f018b4d 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java @@ -23,10 +23,6 @@ import java.util.stream.Collectors; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Service; - import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; // @checkstyle: off @@ -41,8 +37,7 @@ public class CloudMapDiscoveryClient implements DiscoveryClient { private final ServiceDiscoveryClient serviceDiscovery; private final CloudMapProperties properties; - public CloudMapDiscoveryClient(ServiceDiscoveryClient serviceDiscovery, - CloudMapProperties properties) { + public CloudMapDiscoveryClient(ServiceDiscoveryClient serviceDiscovery, CloudMapProperties properties) { this.serviceDiscovery = serviceDiscovery; this.properties = properties; } diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml index 61fe15628..3e657b96b 100644 --- a/spring-cloud-aws-dependencies/pom.xml +++ b/spring-cloud-aws-dependencies/pom.xml @@ -69,6 +69,12 @@ ${project.version} + + io.awspring.cloud + spring-cloud-aws-cloudmap + ${project.version} + + io.awspring.cloud spring-cloud-aws-autoconfigure @@ -129,6 +135,12 @@ ${project.version} + + io.awspring.cloud + spring-cloud-aws-starter-cloudmap + ${project.version} + + io.awspring.cloud spring-cloud-aws-starter-parameter-store 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 index c8651642a..31f82b3dd 100644 --- 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 @@ -21,8 +21,7 @@ io.awspring.cloud - spring-cloud-starter-aws-cloudmap - 3.0.0-SNAPSHOT + spring-cloud-aws-starter-cloudmap 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 index f5f38f4e3..45f2a5450 100644 --- 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 @@ -17,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; diff --git a/spring-cloud-aws-starters/pom.xml b/spring-cloud-aws-starters/pom.xml index fc7f84d37..84586adb7 100644 --- a/spring-cloud-aws-starters/pom.xml +++ b/spring-cloud-aws-starters/pom.xml @@ -14,6 +14,7 @@ spring-cloud-aws-starter + spring-cloud-aws-starter-cloudmap spring-cloud-aws-starter-metrics spring-cloud-aws-starter-parameter-store spring-cloud-aws-starter-secrets-manager @@ -22,7 +23,6 @@ spring-cloud-aws-starter-sns spring-cloud-aws-starter-sqs spring-cloud-aws-starter-dynamodb - spring-cloud-starter-aws-cloudmap diff --git a/spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml b/spring-cloud-aws-starters/spring-cloud-aws-starter-cloudmap/pom.xml similarity index 86% rename from spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml rename to spring-cloud-aws-starters/spring-cloud-aws-starter-cloudmap/pom.xml index d8f17e154..c3c94bc96 100644 --- a/spring-cloud-aws-starters/spring-cloud-starter-aws-cloudmap/pom.xml +++ b/spring-cloud-aws-starters/spring-cloud-aws-starter-cloudmap/pom.xml @@ -9,7 +9,7 @@ 3.0.0-SNAPSHOT - spring-cloud-starter-aws-cloudmap + spring-cloud-aws-starter-cloudmap Spring Cloud AWS Cloud Map Starter Spring Cloud AWS Cloud Map Starter @@ -17,12 +17,10 @@ io.awspring.cloud spring-cloud-aws-cloudmap - 3.0.0-SNAPSHOT io.awspring.cloud spring-cloud-aws-starter - 3.0.0-SNAPSHOT From 9fac576e47f5d5bec91412cb4b95205eb3ede1a4 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 6 Sep 2022 00:23:58 +0200 Subject: [PATCH 3/8] Do not start CloudMap integration for SSM and Secrets Manager tests. --- .../ParameterStoreConfigDataLoaderIntegrationTests.java | 7 ++++--- .../SecretsManagerConfigDataLoaderIntegrationTests.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java index 2d16660db..35fbf6c31 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java @@ -189,7 +189,7 @@ void serviceSpecificEndpointTakesPrecedenceOverGlobalAwsRegion() { "--spring.cloud.aws.endpoint=http://non-existing-host/", "--spring.cloud.aws.parameterstore.endpoint=" + localstack.getEndpointOverride(SSM).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=eu-west-1", + "--spring.cloud.aws.region.static=eu-west-1", "--spring.cloud.aws.cloudmap.enabled=false", "--logging.level.io.awspring.cloud.parameterstore=debug")) { assertThat(context.getEnvironment().getProperty("message")).isEqualTo("value from tests"); } @@ -204,7 +204,7 @@ void parameterStoreClientUsesGlobalRegion() { "--spring.config.import=aws-parameterstore:/config/spring/", "--spring.cloud.aws.endpoint=" + localstack.getEndpointOverride(SSM).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=" + REGION, + "--spring.cloud.aws.region.static=" + REGION, "--spring.cloud.aws.cloudmap.enabled=false", "--logging.level.io.awspring.cloud.parameterstore=debug")) { assertThat(context.getEnvironment().getProperty("message")).isEqualTo("value from tests"); } @@ -216,7 +216,8 @@ private ConfigurableApplicationContext runApplication(SpringApplication applicat "--spring.cloud.aws.parameterstore.region=" + REGION, "--" + endpointProperty + "=" + localstack.getEndpointOverride(SSM).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=eu-west-1", "--logging.level.io.awspring.cloud.parameterstore=debug"); + "--spring.cloud.aws.cloudmap.enabled=false", "--spring.cloud.aws.region.static=eu-west-1", + "--logging.level.io.awspring.cloud.parameterstore=debug"); } private ConfigurableApplicationContext runApplication(SpringApplication application, String springConfigImport) { diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java index de99a5de1..2d9b51e49 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java @@ -228,7 +228,7 @@ void serviceSpecificEndpointTakesPrecedenceOverGlobalAwsRegion() { "--spring.cloud.aws.secretsmanager.endpoint=" + localstack.getEndpointOverride(SECRETSMANAGER).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=eu-west-1")) { + "--spring.cloud.aws.cloudmap.enabled=false", "--spring.cloud.aws.region.static=eu-west-1")) { assertThat(context.getEnvironment().getProperty("message")).isEqualTo("value from tests"); } } @@ -242,7 +242,7 @@ void secretsManagerClientUsesGlobalRegion() { "--spring.config.import=aws-secretsmanager:/config/spring;/config/second", "--spring.cloud.aws.endpoint=" + localstack.getEndpointOverride(SECRETSMANAGER).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=" + REGION)) { + "--spring.cloud.aws.cloudmap.enabled=false", "--spring.cloud.aws.region.static=" + REGION)) { assertThat(context.getEnvironment().getProperty("message")).isEqualTo("value from tests"); } } @@ -257,7 +257,8 @@ private ConfigurableApplicationContext runApplication(SpringApplication applicat "--spring.cloud.aws.secretsmanager.region=" + REGION, "--" + endpointProperty + "=" + localstack.getEndpointOverride(SECRETSMANAGER).toString(), "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", - "--spring.cloud.aws.region.static=eu-west-1", "--logging.level.io.awspring.cloud.secretsmanager=debug"); + "--spring.cloud.aws.cloudmap.enabled=false", "--spring.cloud.aws.region.static=eu-west-1", + "--logging.level.io.awspring.cloud.secretsmanager=debug"); } private static void createSecret(LocalStackContainer localstack, String secretName, String parameterValue, From eed8a3e9e6bc17f422e4b3450b7ddeaf0a97868b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 6 Sep 2022 08:51:26 +0200 Subject: [PATCH 4/8] Polish poms --- spring-cloud-aws-autoconfigure/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/spring-cloud-aws-autoconfigure/pom.xml b/spring-cloud-aws-autoconfigure/pom.xml index 1a06db6ab..084c96996 100644 --- a/spring-cloud-aws-autoconfigure/pom.xml +++ b/spring-cloud-aws-autoconfigure/pom.xml @@ -63,13 +63,11 @@ io.awspring.cloud spring-cloud-aws-sqs - 3.0.0-SNAPSHOT true io.awspring.cloud spring-cloud-aws-cloudmap - 3.0.0-SNAPSHOT true From 1e5f54156f289d2fbdb45dedcde737a836dc19de Mon Sep 17 00:00:00 2001 From: Hari Ohm Prasath Rajagopal Date: Wed, 7 Sep 2022 14:26:41 -0700 Subject: [PATCH 5/8] Addressing code review comments new test cases, readme, instructions --- docs/src/main/asciidoc/cloud-map.adoc | 266 ++++ .../AwsCloudMapStoreClientCustomizer.java | 26 + ...on.java => CloudMapAutoConfiguration.java} | 6 +- .../CloudMapEventPublisherFactory.java | 34 + .../autoconfigure/cloudmap/CloudMapUtils.java | 29 +- .../discovery/CloudMapDiscoveryClient.java | 2 +- .../cloudmap/discovery/package-info.java | 22 + .../autoconfigure/cloudmap/package-info.java | 20 + .../properties/CloudMapProperties.java | 66 +- .../discovery/CloudMapDiscovery.java | 13 +- .../properties/discovery/package-info.java | 20 + .../cloudmap/properties/package-info.java | 20 + .../properties/registration/package-info.java | 20 + .../CloudMapAutoRegistration.java | 7 +- .../cloudmap/registration/package-info.java | 22 + .../main/resources/META-INF/spring.factories | 2 +- .../cloudmap/CloudMapRegisterServiceTest.java | 99 +- .../cloudmap/CloudMapTestConstants.java | 65 + spring-cloud-aws-cloudmap/pom.xml | 10 - .../aws/cloudmap/exceptions/package-info.java | 23 + .../AbstractCrossRegionS3Client.java | 1279 +++++++---------- .../Dockerfile | 5 + .../README.md | 255 ++++ .../src/main/resources/application.properties | 15 +- .../src/main/resources/bootstrap.properties | 1 - 25 files changed, 1478 insertions(+), 849 deletions(-) create mode 100644 docs/src/main/asciidoc/cloud-map.adoc create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/AwsCloudMapStoreClientCustomizer.java rename spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/{CloudMapBootstrapConfiguration.java => CloudMapAutoConfiguration.java} (90%) create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/package-info.java create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java create mode 100644 spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/package-info.java create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/Dockerfile create mode 100644 spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md diff --git a/docs/src/main/asciidoc/cloud-map.adoc b/docs/src/main/asciidoc/cloud-map.adoc new file mode 100644 index 000000000..2bb8b2496 --- /dev/null +++ b/docs/src/main/asciidoc/cloud-map.adoc @@ -0,0 +1,266 @@ +[#spring-cloud-aws-cloudmap] +== Spring Cloud AWS CloudMap +This article talks about the new Spring Cloud AWS CloudMap module. + +=== What is service discovery? + +Service discovery is the mechanism through which a microservices can locate other microservices in the network. Service discovery is a critical part of microservices architecture as it enables the microservices to identify and communicate with each other. + +There are two types of service discovery: Server-side and Client-side. + + 1. Server-side service discovery allows clients applications to find other services through a router (like API gateway or a load balancer). + 2. Client-side service discovery allows clients applications to find services by looking through or querying a service registry, in which service instances and endpoints are all within the service registry. + +=== What is AWS Cloud Map? +https://aws.amazon.com/cloud-map/[AWS Cloud Map] is a client-side service registry and service discovery solution provided as a ready-to-use, highly available service. Rather than build your own client service registry, you can leverage AWS Cloud Map to register your application and its running instances, and then use either the AWS Cloud Map API or DNS lookup to resolve a service's name to a current active endpoint. + +With Cloud Map, you can define custom names for your application resources, and it maintains the updated location of these dynamically changing resources. This increases your application availability because your web service always discovers the most up-to-date locations of its resources. + +=== Spring Cloud integration with AWS Cloud Map + +Spring Cloud AWS adds support for registering and discovering service using AWS Cloud Map through Spring Boot https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-files-importing[config import feature]. + +Maven coordinates, using <>: + +[source,xml] +---- + + io.awspring.cloud + spring-cloud-aws-starter-aws-cloudmap + +---- + + +=== Service registration +To register a microservice to Cloud Map we need to specify the following: + + 1. Namespace - A namespace is a way to group services for an application. When you create a namespace, you specify how you want to discover service instances that you register with AWS Cloud Map + 2. Service name - A service is a template for registering service instances, which allow you to locate the resources for an application using AWS Cloud Map `DiscoverInstances` API action + 3. Service Instance - A service instance contains information about how to locate a resource, such as a web server, for an application. After you register instances, you locate them by using AWS Cloud Map `DiscoverInstances` API action. + +Spring Cloud integration with AWS Cloud Map allows you to register a microservice to Cloud Map using the following configuration: + + 1. Automatic registration - Spring Cloud AWS Cloud Map module automatically registers the microservice to Cloud Map when the application starts. In order to do this, just include `spring-cloud-aws-starter-aws-cloudmap` dependency in your application, and Cloud Map integration module will register your microservice under `default-namespace` namespace and `spring.application.name or default-service` service name. + 2. Manual registration - Cloud Map properties can be provided part of the application configuration, here is a sample: + +```properties +spring.cloud.aws.cloudmap.registry.description=Namespace for sample cloudmap registry service +spring.cloud.aws.cloudmap.registry.port=80 +spring.cloud.aws.cloudmap.registry.service=a-service +spring.cloud.aws.cloudmap.registry.nameSpace=a-namespace +``` + +=== Service discovery +To discover a microservice using Cloud Map we need to specify the following: + +Enable `DiscoveryClient` - You can use `@EnableDiscoveryClient` annotation to integrate with Spring Cloud `DiscoveryClient`, here is a sample: + +```java +@SpringBootApplication +@EnableDiscoveryClient +public class SpringCloudAwsCloudMapSample { + @Autowired + private DiscoveryClient discoveryClient; + + @Bean + ApplicationRunner applicationRunner() { + return args -> LOGGER.info("Total instances: {}", discoveryClient.getServices().size()); + } +} +``` +* Using application configuration to specify the services - You can specify the services that can be discovered using `spring.cloud.aws.cloudmap.discovery.*` property, here is a sample: + +```properties +spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=a-service #array of services +spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=a-namespace +``` + +=== Using ServiceDiscoveryClient + +The starter automatically configures and registers a `ServiceDiscoveryClient` bean in the Spring application context. The `ServiceDiscoveryClient` bean can be used to register or discovery service instances from AWS Cloud Map. + +[source,java] +---- +import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; +... +@Autowired +private ServiceDiscoveryClient serviceDiscoveryClient; +... +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(RegisterInstanceRequest.builder() + .instanceId(serviceInstanceId).serviceId(serviceId).attributes(attributes).build()) + .operationId(); +---- + +=== Customizing ServiceDiscoveryClient + +To use custom `ServiceDiscoveryClient` in `spring.config.import`, provide an implementation of `BootstrapRegistryInitializer`. For example: + +[source,java] +---- +package com.app; + +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; + +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistryInitializer; + +class AWSCloudMapBootstrapConfiguration implements BootstrapRegistryInitializer { + + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(ServiceDiscoveryClient.class, context -> { + AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create("yourAccessKey", "yourSecretKey")); + return ServiceDiscoveryClient.builder().credentialsProvider(awsCredentialsProvider).region(Region.EU_WEST_2).build(); + }); + } +} +---- + +--- + +Note that this class must be listed under `org.springframework.boot.BootstrapRegistryInitializer` key in `META-INF/spring.factories`: + +[source, properties] +---- +org.springframework.boot.BootstrapRegistryInitializer=com.app.AWSCloudMapBootstrapConfiguration +---- + +Note that this class must be listed under `org.springframework.boot.BootstrapRegistryInitializer` key in `META-INF/spring.factories`: + +[source, properties] +---- +org.springframework.boot.BootstrapRegistryInitializer=com.app.AWSCloudMapBootstrapConfiguration +---- + +If you want to use autoconfigured `ServiceDiscoveryClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerCloudMap`: +Autoconfiguration will configure `ServiceDiscoveryClient` Bean with provided values after that, for example: + +If you want to use autoconfigured `ServiceDiscoveryClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerCloudMap`: +Autoconfiguration will configure `ServiceDiscoveryClient` Bean with provided values after that, for example: + +[source,java] +---- +package com.app; + +import io.awspring.cloud.autoconfigure.cloudmap.AwsCloudMapStoreClientCustomizer; +import java.time.Duration; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistryInitializer; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.ssm.SsmClientBuilder; + +class AWSCloudMapBootstrapConfiguration implements BootstrapRegistryInitializer { + + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(AwsCloudMapStoreClientCustomizer.class, + context -> new AwsCloudMapStoreClientCustomizer() { + + @Override + public ClientOverrideConfiguration overrideConfiguration() { + return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500)) + .build(); + } + + @Override + public SdkHttpClient httpClient() { + return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build(); + } + }); + } +} +---- + +=== Configuration + +The Spring Boot Starter for Cloud Map integration provides the following configuration options for service registration: + +[cols="4,3,1,1"] +|=== +| Name | Description | Required | Default value + +| `spring.cloud.aws.cloudmap.registry.description` | Namespace for sample cloudmap registry service. | No | `default-namespace` +| `spring.cloud.aws.cloudmap.registry.port` | Port in which the microservice is exposed. | No | `null` +| `spring.cloud.aws.cloudmap.registry.service` | Service name for registering the cloudmap service. | No | `default-service or spring.application.name` +| `spring.cloud.aws.cloudmap.region` | Configures region used by `ServiceDiscoveryClient`. | No | `null` +|=== + + +The Spring Boot Starter for Cloud Map integration provides the following configuration options for discovering services: + +[cols="4,3,1,1"] +|=== +| Name | Description | Required | Default value + +| `spring.cloud.aws.cloudmap.discovery.discoveryList[*].service` | Array of Cloudmap services the module will discover part of the startup. | No | `null` +| `spring.cloud.aws.cloudmap.discovery.discoveryList[*].nameSpace` | Array of Cloudmap namespaces the module will discover part of the startup. | No | `null` +| `spring.cloud.aws.cloudmap.region` | Configures region used by `ServiceDiscoveryClient`. | No | `null` +|=== + +=== IAM Permissions +Following IAM permissions are required by Spring Cloud AWS to register and discover services in AWS Cloud Map: + +[cols="1"] +|=== +| **Policies** +| `servicediscovery:ListServices` +| `servicediscovery:GetOperation` +| `servicediscovery:DiscoverInstances` +| `servicediscovery:DeleteNamespace` +| `servicediscovery:ListNamespaces` +| `servicediscovery:RegisterInstance` +| `servicediscovery:CreateService` +| `servicediscovery:DeregisterInstance` +| `servicediscovery:DeleteService` +| `servicediscovery:GetNamespace` +| `servicediscovery:GetInstance` +| `servicediscovery:GetService` +| `servicediscovery:ListInstances` +|=== + +Sample IAM policy: + +[source,json,indent=0] +---- +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Sid1", + "Effect": "Allow", + "Action": [ + "servicediscovery:ListServices", + "servicediscovery:GetOperation", + "servicediscovery:DiscoverInstances", + "servicediscovery:DeleteNamespace", + "servicediscovery:ListNamespaces", + "servicediscovery:RegisterInstance", + "servicediscovery:CreateService", + "servicediscovery:DeregisterInstance", + "servicediscovery:DeleteService", + "servicediscovery:GetNamespace", + "servicediscovery:GetInstance", + "servicediscovery:GetService", + "servicediscovery:ListInstances" + ], + "Resource": "*" + } + ] +} +---- + diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/AwsCloudMapStoreClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/AwsCloudMapStoreClientCustomizer.java new file mode 100644 index 000000000..eeffa310b --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/AwsCloudMapStoreClientCustomizer.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 io.awspring.cloud.autoconfigure.cloudmap; + +import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; +import software.amazon.awssdk.services.ssm.SsmClientBuilder; + +/** + * @author Hari Ohm Prasath + * @since 3.0.0 + */ +public interface AwsCloudMapStoreClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java similarity index 90% rename from spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java rename to spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java index bb37b6084..9aac8e2bd 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapBootstrapConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java @@ -22,7 +22,6 @@ import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import org.springframework.beans.factory.ObjectProvider; -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; @@ -40,14 +39,13 @@ */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(CloudMapProperties.class) -@ConditionalOnClass({ ServiceDiscoveryClient.class, ServiceRegistration.class, CloudMapAutoRegistration.class }) @ConditionalOnProperty(prefix = CloudMapProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true) -public class CloudMapBootstrapConfiguration { +public class CloudMapAutoConfiguration { private final ApplicationContext context; private final CloudMapProperties properties; - public CloudMapBootstrapConfiguration(CloudMapProperties properties, ApplicationContext context) { + public CloudMapAutoConfiguration(CloudMapProperties properties, ApplicationContext context) { this.properties = properties; this.context = context; } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java new file mode 100644 index 000000000..a9f491426 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java @@ -0,0 +1,34 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CloudMapEventPublisherFactory { + + @Bean + public ApplicationEventPublisher createListener() { + return new ApplicationEventPublisher() { + @Override + public void publishEvent(Object event) { + + } + }; + } +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java index d30b6adec..1126773c8 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java @@ -36,7 +36,7 @@ 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; +import org.springframework.web.client.RestTemplate; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ec2.Ec2Client; @@ -146,7 +146,7 @@ public class CloudMapUtils { final ObjectMapper JSON_MAPPER = new ObjectMapper(); - private final WebClient WEB_CLIENT = WebClient.create(); + private RestTemplate restTemplate; private Ec2Client ec2Client; @@ -157,7 +157,8 @@ public class CloudMapUtils { * @return map containing ip address and vpcid */ public Map getRegistrationAttributes() { - String deploymentPlatform = System.getenv(DEPLOYMENT_PLATFORM); + String deploymentPlatform = System.getenv(DEPLOYMENT_PLATFORM) == null ? System.getProperty(DEPLOYMENT_PLATFORM) + : System.getenv(DEPLOYMENT_PLATFORM); LOGGER.info("Deployment platform passed in {} ", deploymentPlatform); if (StringUtils.hasText(deploymentPlatform) && EKS.equalsIgnoreCase(deploymentPlatform.trim())) return getEksRegistrationAttributes(); @@ -544,7 +545,7 @@ private Map getEcsRegistrationAttributes() { .describeSubnets(DescribeSubnetsRequest.builder() .filters(Filter.builder().name("cidr-block").values(cidrBlock).build()).build()) .subnets().get(0).vpcId(); - LOGGER.info("IPv4Address {} - CIDR Block {} - VPC ID {}", ipv4Address, cidrBlock, vpcId); + LOGGER.info("IPv4Address {} - VPC ID {}", ipv4Address, vpcId); return getCloudMapAttributes(ipv4Address, vpcId); } catch (Exception e) { @@ -559,7 +560,7 @@ private Map getEcsRegistrationAttributes() { * @return response as string */ private String getUrlResponse(String url) { - return WEB_CLIENT.get().uri(url).retrieve().bodyToMono(String.class).block(); + return getRestTemplate().getForEntity(url, String.class).getBody(); } /** @@ -587,4 +588,22 @@ Ec2Client getEc2Client() { return ec2Client; } + /** + * Get Rest Template + * @return restTemplate + */ + RestTemplate getRestTemplate() { + if (restTemplate == null) { + restTemplate = new RestTemplate(); + } + return restTemplate; + } + + void setRestTemplate(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + void setEc2Client(Ec2Client ec2Client) { + this.ec2Client = ec2Client; + } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java index 34f018b4d..9021a7d51 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/CloudMapDiscoveryClient.java @@ -55,7 +55,7 @@ public String description() { @Override public List getServices() { final List discoveryProperties = properties.getDiscovery().getDiscoveryList(); - if (discoveryProperties != null && !discoveryProperties.isEmpty()) { + if (!discoveryProperties.isEmpty()) { return UTILS.listServices(serviceDiscovery, discoveryProperties); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/package-info.java new file mode 100644 index 000000000..ee1ba4995 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/discovery/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Discovery client implementation for cloud map + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package io.awspring.cloud.autoconfigure.cloudmap.discovery; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/package-info.java new file mode 100644 index 000000000..75e1b6e37 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Spring cloud Cloud map auto configuration and implementation classes + */ +package io.awspring.cloud.autoconfigure.cloudmap; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java index 9f245dd97..785b3af8b 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java @@ -18,10 +18,8 @@ import io.awspring.cloud.autoconfigure.AwsClientProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscovery; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; -import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; +import org.springframework.boot.context.properties.NestedConfigurationProperty; /** * POJO to capture all cloudmap integration parameters (both registry and discovery). @@ -30,46 +28,19 @@ * @since 3.0 */ @ConfigurationProperties(CloudMapProperties.CONFIG_PREFIX) -public class CloudMapProperties extends AwsClientProperties implements EnvironmentAware { +public class CloudMapProperties extends AwsClientProperties { /** * Default cloudmap prefix. */ public static final String CONFIG_PREFIX = "spring.cloud.aws.cloudmap"; + @NestedConfigurationProperty private CloudMapRegistryProperties registry; + @NestedConfigurationProperty 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; } @@ -86,35 +57,10 @@ 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 + '\'' + '}'; + return "AwsCloudMapProperties{" + "registry=" + registry + ", discovery=" + discovery + ", region='" + + getRegion() + "'}"; } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java index 02e8e2780..5e91ad17d 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/CloudMapDiscovery.java @@ -25,19 +25,8 @@ */ 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; } @@ -48,7 +37,7 @@ public void setDiscoveryList(List discoveryList) { @Override public String toString() { - return "AwsCloudMapDiscovery{" + "failFast=" + failFast + ", discoveryList=" + discoveryList.toString() + '}'; + return "AwsCloudMapDiscovery{" + "discoveryList=" + discoveryList.toString() + '}'; } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/package-info.java new file mode 100644 index 000000000..1f9c8369c --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/discovery/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Properties to capture all the discovery parameters + */ +package io.awspring.cloud.autoconfigure.cloudmap.properties.discovery; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/package-info.java new file mode 100644 index 000000000..286e0aaea --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Properties to capture cloud map both discovery and registration properties + */ +package io.awspring.cloud.autoconfigure.cloudmap.properties; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/package-info.java new file mode 100644 index 000000000..83ec71459 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Registration client implementation for cloud map + */ +package io.awspring.cloud.autoconfigure.cloudmap.properties.registration; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java index a5c6017e9..2a650b533 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java @@ -17,6 +17,7 @@ import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; @@ -29,6 +30,7 @@ import org.springframework.context.event.SmartApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; +import org.springframework.lang.Nullable; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; public class CloudMapAutoRegistration @@ -44,9 +46,10 @@ public class CloudMapAutoRegistration private final CloudMapUtils UTILS = CloudMapUtils.getInstance(); + @Nullable private Environment environment; - private Map attributesMap; + private Map attributesMap = new HashMap<>(); public CloudMapAutoRegistration(ApplicationContext context, ServiceDiscoveryClient serviceDiscovery, CloudMapRegistryProperties properties) { @@ -97,7 +100,7 @@ public void start() { @Override public void stop() { - if (this.running.get() && attributesMap != null && attributesMap.containsKey(UTILS.SERVICE_INSTANCE_ID)) { + if (this.running.get() && !attributesMap.isEmpty() && attributesMap.containsKey(UTILS.SERVICE_INSTANCE_ID)) { UTILS.deregisterInstance(serviceDiscovery, attributesMap); this.running.set(false); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/package-info.java new file mode 100644 index 000000000..81c5e3ad3 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Auto register implementation for cloud map + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package io.awspring.cloud.autoconfigure.cloudmap.registration; diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories index 7c17e2757..152a9da19 100644 --- a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories @@ -9,7 +9,7 @@ io.awspring.cloud.autoconfigure.s3.S3AutoConfiguration,\ io.awspring.cloud.autoconfigure.sns.SnsAutoConfiguration,\ io.awspring.cloud.autoconfigure.sqs.SqsAutoConfiguration,\ io.awspring.cloud.autoconfigure.dynamodb.DynamoDbAutoConfiguration,\ -io.awspring.cloud.autoconfigure.cloudmap.CloudMapBootstrapConfiguration +io.awspring.cloud.autoconfigure.cloudmap.CloudMapAutoConfiguration # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java index 5eb964d58..353bf2cdf 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.ServiceRegistration; import java.util.Collections; @@ -28,6 +29,12 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; +import software.amazon.awssdk.services.ec2.model.Subnet; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; import software.amazon.awssdk.services.servicediscovery.model.CreatePrivateDnsNamespaceRequest; import software.amazon.awssdk.services.servicediscovery.model.CreatePrivateDnsNamespaceResponse; @@ -57,12 +64,23 @@ */ public class CloudMapRegisterServiceTest { + private static final String DEPLOYMENT_PLATFORM = "DEPLOYMENT_PLATFORM"; + private static final String ECS = "ECS"; + private static final String EKS = "EKS"; private final ServiceDiscoveryClient serviceDiscovery = mock(ServiceDiscoveryClient.class); + private final Ec2Client ec2Client = mock(Ec2Client.class); private final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + private final RestTemplate restTemplate = mock(RestTemplate.class); + private final Environment environment = mock(Environment.class); + public CloudMapRegisterServiceTest() { + cloudMapUtils.setEc2Client(ec2Client); + cloudMapUtils.setRestTemplate(restTemplate); + } + @Test public void cloudMapRegisterInstancesNameSpaceAndServiceExists() { final ListNamespacesResponse response = getListNamespacesResponse(); @@ -77,16 +95,25 @@ public void cloudMapRegisterInstancesNameSpaceAndServiceExists() { when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + + System.setProperty(DEPLOYMENT_PLATFORM, ECS); + when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) + .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); + when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( + DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); } @Test - public void cloudMapRegisterInstanceWithNoNameSpace() { + public void cloudMapRegisterInstanceEcsWithNoNameSpace() { final ListNamespacesResponse listNamespacesResponse = getListNamespacesResponse(); final ListServicesResponse listServicesResponse = getListServicesResponse(); final RegisterInstanceResponse registerInstanceRequest = getRegisterInstanceResponse(); final GetOperationResponse operationResponse = getOperationResponse(); final CreatePrivateDnsNamespaceResponse createPrivateDnsNamespaceResponse = getCreatePrivateDnsNamespaceResponse(); + cloudMapUtils.setRestTemplate(restTemplate); + cloudMapUtils.setEc2Client(ec2Client); when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn( ListNamespacesResponse.builder().namespaces(Collections.emptyList()).build(), listNamespacesResponse); @@ -100,6 +127,50 @@ public void cloudMapRegisterInstanceWithNoNameSpace() { when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + + System.setProperty(DEPLOYMENT_PLATFORM, EKS); + when(restTemplate.getForEntity(String.format("%s/local-ipv4", CloudMapUtils.EC2_METADATA_URL), String.class)) + .thenReturn(ResponseEntity.ok("10.1.1.1")); + when(restTemplate.getForEntity(String.format("%s/network/interfaces/macs", CloudMapUtils.EC2_METADATA_URL), + String.class)).thenReturn(ResponseEntity.ok("MacId/Id")); + when(restTemplate.getForEntity( + String.format("%s/network/interfaces/macs/%s/vpc-id", CloudMapUtils.EC2_METADATA_URL, "MacId"), + String.class)).thenReturn(ResponseEntity.ok("vpcId")); + when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( + DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); + + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + } + + @Test + public void cloudMapRegisterInstanceEksWithNoNameSpace() { + final ListNamespacesResponse listNamespacesResponse = getListNamespacesResponse(); + final ListServicesResponse listServicesResponse = getListServicesResponse(); + final RegisterInstanceResponse registerInstanceRequest = getRegisterInstanceResponse(); + final GetOperationResponse operationResponse = getOperationResponse(); + final CreatePrivateDnsNamespaceResponse createPrivateDnsNamespaceResponse = getCreatePrivateDnsNamespaceResponse(); + cloudMapUtils.setRestTemplate(restTemplate); + cloudMapUtils.setEc2Client(ec2Client); + + when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn( + ListNamespacesResponse.builder().namespaces(Collections.emptyList()).build(), listNamespacesResponse); + when(serviceDiscovery.createPrivateDnsNamespace(any(CreatePrivateDnsNamespaceRequest.class))) + .thenReturn(createPrivateDnsNamespaceResponse); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + when(serviceDiscovery.registerInstance((any(RegisterInstanceRequest.class)))) + .thenReturn(registerInstanceRequest); + when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + + System.setProperty(DEPLOYMENT_PLATFORM, ECS); + when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) + .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); + when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( + DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); } @@ -123,9 +194,29 @@ public void cloudMapRegisterInstancesWithNoService() { .thenReturn(registerInstanceResponse); when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); + System.setProperty(DEPLOYMENT_PLATFORM, ECS); + when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) + .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); + when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( + DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); } + @Test + public void listService() { + CloudMapDiscoveryProperties cloudMapDiscoveryProperties = new CloudMapDiscoveryProperties(); + cloudMapDiscoveryProperties.setService(CloudMapTestUtils.SERVICE); + cloudMapDiscoveryProperties.setNameSpace(CloudMapTestUtils.NAMESPACE); + final ListNamespacesResponse listNamespacesResponse = getListNamespacesResponse(); + final ListServicesResponse listServicesResponse = getListServicesResponse(); + + when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn(listNamespacesResponse); + when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); + + cloudMapUtils.listServices(serviceDiscovery, Collections.singletonList(cloudMapDiscoveryProperties)); + } + @Test public void deRegisterInstances() { try { @@ -173,12 +264,6 @@ private ListNamespacesResponse getListNamespacesResponse() { return ListNamespacesResponse.builder().namespaces(Collections.singleton(summary)).build(); } - 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); diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java new file mode 100644 index 000000000..d0b7810d6 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java @@ -0,0 +1,65 @@ +/* + * 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 io.awspring.cloud.autoconfigure.cloudmap; + +/** + * Mock Responses from Ec2 meta-data endpoint + */ +public class CloudMapTestConstants { + /* + * Mock response + */ + public static final String ECS_REPONSE_JSON = "{\n" + " \"Cluster\": \"default\",\n" + + " \"TaskARN\": \"arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3\",\n" + + " \"Family\": \"nginx\",\n" + " \"Revision\": \"5\",\n" + " \"DesiredStatus\": \"RUNNING\",\n" + + " \"KnownStatus\": \"RUNNING\",\n" + " \"Containers\": [\n" + " {\n" + + " \"DockerId\": \"731a0d6a3b4210e2448339bc7015aaa79bfe4fa256384f4102db86ef94cbbc4c\",\n" + + " \"Name\": \"~internal~ecs~pause\",\n" + + " \"DockerName\": \"ecs-nginx-5-internalecspause-acc699c0cbf2d6d11700\",\n" + + " \"Image\": \"amazon/amazon-ecs-pause:0.1.0\",\n" + " \"ImageID\": \"\",\n" + + " \"Labels\": {\n" + " \"com.amazonaws.ecs.cluster\": \"default\",\n" + + " \"com.amazonaws.ecs.container-name\": \"~internal~ecs~pause\",\n" + + " \"com.amazonaws.ecs.task-arn\": \"arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3\",\n" + + " \"com.amazonaws.ecs.task-definition-family\": \"nginx\",\n" + + " \"com.amazonaws.ecs.task-definition-version\": \"5\"\n" + " },\n" + + " \"DesiredStatus\": \"RESOURCES_PROVISIONED\",\n" + + " \"KnownStatus\": \"RESOURCES_PROVISIONED\",\n" + " \"Limits\": {\n" + " \"CPU\": 0,\n" + + " \"Memory\": 0\n" + " },\n" + " \"CreatedAt\": \"2018-02-01T20:55:08.366329616Z\",\n" + + " \"StartedAt\": \"2018-02-01T20:55:09.058354915Z\",\n" + " \"Type\": \"CNI_PAUSE\",\n" + + " \"Networks\": [\n" + " {\n" + " \"NetworkMode\": \"awsvpc\",\n" + + " \"IPv4Addresses\": [\n" + " \"10.0.2.106\"\n" + " ],\n" + + " \"IPv4SubnetCIDRBlock\": [\n" + " \"10.0.2.106\"\n" + " ]\n" + + " }\n" + " ]\n" + " },\n" + " {\n" + + " \"DockerId\": \"43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946\",\n" + + " \"Name\": \"nginx-curl\",\n" + + " \"DockerName\": \"ecs-nginx-5-nginx-curl-ccccb9f49db0dfe0d901\",\n" + + " \"Image\": \"nrdlngr/nginx-curl\",\n" + + " \"ImageID\": \"sha256:2e00ae64383cfc865ba0a2ba37f61b50a120d2d9378559dcd458dc0de47bc165\",\n" + + " \"Labels\": {\n" + " \"com.amazonaws.ecs.cluster\": \"default\",\n" + + " \"com.amazonaws.ecs.container-name\": \"nginx-curl\",\n" + + " \"com.amazonaws.ecs.task-arn\": \"arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3\",\n" + + " \"com.amazonaws.ecs.task-definition-family\": \"nginx\",\n" + + " \"com.amazonaws.ecs.task-definition-version\": \"5\"\n" + " },\n" + + " \"DesiredStatus\": \"RUNNING\",\n" + " \"KnownStatus\": \"RUNNING\",\n" + + " \"Limits\": {\n" + " \"CPU\": 512,\n" + " \"Memory\": 512\n" + " },\n" + + " \"CreatedAt\": \"2018-02-01T20:55:10.554941919Z\",\n" + + " \"StartedAt\": \"2018-02-01T20:55:11.064236631Z\",\n" + " \"Type\": \"NORMAL\",\n" + + " \"Networks\": [\n" + " {\n" + " \"NetworkMode\": \"awsvpc\",\n" + + " \"IPv4Addresses\": [\n" + " \"10.0.2.106\"\n" + " ]\n" + " }\n" + + " ]\n" + " }\n" + " ],\n" + " \"PullStartedAt\": \"2018-02-01T20:55:09.372495529Z\",\n" + + " \"PullStoppedAt\": \"2018-02-01T20:55:10.552018345Z\",\n" + " \"AvailabilityZone\": \"us-east-2b\"\n" + + "}"; +} diff --git a/spring-cloud-aws-cloudmap/pom.xml b/spring-cloud-aws-cloudmap/pom.xml index d9a955898..b92741f34 100644 --- a/spring-cloud-aws-cloudmap/pom.xml +++ b/spring-cloud-aws-cloudmap/pom.xml @@ -44,16 +44,6 @@ 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/exceptions/package-info.java b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/package-info.java new file mode 100644 index 000000000..3c7422241 --- /dev/null +++ b/spring-cloud-aws-cloudmap/src/main/java/org/springframework/cloud/aws/cloudmap/exceptions/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * {@link org.springframework.boot.context.config.ConfigDataLoader} implementation for AWS CloudMap. + */ + +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.cloud.aws.cloudmap.exceptions; diff --git a/spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/main/java/io/awspring/cloud/s3/crossregion/AbstractCrossRegionS3Client.java b/spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/main/java/io/awspring/cloud/s3/crossregion/AbstractCrossRegionS3Client.java index 309a0f877..0ce55c8df 100644 --- a/spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/main/java/io/awspring/cloud/s3/crossregion/AbstractCrossRegionS3Client.java +++ b/spring-cloud-aws-s3-parent/spring-cloud-aws-s3-cross-region-client/src/main/java/io/awspring/cloud/s3/crossregion/AbstractCrossRegionS3Client.java @@ -16,753 +16,544 @@ package io.awspring.cloud.s3.crossregion; import java.util.function.Function; +import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.services.s3.S3Client; abstract class AbstractCrossRegionS3Client implements S3Client { - abstract R executeInBucketRegion(String bucket, Function fn); - - abstract R executeInDefaultRegion(Function fn); - - @Override - public String serviceName() { - return S3Client.SERVICE_NAME; - } - - @Override - public software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse abortMultipartUpload( - software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.abortMultipartUpload(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse completeMultipartUpload( - software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.completeMultipartUpload(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.CopyObjectResponse copyObject( - software.amazon.awssdk.services.s3.model.CopyObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.copyObject(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.CreateBucketResponse createBucket( - software.amazon.awssdk.services.s3.model.CreateBucketRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.createBucket(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse createMultipartUpload( - software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.createMultipartUpload(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketResponse deleteBucket( - software.amazon.awssdk.services.s3.model.DeleteBucketRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucket(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketAnalyticsConfigurationResponse deleteBucketAnalyticsConfiguration( - software.amazon.awssdk.services.s3.model.DeleteBucketAnalyticsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketAnalyticsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketCorsResponse deleteBucketCors( - software.amazon.awssdk.services.s3.model.DeleteBucketCorsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketCors(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketEncryptionResponse deleteBucketEncryption( - software.amazon.awssdk.services.s3.model.DeleteBucketEncryptionRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketEncryption(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketIntelligentTieringConfigurationResponse deleteBucketIntelligentTieringConfiguration( - software.amazon.awssdk.services.s3.model.DeleteBucketIntelligentTieringConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketIntelligentTieringConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketInventoryConfigurationResponse deleteBucketInventoryConfiguration( - software.amazon.awssdk.services.s3.model.DeleteBucketInventoryConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketInventoryConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketLifecycleResponse deleteBucketLifecycle( - software.amazon.awssdk.services.s3.model.DeleteBucketLifecycleRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketLifecycle(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketMetricsConfigurationResponse deleteBucketMetricsConfiguration( - software.amazon.awssdk.services.s3.model.DeleteBucketMetricsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketMetricsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketOwnershipControlsResponse deleteBucketOwnershipControls( - software.amazon.awssdk.services.s3.model.DeleteBucketOwnershipControlsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketOwnershipControls(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketPolicyResponse deleteBucketPolicy( - software.amazon.awssdk.services.s3.model.DeleteBucketPolicyRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketPolicy(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketReplicationResponse deleteBucketReplication( - software.amazon.awssdk.services.s3.model.DeleteBucketReplicationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketReplication(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketTaggingResponse deleteBucketTagging( - software.amazon.awssdk.services.s3.model.DeleteBucketTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketTagging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteBucketWebsiteResponse deleteBucketWebsite( - software.amazon.awssdk.services.s3.model.DeleteBucketWebsiteRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketWebsite(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteObjectResponse deleteObject( - software.amazon.awssdk.services.s3.model.DeleteObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObject(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteObjectTaggingResponse deleteObjectTagging( - software.amazon.awssdk.services.s3.model.DeleteObjectTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObjectTagging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeleteObjectsResponse deleteObjects( - software.amazon.awssdk.services.s3.model.DeleteObjectsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObjects(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.DeletePublicAccessBlockResponse deletePublicAccessBlock( - software.amazon.awssdk.services.s3.model.DeletePublicAccessBlockRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deletePublicAccessBlock(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationResponse getBucketAccelerateConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAccelerateConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketAclResponse getBucketAcl( - software.amazon.awssdk.services.s3.model.GetBucketAclRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAcl(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketAnalyticsConfigurationResponse getBucketAnalyticsConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketAnalyticsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAnalyticsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketCorsResponse getBucketCors( - software.amazon.awssdk.services.s3.model.GetBucketCorsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketCors(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse getBucketEncryption( - software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketEncryption(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketIntelligentTieringConfigurationResponse getBucketIntelligentTieringConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketIntelligentTieringConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketIntelligentTieringConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketInventoryConfigurationResponse getBucketInventoryConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketInventoryConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketInventoryConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationResponse getBucketLifecycleConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLifecycleConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketLocationResponse getBucketLocation( - software.amazon.awssdk.services.s3.model.GetBucketLocationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLocation(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketLoggingResponse getBucketLogging( - software.amazon.awssdk.services.s3.model.GetBucketLoggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLogging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketMetricsConfigurationResponse getBucketMetricsConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketMetricsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketMetricsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketNotificationConfigurationResponse getBucketNotificationConfiguration( - software.amazon.awssdk.services.s3.model.GetBucketNotificationConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketNotificationConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketOwnershipControlsResponse getBucketOwnershipControls( - software.amazon.awssdk.services.s3.model.GetBucketOwnershipControlsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketOwnershipControls(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketPolicyResponse getBucketPolicy( - software.amazon.awssdk.services.s3.model.GetBucketPolicyRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketPolicy(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketPolicyStatusResponse getBucketPolicyStatus( - software.amazon.awssdk.services.s3.model.GetBucketPolicyStatusRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketPolicyStatus(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketReplicationResponse getBucketReplication( - software.amazon.awssdk.services.s3.model.GetBucketReplicationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketReplication(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketRequestPaymentResponse getBucketRequestPayment( - software.amazon.awssdk.services.s3.model.GetBucketRequestPaymentRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketRequestPayment(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketTaggingResponse getBucketTagging( - software.amazon.awssdk.services.s3.model.GetBucketTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketTagging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketVersioningResponse getBucketVersioning( - software.amazon.awssdk.services.s3.model.GetBucketVersioningRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketVersioning(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetBucketWebsiteResponse getBucketWebsite( - software.amazon.awssdk.services.s3.model.GetBucketWebsiteRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketWebsite(p0)); - } - - @Override - public software.amazon.awssdk.core.ResponseInputStream getObject( - software.amazon.awssdk.services.s3.model.GetObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectResponse getObject( - software.amazon.awssdk.services.s3.model.GetObjectRequest p0, java.nio.file.Path p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0, p1)); - } - - @Override - public ReturnT getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest p0, - software.amazon.awssdk.core.sync.ResponseTransformer p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectAclResponse getObjectAcl( - software.amazon.awssdk.services.s3.model.GetObjectAclRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAcl(p0)); - } - - @Override - public software.amazon.awssdk.core.ResponseBytes getObjectAsBytes( - software.amazon.awssdk.services.s3.model.GetObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAsBytes(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectAttributesResponse getObjectAttributes( - software.amazon.awssdk.services.s3.model.GetObjectAttributesRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAttributes(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectLegalHoldResponse getObjectLegalHold( - software.amazon.awssdk.services.s3.model.GetObjectLegalHoldRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectLegalHold(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationResponse getObjectLockConfiguration( - software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectLockConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectRetentionResponse getObjectRetention( - software.amazon.awssdk.services.s3.model.GetObjectRetentionRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectRetention(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse getObjectTagging( - software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTagging(p0)); - } - - @Override - public software.amazon.awssdk.core.ResponseInputStream getObjectTorrent( - software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetObjectTorrentResponse getObjectTorrent( - software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0, java.nio.file.Path p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0, p1)); - } - - @Override - public ReturnT getObjectTorrent(software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0, - software.amazon.awssdk.core.sync.ResponseTransformer p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0, p1)); - } - - @Override - public software.amazon.awssdk.core.ResponseBytes getObjectTorrentAsBytes( - software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrentAsBytes(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.GetPublicAccessBlockResponse getPublicAccessBlock( - software.amazon.awssdk.services.s3.model.GetPublicAccessBlockRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getPublicAccessBlock(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.HeadBucketResponse headBucket( - software.amazon.awssdk.services.s3.model.HeadBucketRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.headBucket(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.HeadObjectResponse headObject( - software.amazon.awssdk.services.s3.model.HeadObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.headObject(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListBucketAnalyticsConfigurationsResponse listBucketAnalyticsConfigurations( - software.amazon.awssdk.services.s3.model.ListBucketAnalyticsConfigurationsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketAnalyticsConfigurations(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListBucketIntelligentTieringConfigurationsResponse listBucketIntelligentTieringConfigurations( - software.amazon.awssdk.services.s3.model.ListBucketIntelligentTieringConfigurationsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketIntelligentTieringConfigurations(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListBucketInventoryConfigurationsResponse listBucketInventoryConfigurations( - software.amazon.awssdk.services.s3.model.ListBucketInventoryConfigurationsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketInventoryConfigurations(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListBucketMetricsConfigurationsResponse listBucketMetricsConfigurations( - software.amazon.awssdk.services.s3.model.ListBucketMetricsConfigurationsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketMetricsConfigurations(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListBucketsResponse listBuckets( - software.amazon.awssdk.services.s3.model.ListBucketsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInDefaultRegion(s3Client -> s3Client.listBuckets(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListMultipartUploadsResponse listMultipartUploads( - software.amazon.awssdk.services.s3.model.ListMultipartUploadsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listMultipartUploads(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.paginators.ListMultipartUploadsIterable listMultipartUploadsPaginator( - software.amazon.awssdk.services.s3.model.ListMultipartUploadsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listMultipartUploadsPaginator(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse listObjectVersions( - software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectVersions(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.paginators.ListObjectVersionsIterable listObjectVersionsPaginator( - software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectVersionsPaginator(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListObjectsResponse listObjects( - software.amazon.awssdk.services.s3.model.ListObjectsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjects(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListObjectsV2Response listObjectsV2( - software.amazon.awssdk.services.s3.model.ListObjectsV2Request p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectsV2(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable listObjectsV2Paginator( - software.amazon.awssdk.services.s3.model.ListObjectsV2Request p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectsV2Paginator(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.ListPartsResponse listParts( - software.amazon.awssdk.services.s3.model.ListPartsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listParts(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.paginators.ListPartsIterable listPartsPaginator( - software.amazon.awssdk.services.s3.model.ListPartsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listPartsPaginator(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationResponse putBucketAccelerateConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAccelerateConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketAclResponse putBucketAcl( - software.amazon.awssdk.services.s3.model.PutBucketAclRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAcl(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketAnalyticsConfigurationResponse putBucketAnalyticsConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketAnalyticsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAnalyticsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketCorsResponse putBucketCors( - software.amazon.awssdk.services.s3.model.PutBucketCorsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketCors(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketEncryptionResponse putBucketEncryption( - software.amazon.awssdk.services.s3.model.PutBucketEncryptionRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketEncryption(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketIntelligentTieringConfigurationResponse putBucketIntelligentTieringConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketIntelligentTieringConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketIntelligentTieringConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketInventoryConfigurationResponse putBucketInventoryConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketInventoryConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketInventoryConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationResponse putBucketLifecycleConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketLifecycleConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketLoggingResponse putBucketLogging( - software.amazon.awssdk.services.s3.model.PutBucketLoggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketLogging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketMetricsConfigurationResponse putBucketMetricsConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketMetricsConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketMetricsConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketNotificationConfigurationResponse putBucketNotificationConfiguration( - software.amazon.awssdk.services.s3.model.PutBucketNotificationConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketNotificationConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketOwnershipControlsResponse putBucketOwnershipControls( - software.amazon.awssdk.services.s3.model.PutBucketOwnershipControlsRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketOwnershipControls(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketPolicyResponse putBucketPolicy( - software.amazon.awssdk.services.s3.model.PutBucketPolicyRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketPolicy(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketReplicationResponse putBucketReplication( - software.amazon.awssdk.services.s3.model.PutBucketReplicationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketReplication(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketRequestPaymentResponse putBucketRequestPayment( - software.amazon.awssdk.services.s3.model.PutBucketRequestPaymentRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketRequestPayment(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketTaggingResponse putBucketTagging( - software.amazon.awssdk.services.s3.model.PutBucketTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketTagging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketVersioningResponse putBucketVersioning( - software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketVersioning(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutBucketWebsiteResponse putBucketWebsite( - software.amazon.awssdk.services.s3.model.PutBucketWebsiteRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketWebsite(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectResponse putObject( - software.amazon.awssdk.services.s3.model.PutObjectRequest p0, java.nio.file.Path p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObject(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectResponse putObject( - software.amazon.awssdk.services.s3.model.PutObjectRequest p0, - software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObject(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectAclResponse putObjectAcl( - software.amazon.awssdk.services.s3.model.PutObjectAclRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectAcl(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectLegalHoldResponse putObjectLegalHold( - software.amazon.awssdk.services.s3.model.PutObjectLegalHoldRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectLegalHold(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectLockConfigurationResponse putObjectLockConfiguration( - software.amazon.awssdk.services.s3.model.PutObjectLockConfigurationRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectLockConfiguration(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectRetentionResponse putObjectRetention( - software.amazon.awssdk.services.s3.model.PutObjectRetentionRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectRetention(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutObjectTaggingResponse putObjectTagging( - software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectTagging(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.PutPublicAccessBlockResponse putPublicAccessBlock( - software.amazon.awssdk.services.s3.model.PutPublicAccessBlockRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putPublicAccessBlock(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.RestoreObjectResponse restoreObject( - software.amazon.awssdk.services.s3.model.RestoreObjectRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.restoreObject(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.UploadPartResponse uploadPart( - software.amazon.awssdk.services.s3.model.UploadPartRequest p0, java.nio.file.Path p1) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPart(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.UploadPartResponse uploadPart( - software.amazon.awssdk.services.s3.model.UploadPartRequest p0, - software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPart(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.UploadPartCopyResponse uploadPartCopy( - software.amazon.awssdk.services.s3.model.UploadPartCopyRequest p0) - throws AwsServiceException, SdkClientException { - return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPartCopy(p0)); - } - - @Override - public software.amazon.awssdk.services.s3.model.WriteGetObjectResponseResponse writeGetObjectResponse( - software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest p0, java.nio.file.Path p1) - throws AwsServiceException, SdkClientException { - return executeInDefaultRegion(s3Client -> s3Client.writeGetObjectResponse(p0, p1)); - } - - @Override - public software.amazon.awssdk.services.s3.model.WriteGetObjectResponseResponse writeGetObjectResponse( - software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest p0, - software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { - return executeInDefaultRegion(s3Client -> s3Client.writeGetObjectResponse(p0, p1)); - } + abstract R executeInBucketRegion(String bucket, Function fn); + + abstract R executeInDefaultRegion(Function fn); + + @Override + public String serviceName() { + return S3Client.SERVICE_NAME; + } + + @Override + public software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse abortMultipartUpload(software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.abortMultipartUpload(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse completeMultipartUpload(software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.completeMultipartUpload(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.CopyObjectResponse copyObject(software.amazon.awssdk.services.s3.model.CopyObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.copyObject(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.CreateBucketResponse createBucket(software.amazon.awssdk.services.s3.model.CreateBucketRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.createBucket(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse createMultipartUpload(software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.createMultipartUpload(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketResponse deleteBucket(software.amazon.awssdk.services.s3.model.DeleteBucketRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucket(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketAnalyticsConfigurationResponse deleteBucketAnalyticsConfiguration(software.amazon.awssdk.services.s3.model.DeleteBucketAnalyticsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketAnalyticsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketCorsResponse deleteBucketCors(software.amazon.awssdk.services.s3.model.DeleteBucketCorsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketCors(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketEncryptionResponse deleteBucketEncryption(software.amazon.awssdk.services.s3.model.DeleteBucketEncryptionRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketEncryption(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketIntelligentTieringConfigurationResponse deleteBucketIntelligentTieringConfiguration(software.amazon.awssdk.services.s3.model.DeleteBucketIntelligentTieringConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketIntelligentTieringConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketInventoryConfigurationResponse deleteBucketInventoryConfiguration(software.amazon.awssdk.services.s3.model.DeleteBucketInventoryConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketInventoryConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketLifecycleResponse deleteBucketLifecycle(software.amazon.awssdk.services.s3.model.DeleteBucketLifecycleRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketLifecycle(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketMetricsConfigurationResponse deleteBucketMetricsConfiguration(software.amazon.awssdk.services.s3.model.DeleteBucketMetricsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketMetricsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketOwnershipControlsResponse deleteBucketOwnershipControls(software.amazon.awssdk.services.s3.model.DeleteBucketOwnershipControlsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketOwnershipControls(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketPolicyResponse deleteBucketPolicy(software.amazon.awssdk.services.s3.model.DeleteBucketPolicyRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketPolicy(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketReplicationResponse deleteBucketReplication(software.amazon.awssdk.services.s3.model.DeleteBucketReplicationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketReplication(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketTaggingResponse deleteBucketTagging(software.amazon.awssdk.services.s3.model.DeleteBucketTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketTagging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteBucketWebsiteResponse deleteBucketWebsite(software.amazon.awssdk.services.s3.model.DeleteBucketWebsiteRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteBucketWebsite(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteObjectResponse deleteObject(software.amazon.awssdk.services.s3.model.DeleteObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObject(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteObjectTaggingResponse deleteObjectTagging(software.amazon.awssdk.services.s3.model.DeleteObjectTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObjectTagging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeleteObjectsResponse deleteObjects(software.amazon.awssdk.services.s3.model.DeleteObjectsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deleteObjects(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.DeletePublicAccessBlockResponse deletePublicAccessBlock(software.amazon.awssdk.services.s3.model.DeletePublicAccessBlockRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.deletePublicAccessBlock(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationResponse getBucketAccelerateConfiguration(software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAccelerateConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketAclResponse getBucketAcl(software.amazon.awssdk.services.s3.model.GetBucketAclRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAcl(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketAnalyticsConfigurationResponse getBucketAnalyticsConfiguration(software.amazon.awssdk.services.s3.model.GetBucketAnalyticsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketAnalyticsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketCorsResponse getBucketCors(software.amazon.awssdk.services.s3.model.GetBucketCorsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketCors(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse getBucketEncryption(software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketEncryption(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketIntelligentTieringConfigurationResponse getBucketIntelligentTieringConfiguration(software.amazon.awssdk.services.s3.model.GetBucketIntelligentTieringConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketIntelligentTieringConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketInventoryConfigurationResponse getBucketInventoryConfiguration(software.amazon.awssdk.services.s3.model.GetBucketInventoryConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketInventoryConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationResponse getBucketLifecycleConfiguration(software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLifecycleConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketLocationResponse getBucketLocation(software.amazon.awssdk.services.s3.model.GetBucketLocationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLocation(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketLoggingResponse getBucketLogging(software.amazon.awssdk.services.s3.model.GetBucketLoggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketLogging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketMetricsConfigurationResponse getBucketMetricsConfiguration(software.amazon.awssdk.services.s3.model.GetBucketMetricsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketMetricsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketNotificationConfigurationResponse getBucketNotificationConfiguration(software.amazon.awssdk.services.s3.model.GetBucketNotificationConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketNotificationConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketOwnershipControlsResponse getBucketOwnershipControls(software.amazon.awssdk.services.s3.model.GetBucketOwnershipControlsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketOwnershipControls(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketPolicyResponse getBucketPolicy(software.amazon.awssdk.services.s3.model.GetBucketPolicyRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketPolicy(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketPolicyStatusResponse getBucketPolicyStatus(software.amazon.awssdk.services.s3.model.GetBucketPolicyStatusRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketPolicyStatus(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketReplicationResponse getBucketReplication(software.amazon.awssdk.services.s3.model.GetBucketReplicationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketReplication(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketRequestPaymentResponse getBucketRequestPayment(software.amazon.awssdk.services.s3.model.GetBucketRequestPaymentRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketRequestPayment(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketTaggingResponse getBucketTagging(software.amazon.awssdk.services.s3.model.GetBucketTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketTagging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketVersioningResponse getBucketVersioning(software.amazon.awssdk.services.s3.model.GetBucketVersioningRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketVersioning(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetBucketWebsiteResponse getBucketWebsite(software.amazon.awssdk.services.s3.model.GetBucketWebsiteRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getBucketWebsite(p0)); + } + + @Override + public software.amazon.awssdk.core.ResponseInputStream getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectResponse getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest p0, java.nio.file.Path p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0, p1)); + } + + @Override + public ReturnT getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest p0, software.amazon.awssdk.core.sync.ResponseTransformer p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObject(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectAclResponse getObjectAcl(software.amazon.awssdk.services.s3.model.GetObjectAclRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAcl(p0)); + } + + @Override + public software.amazon.awssdk.core.ResponseBytes getObjectAsBytes(software.amazon.awssdk.services.s3.model.GetObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAsBytes(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectAttributesResponse getObjectAttributes(software.amazon.awssdk.services.s3.model.GetObjectAttributesRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectAttributes(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectLegalHoldResponse getObjectLegalHold(software.amazon.awssdk.services.s3.model.GetObjectLegalHoldRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectLegalHold(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationResponse getObjectLockConfiguration(software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectLockConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectRetentionResponse getObjectRetention(software.amazon.awssdk.services.s3.model.GetObjectRetentionRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectRetention(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse getObjectTagging(software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTagging(p0)); + } + + @Override + public software.amazon.awssdk.core.ResponseInputStream getObjectTorrent(software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetObjectTorrentResponse getObjectTorrent(software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0, java.nio.file.Path p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0, p1)); + } + + @Override + public ReturnT getObjectTorrent(software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0, software.amazon.awssdk.core.sync.ResponseTransformer p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrent(p0, p1)); + } + + @Override + public software.amazon.awssdk.core.ResponseBytes getObjectTorrentAsBytes(software.amazon.awssdk.services.s3.model.GetObjectTorrentRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getObjectTorrentAsBytes(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.GetPublicAccessBlockResponse getPublicAccessBlock(software.amazon.awssdk.services.s3.model.GetPublicAccessBlockRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.getPublicAccessBlock(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.HeadBucketResponse headBucket(software.amazon.awssdk.services.s3.model.HeadBucketRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.headBucket(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.HeadObjectResponse headObject(software.amazon.awssdk.services.s3.model.HeadObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.headObject(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListBucketAnalyticsConfigurationsResponse listBucketAnalyticsConfigurations(software.amazon.awssdk.services.s3.model.ListBucketAnalyticsConfigurationsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketAnalyticsConfigurations(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListBucketIntelligentTieringConfigurationsResponse listBucketIntelligentTieringConfigurations(software.amazon.awssdk.services.s3.model.ListBucketIntelligentTieringConfigurationsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketIntelligentTieringConfigurations(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListBucketInventoryConfigurationsResponse listBucketInventoryConfigurations(software.amazon.awssdk.services.s3.model.ListBucketInventoryConfigurationsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketInventoryConfigurations(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListBucketMetricsConfigurationsResponse listBucketMetricsConfigurations(software.amazon.awssdk.services.s3.model.ListBucketMetricsConfigurationsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listBucketMetricsConfigurations(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListBucketsResponse listBuckets(software.amazon.awssdk.services.s3.model.ListBucketsRequest p0) throws AwsServiceException, SdkClientException { + return executeInDefaultRegion(s3Client -> s3Client.listBuckets(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListMultipartUploadsResponse listMultipartUploads(software.amazon.awssdk.services.s3.model.ListMultipartUploadsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listMultipartUploads(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.paginators.ListMultipartUploadsIterable listMultipartUploadsPaginator(software.amazon.awssdk.services.s3.model.ListMultipartUploadsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listMultipartUploadsPaginator(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse listObjectVersions(software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectVersions(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.paginators.ListObjectVersionsIterable listObjectVersionsPaginator(software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectVersionsPaginator(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListObjectsResponse listObjects(software.amazon.awssdk.services.s3.model.ListObjectsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjects(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListObjectsV2Response listObjectsV2(software.amazon.awssdk.services.s3.model.ListObjectsV2Request p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectsV2(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable listObjectsV2Paginator(software.amazon.awssdk.services.s3.model.ListObjectsV2Request p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listObjectsV2Paginator(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.ListPartsResponse listParts(software.amazon.awssdk.services.s3.model.ListPartsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listParts(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.paginators.ListPartsIterable listPartsPaginator(software.amazon.awssdk.services.s3.model.ListPartsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.listPartsPaginator(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationResponse putBucketAccelerateConfiguration(software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAccelerateConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketAclResponse putBucketAcl(software.amazon.awssdk.services.s3.model.PutBucketAclRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAcl(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketAnalyticsConfigurationResponse putBucketAnalyticsConfiguration(software.amazon.awssdk.services.s3.model.PutBucketAnalyticsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketAnalyticsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketCorsResponse putBucketCors(software.amazon.awssdk.services.s3.model.PutBucketCorsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketCors(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketEncryptionResponse putBucketEncryption(software.amazon.awssdk.services.s3.model.PutBucketEncryptionRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketEncryption(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketIntelligentTieringConfigurationResponse putBucketIntelligentTieringConfiguration(software.amazon.awssdk.services.s3.model.PutBucketIntelligentTieringConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketIntelligentTieringConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketInventoryConfigurationResponse putBucketInventoryConfiguration(software.amazon.awssdk.services.s3.model.PutBucketInventoryConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketInventoryConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationResponse putBucketLifecycleConfiguration(software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketLifecycleConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketLoggingResponse putBucketLogging(software.amazon.awssdk.services.s3.model.PutBucketLoggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketLogging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketMetricsConfigurationResponse putBucketMetricsConfiguration(software.amazon.awssdk.services.s3.model.PutBucketMetricsConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketMetricsConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketNotificationConfigurationResponse putBucketNotificationConfiguration(software.amazon.awssdk.services.s3.model.PutBucketNotificationConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketNotificationConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketOwnershipControlsResponse putBucketOwnershipControls(software.amazon.awssdk.services.s3.model.PutBucketOwnershipControlsRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketOwnershipControls(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketPolicyResponse putBucketPolicy(software.amazon.awssdk.services.s3.model.PutBucketPolicyRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketPolicy(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketReplicationResponse putBucketReplication(software.amazon.awssdk.services.s3.model.PutBucketReplicationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketReplication(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketRequestPaymentResponse putBucketRequestPayment(software.amazon.awssdk.services.s3.model.PutBucketRequestPaymentRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketRequestPayment(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketTaggingResponse putBucketTagging(software.amazon.awssdk.services.s3.model.PutBucketTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketTagging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketVersioningResponse putBucketVersioning(software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketVersioning(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutBucketWebsiteResponse putBucketWebsite(software.amazon.awssdk.services.s3.model.PutBucketWebsiteRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putBucketWebsite(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectResponse putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest p0, java.nio.file.Path p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObject(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectResponse putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest p0, software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObject(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectAclResponse putObjectAcl(software.amazon.awssdk.services.s3.model.PutObjectAclRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectAcl(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectLegalHoldResponse putObjectLegalHold(software.amazon.awssdk.services.s3.model.PutObjectLegalHoldRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectLegalHold(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectLockConfigurationResponse putObjectLockConfiguration(software.amazon.awssdk.services.s3.model.PutObjectLockConfigurationRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectLockConfiguration(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectRetentionResponse putObjectRetention(software.amazon.awssdk.services.s3.model.PutObjectRetentionRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectRetention(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutObjectTaggingResponse putObjectTagging(software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putObjectTagging(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.PutPublicAccessBlockResponse putPublicAccessBlock(software.amazon.awssdk.services.s3.model.PutPublicAccessBlockRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.putPublicAccessBlock(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.RestoreObjectResponse restoreObject(software.amazon.awssdk.services.s3.model.RestoreObjectRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.restoreObject(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.UploadPartResponse uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest p0, java.nio.file.Path p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPart(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.UploadPartResponse uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest p0, software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPart(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.UploadPartCopyResponse uploadPartCopy(software.amazon.awssdk.services.s3.model.UploadPartCopyRequest p0) throws AwsServiceException, SdkClientException { + return executeInBucketRegion(p0.bucket(), s3Client -> s3Client.uploadPartCopy(p0)); + } + + @Override + public software.amazon.awssdk.services.s3.model.WriteGetObjectResponseResponse writeGetObjectResponse(software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest p0, java.nio.file.Path p1) throws AwsServiceException, SdkClientException { + return executeInDefaultRegion(s3Client -> s3Client.writeGetObjectResponse(p0, p1)); + } + + @Override + public software.amazon.awssdk.services.s3.model.WriteGetObjectResponseResponse writeGetObjectResponse(software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest p0, software.amazon.awssdk.core.sync.RequestBody p1) throws AwsServiceException, SdkClientException { + return executeInDefaultRegion(s3Client -> s3Client.writeGetObjectResponse(p0, p1)); + } } + 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..ef95ad6e1 --- /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-3.0.0-SNAPSHOT.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/README.md b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md new file mode 100644 index 000000000..7007910b2 --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md @@ -0,0 +1,255 @@ +# Spring Cloud AWS Cloud Map Sample + +Here is a step by step guide to run the sample application. + +---- + +## Prerequisites + +* Active internet connection +* Java 8 or above +* AWS credentials configured in `~/.aws/credentials` or as environment variables (see [AWS SDK documentation](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html)) +* Docker installed + +## Running the sample + +* Build the sample application + +```bash +mvn clean install +``` + +* Run a docker build to create a docker image using the `Dockerfile` defined in the root directory of this sample project: + +```bash +docker build -t aws-samples-cloudmap . +``` + +* Create a ECS Cluster using AWS CLI: + +```bash +aws ecs create-cluster --cluster-name cloudmap-sample +``` + +* Create a ECR repository using AWS CLI: + +```bash +aws ecr create-repository --repository-name cloudmap-sample +``` + +* Login to ECR, tag the docker image and push it to ECR: + +```bash +$(aws ecr get-login --no-include-email --region us-east-1) +docker tag aws-samples-cloudmap:latest .dkr.ecr.us-east-1.amazonaws.com/cloudmap-sample:latest +docker push .dkr.ecr.us-east-1.amazonaws.com/cloudmap-sample:latest +``` + +* Create a IAM role for ECS task execution: + +```bash +aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json +``` + +**task-execution-assume-role.json** + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +* Attach the following IAM policy to the role created in the previous step: + +```bash +aws iam put-role-policy --role-name ecsTaskExecutionRole --policy-name ecsTaskExecutionRole --policy-document file://task-execution-role-policy.json +``` + +**task-execution-role-policy.json** + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Effect": "Allow", + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:DescribeRepositories" + ], + "Resource": "*" + } + ] +} +``` + +* Create a ECS Task role: + +```bash +aws iam create-role --role-name ecsTaskRole --assume-role-policy-document file://task-assume-role.json +``` + +**task-assume-role.json** + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +* Attach the following IAM policy to the role created in the previous step: + +```bash +aws iam put-role-policy --role-name ecsTaskRole --policy-name ecsTaskRole --policy-document file://task-role-policy.json +``` + +**task-role-policy.json** + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "servicediscovery:CreateService", + "servicediscovery:DeleteService", + "servicediscovery:GetService", + "servicediscovery:ListInstances", + "servicediscovery:ListNamespaces", + "servicediscovery:ListServices", + "servicediscovery:RegisterInstance", + "servicediscovery:DeregisterInstance", + "servicediscovery:ListOperations", + "servicediscovery:GetOperation" + ], + "Resource": "*" + } + ] +} +``` + +* Create a Task Definition using AWS CLI: + +```bash +aws ecs register-task-definition --name cloudmap-sample-definition --cli-input-json file://task-definition.json +``` + +**Task definition file** + +```json +{ + "executionRoleArn": "arn:aws:iam:::role/ecsTaskExecutionRole", + "containerDefinitions": [ + { + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/CloudMap", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + }, + "cpu": 0, + "environment": [ + { + "name": "DEPLOYMENT_PLATFORM", + "value": "ECS" + } + ], + "image": ".dkr.ecr.us-east-1.amazonaws.com/cloudmap-sample:latest", + "name": "mainContainer" + } + ], + "memory": "7168", + "taskRoleArn": "arn:aws:iam:::role/ecsTaskRole", + "family": "CloudMap", + "requiresCompatibilities": [ + "FARGATE" + ], + "networkMode": "awsvpc", + "runtimePlatform": { + "operatingSystemFamily": "LINUX", + }, + "cpu": "2048", +} +``` + +* Run the task using AWS CLI: + +```bash +aws ecs run-task --cluster cloudmap-sample --task-definition cloudmap-sample-definition --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[],securityGroups=[],assignPublicIp=ENABLED}" +``` + +## Verify Cloud Map service registration in AWS console + +* Login to AWS console and navigate to Cloud Map service. You should see a namespace (`a-namespace`) and a service (`a-service`) created. Under the service, you should see a instance registered with the IP address of the container running in ECS. + +## Clean up + +* Stop the task using AWS CLI: + +```bash +aws ecs stop-task --cluster cloudmap-sample --task +``` + +* Delete the task definition using AWS CLI: + +```bash +aws ecs deregister-task-definition --task-definition cloudmap-sample-definition +``` + +* Delete the ECS cluster using AWS CLI: + +```bash +aws ecs delete-cluster --cluster cloudmap-sample +``` + +* Detach the associated policies + +```bash +aws iam detach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam:::policy/ecsTaskExecutionRole +aws iam detach-role-policy --role-name ecsTaskRole --policy-arn arn:aws:iam:::policy/service-role/AmazonECSTaskExecutionRolePolicy +``` + +* Delete the IAM role for ECS task execution using AWS CLI: + +```bash +aws iam delete-role --role-name ecsTaskExecutionRole +aws iam delete-role --role-name ecsTaskRole +``` + +* Delete ECR repository using AWS CLI: + +```bash +aws ecr delete-repository --repository-name cloudmap-sample --force +``` \ No newline at end of file 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 index 15693f6b4..842630ce9 100644 --- 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 @@ -1,2 +1,13 @@ -# importing cloudmap configuration files -spring.config.import=bootstrap.properties +spring.cloud.aws.cloudmap.enabled=true +spring.application.name=cloudmap-namespace-here + +# Discover existing cloudmap instances +spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=a-service +spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=a-namespace + +# Register new instance + +spring.cloud.aws.cloudmap.registry.description=Namespace for sample cloudmap registry service +spring.cloud.aws.cloudmap.registry.port=80 +spring.cloud.aws.cloudmap.registry.service=a-service +spring.cloud.aws.cloudmap.registry.nameSpace=a-namespace 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 index 8c5a60d85..88533696b 100644 --- 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 @@ -3,7 +3,6 @@ spring.cloud.aws.cloudmap.enabled=true spring.application.name=cloudmap-namespace-here # Discover existing cloudmap instances -spring.cloud.aws.cloudmap.discovery.failFast=false spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=a-service spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=a-namespace From da461c380ce66b3934d531f20a3bf4e3df017ec9 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 9 Sep 2022 10:06:15 +0200 Subject: [PATCH 6/8] Refactor `ApplicationContext` -> `ApplicationEventPublisher`. --- .../cloudmap/CloudMapAutoConfiguration.java | 11 +++--- .../CloudMapEventPublisherFactory.java | 34 ------------------- .../CloudMapAutoRegistration.java | 10 +++--- 3 files changed, 10 insertions(+), 45 deletions(-) delete mode 100644 spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java index 9aac8e2bd..6449339c8 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java @@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; @@ -42,12 +42,10 @@ @ConditionalOnProperty(prefix = CloudMapProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true) public class CloudMapAutoConfiguration { - private final ApplicationContext context; private final CloudMapProperties properties; - public CloudMapAutoConfiguration(CloudMapProperties properties, ApplicationContext context) { + public CloudMapAutoConfiguration(CloudMapProperties properties) { this.properties = properties; - this.context = context; } @ConditionalOnMissingBean @@ -60,8 +58,9 @@ public ServiceDiscoveryClient discoveryClient(AwsClientBuilderConfigurer awsClie @Bean @ConditionalOnMissingBean - CloudMapAutoRegistration createAutoRegistration(ServiceDiscoveryClient serviceDiscovery) { - return new CloudMapAutoRegistration(context, serviceDiscovery, properties.getRegistry()); + CloudMapAutoRegistration createAutoRegistration(ApplicationEventPublisher eventPublisher, + ServiceDiscoveryClient serviceDiscovery) { + return new CloudMapAutoRegistration(eventPublisher, serviceDiscovery, properties.getRegistry()); } @Bean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java deleted file mode 100644 index a9f491426..000000000 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapEventPublisherFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 io.awspring.cloud.autoconfigure.cloudmap; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CloudMapEventPublisherFactory { - - @Bean - public ApplicationEventPublisher createListener() { - return new ApplicationEventPublisher() { - @Override - public void publishEvent(Object event) { - - } - }; - } -} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java index 2a650b533..43c4a28f0 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java @@ -22,8 +22,8 @@ import java.util.concurrent.atomic.AtomicBoolean; 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.ApplicationEventPublisher; import org.springframework.context.EnvironmentAware; import org.springframework.context.SmartLifecycle; import org.springframework.context.event.ContextClosedEvent; @@ -40,7 +40,7 @@ public class CloudMapAutoRegistration private final CloudMapRegistryProperties properties; - private final ApplicationContext context; + private final ApplicationEventPublisher eventPublisher; private final AtomicBoolean running = new AtomicBoolean(false); @@ -51,9 +51,9 @@ public class CloudMapAutoRegistration private Map attributesMap = new HashMap<>(); - public CloudMapAutoRegistration(ApplicationContext context, ServiceDiscoveryClient serviceDiscovery, + public CloudMapAutoRegistration(ApplicationEventPublisher eventPublisher, ServiceDiscoveryClient serviceDiscovery, CloudMapRegistryProperties properties) { - this.context = context; + this.eventPublisher = eventPublisher; this.serviceDiscovery = serviceDiscovery; this.properties = properties; } @@ -92,7 +92,7 @@ public void start() { 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.eventPublisher.publishEvent(new InstanceRegisteredEvent<>(this, attributesMap)); this.running.set(true); } } From f6e866797119a7db1c0bd92f762afcf2a4ab44e1 Mon Sep 17 00:00:00 2001 From: Hari Ohm Prasath Rajagopal Date: Sat, 17 Sep 2022 22:33:09 -0700 Subject: [PATCH 7/8] Code refactoring and integration tests using local containers --- .../cloudmap/CloudMapAutoConfiguration.java | 21 +- .../autoconfigure/cloudmap/CloudMapUtils.java | 358 ++++++++++-------- .../properties/CloudMapProperties.java | 16 +- .../registration/ServiceRegistration.java | 10 +- .../CloudMapAutoRegistration.java | 16 +- .../cloudmap/CloudMapRegisterServiceTest.java | 38 +- .../cloudmap/CloudMapTestConstants.java | 2 + .../EcsCloudMapIntegrationTest.java | 177 +++++++++ .../EksCloudMapIntegrationTest.java | 177 +++++++++ .../integration/IntegrationTestUtil.java | 121 ++++++ 10 files changed, 742 insertions(+), 194 deletions(-) create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EcsCloudMapIntegrationTest.java create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EksCloudMapIntegrationTest.java create mode 100644 spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/IntegrationTestUtil.java diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java index 6449339c8..bf171749a 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapAutoConfiguration.java @@ -28,6 +28,9 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.Ec2ClientBuilder; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClientBuilder; @@ -56,11 +59,20 @@ public ServiceDiscoveryClient discoveryClient(AwsClientBuilderConfigurer awsClie .configure(ServiceDiscoveryClient.builder(), this.properties, configurer.getIfAvailable()).build(); } + @ConditionalOnMissingBean + @Bean + public Ec2Client ec2Client(AwsClientBuilderConfigurer awsClientBuilderConfigurer, + ObjectProvider> configurer) { + return awsClientBuilderConfigurer + .configure(Ec2Client.builder(), this.properties, configurer.getIfAvailable()).build(); + } + @Bean @ConditionalOnMissingBean CloudMapAutoRegistration createAutoRegistration(ApplicationEventPublisher eventPublisher, - ServiceDiscoveryClient serviceDiscovery) { - return new CloudMapAutoRegistration(eventPublisher, serviceDiscovery, properties.getRegistry()); + ServiceDiscoveryClient serviceDiscovery, Ec2Client ec2Client) { + return new CloudMapAutoRegistration(eventPublisher, serviceDiscovery, ec2Client, + properties.getRegistry(), properties.getDeploymentPlatform()); } @Bean @@ -71,8 +83,9 @@ CloudMapDiscoveryClient createDiscoveryClient(ServiceDiscoveryClient serviceDisc @Bean @ConditionalOnMissingBean - ServiceRegistration serviceRegistration() { - return new ServiceRegistration(properties.getRegistry()); + ServiceRegistration serviceRegistration(Ec2Client ec2Client) { + return new ServiceRegistration(properties.getRegistry(), ec2Client, + properties.getDeploymentPlatform()); } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java index 1126773c8..664853214 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapUtils.java @@ -15,11 +15,6 @@ */ package io.awspring.cloud.autoconfigure.cloudmap; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapServiceInstance; -import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; -import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -28,17 +23,14 @@ import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapServiceInstance; +import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; +import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.client.ServiceInstance; -import org.springframework.core.env.Environment; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; import software.amazon.awssdk.services.ec2.model.Filter; @@ -67,6 +59,14 @@ import software.amazon.awssdk.services.servicediscovery.model.ServiceFilter; import software.amazon.awssdk.services.servicediscovery.model.ServiceSummary; +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.client.ServiceInstance; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + /** * Uses Fargate Metadata URL to retrieve IPv4 address and VPC ID to register instances to cloudmap. * @@ -75,12 +75,36 @@ */ public class CloudMapUtils { + public static final String AWS_REGION = getProperty("AWS_REGION", "us-east-1"); + /* - * Singleton instance + * Metadata URL to retrieve IPv4 address and VPC ID for EKS */ - private static CloudMapUtils cloudMapUtils = null; - - public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data"; + public static final String EC2_METADATA = "EC2_METADATA"; + /** + * Default meta data URL for EKS + */ + public static final String EC2_METADATA_URL = getProperty(EC2_METADATA, "http://169.254.169.254/latest/meta-data"); + /** + * Default namespace for cloudmap registration + */ + public static final String DEFAULT_NAMESPACE = "default.namespace"; + /** + * Default service name for cloudmap registration + */ + public static final String DEFAULT_SERVICE = "default.service"; + /* + * Metadata URL + */ + public static final String ECS_CONTAINER_METADATA_URI_V_4 = "ECS_CONTAINER_METADATA_URI_V4"; + /* + * Deployment platform type EKS + */ + public static final String EKS = "EKS"; + /* + * Deployment platform type ECS + */ + public static final String ECS = "ECS"; /* * AWS VPC ID */ @@ -110,19 +134,9 @@ public class CloudMapUtils { */ 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 + * Singleton instance */ - public final String ECS_CONTAINER_METADATA_URI_V_4 = "ECS_CONTAINER_METADATA_URI_V4"; + private static CloudMapUtils cloudMapUtils = null; /* * Request attributes - NamespaceID @@ -148,21 +162,51 @@ public class CloudMapUtils { private RestTemplate restTemplate; - private Ec2Client ec2Client; + public static CloudMapUtils getInstance() { + if (cloudMapUtils == null) { + cloudMapUtils = new CloudMapUtils(); + } + return cloudMapUtils; + } + + /** + * Helper method to get properties from system + * @param key name of the property + * @return value of the property + */ + private static String getProperty(String key) { + return System.getenv(key) == null ? System.getProperty(key) : System.getenv(key); + } + + /** + * Helper method to fall back to default value if property is not set + * @param key name of the property + * @param defaultValue default value + * @return value of the property + */ + private static String getProperty(String key, String defaultValue) { + final String value = getProperty(key); + if (StringUtils.hasText(value)) { + return value; + } + else { + return defaultValue; + } + } /** * 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 + * @param ec2Client - AWS Ec2 client + * @param deploymentPlatform - Deployment platform * @return map containing ip address and vpcid */ - public Map getRegistrationAttributes() { - String deploymentPlatform = System.getenv(DEPLOYMENT_PLATFORM) == null ? System.getProperty(DEPLOYMENT_PLATFORM) - : System.getenv(DEPLOYMENT_PLATFORM); + public Map getRegistrationAttributes(Ec2Client ec2Client, String deploymentPlatform) { LOGGER.info("Deployment platform passed in {} ", deploymentPlatform); if (StringUtils.hasText(deploymentPlatform) && EKS.equalsIgnoreCase(deploymentPlatform.trim())) return getEksRegistrationAttributes(); - return getEcsRegistrationAttributes(); + return getEcsRegistrationAttributes(ec2Client); } /** @@ -175,13 +219,13 @@ public String getNameSpaceId(final ServiceDiscoveryClient serviceDiscovery, fina String token; do { ListNamespacesResponse nameSpaceResponse = serviceDiscovery - .listNamespaces(ListNamespacesRequest.builder().build()); + .listNamespaces(ListNamespacesRequest.builder().build()); token = nameSpaceResponse.nextToken(); List namespaceSummaries = nameSpaceResponse.namespaces(); if (namespaceSummaries != null) { Optional namespaceId = namespaceSummaries.stream().filter(n -> n.name().equals(nameSpace)) - .map(NamespaceSummary::id).findFirst(); + .map(NamespaceSummary::id).findFirst(); if (namespaceId.isPresent()) return namespaceId.get(); } @@ -200,7 +244,7 @@ public String getNameSpaceId(final ServiceDiscoveryClient serviceDiscovery, fina * @return list of cloudmap services */ public List listServices(final ServiceDiscoveryClient serviceDiscovery, - List discoveryProperties) { + List discoveryProperties) { final List serviceList = new ArrayList<>(); if (discoveryProperties != null && !discoveryProperties.isEmpty()) { @@ -215,22 +259,22 @@ public List listServices(final ServiceDiscoveryClient serviceDiscovery, if (StringUtils.hasText(nameSpaceId)) { // Filter cloudmap services final ServiceFilter serviceFilter = ServiceFilter.builder().name(NAMESPACE_ID).condition("EQ") - .values(nameSpaceId).build(); + .values(nameSpaceId).build(); final ListServicesRequest servicesRequest = ListServicesRequest.builder().filters(serviceFilter) - .build(); + .build(); final ListServicesResponse response = serviceDiscovery.listServices(servicesRequest); if (StringUtils.hasText(response.nextToken())) token = response.nextToken(); if (StringUtils.hasText(serviceName)) { serviceList.addAll(response.services().stream().filter(r -> r.name().equals(d.getService())) - .map(r -> generateServiceId(nameSpace, r.name())).collect(Collectors.toList())); + .map(r -> generateServiceId(nameSpace, r.name())).collect(Collectors.toList())); if (serviceList.size() == discoveryProperties.size()) return serviceList; } else serviceList.addAll(response.services().stream() - .map(r -> generateServiceId(nameSpace, r.name())).collect(Collectors.toList())); + .map(r -> generateServiceId(nameSpace, r.name())).collect(Collectors.toList())); } else LOGGER.warn("Namespace is empty"); @@ -250,9 +294,9 @@ public List listServices(final ServiceDiscoveryClient serviceDiscovery, * @return list of http instances */ public List listInstances(final ServiceDiscoveryClient serviceDiscovery, - final String namespace, String serviceName) { + final String namespace, String serviceName) { final DiscoverInstancesRequest dRequest = DiscoverInstancesRequest.builder().namespaceName(namespace) - .serviceName(serviceName).build(); + .serviceName(serviceName).build(); return serviceDiscovery.discoverInstances(dRequest).instances(); } @@ -270,83 +314,81 @@ public ServiceInstance getServiceInstance(HttpInstanceSummary 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 ec2Client - AWS Ec2 client * @param properties Cloud map registry properties * @param environment Spring environment + * @param deploymentPlatform Deployment platform * @return map of registration properties */ public Map registerInstance(final ServiceDiscoveryClient 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)); - } + final Ec2Client ec2Client, final CloudMapRegistryProperties properties, + final Environment environment, final String deploymentPlatform) { + String nameSpace = properties != null ? properties.getNameSpace() : null; + if (!StringUtils.hasText(nameSpace)) + nameSpace = DEFAULT_NAMESPACE; - // 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); - } + String service = properties != null ? properties.getService() : null; + if (!StringUtils.hasText(service)) + service = environment.getProperty("spring.application.name"); - 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); + if (!StringUtils.hasText(service)) + service = DEFAULT_SERVICE; - // Register instance - final String operationId = serviceDiscovery.registerInstance(RegisterInstanceRequest.builder() - .instanceId(serviceInstanceId).serviceId(serviceId).attributes(attributes).build()) - .operationId(); - LOGGER.debug("Register instance initiated, polling for completion {}", operationId); + final String serviceInstanceId = UUID.randomUUID().toString(); - // 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()); + LOGGER.info("Registration details namespace {} - service {} - serviceInstance {}", nameSpace, service, + serviceInstanceId); + Map registrationDetails = getRegistrationAttributes(ec2Client, deploymentPlatform); + String nameSpaceId = getNameSpaceId(serviceDiscovery, nameSpace); + try { + // Create namespace if not exists + if (!StringUtils.hasText(nameSpaceId)) { + LOGGER.debug("Namespace " + nameSpace + "not available so creating"); + nameSpaceId = createNameSpace(serviceDiscovery, nameSpace, + properties != null ? properties.getDescription() : null, + registrationDetails.get(VPC_ID)); } - catch (MaxRetryExceededException e) { - LOGGER.error("Maximum number of retry exceeded for registering instance with {} for {}", nameSpace, - service, e); + + // 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, 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(RegisterInstanceRequest.builder() + .instanceId(serviceInstanceId).serviceId(serviceId).attributes(attributes).build()) + .operationId(); + LOGGER.debug("Register instance initiated, polling for completion {}", operationId); + + // Poll for completion + pollForCompletion(serviceDiscovery, operationId); + + return attributes; } - else { - LOGGER.info("Service registration skipped"); + 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); } return null; @@ -355,19 +397,19 @@ public Map registerInstance(final ServiceDiscoveryClient service /** * Create Cloudmap namespace. * @param serviceDiscovery AWS Service discovery - * @param properties cloudmap properties + * @param nameSpace Cloudmap namespace + * @param description Cloudmap namespace description * @param vpcId VPC ID * @return NamespaceID * @throws CreateNameSpaceException thrown in case of runtime exception */ - private String createNameSpace(ServiceDiscoveryClient serviceDiscovery, CloudMapRegistryProperties properties, - String vpcId) throws CreateNameSpaceException { - final String nameSpace = properties.getNameSpace(); + private String createNameSpace(ServiceDiscoveryClient serviceDiscovery, String nameSpace, String description, + String vpcId) throws CreateNameSpaceException { try { // Create namespace final String operationId = serviceDiscovery.createPrivateDnsNamespace(CreatePrivateDnsNamespaceRequest - .builder().name(nameSpace).vpc(vpcId).description(properties.getDescription()).build()) - .operationId(); + .builder().name(nameSpace).vpc(vpcId).description(description).build()) + .operationId(); LOGGER.info("Creating namespace {} with operationId {}", nameSpace, operationId); // Wait till completion @@ -402,12 +444,12 @@ private String createNameSpace(ServiceDiscoveryClient serviceDiscovery, CloudMap * @throws CreateServiceException thrown in case of runtime exception */ private String createService(ServiceDiscoveryClient serviceDiscovery, String nameSpaceId, String service) - throws CreateServiceException { + throws CreateServiceException { try { CreateServiceRequest serviceRequest = CreateServiceRequest.builder().name(service).namespaceId(nameSpaceId) - .dnsConfig(DnsConfig.builder().dnsRecords(DnsRecord.builder().type(RecordType.A).ttl(300L).build()) - .build()) - .build(); + .dnsConfig(DnsConfig.builder().dnsRecords(DnsRecord.builder().type(RecordType.A).ttl(300L).build()) + .build()) + .build(); final String serviceId = serviceDiscovery.createService(serviceRequest).service().id(); LOGGER.info("Service ID create {} for {} with namespace {}", serviceId, service, nameSpaceId); @@ -433,7 +475,7 @@ public String generateServiceId(final String namespace, final String serviceName * @param attributeMap Service discovery attributes */ public void deregisterInstance(final ServiceDiscoveryClient serviceDiscovery, - final Map attributeMap) { + final Map attributeMap) { try { final String serviceInstanceId = attributeMap.get(SERVICE_INSTANCE_ID); final String serviceId = attributeMap.get(SERVICE_ID); @@ -442,7 +484,7 @@ public void deregisterInstance(final ServiceDiscoveryClient serviceDiscovery, // Deregister instance String operationId = serviceDiscovery.deregisterInstance( DeregisterInstanceRequest.builder().instanceId(serviceInstanceId).serviceId(serviceId).build()) - .operationId(); + .operationId(); // Wait till completion pollForCompletion(serviceDiscovery, operationId); @@ -455,13 +497,6 @@ public void deregisterInstance(final ServiceDiscoveryClient serviceDiscovery, } } - 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 @@ -469,12 +504,12 @@ public static CloudMapUtils getInstance() { * @param serviceName name of the cloudmap service * @return Cloudmap service ID */ - private String getServiceId(ServiceDiscoveryClient serviceDiscovery, String nameSpaceId, String serviceName) { + public String getServiceId(ServiceDiscoveryClient serviceDiscovery, String nameSpaceId, String serviceName) { ServiceFilter filter = ServiceFilter.builder().name(NAMESPACE_ID).values(Collections.singletonList(nameSpaceId)) - .build(); + .build(); Optional serviceSummary = serviceDiscovery - .listServices(ListServicesRequest.builder().filters(filter).build()).services().stream() - .filter(s -> serviceName.equals(s.name())).findFirst(); + .listServices(ListServicesRequest.builder().filters(filter).build()).services().stream() + .filter(s -> serviceName.equals(s.name())).findFirst(); return serviceSummary.map(ServiceSummary::id).orElse(null); } @@ -486,15 +521,15 @@ private String getServiceId(ServiceDiscoveryClient serviceDiscovery, String name * @throws MaxRetryExceededException thrown if maximum polling duration has exceeded */ private void pollForCompletion(ServiceDiscoveryClient serviceDiscovery, String operationId) - throws InterruptedException, MaxRetryExceededException { + throws InterruptedException, MaxRetryExceededException { Operation operation = serviceDiscovery - .getOperation(GetOperationRequest.builder().operationId(operationId).build()).operation(); + .getOperation(GetOperationRequest.builder().operationId(operationId).build()).operation(); int counter = 0; LOGGER.info("Operation ID {} will be polled", operationId); while ((SUBMITTED.equalsIgnoreCase(operation.statusAsString()) - || PENDING.equalsIgnoreCase(operation.statusAsString())) && counter < MAX_POLL) { + || PENDING.equalsIgnoreCase(operation.statusAsString())) && counter < MAX_POLL) { operation = serviceDiscovery.getOperation(GetOperationRequest.builder().operationId(operationId).build()) - .operation(); + .operation(); Thread.sleep(2000); counter++; } @@ -515,7 +550,7 @@ private Map getEksRegistrationAttributes() { 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); + macAddress); final String vpcId = getUrlResponse(vpcUrl); LOGGER.info("Metadata details IP Address {}, macAddress {} - VPCId {}", ipAddress, macAddress, vpcId); return getCloudMapAttributes(ipAddress, vpcId); @@ -529,22 +564,22 @@ private Map getEksRegistrationAttributes() { /** * Get CloudMap attributes for ECS platform + * @param ec2Client - AWS Ec2 client * @return map of cloud map attributes with Ipaddress and vpcid */ - private Map getEcsRegistrationAttributes() { + private Map getEcsRegistrationAttributes(Ec2Client ec2Client) { try { - String metaDataUrl = System.getenv(ECS_CONTAINER_METADATA_URI_V_4); + String metaDataUrl = getProperty(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(DescribeSubnetsRequest.builder() - .filters(Filter.builder().name("cidr-block").values(cidrBlock).build()).build()) - .subnets().get(0).vpcId(); + final String ipv4Address = getData(jsonNode.get("IPv4Addresses")); + final String cidrBlock = getData(jsonNode.get("IPv4SubnetCIDRBlock")); + final String vpcId = ec2Client.describeSubnets(DescribeSubnetsRequest.builder() + .filters(Filter.builder().name("cidr-block").values(cidrBlock).build()).build()) + .subnets().get(0).vpcId(); LOGGER.info("IPv4Address {} - VPC ID {}", ipv4Address, vpcId); return getCloudMapAttributes(ipv4Address, vpcId); } @@ -563,6 +598,23 @@ private String getUrlResponse(String url) { return getRestTemplate().getForEntity(url, String.class).getBody(); } + /** + * Helper method to get data from JsonNode + * @param object JsonNode object + * @return data as string + */ + private String getData(JsonNode object) { + if (object != null) { + if (object.isArray()) { + return object.get(0).asText(); + } + else { + return object.asText(); + } + } + return null; + } + /** * Returns hash map of cloudmap attributes * @param ipv4Address IP Address of the instance @@ -576,18 +628,6 @@ private Map getCloudMapAttributes(String ipv4Address, String vpc return attributes; } - /** - * Get Ec2 client - * @return ec2 client object - */ - Ec2Client getEc2Client() { - if (ec2Client == null) { - ec2Client = Ec2Client.builder().region(Region.of(System.getenv("AWS_REGION"))) - .credentialsProvider(DefaultCredentialsProvider.builder().build()).build(); - } - return ec2Client; - } - /** * Get Rest Template * @return restTemplate @@ -602,8 +642,4 @@ RestTemplate getRestTemplate() { void setRestTemplate(RestTemplate restTemplate) { this.restTemplate = restTemplate; } - - void setEc2Client(Ec2Client ec2Client) { - this.ec2Client = ec2Client; - } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java index 785b3af8b..221f75e35 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/CloudMapProperties.java @@ -18,6 +18,7 @@ import io.awspring.cloud.autoconfigure.AwsClientProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscovery; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; + import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; @@ -35,6 +36,11 @@ public class CloudMapProperties extends AwsClientProperties { */ public static final String CONFIG_PREFIX = "spring.cloud.aws.cloudmap"; + /** + * Compute platform `ECS` or `EKS`. + */ + private String deploymentPlatform; + @NestedConfigurationProperty private CloudMapRegistryProperties registry; @@ -57,10 +63,18 @@ public void setDiscovery(CloudMapDiscovery discovery) { this.discovery = discovery; } + public String getDeploymentPlatform() { + return deploymentPlatform; + } + + public void setDeploymentPlatform(String deploymentPlatform) { + this.deploymentPlatform = deploymentPlatform; + } + @Override public String toString() { return "AwsCloudMapProperties{" + "registry=" + registry + ", discovery=" + discovery + ", region='" - + getRegion() + "'}"; + + getRegion() + "', platform='" + getDeploymentPlatform() + '\'' + "}"; } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java index 4eb845b0c..864e508d2 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/properties/registration/ServiceRegistration.java @@ -15,10 +15,13 @@ */ package io.awspring.cloud.autoconfigure.cloudmap.properties.registration; -import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; import java.net.URI; import java.util.Map; import java.util.UUID; + +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import software.amazon.awssdk.services.ec2.Ec2Client; + import org.springframework.cloud.client.serviceregistry.Registration; public class ServiceRegistration implements Registration { @@ -29,8 +32,9 @@ public class ServiceRegistration implements Registration { private final CloudMapUtils UTILS = CloudMapUtils.getInstance(); - public ServiceRegistration(CloudMapRegistryProperties properties) { - registrationDetails = UTILS.getRegistrationAttributes(); + public ServiceRegistration(CloudMapRegistryProperties properties, Ec2Client ec2Client, + String deploymentPlatform) { + registrationDetails = UTILS.getRegistrationAttributes(ec2Client, deploymentPlatform); this.properties = properties; } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java index 43c4a28f0..8f2e07866 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cloudmap/registration/CloudMapAutoRegistration.java @@ -17,9 +17,11 @@ import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; + import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; + import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistration; import org.springframework.context.ApplicationEvent; @@ -31,14 +33,17 @@ import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.lang.Nullable; + +import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; public class CloudMapAutoRegistration - implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener, EnvironmentAware { + implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener, EnvironmentAware { private final ServiceDiscoveryClient serviceDiscovery; private final CloudMapRegistryProperties properties; + private final Ec2Client ec2Client; private final ApplicationEventPublisher eventPublisher; @@ -49,13 +54,17 @@ public class CloudMapAutoRegistration @Nullable private Environment environment; + private String deploymentPlatform; + private Map attributesMap = new HashMap<>(); public CloudMapAutoRegistration(ApplicationEventPublisher eventPublisher, ServiceDiscoveryClient serviceDiscovery, - CloudMapRegistryProperties properties) { + Ec2Client ec2Client, CloudMapRegistryProperties properties, String deploymentPlatform) { this.eventPublisher = eventPublisher; this.serviceDiscovery = serviceDiscovery; this.properties = properties; + this.ec2Client = ec2Client; + this.deploymentPlatform = deploymentPlatform; } @Override @@ -89,7 +98,8 @@ public void onApplicationEvent(ApplicationEvent event) { @Override public void start() { if (!this.running.get()) { - final Map attributesMap = UTILS.registerInstance(serviceDiscovery, properties, environment); + final Map attributesMap = UTILS.registerInstance(serviceDiscovery, ec2Client, properties, + environment, deploymentPlatform); if (attributesMap != null && attributesMap.containsKey(UTILS.SERVICE_INSTANCE_ID)) { this.attributesMap = attributesMap; this.eventPublisher.publishEvent(new InstanceRegisteredEvent<>(this, attributesMap)); diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java index 353bf2cdf..c426b4140 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapRegisterServiceTest.java @@ -15,22 +15,15 @@ */ package io.awspring.cloud.autoconfigure.cloudmap; -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; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import io.awspring.cloud.autoconfigure.cloudmap.properties.discovery.CloudMapDiscoveryProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.CloudMapRegistryProperties; import io.awspring.cloud.autoconfigure.cloudmap.properties.registration.ServiceRegistration; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.core.env.Environment; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; @@ -56,6 +49,15 @@ import software.amazon.awssdk.services.servicediscovery.model.Service; import software.amazon.awssdk.services.servicediscovery.model.ServiceSummary; +import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +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} * @@ -64,7 +66,6 @@ */ public class CloudMapRegisterServiceTest { - private static final String DEPLOYMENT_PLATFORM = "DEPLOYMENT_PLATFORM"; private static final String ECS = "ECS"; private static final String EKS = "EKS"; private final ServiceDiscoveryClient serviceDiscovery = mock(ServiceDiscoveryClient.class); @@ -77,7 +78,6 @@ public class CloudMapRegisterServiceTest { private final Environment environment = mock(Environment.class); public CloudMapRegisterServiceTest() { - cloudMapUtils.setEc2Client(ec2Client); cloudMapUtils.setRestTemplate(restTemplate); } @@ -96,13 +96,12 @@ public void cloudMapRegisterInstancesNameSpaceAndServiceExists() { when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); - System.setProperty(DEPLOYMENT_PLATFORM, ECS); when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); - assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, ec2Client, getProperties(), environment, ECS)).isNotEmpty(); } @Test @@ -113,7 +112,6 @@ public void cloudMapRegisterInstanceEcsWithNoNameSpace() { final GetOperationResponse operationResponse = getOperationResponse(); final CreatePrivateDnsNamespaceResponse createPrivateDnsNamespaceResponse = getCreatePrivateDnsNamespaceResponse(); cloudMapUtils.setRestTemplate(restTemplate); - cloudMapUtils.setEc2Client(ec2Client); when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn( ListNamespacesResponse.builder().namespaces(Collections.emptyList()).build(), listNamespacesResponse); @@ -128,7 +126,6 @@ public void cloudMapRegisterInstanceEcsWithNoNameSpace() { when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); - System.setProperty(DEPLOYMENT_PLATFORM, EKS); when(restTemplate.getForEntity(String.format("%s/local-ipv4", CloudMapUtils.EC2_METADATA_URL), String.class)) .thenReturn(ResponseEntity.ok("10.1.1.1")); when(restTemplate.getForEntity(String.format("%s/network/interfaces/macs", CloudMapUtils.EC2_METADATA_URL), @@ -139,7 +136,7 @@ public void cloudMapRegisterInstanceEcsWithNoNameSpace() { when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); - assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, ec2Client, getProperties(), environment, ECS)).isNotEmpty(); } @Test @@ -150,7 +147,6 @@ public void cloudMapRegisterInstanceEksWithNoNameSpace() { final GetOperationResponse operationResponse = getOperationResponse(); final CreatePrivateDnsNamespaceResponse createPrivateDnsNamespaceResponse = getCreatePrivateDnsNamespaceResponse(); cloudMapUtils.setRestTemplate(restTemplate); - cloudMapUtils.setEc2Client(ec2Client); when(serviceDiscovery.listNamespaces(any(ListNamespacesRequest.class))).thenReturn( ListNamespacesResponse.builder().namespaces(Collections.emptyList()).build(), listNamespacesResponse); @@ -165,13 +161,12 @@ public void cloudMapRegisterInstanceEksWithNoNameSpace() { when(serviceDiscovery.listServices(any(ListServicesRequest.class))).thenReturn(listServicesResponse); - System.setProperty(DEPLOYMENT_PLATFORM, ECS); when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); - assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, ec2Client, getProperties(), environment, EKS)).isNotEmpty(); } @Test @@ -194,13 +189,12 @@ public void cloudMapRegisterInstancesWithNoService() { .thenReturn(registerInstanceResponse); when(serviceDiscovery.getOperation((any(GetOperationRequest.class)))).thenReturn(operationResponse); - System.setProperty(DEPLOYMENT_PLATFORM, ECS); when(restTemplate.getForEntity(CloudMapUtils.EC2_METADATA_URL + "/task", String.class)) .thenReturn(ResponseEntity.ok(CloudMapTestConstants.ECS_REPONSE_JSON)); when(ec2Client.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn( DescribeSubnetsResponse.builder().subnets(Subnet.builder().vpcId("vpc-id").build()).build()); - assertThat(cloudMapUtils.registerInstance(serviceDiscovery, getProperties(), environment)).isNotEmpty(); + assertThat(cloudMapUtils.registerInstance(serviceDiscovery, ec2Client, getProperties(), environment, ECS)).isNotEmpty(); } @Test diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java index d0b7810d6..f4ae4c3b7 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/CloudMapTestConstants.java @@ -17,6 +17,8 @@ /** * Mock Responses from Ec2 meta-data endpoint + * @author Hari Ohm Prasath + * @since 3.0 */ public class CloudMapTestConstants { /* diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EcsCloudMapIntegrationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EcsCloudMapIntegrationTest.java new file mode 100644 index 000000000..2073ebb2f --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EcsCloudMapIntegrationTest.java @@ -0,0 +1,177 @@ +/* + * 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.autoconfigure.cloudmap.integration; + +import java.time.Duration; +import java.util.List; + +import io.awspring.cloud.autoconfigure.cloudmap.AwsCloudMapStoreClientCustomizer; +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapDiscoveryClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; + +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistryInitializer; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.ConfigurableApplicationContext; + +import static io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils.ECS; +import static io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils.ECS_CONTAINER_METADATA_URI_V_4; + +/** + * Integration test for Ecs based Cloud Map registration. + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +@Testcontainers +@ExtendWith(OutputCaptureExtension.class) +public class EcsCloudMapIntegrationTest { + + // Local stack container with Cloud Map and Ec2 services enabled + @Container + private static final LocalStackContainer localStackContainer = new LocalStackContainer( + DockerImageName.parse("localstack/localstack:1.1.0")) + .withServices(LocalStackContainer.EnabledService.named("servicediscovery"), LocalStackContainer.Service.EC2) + .withEnv(IntegrationTestUtil.LOCAL_STACK_API, IntegrationTestUtil.LOCAL_STACK_API_KEY) + .withReuse(true); + + /** + * Create all the pre-requisites for the test. + */ + @BeforeAll + static void beforeAll() { + final String vpcId = IntegrationTestUtil.createVpc(localStackContainer); + IntegrationTestUtil.createSubnet(localStackContainer, vpcId); + IntegrationTestUtil.createCloudMapResources(localStackContainer, IntegrationTestUtil.TEST_DEFAULT_NAMESPACE + , IntegrationTestUtil.TEST_DEFAULT_SERVICE, vpcId); + } + + /** + * Register a service with no specification provided in application.properties. + */ + @Test + void registerEcsContainerWithCloudMapWithNoSpecification() { + SpringApplication application = new SpringApplication(EcsApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new AwsConfigurerClientConfiguration()); + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + ECS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + final ServiceDiscoveryClient serviceDiscoveryClient = context.getBean(ServiceDiscoveryClient.class); + final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + Assertions.assertNotNull(cloudMapUtils.getNameSpaceId(serviceDiscoveryClient, CloudMapUtils.DEFAULT_NAMESPACE)); + final String serviceId = cloudMapUtils.getServiceId(serviceDiscoveryClient, CloudMapUtils.DEFAULT_NAMESPACE, + CloudMapUtils.DEFAULT_SERVICE); + Assertions.assertNotNull(serviceId); + Assertions.assertEquals(1, IntegrationTestUtil.getCloudMapRegisteredInstances(localStackContainer, serviceId)); + } + } + + /** + * Register a service with defined specifications like namespace, service, etc + */ + @Test + void registerEcsContainerWithCloudMapWithDefinedSpecification() { + SpringApplication application = new SpringApplication(EcsApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new AwsConfigurerClientConfiguration()); + + final String nameSpace = "a.namespace"; + final String service = "a.service"; + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.registry.nameSpace=" + nameSpace); + properties.add("--spring.cloud.aws.cloudmap.registry.service=" + service); + properties.add("--spring.cloud.aws.cloudmap.registry.description=Name space description"); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + ECS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + final ServiceDiscoveryClient serviceDiscoveryClient = context.getBean(ServiceDiscoveryClient.class); + final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + Assertions.assertNotNull(cloudMapUtils.getNameSpaceId(serviceDiscoveryClient, nameSpace)); + final String serviceId = cloudMapUtils.getServiceId(serviceDiscoveryClient, nameSpace, service); + Assertions.assertNotNull(serviceId); + Assertions.assertEquals(1, IntegrationTestUtil.getCloudMapRegisteredInstances(localStackContainer, serviceId)); + } + } + + /** + * Discover the service by pre-creating the namepsace and service. + */ + @Test + void discoverEcsCloudMapInstancesWithNoSpecification() { + SpringApplication application = new SpringApplication(EcsCloudMapIntegrationTest.EcsApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new EcsCloudMapIntegrationTest.AwsConfigurerClientConfiguration()); + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=" + IntegrationTestUtil.TEST_DEFAULT_NAMESPACE); + properties.add("--spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=" + IntegrationTestUtil.TEST_DEFAULT_SERVICE); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + ECS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + CloudMapDiscoveryClient discoveryClient = context.getBean(CloudMapDiscoveryClient.class); + final List services = discoveryClient.getServices(); + Assertions.assertNotNull(services); + Assertions.assertEquals(1, services.size()); + Assertions.assertEquals(String.format("%s@%s", IntegrationTestUtil.TEST_DEFAULT_NAMESPACE, + IntegrationTestUtil.TEST_DEFAULT_SERVICE), services.get(0)); + } + } + + @SpringBootApplication + static class EcsApp { + static { + System.setProperty(ECS_CONTAINER_METADATA_URI_V_4, IntegrationTestUtil.META_DATA_MOCK_RESPONSE); + } + } + + static class AwsConfigurerClientConfiguration implements BootstrapRegistryInitializer { + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(AwsCloudMapStoreClientCustomizer.class, + context -> new AwsCloudMapStoreClientCustomizer() { + @Override + public ClientOverrideConfiguration overrideConfiguration() { + return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(2828)) + .build(); + } + + @Override + public SdkHttpClient httpClient() { + return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build(); + } + }); + } + } +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EksCloudMapIntegrationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EksCloudMapIntegrationTest.java new file mode 100644 index 000000000..9ebd32908 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/EksCloudMapIntegrationTest.java @@ -0,0 +1,177 @@ +/* + * 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.autoconfigure.cloudmap.integration; + +import java.time.Duration; +import java.util.List; + +import io.awspring.cloud.autoconfigure.cloudmap.AwsCloudMapStoreClientCustomizer; +import io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils; +import io.awspring.cloud.autoconfigure.cloudmap.discovery.CloudMapDiscoveryClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.servicediscovery.ServiceDiscoveryClient; + +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistryInitializer; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.ConfigurableApplicationContext; + +import static io.awspring.cloud.autoconfigure.cloudmap.CloudMapUtils.EKS; + +/** + * Integration test for Eks based Cloud Map registration. + * + * @author Hari Ohm Prasath + * @since 3.0 + */ +@Testcontainers +@ExtendWith(OutputCaptureExtension.class) +public class EksCloudMapIntegrationTest { + + // Local stack container with Cloud Map and Ec2 services enabled + @Container + private static final LocalStackContainer localStackContainer = new LocalStackContainer( + DockerImageName.parse("localstack/localstack:1.1.0")) + .withServices(LocalStackContainer.EnabledService.named("servicediscovery"), LocalStackContainer.Service.EC2) + .withEnv(IntegrationTestUtil.LOCAL_STACK_API, IntegrationTestUtil.LOCAL_STACK_API_KEY) + .withReuse(true); + + /** + * Create all the pre-requisites for the test. + */ + @BeforeAll + static void beforeAll() { + final String vpcId = IntegrationTestUtil.createVpc(localStackContainer); + IntegrationTestUtil.createSubnet(localStackContainer, vpcId); + IntegrationTestUtil.createCloudMapResources(localStackContainer, IntegrationTestUtil.TEST_DEFAULT_NAMESPACE + , IntegrationTestUtil.TEST_DEFAULT_SERVICE, vpcId); + } + + /** + * Register a service with no specification provided in application.properties. + */ + @Test + void registerEksContainerWithCloudMapWithNoSpecification() { + SpringApplication application = new SpringApplication(EksApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new AwsConfigurerClientConfiguration()); + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + EKS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + final ServiceDiscoveryClient serviceDiscoveryClient = context.getBean(ServiceDiscoveryClient.class); + final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + Assertions.assertNotNull(cloudMapUtils.getNameSpaceId(serviceDiscoveryClient, CloudMapUtils.DEFAULT_NAMESPACE)); + final String serviceId = cloudMapUtils.getServiceId(serviceDiscoveryClient, CloudMapUtils.DEFAULT_NAMESPACE, + CloudMapUtils.DEFAULT_SERVICE); + Assertions.assertNotNull(serviceId); + Assertions.assertEquals(1, IntegrationTestUtil.getCloudMapRegisteredInstances(localStackContainer, serviceId)); + } + } + + /** + * Register a service with defined specifications like namespace, service, etc + */ + @Test + void registerEksContainerWithCloudMapWithDefinedSpecification() { + SpringApplication application = new SpringApplication(EcsCloudMapIntegrationTest.EcsApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new EcsCloudMapIntegrationTest.AwsConfigurerClientConfiguration()); + + final String nameSpace = "a.namespace"; + final String service = "a.service"; + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.registry.nameSpace=" + nameSpace); + properties.add("--spring.cloud.aws.cloudmap.registry.service=" + service); + properties.add("--spring.cloud.aws.cloudmap.registry.description=Name space description"); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + EKS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + final ServiceDiscoveryClient serviceDiscoveryClient = context.getBean(ServiceDiscoveryClient.class); + final CloudMapUtils cloudMapUtils = CloudMapUtils.getInstance(); + Assertions.assertNotNull(cloudMapUtils.getNameSpaceId(serviceDiscoveryClient, nameSpace)); + final String serviceId = cloudMapUtils.getServiceId(serviceDiscoveryClient, nameSpace, service); + Assertions.assertNotNull(serviceId); + Assertions.assertEquals(1, IntegrationTestUtil.getCloudMapRegisteredInstances(localStackContainer, serviceId)); + } + } + + /** + * Discover the service by pre-creating the namepsace and service. + */ + @Test + void discoverEksCloudMapInstancesWithNoSpecification() { + SpringApplication application = new SpringApplication(EcsCloudMapIntegrationTest.EcsApp.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new EcsCloudMapIntegrationTest.AwsConfigurerClientConfiguration()); + + List properties = IntegrationTestUtil.getCloudMapSpringBootProperties(localStackContainer); + properties.add("--spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=" + IntegrationTestUtil.TEST_DEFAULT_NAMESPACE); + properties.add("--spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=" + IntegrationTestUtil.TEST_DEFAULT_SERVICE); + properties.add("--spring.cloud.aws.cloudmap.deploymentPlatform=" + EKS); + + try (ConfigurableApplicationContext context = application.run(properties.toArray(new String[0]))) { + CloudMapDiscoveryClient discoveryClient = context.getBean(CloudMapDiscoveryClient.class); + final List services = discoveryClient.getServices(); + Assertions.assertNotNull(services); + Assertions.assertEquals(1, services.size()); + Assertions.assertEquals(String.format("%s@%s", IntegrationTestUtil.TEST_DEFAULT_NAMESPACE, + IntegrationTestUtil.TEST_DEFAULT_SERVICE), services.get(0)); + } + } + + @SpringBootApplication + static class EksApp { + static { + System.setProperty(CloudMapUtils.EC2_METADATA, IntegrationTestUtil.META_DATA_MOCK_RESPONSE); + } + } + + static class AwsConfigurerClientConfiguration implements BootstrapRegistryInitializer { + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(AwsCloudMapStoreClientCustomizer.class, + context -> new AwsCloudMapStoreClientCustomizer() { + + @Override + public ClientOverrideConfiguration overrideConfiguration() { + return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(2828)) + .build(); + } + + @Override + public SdkHttpClient httpClient() { + return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build(); + } + }); + } + } +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/IntegrationTestUtil.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/IntegrationTestUtil.java new file mode 100644 index 000000000..6c0813306 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cloudmap/integration/IntegrationTestUtil.java @@ -0,0 +1,121 @@ +/* + * 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.autoconfigure.cloudmap.integration; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.localstack.LocalStackContainer; + +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SSM; + +/** + * Utility class for integration tests. + * @author Hari Ohm Prasath + * @since 3.0 + */ +public class IntegrationTestUtil { + public static final String REGION = "us-east-1"; + public static final String CIDR_BLOCK = "10.0.0.0/16"; + public static final String META_DATA_MOCK_RESPONSE = "https://5qjz8e33hj.api.quickmocker.com"; + public static final String LOCAL_STACK_API = "LOCALSTACK_API_KEY"; + public static final String LOCAL_STACK_API_KEY = ""; // TODO: Add your localstack api key here + + public static final String TEST_DEFAULT_NAMESPACE = "test.namespace"; + public static final String TEST_DEFAULT_SERVICE = "test.service"; + + public static String createVpc(LocalStackContainer localStackContainer) { + try { + final Container.ExecResult execResult = localStackContainer.execInContainer("awslocal", "ec2", "create-vpc", + "--cidr-block", CIDR_BLOCK, "--region", REGION); + if (execResult.getExitCode() != 0) { + throw new RuntimeException("Failed to create VPC: " + execResult.getStderr()); + } + else { + return execResult.getStdout().split("VpcId")[1].split("\"")[2]; + } + } + catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void createSubnet(LocalStackContainer localStackContainer, String vpcId) { + try { + localStackContainer.execInContainer("awslocal", "ec2", "create-subnet", "--vpc-id", vpcId, + "--cidr-block", CIDR_BLOCK, "--region", REGION); + } + catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void createCloudMapResources(LocalStackContainer localStackContainer, String namespace, + String service, String vpcId) { + try { + // Create namespace + localStackContainer.execInContainer("awslocal", "servicediscovery", + "create-private-dns-namespace", "--name", namespace, "--region", REGION, "--vpc", vpcId); + + // List namespaces and get the id + final Container.ExecResult listNameSpaceOutput = localStackContainer.execInContainer("awslocal", "servicediscovery", + "list-namespaces", "--region", REGION); + final String namespaceId = listNameSpaceOutput.getStdout().split("Id")[1].split("\"")[2]; + + // Create service + final String serviceId = localStackContainer.execInContainer("awslocal", "servicediscovery", + "create-service", "--name", service, "--namespace-id", namespaceId, "--region", REGION) + .getStdout().split("Id")[1].split("\"")[2]; + localStackContainer.execInContainer("awslocal", "servicediscovery", "register-instance", + "--service-id", serviceId, "--attributes", "IPV4_ADDRESS=10.0.0.1,VPC_ID=" + vpcId, "--region", REGION); + } + catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static int getCloudMapRegisteredInstances(LocalStackContainer localStackContainer, String serviceId) { + try { + final Container.ExecResult execResult = localStackContainer.execInContainer("awslocal", "servicediscovery", + "list-instances", "--service-id", serviceId, "--region", REGION); + if (execResult.getExitCode() != 0) { + throw new RuntimeException("Failed to list instances: " + execResult.getStderr()); + } + else { + return execResult.getStdout().split("InstanceId").length; + } + } + catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static List getCloudMapSpringBootProperties(LocalStackContainer localStackContainer) { + List properties = new ArrayList<>(); + properties.add("--spring.cloud.aws.cloudmap.region=" + REGION); + properties.add("--spring.cloud.aws.cloudmap=http://non-existing-host/"); + properties.add("--spring.cloud.aws.cloudmap.endpoint=" + localStackContainer.getEndpointOverride(SSM) + .toString()); + properties.add("--spring.cloud.aws.credentials.access-key=noop"); + properties.add("--spring.cloud.aws.credentials.secret-key=noop"); + properties.add("--spring.cloud.aws.region.static=" + REGION); + properties.add("--spring.cloud.aws.cloudmap.enabled=true"); + + return properties; + } +} From 6efdbee3fff0b9266ee7e525c52a317203459071 Mon Sep 17 00:00:00 2001 From: Hari Ohm Prasath Rajagopal Date: Thu, 22 Sep 2022 08:32:44 -0700 Subject: [PATCH 8/8] Updates to README.md and deployment platform --- .../spring-cloud-aws-cloud-map-sample/README.md | 12 +++--------- .../src/main/resources/application.properties | 2 ++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md index 7007910b2..0881097ea 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md +++ b/spring-cloud-aws-samples/spring-cloud-aws-cloud-map-sample/README.md @@ -180,12 +180,6 @@ aws ecs register-task-definition --name cloudmap-sample-definition --cli-input-j } }, "cpu": 0, - "environment": [ - { - "name": "DEPLOYMENT_PLATFORM", - "value": "ECS" - } - ], "image": ".dkr.ecr.us-east-1.amazonaws.com/cloudmap-sample:latest", "name": "mainContainer" } @@ -198,9 +192,9 @@ aws ecs register-task-definition --name cloudmap-sample-definition --cli-input-j ], "networkMode": "awsvpc", "runtimePlatform": { - "operatingSystemFamily": "LINUX", + "operatingSystemFamily": "LINUX" }, - "cpu": "2048", + "cpu": "2048" } ``` @@ -252,4 +246,4 @@ aws iam delete-role --role-name ecsTaskRole ```bash aws ecr delete-repository --repository-name cloudmap-sample --force -``` \ No newline at end of file +``` 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 index 842630ce9..d10a15ca3 100644 --- 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 @@ -1,6 +1,8 @@ spring.cloud.aws.cloudmap.enabled=true spring.application.name=cloudmap-namespace-here +spring.cloud.aws.cloudmap.deploymentPlatform=ECS + # Discover existing cloudmap instances spring.cloud.aws.cloudmap.discovery.discoveryList[0].service=a-service spring.cloud.aws.cloudmap.discovery.discoveryList[0].nameSpace=a-namespace