diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java index d9d23e60..118d0a1e 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicator.java @@ -15,8 +15,7 @@ */ package org.springframework.cloud.vault.config; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.util.StringUtils; import org.springframework.vault.core.VaultOperations; @@ -28,7 +27,7 @@ import org.springframework.vault.support.VaultHealth; * @author Stuart Ingram * @author Mark Paluch */ -public class VaultHealthIndicator implements HealthIndicator { +public class VaultHealthIndicator extends AbstractHealthIndicator { private final VaultOperations vaultOperations; @@ -36,41 +35,38 @@ public class VaultHealthIndicator implements HealthIndicator { this.vaultOperations = vaultOperations; } + @Override - public Health health() { + protected void doHealthCheck(Builder builder) { try { VaultHealth vaultHealthResponse = vaultOperations.opsForSys().health(); - Builder healthBuilder = getHealthBuilder(vaultHealthResponse); + if (!vaultHealthResponse.isInitialized()) { + builder.down().withDetail("state", "Vault uninitialized"); + } + else - if (StringUtils.hasText(vaultHealthResponse.getVersion())) { - healthBuilder = healthBuilder.withDetail("version", - vaultHealthResponse.getVersion()); + if (vaultHealthResponse.isSealed()) { + builder.down().withDetail("state", "Vault sealed"); + } + else + + if (vaultHealthResponse.isStandby()) { + builder.up().withDetail("state", "Vault in standby"); + } + else { + builder.up(); } - return healthBuilder.build(); + if (StringUtils.hasText(vaultHealthResponse.getVersion())) { + builder.withDetail("version", + vaultHealthResponse.getVersion()); + } } catch (Exception e) { - return Health.down(e).build(); + builder.down(e); } } - - private Builder getHealthBuilder(VaultHealth vaultHealthResponse) { - - if (!vaultHealthResponse.isInitialized()) { - return Health.down().withDetail("state", "Vault uninitialized"); - } - - if (vaultHealthResponse.isSealed()) { - return Health.down().withDetail("state", "Vault sealed"); - } - - if (vaultHealthResponse.isStandby()) { - return Health.up().withDetail("state", "Vault in standby"); - } - - return Health.up(); - } } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorAutoConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorAutoConfiguration.java new file mode 100644 index 00000000..9199ba45 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-2018 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.config; + +import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for Vault providing beans for the application context. + * + * @author Mark Paluch + * @since 2.0 + */ +@Configuration +@ConditionalOnEnabledHealthIndicator("vault") +@ConditionalOnBean(VaultBootstrapConfiguration.class) +@ConditionalOnClass(HealthIndicator.class) +@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) +@Import({ VaultHealthIndicatorConfiguration.class, + VaultReactiveHealthIndicatorConfiguration.class }) +public class VaultHealthIndicatorAutoConfiguration { +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java index 40ce6a67..dbd3e3cc 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 the original author or authors. + * Copyright 2018 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. @@ -18,33 +18,23 @@ package org.springframework.cloud.vault.config; import java.util.Map; import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthIndicatorConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.vault.core.VaultOperations; /** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration - * Auto-configuration} for Vault providing beans for the application context. + * Configuration for {@link VaultHealthIndicator}. * * @author Stuart Ingram * @author Mark Paluch * @since 1.1 */ @Configuration -@ConditionalOnClass(HealthIndicator.class) -@ConditionalOnBean(VaultBootstrapConfiguration.class) -@ConditionalOnEnabledHealthIndicator("vault") -@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) -@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) -public class VaultHealthIndicatorConfiguration extends +@ConditionalOnBean(VaultOperations.class) +class VaultHealthIndicatorConfiguration extends CompositeHealthIndicatorConfiguration { private final Map vaultTemplates; diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicator.java new file mode 100644 index 00000000..8c923d84 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicator.java @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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.config; + +import java.lang.reflect.UndeclaredThrowableException; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; +import org.springframework.vault.core.ReactiveVaultOperations; +import org.springframework.vault.support.VaultHealth; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +/** + * Reactive health indicator reporting Vault's availability. + * + * @author Mark Paluch + * @since 2.0 + */ +public class VaultReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + private final ReactiveVaultOperations vaultOperations; + + public VaultReactiveHealthIndicator(ReactiveVaultOperations vaultOperations) { + this.vaultOperations = vaultOperations; + } + + @Override + protected Mono doHealthCheck(Builder builder) { + + try { + + return vaultOperations + .doWithSession(it -> it.get().uri("sys/health").exchange()) + .flatMap(it -> it.bodyToMono(VaultHealthImpl.class)) + .onErrorResume(WebClientResponseException.class, + VaultReactiveHealthIndicator::deserializeError) + .map(vaultHealthResponse -> { + return getHealth(builder, vaultHealthResponse); + }); + + } + catch (Exception e) { + return Mono.just(builder.down(e).build()); + } + } + + private static Mono deserializeError( + WebClientResponseException e) { + + try { + ObjectMapper mapper = new ObjectMapper(); + return Mono.just(mapper.readValue(e.getResponseBodyAsByteArray(), + VaultHealthImpl.class)); + } + catch (Exception jsonError) { + UndeclaredThrowableException t = new UndeclaredThrowableException(jsonError); + t.addSuppressed(e); + return Mono.error(t); + } + } + + private static Health getHealth(Builder builder, VaultHealthImpl vaultHealthResponse) { + + if (!vaultHealthResponse.isInitialized()) { + builder.withDetail("state", "Vault uninitialized"); + } + else + + if (vaultHealthResponse.isSealed()) { + builder.down().withDetail("state", "Vault sealed"); + } + else + + if (vaultHealthResponse.isStandby()) { + builder.up().withDetail("state", "Vault in standby"); + } + else { + builder.up(); + } + + if (StringUtils.hasText(vaultHealthResponse.getVersion())) { + builder.withDetail("version", vaultHealthResponse.getVersion()); + } + return builder.build(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + static class VaultHealthImpl implements VaultHealth { + + private final boolean initialized; + private final boolean sealed; + private final boolean standby; + private final int serverTimeUtc; + + @Nullable + private final String version; + + private VaultHealthImpl(@JsonProperty("initialized") boolean initialized, + @JsonProperty("sealed") boolean sealed, + @JsonProperty("standby") boolean standby, + @JsonProperty("server_time_utc") int serverTimeUtc, + @Nullable @JsonProperty("version") String version) { + + this.initialized = initialized; + this.sealed = sealed; + this.standby = standby; + this.serverTimeUtc = serverTimeUtc; + this.version = version; + } + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicatorConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicatorConfiguration.java new file mode 100644 index 00000000..7e6dabc1 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveHealthIndicatorConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018 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.config; + +import java.util.Map; + +import reactor.core.publisher.Flux; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthIndicatorConfiguration; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.vault.core.ReactiveVaultOperations; + +/** + * Configuration for {@link VaultReactiveHealthIndicator}. + * + * @author Mark Paluch + * @since 2.0 + */ +@Configuration +@ConditionalOnClass(Flux.class) +@ConditionalOnBean(ReactiveVaultOperations.class) +class VaultReactiveHealthIndicatorConfiguration + extends + CompositeReactiveHealthIndicatorConfiguration { + + private final Map reactiveVaultTemplates; + + public VaultReactiveHealthIndicatorConfiguration( + Map reactiveVaultTemplates) { + this.reactiveVaultTemplates = reactiveVaultTemplates; + } + + @Bean + @ConditionalOnMissingBean(name = { "vaultReactiveHealthIndicator" }) + public ReactiveHealthIndicator vaultHealthIndicator() { + return this.createHealthIndicator(this.reactiveVaultTemplates); + } +} diff --git a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories index 624d5011..31d0a037 100644 --- a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories @@ -1,6 +1,6 @@ # Auto-Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.vault.config.VaultHealthIndicatorConfiguration +org.springframework.cloud.vault.config.VaultHealthIndicatorAutoConfiguration # Bootstrap Configuration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.vault.config.DiscoveryClientVaultBootstrapConfiguration,\