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 committed Jul 25, 2019
1 parent f8894e0 commit 022af82
Show file tree
Hide file tree
Showing 39 changed files with 452 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.config.client.ConfigClientProperties.Credentials;
import org.springframework.cloud.config.environment.Environment;
Expand All @@ -46,6 +49,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 +60,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 Down Expand Up @@ -105,10 +110,9 @@ public org.springframework.core.env.PropertySource<?> locate(
// xml
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source
.getSource();
composite.addPropertySource(
new MapPropertySource(source.getName(), map));
Map<String, Object> map = translateOrigins((Map<String, Object>) source.getSource());
composite.addPropertySource(new OriginTrackedMapPropertySource(source
.getName(), map));
}
}

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

private Map<String, Object> translateOrigins(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(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,6 +235,7 @@ 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);
Expand Down Expand Up @@ -321,4 +349,47 @@ protected Map<String, String> getHeaders() {

}

static class ConfigServiceOrigin implements Origin {

private Object origin;

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

@Override
public String toString() {
return "Remote file "+this.origin.toString();
}
}

static class OriginTrackedMapPropertySource extends MapPropertySource
implements OriginLookup<String> {

@SuppressWarnings({ "unchecked", "rawtypes" })
OriginTrackedMapPropertySource(String name, Map source) {
super(name, source);
}

@Override
public Object getProperty(String name) {
Object value = super.getProperty(name);
if (value instanceof OriginTrackedValue) {
return ((OriginTrackedValue) value).getValue();
}
return value;
}

@Override
public Origin getOrigin(String name) {
Object value = super.getProperty(name);
if (value instanceof OriginTrackedValue) {
return ((OriginTrackedValue) value).getOrigin();
}
return null;
}

}

}
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
*
* http://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,35 @@
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 @@ -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 @@ -43,14 +43,19 @@ public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment,
this.order = properties.getOrder();
}

@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) {
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,23 @@ 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());
} else {
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,23 @@ 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.yaml.snakeyaml.nodes.Tag;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.EnvironmentMediaType;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -104,12 +105,28 @@ public void setAcceptEmpty(boolean acceptEmpty) {
@RequestMapping("/{name}/{profiles:.*[^-].*}")
public Environment defaultLabel(@PathVariable String name,
@PathVariable String profiles) {
return labelled(name, profiles, null);
return getEnvironment(name, profiles, null, false);
}

@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name,
@PathVariable String profiles) {
return getEnvironment(name, profiles, null, true);
}

@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, false);
}

@RequestMapping(path = "/{name}/{profiles}/{label:.*}", produces = EnvironmentMediaType.V2_JSON)
public Environment labelledIncludeOrigin(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, true);
}

public Environment getEnvironment(String name, String profiles, String label, boolean includeOrigin) {
if (name != null && name.contains("(_)")) {
// "(_)" is uncommon in a git repo name, but "/" cannot be matched
// by Spring MVC
Expand All @@ -120,7 +137,7 @@ public Environment labelled(@PathVariable String name, @PathVariable String prof
// by Spring MVC
label = label.replace("(_)", "/");
}
Environment environment = this.repository.findOne(name, profiles, label);
Environment environment = this.repository.findOne(name, profiles, label, includeOrigin);
if (!this.acceptEmpty
&& (environment == null || environment.getPropertySources().isEmpty())) {
throw new EnvironmentNotFoundException("Profile Not found");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ public EnvironmentEncryptorEnvironmentRepository(EnvironmentRepository delegate,

@Override
public Environment findOne(String name, String profiles, String label) {
Environment environment = this.delegate.findOne(name, profiles, label);
return findOne(name, profiles, label, false);
}

@Override
public Environment findOne(String name, String profiles, String label, boolean includeOrigin) {
Environment environment = this.delegate.findOne(name, profiles, label, includeOrigin);
if (this.environmentEncryptor != null) {
environment = this.environmentEncryptor.decrypt(environment);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ public interface EnvironmentRepository {

Environment findOne(String application, String profile, String label);

default Environment findOne(String application, String profile, String label, boolean includeOrigin) {
return findOne(application, profile, label);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public org.springframework.core.env.PropertySource<?> locate(
Environment environment) {
CompositePropertySource composite = new CompositePropertySource("configService");
for (PropertySource source : this.repository
.findOne(this.name, this.profiles, this.label).getPropertySources()) {
.findOne(this.name, this.profiles, this.label, false).getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source.getSource();
composite.addPropertySource(new MapPropertySource(source.getName(), map));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public void setSql(String sql) {

@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) {
String config = application;
if (StringUtils.isEmpty(label)) {
label = "master";
Expand Down
Loading

0 comments on commit 022af82

Please sign in to comment.