Skip to content

Commit

Permalink
Adds support for property origin tracking in config server/client
Browse files Browse the repository at this point in the history
Adds a new format if requested using v2 Accept header, otherwise the
format is backwards compatible so older clients will work with new
servers.

fixes gh-866
  • Loading branch information
spencergibb authored Aug 2, 2019
1 parent cbcdcde commit 7f547f8
Show file tree
Hide file tree
Showing 42 changed files with 479 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.bootstrap.support.OriginTrackedCompositePropertySource;
import org.springframework.cloud.config.client.ConfigClientProperties.Credentials;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
Expand All @@ -46,6 +50,7 @@
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.retry.annotation.Retryable;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
Expand All @@ -56,6 +61,7 @@
import static org.springframework.cloud.config.client.ConfigClientProperties.AUTHORIZATION;
import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER;
import static org.springframework.cloud.config.client.ConfigClientProperties.TOKEN_HEADER;
import static org.springframework.cloud.config.environment.EnvironmentMediaType.V2_JSON;

/**
* @author Dave Syer
Expand All @@ -81,7 +87,8 @@ public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperti
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new CompositePropertySource("configService");
CompositePropertySource composite = new OriginTrackedCompositePropertySource(
"configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties) : this.restTemplate;
Exception error = null;
Expand All @@ -100,15 +107,15 @@ public org.springframework.core.env.PropertySource<?> locate(
if (result != null) {
log(result);

if (result.getPropertySources() != null) { // result.getPropertySources()
// can be null if using
// xml
// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source
.getSource();
Map<String, Object> map = translateOrigins(source.getName(),
(Map<String, Object>) source.getSource());
composite.addPropertySource(
new MapPropertySource(source.getName(), map));
new OriginTrackedMapPropertySource(source.getName(),
map));
}
}

Expand Down Expand Up @@ -171,6 +178,32 @@ private void log(Environment result) {
}
}

private Map<String, Object> translateOrigins(String name,
Map<String, Object> source) {
Map<String, Object> withOrigins = new HashMap<>();
for (Map.Entry<String, Object> entry : source.entrySet()) {
boolean hasOrigin = false;

if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) entry.getValue();
if (value.size() == 2 && value.containsKey("origin")
&& value.containsKey("value")) {
Origin origin = new ConfigServiceOrigin(name, value.get("origin"));
OriginTrackedValue trackedValue = OriginTrackedValue
.of(value.get("value"), origin);
withOrigins.put(entry.getKey(), trackedValue);
hasOrigin = true;
}
}

if (!hasOrigin) {
withOrigins.put(entry.getKey(), entry.getValue());
}
}
return withOrigins;
}

private void putValue(HashMap<String, Object> map, String key, String value) {
if (StringUtils.hasText(value)) {
map.put(key, value);
Expand Down Expand Up @@ -208,14 +241,15 @@ private Environment getRemoteEnvironment(RestTemplate restTemplate,

try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(
Collections.singletonList(MediaType.parseMediaType(V2_JSON)));
addAuthorizationToken(properties, headers, username, password);
if (StringUtils.hasText(token)) {
headers.add(TOKEN_HEADER, token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add(STATE_HEADER, state);
}
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Expand Down Expand Up @@ -321,4 +355,25 @@ protected Map<String, String> getHeaders() {

}

static class ConfigServiceOrigin implements Origin {

private final String remotePropertySource;

private final Object origin;

ConfigServiceOrigin(String remotePropertySource, Object origin) {
this.remotePropertySource = remotePropertySource;
Assert.notNull(origin, "origin may not be null");
this.origin = origin;

}

@Override
public String toString() {
return "Config Server " + this.remotePropertySource + ":"
+ this.origin.toString();
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2012-2017 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.config.environment;

/**
* Media types that can be consumed and produced by Environment endpoints.
*
* @author Spencer Gibb
* @since 2.0.0
*/
public final class EnvironmentMediaType {

/**
* Constant for the Config Server V1 media type.
*/
public static final String V1_JSON = "application/vnd.spring-cloud.config-server.v1+json";

/**
* Constant for the Config Server V2 media type.
*/
public static final String V2_JSON = "application/vnd.spring-cloud.config-server.v2+json";

private EnvironmentMediaType() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.config.environment;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* A description of a property's value, including its origin if available.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class PropertyValueDescriptor {

private final Object value;

private String origin;

@JsonCreator
public PropertyValueDescriptor(@JsonProperty("value") Object value,
@JsonProperty("origin") String origin) {
this.value = value;
this.origin = origin;
}

public Object getValue() {
return this.value;
}

public String getOrigin() {
return this.origin;
}

public void setOrigin(String origin) {
this.origin = origin;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.cloud.config.client.ConfigClientProperties.AUTHORIZATION;
import static org.springframework.cloud.config.environment.EnvironmentMediaType.V2_JSON;

public class ConfigServicePropertySourceLocatorTests {

Expand Down Expand Up @@ -83,7 +84,7 @@ public void sunnyDay() {

HttpEntity httpEntity = argumentCaptor.getValue();
assertThat(httpEntity.getHeaders().getAccept())
.containsExactly(MediaType.APPLICATION_JSON);
.containsExactly(MediaType.parseMediaType(V2_JSON));
}

@Test
Expand Down
7 changes: 7 additions & 0 deletions spring-cloud-config-sample/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
<artifactId>spring-cloud-config-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<type>test-jar</type>
<scope>test</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
17 changes: 16 additions & 1 deletion spring-cloud-config-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,20 @@
<start-class>org.springframework.cloud.config.server.ConfigServerApplication
</start-class>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected void doHealthCheck(Health.Builder builder) throws Exception {

try {
Environment environment = this.environmentRepository.findOne(application,
profiles, repository.getLabel());
profiles, repository.getLabel(), false);

HashMap<String, Object> detail = new HashMap<>();
detail.put("name", environment.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment,
@Override
public synchronized Environment findOne(String application, String profile,
String label) {
return findOne(application, profile, label, false);
}

@Override
public synchronized Environment findOne(String application, String profile,
String label, boolean includeOrigin) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
getEnvironment(), new NativeEnvironmentProperties());
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
Environment result = delegate.findOne(application, profile, "");
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,25 @@ public CompositeEnvironmentRepository(

@Override
public Environment findOne(String application, String profile, String label) {
return findOne(application, profile, label, false);
}

@Override
public Environment findOne(String application, String profile, String label,
boolean includeOrigin) {
Environment env = new Environment(application, new String[] { profile }, label,
null, null);
if (this.environmentRepositories.size() == 1) {
Environment envRepo = this.environmentRepositories.get(0).findOne(application,
profile, label);
profile, label, includeOrigin);
env.addAll(envRepo.getPropertySources());
env.setVersion(envRepo.getVersion());
env.setState(envRepo.getState());
}
else {
for (EnvironmentRepository repo : this.environmentRepositories) {
env.addAll(
repo.findOne(application, profile, label).getPropertySources());
for (EnvironmentRepository repo : environmentRepositories) {
env.addAll(repo.findOne(application, profile, label, includeOrigin)
.getPropertySources());
}
}
return env;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

package org.springframework.cloud.config.server.environment;

import java.util.Map;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.environment.PropertyValueDescriptor;

/**
* @author Dave Syer
Expand All @@ -31,9 +34,24 @@ public Environment clean(Environment value, String workingDir, String uri) {
String name = source.getName().replace(workingDir, "");
name = name.replace("applicationConfig: [", "");
name = uri + "/" + name.replace("]", "");
result.add(new PropertySource(name, source.getSource()));
result.add(new PropertySource(name, clean(source.getSource(), uri)));
}
return result;
}

protected Map<?, ?> clean(Map<?, ?> source, String uri) {
for (Map.Entry<?, ?> entry : source.entrySet()) {
if (entry.getValue() instanceof PropertyValueDescriptor) {
PropertyValueDescriptor descriptor = (PropertyValueDescriptor) entry
.getValue();
if (!uri.endsWith("/")) {
uri = uri + "/";
}
String updated = descriptor.getOrigin().replace("[", "[" + uri);
descriptor.setOrigin(updated);
}
}
return source;
}

}
Loading

0 comments on commit 7f547f8

Please sign in to comment.