diff --git a/pom.xml b/pom.xml index 3724cc666..9218c751f 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ spring-cloud-aws-actuator spring-cloud-aws-parameter-store-config spring-cloud-aws-secrets-manager-config + spring-cloud-aws-cloud-map-service-discovery spring-cloud-starter-aws spring-cloud-starter-aws-jdbc spring-cloud-starter-aws-messaging diff --git a/spring-cloud-aws-cloud-map-service-discovery/pom.xml b/spring-cloud-aws-cloud-map-service-discovery/pom.xml new file mode 100644 index 000000000..1aa533356 --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/pom.xml @@ -0,0 +1,62 @@ + + + + + 4.0.0 + + org.springframework.cloud + spring-cloud-aws + 3.0.0-SNAPSHOT + + + spring-cloud-aws-cloud-map-service-discovery + Spring Cloud AWS Cloud Map Service Discovery + Spring Cloud AWS Cloud Map Service Discovery + + + + org.springframework + spring-core + + + io.projectreactor + reactor-core + true + + + org.springframework.boot + spring-boot-starter-web + true + + + org.springframework.boot + spring-boot-starter-webflux + true + + + org.springframework.cloud + spring-cloud-commons + ${spring-cloud-commons.version} + + + com.amazonaws + aws-java-sdk-servicediscovery + + + diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClient.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClient.java new file mode 100644 index 000000000..7d581e529 --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClient.java @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.aws.cloudmap; + +import java.util.List; +import java.util.stream.Collectors; + +import com.amazonaws.services.servicediscovery.AWSServiceDiscovery; +import com.amazonaws.services.servicediscovery.model.GetInstanceRequest; +import com.amazonaws.services.servicediscovery.model.Instance; +import com.amazonaws.services.servicediscovery.model.ListInstancesRequest; +import com.amazonaws.services.servicediscovery.model.ListServicesRequest; +import com.amazonaws.services.servicediscovery.model.ServiceSummary; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +public class AwsCloudMapDiscoveryClient implements DiscoveryClient { + + private final AWSServiceDiscovery aws; + + public AwsCloudMapDiscoveryClient(AWSServiceDiscovery aws) { + this.aws = aws; + } + + @Override + public String description() { + return "AWS Cloud Map Discovery Client"; + } + + @Override + public List getInstances(String serviceId) { + ListInstancesRequest listInstancesRequest = new ListInstancesRequest().withServiceId(serviceId); + // TODO pagination + // TODO parallel requests? + // TODO filter on health? + return aws.listInstances(listInstancesRequest).getInstances().stream() + .map(summary -> getInstance(serviceId, summary.getId())).collect(Collectors.toList()); + + } + + private AwsCloudMapServiceInstance getInstance(String serviceId, String instanceId) { + Instance instance = aws + .getInstance(new GetInstanceRequest().withServiceId(serviceId).withInstanceId(instanceId)) + .getInstance(); + return new AwsCloudMapServiceInstance(serviceId, instance); + } + + @Override + public List getServices() { + // TODO pagination + return aws.listServices(new ListServicesRequest()).getServices().stream().map(ServiceSummary::getId) + .collect(Collectors.toList()); + } + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClientConfiguration.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClientConfiguration.java new file mode 100644 index 000000000..70e3dd566 --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryClientConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.aws.cloudmap; + +import com.amazonaws.services.servicediscovery.AWSServiceDiscovery; +import com.amazonaws.services.servicediscovery.AWSServiceDiscoveryClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnDiscoveryEnabled +@ConditionalOnBlockingDiscoveryEnabled +@ConditionalOnAwsCloudMapDiscoveryEnabled +@EnableConfigurationProperties +public class AwsCloudMapDiscoveryClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public AWSServiceDiscovery awsServiceDiscovery() { + return AWSServiceDiscoveryClientBuilder.defaultClient(); + } + + @Bean + @ConditionalOnMissingBean + public AwsCloudMapDiscoveryProperties awsCloudMapDiscoveryProperties() { + return new AwsCloudMapDiscoveryProperties(); + } + + @Bean + @ConditionalOnMissingBean + public AwsCloudMapDiscoveryClient awsCloudMapReactiveDiscoveryClient(AWSServiceDiscovery awsServiceDiscovery) { + return new AwsCloudMapDiscoveryClient(awsServiceDiscovery); + } + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryProperties.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryProperties.java new file mode 100644 index 000000000..cbc4cfdda --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapDiscoveryProperties.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.aws.cloudmap; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("spring.cloud.aws.discovery.cloudmap") +public class AwsCloudMapDiscoveryProperties { + + private boolean enabled = true; + + boolean isEnabled() { + return enabled; + } + + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapServiceInstance.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapServiceInstance.java new file mode 100644 index 000000000..659e68bba --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/AwsCloudMapServiceInstance.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.aws.cloudmap; + +import java.net.URI; +import java.util.Map; + +import com.amazonaws.services.servicediscovery.model.Instance; + +import org.springframework.cloud.client.ServiceInstance; + +public class AwsCloudMapServiceInstance implements ServiceInstance { + + private static final String AWS_INSTANCE_IPV_4 = "AWS_INSTANCE_IPV4"; + + private static final String AWS_INSTANCE_PORT = "AWS_INSTANCE_PORT"; + + private final String serviceId; + + private final Instance instance; + + public AwsCloudMapServiceInstance(String serviceId, Instance instance) { + this.serviceId = serviceId; + this.instance = instance; + } + + @Override + public String getInstanceId() { + return instance.getId(); + } + + @Override + public String getServiceId() { + return serviceId; + } + + @Override + public String getHost() { + // TODO alternate host attributes + return instance.getAttributes().get(AWS_INSTANCE_IPV_4); + } + + @Override + public int getPort() { + // TODO are there other possible values? + String port = instance.getAttributes().get(AWS_INSTANCE_PORT); + // TODO error handling? + return Integer.parseInt(port); + } + + @Override + public boolean isSecure() { + return getPort() == 443; + } + + @Override + public URI getUri() { + return URI.create(String.format("%s://%s/%s", getScheme(), getHost(), getPort())); + } + + @Override + public Map getMetadata() { + return instance.getAttributes(); + } + + @Override + public String getScheme() { + return isSecure() ? "https" : "http"; + } + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/ConditionalOnAwsCloudMapDiscoveryEnabled.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/ConditionalOnAwsCloudMapDiscoveryEnabled.java new file mode 100644 index 000000000..434eaf5ff --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/ConditionalOnAwsCloudMapDiscoveryEnabled.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.aws.cloudmap; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ConditionalOnProperty(value = "spring.cloud.aws.discovery.cloudmap.enabled", matchIfMissing = true) +public @interface ConditionalOnAwsCloudMapDiscoveryEnabled { + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClient.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClient.java new file mode 100644 index 000000000..ae985ac58 --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClient.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2019 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.reactive; + +import com.amazonaws.services.servicediscovery.AWSServiceDiscovery; +import com.amazonaws.services.servicediscovery.model.GetInstanceRequest; +import com.amazonaws.services.servicediscovery.model.GetInstanceResult; +import com.amazonaws.services.servicediscovery.model.ListInstancesRequest; +import com.amazonaws.services.servicediscovery.model.ListServicesRequest; +import com.amazonaws.services.servicediscovery.model.ListServicesResult; +import com.amazonaws.services.servicediscovery.model.ServiceSummary; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.aws.cloudmap.AwsCloudMapServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; + +public class AwsCloudMapReactiveDiscoveryClient implements ReactiveDiscoveryClient { + + private final AWSServiceDiscovery aws; + + public AwsCloudMapReactiveDiscoveryClient(AWSServiceDiscovery aws) { + this.aws = aws; + } + + @Override + public String description() { + return "AWS Cloud Map Reactive Discovery Client"; + } + + @Override + public Flux getInstances(String serviceId) { + ListInstancesRequest request = new ListInstancesRequest().withServiceId(serviceId); + + return Mono.fromSupplier(() -> aws.listInstances(request)).flatMapMany(resp -> Flux + .fromIterable(resp.getInstances()).flatMap(summary -> getInstance(serviceId, summary.getId()))); + } + + private Mono getInstance(String serviceId, String instanceId) { + GetInstanceRequest request = new GetInstanceRequest().withServiceId(serviceId).withInstanceId(instanceId); + + return Mono.fromSupplier(() -> aws.getInstance(request)).map(GetInstanceResult::getInstance) + .map(instance -> new AwsCloudMapServiceInstance(serviceId, instance)); + } + + @Override + public Flux getServices() { + ListServicesRequest request = new ListServicesRequest(); + + return Mono.fromSupplier(() -> aws.listServices(request)).flatMapIterable(ListServicesResult::getServices) + .map(ServiceSummary::getId); + } + +} diff --git a/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClientConfiguration.java b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClientConfiguration.java new file mode 100644 index 000000000..fd479c81c --- /dev/null +++ b/spring-cloud-aws-cloud-map-service-discovery/src/main/java/org/springframework/cloud/aws/cloudmap/reactive/AwsCloudMapReactiveDiscoveryClientConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2019 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.reactive; + +import com.amazonaws.services.servicediscovery.AWSServiceDiscovery; +import com.amazonaws.services.servicediscovery.AWSServiceDiscoveryClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.aws.cloudmap.AwsCloudMapDiscoveryProperties; +import org.springframework.cloud.aws.cloudmap.ConditionalOnAwsCloudMapDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnDiscoveryEnabled +@ConditionalOnReactiveDiscoveryEnabled +@ConditionalOnAwsCloudMapDiscoveryEnabled +@EnableConfigurationProperties +public class AwsCloudMapReactiveDiscoveryClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public AWSServiceDiscovery awsServiceDiscovery() { + return AWSServiceDiscoveryClientBuilder.defaultClient(); + } + + @Bean + @ConditionalOnMissingBean + public AwsCloudMapDiscoveryProperties awsCloudMapDiscoveryProperties() { + return new AwsCloudMapDiscoveryProperties(); + } + + @Bean + @ConditionalOnMissingBean + public AwsCloudMapReactiveDiscoveryClient awsCloudMapReactiveDiscoveryClient( + AWSServiceDiscovery awsServiceDiscovery) { + return new AwsCloudMapReactiveDiscoveryClient(awsServiceDiscovery); + } + +}