diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepository.java index 17c93e5f..6663f864 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepository.java @@ -18,7 +18,6 @@ package org.springframework.cloud.config.server.environment; import java.io.IOException; import java.io.InputStream; -import java.util.Map; import java.util.Properties; import com.amazonaws.services.s3.AmazonS3; @@ -36,8 +35,13 @@ import org.springframework.util.StringUtils; /** * @author Clay McCoy + * @author Scott Frederick */ -public class AwsS3EnvironmentRepository implements EnvironmentRepository, Ordered { +public class AwsS3EnvironmentRepository + implements EnvironmentRepository, Ordered, SearchPathLocator { + + private static final String AWS_S3_RESOURCE_SCHEME = "s3://"; + private static final String PATH_SEPARATOR = "/"; private final AmazonS3 s3Client; @@ -64,39 +68,68 @@ public class AwsS3EnvironmentRepository implements EnvironmentRepository, Ordere } @Override - public Environment findOne(String specifiedApplication, String specifiedProfile, + public Environment findOne(String specifiedApplication, String specifiedProfiles, String specifiedLabel) { final String application = StringUtils.isEmpty(specifiedApplication) ? serverProperties.getDefaultApplicationName() : specifiedApplication; - final String profile = StringUtils.isEmpty(specifiedProfile) - ? serverProperties.getDefaultProfile() : specifiedProfile; + final String profiles = StringUtils.isEmpty(specifiedProfiles) + ? serverProperties.getDefaultProfile() : specifiedProfiles; final String label = StringUtils.isEmpty(specifiedLabel) ? serverProperties.getDefaultLabel() : specifiedLabel; - StringBuilder objectKeyPrefix = new StringBuilder(); - if (!StringUtils.isEmpty(label)) { - objectKeyPrefix.append(label).append("/"); - } - objectKeyPrefix.append(application).append("-").append(profile); - final Environment environment = new Environment(application, profile); + + String[] profileArray = parseProfiles(profiles); + + final Environment environment = new Environment(application, profileArray); environment.setLabel(label); + + for (String profile : profileArray) { + S3ConfigFile s3ConfigFile = getS3ConfigFile(application, profile, label); + if (s3ConfigFile != null) { + environment.setVersion(s3ConfigFile.getVersion()); + + final Properties config = s3ConfigFile.read(); + config.putAll(serverProperties.getOverrides()); + StringBuilder propertySourceName = new StringBuilder().append("s3:") + .append(application); + if (profile != null) { + propertySourceName.append("-").append(profile); + } + environment + .add(new PropertySource(propertySourceName.toString(), config)); + } + } + + return environment; + } + + private String[] parseProfiles(String profiles) { + if (profiles.equals(serverProperties.getDefaultProfile())) { + return new String[] { profiles, null }; + } + return StringUtils.commaDelimitedListToStringArray(profiles); + } + + private S3ConfigFile getS3ConfigFile(String application, String profile, + String label) { + String objectKeyPrefix = buildObjectKeyPrefix(application, profile, label); + final S3ObjectIdBuilder s3ObjectIdBuilder = new S3ObjectIdBuilder() .withBucket(bucketName); - S3ConfigFile s3ConfigFile = getS3ConfigFile(s3ObjectIdBuilder, - objectKeyPrefix.toString()); - if (s3ConfigFile == null) { - throw new NoSuchRepositoryException( - "No such repository: (" - + s3ObjectIdBuilder - .withKey(objectKeyPrefix.toString() - + "(.properties | .yml | .json)") - .build().toString() - + ")"); + + return getS3ConfigFile(s3ObjectIdBuilder, objectKeyPrefix); + } + + private String buildObjectKeyPrefix(String application, String profile, + String label) { + StringBuilder objectKeyPrefix = new StringBuilder(); + if (!StringUtils.isEmpty(label)) { + objectKeyPrefix.append(label).append(PATH_SEPARATOR); } - environment.setVersion(s3ConfigFile.getVersion()); - final Map config = s3ConfigFile.read(); - config.putAll(serverProperties.getOverrides()); - environment.add(new PropertySource(application, config)); - return environment; + objectKeyPrefix.append(application); + if (!StringUtils.isEmpty(profile)) { + objectKeyPrefix.append("-").append(profile); + } + return objectKeyPrefix.toString(); } private S3ConfigFile getS3ConfigFile(S3ObjectIdBuilder s3ObjectIdBuilder, @@ -128,6 +161,15 @@ public class AwsS3EnvironmentRepository implements EnvironmentRepository, Ordere } } + @Override + public Locations getLocations(String application, String profiles, String label) { + String baseLocation = AWS_S3_RESOURCE_SCHEME + bucketName + PATH_SEPARATOR + + application; + + return new Locations(application, profiles, label, null, + new String[] { baseLocation }); + } + } abstract class S3ConfigFile { @@ -142,7 +184,7 @@ abstract class S3ConfigFile { return version; } - abstract Map read(); + abstract Properties read(); } @@ -156,7 +198,7 @@ class PropertyS3ConfigFile extends S3ConfigFile { } @Override - public Map read() { + public Properties read() { Properties props = new Properties(); try (InputStream in = inputStream) { props.load(in); @@ -179,10 +221,10 @@ class YamlS3ConfigFile extends S3ConfigFile { } @Override - public Map read() { + public Properties read() { final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); try (InputStream in = inputStream) { - yaml.setResources(new InputStreamResource(inputStream)); + yaml.setResources(new InputStreamResource(in)); return yaml.getObject(); } catch (IOException e) { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryTests.java index 8d0fdde0..4c6650b6 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsS3EnvironmentRepositoryTests.java @@ -30,10 +30,10 @@ import org.junit.Test; import org.mockito.ArgumentMatcher; import org.springframework.cloud.config.environment.Environment; +import org.springframework.cloud.config.environment.PropertySource; import org.springframework.cloud.config.server.config.ConfigServerProperties; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,8 +50,6 @@ public class AwsS3EnvironmentRepositoryTests { final EnvironmentRepository envRepo = new AwsS3EnvironmentRepository(s3Client, "bucket1", server); - final S3Object s3Object = new S3Object(); - final String propertyContent = "cloudfoundry.enabled=true\n" + "cloudfoundry.accounts[0].name=acc1\n" + "cloudfoundry.accounts[0].user=user1\n" @@ -97,138 +95,133 @@ public class AwsS3EnvironmentRepositoryTests { } @Test - public void failToFindNonexistantObject() { - - Throwable thrown = catchThrowable(() -> { - envRepo.findOne("foo", "bar", null); - }); - - assertThat(thrown).isInstanceOf(NoSuchRepositoryException.class); - assertThat(thrown).hasMessage( - "No such repository: (bucket: bucket1, key: foo-bar(.properties | .yml | .json), versionId: null)"); + public void failToFindNonexistentObject() { + Environment env = envRepo.findOne("foo", "bar", null); + assertThat(env.getPropertySources().size()).isEqualTo(0); } @Test public void findPropertiesObject() throws UnsupportedEncodingException { - final S3ObjectId S3ObjectId = new S3ObjectId("bucket1", "foo-bar.properties"); - final GetObjectRequest request = new GetObjectRequest(S3ObjectId); - s3Object.setObjectContent(new StringInputStream(propertyContent)); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + setupS3("foo-bar.properties", propertyContent); // Pulling content from a .properties file forces a boolean into a String expectedProperties.put("cloudfoundry.enabled", "true"); final Environment env = envRepo.findOne("foo", "bar", null); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "bar" }); - assertThat(env.getLabel()).isEqualTo(null); - assertThat(env.getVersion()).isEqualTo(null); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", null, null, 1, "bar"); } @Test public void findJsonObject() throws UnsupportedEncodingException { - final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", "foo-bar.json"); - final GetObjectRequest request = new GetObjectRequest(s3ObjectId); - s3Object.setObjectContent(new StringInputStream(jsonContent)); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + setupS3("foo-bar.json", jsonContent); final Environment env = envRepo.findOne("foo", "bar", null); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "bar" }); - assertThat(env.getLabel()).isEqualTo(null); - assertThat(env.getVersion()).isEqualTo(null); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", null, null, 1, "bar"); } @Test public void findYamlObject() throws UnsupportedEncodingException { - final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", "foo-bar.yml"); - final GetObjectRequest request = new GetObjectRequest(s3ObjectId); - s3Object.setObjectContent(new StringInputStream(yamlContent)); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + setupS3("foo-bar.yml", yamlContent); final Environment env = envRepo.findOne("foo", "bar", null); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "bar" }); - assertThat(env.getLabel()).isEqualTo(null); - assertThat(env.getVersion()).isEqualTo(null); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", null, null, 1, "bar"); } @Test - public void findDefaultProfileObject() throws UnsupportedEncodingException { - final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", "foo-default.yml"); - final GetObjectRequest request = new GetObjectRequest(s3ObjectId); - s3Object.setObjectContent(new StringInputStream(yamlContent)); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + public void findWithDefaultProfile() throws UnsupportedEncodingException { + setupS3("foo.yml", yamlContent); final Environment env = envRepo.findOne("foo", null, null); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "default" }); - assertThat(env.getLabel()).isEqualTo(null); - assertThat(env.getVersion()).isEqualTo(null); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", null, null, 1, "default", null); } @Test - public void findLabeledObject() throws UnsupportedEncodingException { - final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", "label1/foo-bar.yml"); - final GetObjectRequest request = new GetObjectRequest(s3ObjectId); - s3Object.setObjectContent(new StringInputStream(yamlContent)); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + public void findWithDefaultProfileUsingSuffix() throws UnsupportedEncodingException { + setupS3("foo-default.yml", yamlContent); + + final Environment env = envRepo.findOne("foo", null, null); + + assertExpectedEnvironment(env, "foo", null, null, 1, "default", null); + } + + @Test + public void findWithMultipleProfilesAllFound() throws UnsupportedEncodingException { + setupS3("foo-profile1.yml", yamlContent); + setupS3("foo-profile2.yml", jsonContent); + + final Environment env = envRepo.findOne("foo", "profile1,profile2", null); + + assertExpectedEnvironment(env, "foo", null, null, 2, "profile1", "profile2"); + } + + @Test + public void findWithMultipleProfilesOneFound() throws UnsupportedEncodingException { + setupS3("foo-profile2.yml", jsonContent); + + final Environment env = envRepo.findOne("foo", "profile1,profile2", null); + + assertExpectedEnvironment(env, "foo", null, null, 1, "profile1", "profile2"); + } + + @Test + public void findWithLabel() throws UnsupportedEncodingException { + setupS3("label1/foo-bar.yml", yamlContent); final Environment env = envRepo.findOne("foo", "bar", "label1"); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "bar" }); - assertThat(env.getLabel()).isEqualTo("label1"); - assertThat(env.getVersion()).isEqualTo(null); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", "label1", null, 1, "bar"); } @Test - public void findVersionedObject() throws UnsupportedEncodingException { - final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", "foo-bar.yml"); - final GetObjectRequest request = new GetObjectRequest(s3ObjectId); - s3Object.setObjectContent(new StringInputStream(yamlContent)); - final ObjectMetadata metadata = new ObjectMetadata(); - metadata.setHeader("x-amz-version-id", "v1"); - s3Object.setObjectMetadata(metadata); - when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) - .thenReturn(s3Object); + public void findWithVersion() throws UnsupportedEncodingException { + setupS3("foo-bar.yml", "v1", yamlContent); final Environment env = envRepo.findOne("foo", "bar", null); - assertThat(env.getName()).isEqualTo("foo"); - assertThat(env.getProfiles()).isEqualTo(new String[] { "bar" }); - assertThat(env.getLabel()).isEqualTo(null); - assertThat(env.getVersion()).isEqualTo("v1"); - assertThat(env.getPropertySources().size()).isEqualTo(1); - assertThat(env.getPropertySources().get(0).getSource()) - .isEqualTo(expectedProperties); + assertExpectedEnvironment(env, "foo", null, "v1", 1, "bar"); } - private class GetObjectRequestMatcher implements ArgumentMatcher { + private void setupS3(String fileName, String propertyContent) + throws UnsupportedEncodingException { + setupS3(fileName, null, propertyContent); + } + + private void setupS3(String fileName, String version, String propertyContent) + throws UnsupportedEncodingException { + final S3ObjectId s3ObjectId = new S3ObjectId("bucket1", fileName); + final GetObjectRequest request = new GetObjectRequest(s3ObjectId); + + final S3Object s3Object = new S3Object(); + s3Object.setObjectContent(new StringInputStream(propertyContent)); + + if (version != null) { + final ObjectMetadata metadata = new ObjectMetadata(); + metadata.setHeader("x-amz-version-id", version); + s3Object.setObjectMetadata(metadata); + } + + when(s3Client.getObject(argThat(new GetObjectRequestMatcher(request)))) + .thenReturn(s3Object); + } + + private void assertExpectedEnvironment(Environment env, String applicationName, + String label, String version, int propertySourceCount, String... profiles) { + assertThat(env.getName()).isEqualTo(applicationName); + assertThat(env.getProfiles()).isEqualTo(profiles); + assertThat(env.getLabel()).isEqualTo(label); + assertThat(env.getVersion()).isEqualTo(version); + assertThat(env.getPropertySources().size()).isEqualTo(propertySourceCount); + for (PropertySource ps : env.getPropertySources()) { + assertThat(ps.getSource()).isEqualTo(expectedProperties); + } + } + + private static class GetObjectRequestMatcher + implements ArgumentMatcher { private final GetObjectRequest expected; @@ -238,7 +231,7 @@ public class AwsS3EnvironmentRepositoryTests { @Override public boolean matches(GetObjectRequest actual) { - if (!(actual instanceof GetObjectRequest)) { + if (actual == null) { return false; } return Objects.equals(actual.getBucketName(), expected.getBucketName())