NAME

config-resolver.pl

SYNOPSIS

Extract a single value from some JSON file...

export DBI_DSN=$(config-resolver.pl -p /usr/share/my-app/config.json DBI_DSN)

Create a finalized configuration from a template...

config-resolver.pl \
      -p /usr/share/my-app/config.json \
      -t /usr/share/my-app/my-site.conf.tpl > /etc/apache2/sites-available/my-site.conf  

DESCRIPTION

A command-line utility for dynamically resolving placeholders in templates. It supports a robust variable substitution syntax that includes protocol handlers (plugins) that provide customized resolution of values.

MOTIVATION

Config::Resolver, the engine behind this script, was created to provide a single, powerful, and secure tool for managing complex application configurations. It was designed to solve common challenges in modern, dynamic applications while giving developers an extensible tool.

This utility provides a simple command-line interface to Config::Resolver, allowing you to:

  • Manage Multiple Environments: Use conditional logic (e.g., ${env eq 'prod' ? ...}) to build a single, clean template that adapts to any environment.

  • Securely Fetch Secrets: Keep secrets out of your codebase by using pluggable backends to fetch values at runtime (e.g., ssm://...) right when you need them.

  • Simplify Deployment Scripts: Replace complex and brittle sed, awk, or envsubst logic in deployment scripts (like a docker-entrypoint.sh) with a single, robust, and testable command.

  • Enable Simple Data Transformations: Use safe, "allow-listed" functions (e.g., ${uc(hostname)}) to format values directly in your configuration.

  • Use Plugins: Use plugins for custom variable resolution.

USAGE

config-resolver.pl OPTIONS [key=value key=value]

Utility to extract value from config file, or apply parameters to a template file.

Commands

resolve
dump

Options

-d, --dump                dump the configuration file
-f, --format              output format (json, yml, csv), default: json
-g, --debug               debug output
-h, --help                help
-k, --key                 key to output from configuration file
-o, --outfile             output file, default: STDOUT
-p, --parameter-file      name of the parameter file (JSON)
-V, --parameters          key/value pairs ala CGI (foo=bar&baz=buz)
--plugins                 a comma separated list of plugins
-P, --pretty              pretty print JSON
-S, --separator           separator character for csv format dump
-t, --template            name of the template file
-u, --umask               umask to use output file
-w, --warning-level       "warn" or "error"

See man 'config-resolver.pl' for more details.

OPTION DETAILS

-d, --dump

Outputs all values from the parameters file. Use --format to control the output format.

Note: This is a deprecated. Use the dump command.

default: json

-e, --env

Uses the current environment variables (%ENV) as the parameter source.

This creates a temporary JSON parameter file containing the current environment and uses it for resolution. This is useful for 12-factor apps where configuration is passed solely via environment variables.

config-resolver.pl -e -t app.conf.tpl
-f, --format

Output format. Valid values are 'json', 'yml' or 'csv'.

Note: 'csv' format is only valid for dumping a hash. It will output a list of key/value pairs separated by a separation character(s) (default: ' = '). This will only output the hash to a depth of 1.

default: json

-g, --debug

Logs debug messages to STDERR.

-k, --key

A key to dump from the parameters file.

-P, --pretty

Pretty print JSON output.

default: false

-m, --manifest

Specifies a manifest file (YAML or JSON) for batch-processing multiple templates in a single run.

See "BATCH PROCESSING USING A MANIFEST FILE"

-o, --outfile

Name of the output file to create. If not present, output is sent to STDOUT.

default: STDOUT

-O, --output-dir

The target directory for writing output files.

**Required** when --template is a directory.

-p, -- parameter-file

A JSON or YAML file that contains the parameters used for interpolation.

The parameter file is used to populate the template. Parameters values should be constants or special values of the form:

xxx://key-path

...where "xxx" represents the protocol prefix for a plugin (e.g. ssm:// for retrieving values from thhe AWS SSM Parameter Store API).

default: none

--plugins

A comma delimited list of plugin names. See "PLUGINS"

--plugin xxx:key=value

This is the mechanism you use to pass options to your plugins.

Example:

--plugin ssm:endpoint_url=http://localhost:4566
-V, --parameters

You can supply key/value pairs at the command line to do simple templating operations.

echo 'foo=${foo}' | config-resolver -V 'foo=bar&bar=buz'
-r, --resolve

Resolve the parameters only and print out the resulting JSON.

Example:

config-resolver -p /etc/tbc-prod.json -r -P

config-resolover -p /etc/tbc-prod.json --pretty resolve
-S, --separator

If you want to dump a hash as a flatten list of key/value pairs use the --format csv option. This option will set the separator character(s).

default: ' = '

-t, --template

Name of the template file **or directory**.

**File Mode:** If a file is specified, it treats it as a single template.

**Directory Mode:** If a directory is specified, the script automatically scans it for files ending in .tpl. It then generates a corresponding output file for each template in the directory specified by --output-dir.

See "BATCH PROCESSING USING A MANIFEST FILE" for details.

-u, --umask

A umask to use when creating an output file. The default is the umask of the current process. Use 0027 to create a file that can only be read by the owner and the group that the owner belongs too. Hence, if run by root, the output file will only be readable by root.

default: none

-w, --warn-level

Determines how errors or missing paramters are handled. See below.

warn

Continues on errors but outtputs warning error messages when values cannot be resolved.

error

Halts processing if any value cannot be resolved.

BATCH PROCESSING USING A MANIFEST FILE

This feature allows you to provision a set of configuration files in a single pass. There are two ways to use batch processing:

1. Automatic (Directory Mode)

This mode is ideal for simple 1:1 bulk rendering. If you provide a directory path to --template (and a target --output-dir), the script automatically generates a temporary manifest for you.

It scans for files with a .tpl extension and maps every input template to a corresponding output file, stripping the extension.

# Renders all *.tpl files in /templates to /etc/app
config-resolver.pl --template /opt/app/templates --output-dir /etc/app

2. Manual (Manifest Mode)

For fine-grained control—such as renaming files, using different parameters for specific templates, or setting file permissions - you can manually create a manifest file and pass it via --manifest.

The manifest file must be a HASH containing a globals hash and a jobs array.

Manifest Structure

globals (Optional)

A HASH of default keys applied to every job. Common defaults include parameters, template_path, and umask.

jobs (Required)

An ARRAY of HASHes. Each entry represents a single file to generate. Keys defined here override those in globals.

Inheritance and Merging

For each job, the resolver merges the job-specific keys over the globals keys. After merging, every job must possess at least an outfile and a parameters source.

Convention Over Configuration

You do not always need to explicitly define the template key. If a job omits the template key, but template_path is defined (usually in globals), the resolver derives the template filename from the outfile:

template = template_path / basename(outfile) . '.tpl'

This allows you to define the source directory once in globals and only list the desired output paths in jobs.

Example manifest.yml

# --- Global defaults for all jobs ---
globals:
  parameters: /etc/my-app/common-config.json
  template_path: /opt/my-app/templates
  umask: 0027

# --- List of files to generate ---
jobs:
  # JOB 1: USES CONVENTION
  # 'template' is not specified, so it is derived:
  # -> /opt/my-app/templates/app.properties.tpl
  - outfile: /etc/my-app/app.properties

  # JOB 2: USES CONVENTION
  # 'template' is derived:
  # -> /opt/my-app/templates/database.ini.tpl
  - outfile: /etc/my-app/database.ini

  # JOB 3: "ONE-OFF" (Overrides Globals)
  # This job overrides 'parameters' and 'template'
  # but still inherits 'umask' from globals.
  - parameters: /etc/my-app/nginx-config.json
    template: /opt/my-app/templates/special/nginx.conf.tpl
    outfile: /etc/nginx/sites-available/my-app.conf

PLUGINS

Plugins (or "protocol handlers") are the core feature of Config::Resolver. They allow you to fetch values from external sources directly from within your templates or parameter files.

The script recognizes special value strings formatted as a URI:

protocol://path/to/value

For example, to fetch a value from AWS SSM Parameter Store, you would use:

${ssm:///path/to/my/secret}

Built-in Backends

The resolver engine includes two built-in backends that are always available and do not require any configuration:

env://PATH

Resolves the value from $ENV{PATH}. This is the recommended way to inject environment variables.

${env://USER}
file://PATH

Resolves the value by reading the entire contents of the file at PATH. This is the recommended way to inject secrets, certificates, or tokens when more advanced plugins (AWS SSM, AWS Secrets Manager, etc) are not available.

${file:///var/run/secrets/token}

Plugin Configuration

Many plugins, like ssm, require configuration (e.g., the AWS region, or a custom endpoint for local testing). You can provide this configuration in two ways, which are layered:

1. The RC File (Defaults)

On startup, the script will attempt to load a configuration file from:

$HOME/.config-resolverrc

This file (in JSON or YAML format) is the perfect place to set your team- or user-wide defaults.

Example ~/.config-resolverrc:

{
  "ssm": {
    "region": "us-east-1",
    "endpoint_url": "http://localhost:4566"
  },
  "another_plugin": {
    "foo": "bar"
  }
}

2. Command-Line Overrides (Specifics)

You can override or provide new settings for any plugin on the command line using the --plugin:* option. This is the top layer and will *always* win over the RC file.

The syntax is:

--plugin PROTOCOL:key=value

You can repeat the --plugin option to build up the configuration.

Example:

config-resolver.pl \
    --plugin ssm:region=us-west-2 \
    --plugin ssm:endpoint_url=http://localhost:4566 \
    -t my-template.tpl

The script merges these two sources, giving command-line options final priority, and passes the result to the resolver engine.

Plugin "Setup-Only" Execution Mode

This script does not have a default command (like resolve or dump). This intentional design enables a powerful "init-only" workflow for plugins that need to perform on-demand setup or initialization tasks.

If you run config-resolver.pl *without* a command, it will:

Execute its full initialization phase (parsing all CLI args, loading plugins, and running their new() or init() methods).
Proceed to the run() phase, find no command, and exit cleanly.

This allows you to add features to your plugin's init() method that are triggered by a specific CLI flag. This is the recommended pattern for tasks like seeding a local datastore (e.g., LocalStack) or performing a one-time authentication.

Recipe for a Plugin "Init-Task"

Follow this 3-step recipe to add an on-demand setup task to your plugin.

1. Define Configuration Keys

In your plugin's documentation, define the keys a user must set in their .config-resolverrc file .

# In .config-resolverrc
[plugin ssm]
  endpoint_url = http://localhost:4566
  
  # --- Keys for your one-time setup ---
  seed_file = /opt/my-app/localstack-seed.json
  load_on_init = false # <-- Default to false!
2. Add Logic to Your Plugin's new()

In your plugin's new() constructor, check for your flag.

# In lib/Config/Resolver/Plugin/SSM.pm
sub new {
    my ($class, $options) = @_;
    my $self = $class->SUPER::new($options);

    # --- This is the "on-demand" hook ---
    if ( my $seed_file = $self->get_load_on_init ) {
        print {*STDERR} "Seeding SSM from $seed_file...\n";
        $self->load_data_from_file( $seed_file );
    }
    # --- End of hook ---

    return $self;
}
3. Document the User's Command

Finally, document the command the user must run. Because the CLI flag trumps the config file , this single command will trigger your logic:

# Triggers the one-time setup:
$ config-resolver.pl --plugins SSM --plugin ssm:load_on_init=true

How This Works: The script runs, but no command (like resolve) is given. The init() phase runs, loading your plugin. The --plugin flag overrides the config file, setting load_on_init to true. Your new() method fires, sees the flag, and runs your setup logic. The script then exits cleanly because no command was specified.

This workflow is fully compatible with STDIN. If your plugin's init() task reads from STDIN (e.g., ssm:load=-_), config-resolver.pl will politely detect that the STDIN stream has already been consumed and will not attempt to read from it again.

AUTHOR

Rob Lauer - <rclauer@gmail.com>

SEE ALSO

Config::Resolver, Config::Resolver::Plugin::SSM, Config::Resolver::Plugin::SecretsManager

1 POD Error

The following errors were encountered while parsing the POD:

Around line 992:

Non-ASCII character seen before =encoding in 'control—such'. Assuming UTF-8