Add fail-fast option.

Fixes gh-3.
This commit is contained in:
Mark Paluch
2016-05-13 23:25:26 +02:00
parent f6f3d1feb9
commit d2c356e0ec
5 changed files with 114 additions and 16 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.vault;
import static com.sun.org.apache.xalan.internal.xsltc.compiler.sym.error;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -24,11 +26,13 @@ import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Value;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.http.*;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
@@ -40,6 +44,7 @@ import org.springframework.web.client.RestTemplate;
* @author Mark Paluch
*/
@RequiredArgsConstructor
@CommonsLog
public class VaultClient {
public static final String API_VERSION = "v1";
@@ -61,6 +66,10 @@ public class VaultClient {
String url = buildUrl();
HttpHeaders headers = createHeaders(vaultToken);
Exception error = null;
String errorBody = null;
log.info(String.format("Fetching config from server at: %s", url));
try {
ResponseEntity<VaultResponse> response = this.rest.exchange(url,
HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class,
@@ -68,16 +77,30 @@ public class VaultClient {
HttpStatus status = response.getStatusCode();
if (status == HttpStatus.OK) {
if(response.getBody().getData() != null){
if (response.getBody().getData() != null) {
return secureBackendAccessor.transformProperties(response.getBody().getData());
}
}
} catch (HttpStatusCodeException e) {
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
return null;
}
throw e;
}
catch (HttpServerErrorException e) {
error = e;
if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders()
.getContentType())) {
errorBody = e.getResponseBodyAsString();
}
}
catch (Exception e) {
error = e;
}
if (properties.isFailFast()) {
throw new IllegalStateException(
"Could not locate PropertySource and the fail fast property is set, failing",
error);
}
log.warn(String.format("Could not locate PropertySource: %s"
, (errorBody == null ? error==null ? "key not found" : error.getMessage() : errorBody)));
return Collections.emptyMap();
}

View File

@@ -70,6 +70,11 @@ public class VaultProperties {
@NotEmpty
private String profileSeparator = ",";
/**
* Fail fast if data cannot be obtained from Vault.
*/
private boolean failFast = false;
/**
* Static vault token. Required if {@link #authentication} is {@code TOKEN}.
*/

View File

@@ -18,13 +18,13 @@ package org.springframework.cloud.vault;
import java.util.*;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.Assert;
import lombok.extern.apachecommons.CommonsLog;
/**
* @author Spencer Gibb
* @author Mark Paluch
@@ -36,10 +36,11 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
private String context;
private Map<String, String> properties = new LinkedHashMap<>();
private transient VaultState vaultState;
public VaultPropertySource(String context, VaultClient source, VaultProperties properties, VaultState state) {
public VaultPropertySource(String context, VaultClient source,
VaultProperties properties, VaultState state) {
super(context, source);
this.context = context;
this.vaultProperties = properties;
@@ -62,8 +63,19 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
}
}
catch (Exception e) {
log.error(String.format("Unable to read properties from vault for %s ",
accessor.variables()), e);
String message = String.format(
"Unable to read properties from vault for %s ",
accessor.variables());
if (vaultProperties.isFailFast()) {
if (e instanceof RuntimeException) {
throw e;
}
throw new IllegalStateException(message, e);
}
log.error(message, e);
}
}
}
@@ -76,17 +88,17 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
this.context));
VaultProperties.MySql mySql = vaultProperties.getMysql();
if(mySql.isEnabled()){
if (mySql.isEnabled()) {
accessors.add(SecureBackendAccessors.database(mySql));
}
VaultProperties.PostgreSql postgreSql = vaultProperties.getPostgresql();
if(postgreSql.isEnabled()){
if (postgreSql.isEnabled()) {
accessors.add(SecureBackendAccessors.database(postgreSql));
}
VaultProperties.Cassandra cassandra = vaultProperties.getCassandra();
if(cassandra.isEnabled()){
if (cassandra.isEnabled()) {
accessors.add(SecureBackendAccessors.database(cassandra));
}
return accessors;

View File

@@ -29,6 +29,7 @@ import org.springframework.web.client.RestTemplate;
/**
* Integration tests for {@link VaultClient} using the generic secret backend.
*
* @author Mark Paluch
*/
public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
@@ -39,6 +40,7 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
@Before
public void setUp() throws Exception {
vaultProperties.setFailFast(false);
prepare().writeSecret("app-name", (Map) createData());
vaultClient.setRest(new RestTemplate());
}
@@ -55,6 +57,17 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldReturnNullIfNotFound() throws Exception {
Map<String, String> secretProperties = vaultClient
.read(generic(vaultProperties, "missing"), createToken());
assertThat(secretProperties).isEmpty();
}
@Test(expected = IllegalStateException.class)
public void shouldFailOnFailFast() throws Exception {
vaultProperties.setFailFast(true);
Map<String, String> secretProperties = vaultClient
.read(generic(vaultProperties, "missing"), createToken());
@@ -85,5 +98,4 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
data.put("boolean", "true");
return data;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2016 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.vault.configclient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
/**
* Tests for fail fast option.
*
* @author Mark Paluch
*/
@SpringBootApplication
public class ApplicationFailFastTests {
@Test
public void contextLoads() {
try {
new SpringApplicationBuilder().sources(ApplicationFailFastTests.class).run(
"--server.port=0", "--spring.cloud.vault.failFast=true",
"--spring.cloud.vault.port=9999");
fail("failFast option did not produce an exception");
}
catch (Exception e) {
assertThat(e).hasMessageContaining("fail fast");
}
}
}