Utilities for Configuration
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.
ConfigReaderis the base interface for loading and parsing a config file from a given path.ConfigReaderSupplierselects the appropriate reader for a given file, either as a fixed reader or by detecting the file type.ConfigReaderProvideris a service provider interface that can be registered throughServiceLoaderor the module system to add support for new file formats.YamlConfigReaderis the built-in reader for parsing YAML (1.1) files usingjackson-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.
gopass: Use the gopass kubernetes plugin. To automate deployment you can usefluxwith the flux-secret plugin.HashiCorp Vault: Use the Vault kubernetes-sidecar to load and update secrets.AWS Secrets Manager: Use the AWS official driver to load secrets from AWS into your EKS cluster.
Additionally, some password managers can be used to manage secrets for services:
1password: Use the kubernetes injector to handle secrets from 1password.LastPass: Use the LastPass Operator to create and manage secrets from a LastPass account.