All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.config.vault.VaultConfigSource Maven / Gradle / Ivy

The newest version!
package io.scalecube.config.vault;

import com.bettercloud.vault.EnvironmentLoader;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.response.LogicalResponse;
import io.scalecube.config.ConfigProperty;
import io.scalecube.config.ConfigSourceNotAvailableException;
import io.scalecube.config.source.ConfigSource;
import io.scalecube.config.source.LoadedConfigProperty;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

/**
 * This class is an implementation of {@link ConfigSource} for Vault.
 *
 * @see Vault Project
 */
public class VaultConfigSource implements ConfigSource {

  private static final Logger LOGGER = System.getLogger(VaultConfigSource.class.getName());

  private static final EnvironmentLoader ENVIRONMENT_LOADER = new EnvironmentLoader();

  private static final String PATHS_SEPARATOR = ":";

  private final VaultInvoker vault;
  private final Collection secretsPaths;

  private VaultConfigSource(VaultInvoker vault, Collection secretsPaths) {
    this.vault = vault;
    this.secretsPaths = new ArrayList<>(secretsPaths);
  }

  @Override
  public Map loadConfig() {
    Map result = new HashMap<>();
    for (String path : secretsPaths) {
      try {
        LogicalResponse response = vault.invoke(vault -> vault.logical().read(path));
        final Map pathProps =
            response.getData().entrySet().stream()
                .map(LoadedConfigProperty::withNameAndValue)
                .map(LoadedConfigProperty.Builder::build)
                .collect(Collectors.toMap(LoadedConfigProperty::name, Function.identity()));
        result.putAll(pathProps);
      } catch (VaultException ex) {
        if (ex.getHttpStatusCode() == 404) {
          LOGGER.log(Level.ERROR, "Unable to load config properties from: " + path);
        } else {
          throw new ConfigSourceNotAvailableException(ex);
        }
      } catch (Exception ex) {
        LOGGER.log(Level.ERROR, "Unable to load config properties from: " + path, ex);
        throw new ConfigSourceNotAvailableException(ex);
      }
    }
    return result;
  }

  public static final class Builder {

    private Function builderFunction =
        Function.identity();

    private VaultInvoker invoker;

    private Set secretsPaths =
        Optional.ofNullable(
                Optional.ofNullable(ENVIRONMENT_LOADER.loadVariable("VAULT_SECRETS_PATH"))
                    .orElse(ENVIRONMENT_LOADER.loadVariable("VAULT_SECRETS_PATHS")))
            .map(s -> s.split(PATHS_SEPARATOR))
            .map(Arrays::asList)
            .map(HashSet::new)
            .orElseGet(HashSet::new);

    public Builder() {}

    /**
     * Appends {@code secretsPath} to {@code secretsPaths}.
     *
     * @param secretsPath secretsPath (may contain value with paths separated by {@code :})
     * @return this builder
     * @deprecated will be removed in future releases without notice, use {@link
     *     #addSecretsPath(String...)} or {@link #secretsPaths(Collection)}.
     */
    @Deprecated
    public Builder secretsPath(String secretsPath) {
      this.secretsPaths.addAll(toSecretsPaths(Collections.singletonList(secretsPath)));
      return this;
    }

    /**
     * Appends one or several secretsPath\es to {@code secretsPaths}.
     *
     * @param secretsPath one or several secretsPath\es (each value may contain paths separated by
     *     {@code :})
     * @return this builder
     */
    public Builder addSecretsPath(String... secretsPath) {
      this.secretsPaths.addAll(toSecretsPaths(Arrays.asList(secretsPath)));
      return this;
    }

    /**
     * Setter for {@code secretsPaths}.
     *
     * @param secretsPaths collection of secretsPath\es (each value may contain paths separated by
     *     colon)
     * @return this builder
     */
    public Builder secretsPaths(Collection secretsPaths) {
      this.secretsPaths = toSecretsPaths(secretsPaths);
      return this;
    }

    private static Set toSecretsPaths(Collection secretsPaths) {
      return secretsPaths.stream()
          .flatMap(s -> Arrays.stream(s.split(PATHS_SEPARATOR)))
          .collect(Collectors.toSet());
    }

    public Builder invoker(VaultInvoker invoker) {
      this.invoker = invoker;
      return this;
    }

    public Builder vault(UnaryOperator opts) {
      this.builderFunction = this.builderFunction.andThen(opts);
      return this;
    }

    public Builder config(UnaryOperator vaultConfig) {
      this.builderFunction = this.builderFunction.andThen(b -> b.options(vaultConfig));
      return this;
    }

    public Builder tokenSupplier(VaultTokenSupplier supplier) {
      this.builderFunction = this.builderFunction.andThen(b -> b.tokenSupplier(supplier));
      return this;
    }

    /**
     * Builds vault config source.
     *
     * @return instance of {@link VaultConfigSource}
     */
    public VaultConfigSource build() {
      return new VaultConfigSource(
          invoker != null ? invoker : builderFunction.apply(new VaultInvoker.Builder()).build(),
          secretsPaths);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy