configdir 0.3.1
File names for keys. File contents for values.
Requires Python: >=3.6,<4.0

=========
ConfigDir
=========

File names for keys. File contents for values.

For security, simplicity, and consistency, all configuration is in files |--|
not environmental variables.

.. contents:: :local:


Features
--------

- Security |--| Environmental variables set on a Docker image or container are
  injectable outside the container. This means environmental variables are
  insecure for secrets like passwords or encryption keys.
- Simplicity |--| ConfigDir is simple to parse. This makes it easy for any
  language to parse. It is particularly useful for shell interpreters like
  bash.
- Consistency |--| Rather than have two or more ways to configure an
  application securely (files for secrets, environmental variables for
  everything else), all configuration is done as files.
- Kubernetes Integration |--| Easy to generate from ConfigMaps_ or Secrets_
- Variable Interpolation |--| Configuration values can reference other
  configuration values. Kubernetes only interpolates variables for
  ``command``, ``args``, or ``env`` fields so Kubernetes variable
  interpolation can not be used when volume mounting ConfigMaps_ or
  Secrets_ as files.


ConfigDir Standard
------------------

Configuration is mounted as a directory of files in ``/configdir``
(by default). The configuration directory can be overridden with the
environmental variable ``CONFIGDIR``.

Each key is create as a file named ``/configdir/{key}``. The configuration,
when parsed, will be a dictionary where the keys of the dictionary are
the child file names.

Nested keys are allowed: ``/configdir/{key}/{sub-key}``. If the parent key
is a directory, the value will be a dictionary where the keys of the
dictionary are the child file names. There is no limit to directory nesting
depth.


Filenames
~~~~~~~~~

Each filename consists of up to two parts: the ``name`` and an optional
``extension``. Only the ``name`` portion is used for the configuration key.

- ``name`` must consist solely of lowercase ascii letters, uppercase ascii
  letters, digits, and underscore and must not begin with a digit
  (``[a-zA-Z_][a-zA-Z0-9_]*``).
- ``extension`` must be omitted or be one of the `File Types`_ below.


File Contents
~~~~~~~~~~~~~

Trailing and leading whitespace is removed from file contents (except for
``.bin`` files).


Variable Interpolation
++++++++++++++++++++++

Files can contain template place holders for other config keys. Placeholders
are enclosed with squiggly braces (``{{PLACEHOLDER}}``).

- Placeholders can refer to nested keys using dot (``.``)
  notation: ``{{PARENT_KEY.NESTED_KEY}}``
- Placeholders can refer to nested list items using at (``@``)
  notation: ``{{PARENT_KEY@1}}``
- Placeholders may only reference number or string values.

File Types
++++++++++

- A File with no extension has a value that is UTF-8 encoded and can
  contain interpolated variables.
- ``.json`` will automatically be decoded as JSON then each value will be
  interpolated.
- ``.yaml`` will automatically be decoded as YAML then each value will be
  interpolated.
- ``.bin`` will not be UTF-8 decoded, will not be interpolated, and will be
  loaded as binary.
- ``.uri`` will be decoded as a UTF-8 string then interpolated and each 
  interpolated value will be URL encoded. 

Example
~~~~~~~


Directory Contents
++++++++++++++++++

=================================================================  ========
File Path                                                          Contents
=================================================================  ========
``/configdir/REDIS_URI.uri``                                       ``redis://:{{REDIS_PASSWORD}}@{{REDIS_HOST}}:{{REDIS_PORT}}/{{REDIS_SENTINEL_MASTER}}/0``
``/configdir/REDIS_PASSWORD``                                      ``$3cr3t``
``/configdir/REDIS_SENTINEL_MASTER``                               ``mymaster``
``/configdir/REDIS_HOST``                                          ``redis-sentinel``
``/configdir/REDIS_PORT``                                          ``6319``
``/configdir/CELERY_BROKER_URL.uri``                               ``sentinel://{{REDIS_HOST}}:{{REDIS_PORT}}``
``/configdir/CELERY_BROKER_TRANSPORT_OPTIONS/master_name``         ``{{REDIS_SENTINEL_MASTER}}``
``/configdir/CELERY_BROKER_TRANSPORT_OPTIONS/visibility_timeout``  ``3600``
``/configdir/POSTGRES_DB_URI.uri``                                 ``postgresql://root:postgres@postgres:5432/database``
``/configdir/SMTP/username``                                       ``sender``
``/configdir/SMTP/password``                                       ``$3cr3t``
``/configdir/SMTP/port``                                           ``542``
``/configdir/SMTP/host``                                           ``mail.mailgun.com``
``/configdir/SMTP/certificate.bin``	                               ``<binary>``
``/configdir/KEYS.json``                                           ``{"keys": {"id": 1, "private_key": "<pem>"}}``
``/configdir/PUBLIC_KEY.bin``	                                     ``<binary>``
``/configdir/BLOBSTORE_URI.uri``                                   ``http://{{BLOBSTORE_KEY}}:{{BLOBSTORE_SECRET}}@blobstore/``
``/configdir/BLOBSTORE_KEY``                                       ``aderattsrngmvoc1gogj54``
``/configdir/BLOBSTORE_SECRET``                                    ``C/Ahz+pAA/NMWirgEy4Pg==``

=================================================================  ========


Parsed ConfigDir
++++++++++++++++

.. code-block:: json

  {
    "PUBLIC_KEY": "<binary>",
    "KEYS": {
      "keys": {
        "id": 1,
        "private_key": "<pem>"
      }
    },
    "CELERY_BROKER_TRANSPORT_OPTIONS": {
      "master_name": "mymaster",
      "visibility_timeout": "3600"
    },
    "CELERY_BROKER_URL": "sentinel://redis-sentinel:6319",
    "POSTGRES_DB_URI": "postgresql://root:postgres@postgres:5432/database",
    "REDIS_HOST": "redis-sentinel",
    "REDIS_PASSWORD": "$3cr3t",
    "REDIS_PORT": "6319",
    "REDIS_SENTINEL_MASTER": "mymaster",
    "REDIS_URI": "redis://:%243cr3t@redis-sentinel:6319/mymaster/0",
    "SMTP": {
      "certificate": "<binary>",
      "host": "mail.mailgun.com",
      "password": "$3cr3t",
      "port": "542"
    },
    "BLOBSTORE_URI": "http://aderattsrngmvoc1gogj54:C%2FAhz%2BpAA%2FNMWirgEy4Pg%3D%3D@blobstore/",
    "BLOBSTORE_KEY": "aderattsrngmvoc1gogj54",
    "BLOBSTORE_SECRET": "C/Ahz+pAA/NMWirgEy4Pg=="    
  }


Python Usage
------------

.. code-block:: python

  from configdir import configdir
  config = configdir()
  print(config["REDIS_URI"])


Similar Projects
----------------

- `envdir <http://cr.yp.to/daemontools/envdir.html>`_ |--| Launches a
  sub-process with configuration directory contents as environmental
  variables. There is also a `Python port <https://github.com/jezdez/envdir>`_.
- `confdir <https://github.com/misli/python-confdir>`_ |--| Python
  based.

.. _ConfigMaps: https://kubernetes.io/docs/concepts/configuration/configmap/
.. _Secrets: https://kubernetes.io/docs/concepts/configuration/secret/
.. |--| unicode:: U+2013   .. en dash