Utilities for Configuration

GitLab Docs Pipeline Coverage License
Java module with utilities for managing and loading configuration and secrets. The library is designed to be kubernetes safe, handling how it uses config maps, mounted secrets and similar file-based configuration patterns.

See morimekta.net/utils for procedures on releases.

Getting Started

To add to maven, add this to the dependencies section in pom.xml:

<dependency>
    <groupId>net.morimekta.utils</groupId>
    <artifactId>config</artifactId>
    <version>3.3.1</version>
</dependency>

To add to gradle, add this to the dependencies block in build.gradle:

implementation 'net.morimekta.utils:config:3.3.1'

Configuration

The configuration helpers serve two purposes: simplifying the initial config setup and allowing for live-updated configuration. The library supports loading configuration from YAML files by default, and can be extended with custom readers for other formats through the ConfigReaderProvider service interface.

ConfigSupplier

The ConfigSupplier is a supplier class for loading a single config file and providing its parsed content. It is designed to be set up early (for example when defining command line arguments) and loaded later (for example when parsing the arguments). Once loaded, the config is available through the standard Supplier.get() method.

The supplier can also monitor the file for live updates, triggering change listeners whenever the file content changes on disk.

import net.morimekta.config.ConfigSupplier;

class MyApplication {
    private final ConfigSupplier<MyAppConfig> config =
            ConfigSupplier.yamlConfig(MyAppConfig.class);

    void initialize(ArgParser argParser) {
        argParser.add(Option
                .optionLong("--config", "Config file path",
                            ValueParser.path(config::loadAndMonitorUnchecked))
                .required());
    }

    void onStart() {
        var myConfig = config.get();
        // Config is loaded and available.
    }
}

It is also possible to load configuration more directly using the static factory methods:

var config = ConfigSupplier.yamlConfig(MyAppConfig.class, Path.of("/etc/app/config.yml"));

ConfigWatcher

The ConfigWatcher watches a directory for config files of the same type and notifies listeners on any changes including new files, updates and deletions. It parses each config file using the provided reader and exposes an event listener interface for easy integration with metrics.

import net.morimekta.config.ConfigWatcher;
import net.morimekta.config.readers.YamlConfigReader;

var watcher = new ConfigWatcher<>(
        Path.of("/etc/app/configs"),
        new YamlConfigReader<>(MyAppConfig.class),
        config -> config.name);

watcher.addChangeListener((changeType, config) -> {
    System.out.println(changeType.lowercase() + ": " + config.name);
});

watcher.start();

Configuration Readers

The library provides a pluggable system for reading config files.

  • ConfigReader is the base interface for loading and parsing a config file from a given path.
  • ConfigReaderSupplier selects the appropriate reader for a given file, either as a fixed reader or by detecting the file type.
  • ConfigReaderProvider is a service provider interface that can be registered through ServiceLoader or the module system to add support for new file formats.
  • YamlConfigReader is the built-in reader for parsing YAML (1.1) files using jackson-dataformat-yaml. It automatically discovers and registers available jackson modules.

Secrets

One of the important purposes of this library is to codify the separation of secrets from configuration. Secrets must be protected from visibility, while configuration otherwise should not need that protection.

SecretsManager

The SecretsManager manages secrets located in a single directory, where each non-hidden file represents one secret. It watches the directory for changes and updates loaded secrets as files are modified on disk. This is based on the standard kubernetes pattern for handling secrets, where most KMS-managed secrets have a way of creating native k8s Secret resources from their own system.

import net.morimekta.config.SecretsManager;

var secrets = new SecretsManager(Path.of("/run/secrets"));
var dbPassword = secrets.getAsString("db-password");

Secret

The Secret class holds an individual secret value and supports live updates through the SecretListener interface. When deserialized from a YAML config file using jackson, it can resolve the secret value from an environment variable using the ${ENV_VAR} syntax. If a SecretsManager has been set in the deserialization context, the secret is first looked up in the secret manager before falling back to the environment.

---
secretsManager:
  dir: "/run/secrets"
databasePassword: "${DB_PASSWORD}"

SecretValueDeserializer

The SecretValueDeserializer works like the Secret deserializer but targets plain String fields instead. It resolves ${SECRET_NAME} references from the secrets manager or environment variables and returns the value as a string. The SecretValueModule can be registered to apply this deserializer to all string fields during deserialization.

KMS Integrations

For most secret providers there already exist ways of managing k8s secrets, resulting in native Secret resources that can be mounted and used in the same way. The goal is that your application should not need to care where a secret comes from. Secrets are always available the same way, and moving from one provider to another should be entirely transparent.

Any system that can generate native Secret resources in kubernetes with a single file per secret entry is supported. Systems that can update the secret in place will also allow for live secret updates without service restarts.

Additionally, some password managers can be used to manage secrets for services: