diff --git a/src/main/java/org/springframework/cloud/vault/VaultClient.java b/src/main/java/org/springframework/cloud/vault/VaultClient.java index d8b614e3..0fe16d85 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultClient.java +++ b/src/main/java/org/springframework/cloud/vault/VaultClient.java @@ -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 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(); } diff --git a/src/main/java/org/springframework/cloud/vault/VaultProperties.java b/src/main/java/org/springframework/cloud/vault/VaultProperties.java index dcb0d057..c7ecdecd 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultProperties.java +++ b/src/main/java/org/springframework/cloud/vault/VaultProperties.java @@ -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}. */ diff --git a/src/main/java/org/springframework/cloud/vault/VaultPropertySource.java b/src/main/java/org/springframework/cloud/vault/VaultPropertySource.java index edb25c97..0cc7fb18 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultPropertySource.java +++ b/src/main/java/org/springframework/cloud/vault/VaultPropertySource.java @@ -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 { private String context; private Map 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 { } } 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 { 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; diff --git a/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java index 6112c8ec..31dcf714 100644 --- a/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java @@ -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 secretProperties = vaultClient + .read(generic(vaultProperties, "missing"), createToken()); + + assertThat(secretProperties).isEmpty(); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOnFailFast() throws Exception { + + vaultProperties.setFailFast(true); + Map secretProperties = vaultClient .read(generic(vaultProperties, "missing"), createToken()); @@ -85,5 +98,4 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests { data.put("boolean", "true"); return data; } - } diff --git a/src/test/java/org/springframework/cloud/vault/configclient/ApplicationFailFastTests.java b/src/test/java/org/springframework/cloud/vault/configclient/ApplicationFailFastTests.java new file mode 100644 index 00000000..4da1eaa4 --- /dev/null +++ b/src/test/java/org/springframework/cloud/vault/configclient/ApplicationFailFastTests.java @@ -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"); + } + } +} \ No newline at end of file