A library for declaring configuration vars and setting their values in a centralized fashion. Included tooling allows one to gather and emit all config vars and their docstrings, default values, etc.
Applications and libraries wishing to declare config vars should add the
following to the project.clj
file:
Applications may also include the following to ease
generating a config.edn
file:
:aliases {"config" ["run" "-m" "outpace.config.generate"]}
Most configuration systems rely on consumers pulling named values from something akin to a global map (cf. environ). This yields a number of negative consequences:
This library attempts to address the above issues by:
Configuration is provided in an EDN map of namespaced symbols (naming a config var) to the value to be bound to the corresponding var:
{
com.example/greeting "Hello World!"
com.example/tree {:id 1, :children #{{:id 2} {:id 3}}}
com.example/aws-secret-key #config/env "AWS_SECRET_KEY"
com.example/path #config/property "some.path"
com.example/db-password #config/file "db-password.txt"
com.example/secret-edn #config/edn #config/file "secret_key.edn"
}
As shown above, custom data-reader tags can be used to pull values from external sources.
The configuration EDN map is provided to an application in one of the following ways:
config.edn
file in the current working directory.config.edn
java system property (e.g., a command line arg
-Dconfig.edn=...
). The value can be any string consumable by
clojure.java.io/reader
.resource.config.edn
java system property pointing to a resource file.outpace.config.bootstrap/explicit-config-source
to any non-nil
value consumable by
clojure.java.io/reader
.The :profiles
entry of your project.clj
file can be used to set the system
property for an environment-specific configuration EDN file:
:profiles {:test {:jvm-opts ["-Dconfig.edn=test-config.edn"]}
:prod {:jvm-opts ["-Dconfig.edn=prod-config.edn"]}}
Alternatively, different profiles can share the same config file name, but use different resource paths:
:profiles {:test {:resource-paths ["test_resources"] :jvm-opts ["-Dresource.config.edn=app-config.edn"]}
:prod {:resource-paths ["prod_resources"] :jvm-opts ["-Dresource.config.edn=app-config.edn"]}}
Declaring config vars is straightforward:
(require '[outpace.config :refer [defconfig]])
(defconfig my-var)
(defconfig var-with-default 42)
(defconfig ^:dynamic *rebindable-var*)
(defconfig ^:required required-var)
(defconfig ^{:validate [number? "Must be a number."
even? "Must be even."]}
an-even-number)
As shown above, the defconfig
form supports anything a regular def
form
does, as well as the following metadata:
:required
When true, an exception will be thrown if no default nor
configured value is provided. See also defconfig!
:validate
A vector of alternating single-arity predicates and error
messages. After a value is set on the var, an exception will be thrown when a
predicate, passed the set value, yields false.The outpace.config
namespace includes the current state of the configuration,
and while it can be used by code to explicitly pull config values, this is
strongly discouraged; just use defconfig
.
Recognizing that it is not always appropriate to provide configuration values directly in the config file, custom data-readers can be used to instead convert a tagged literal in the config to an externally-provided value. This allows one to still grasp the full configuration of an app, and at least know where a value will come from, if not the value itself.
The provided data-readers' tags are:
#config/env
Tags a string, interpreted as the name of an environment
variable, and yields the string value of the environment variable. If the
environment does not have that entry, then the var will use its default value
or remain unbound.#config/property
Tags a string, interpreted as the name of java system
property, and yields the string value of the property. If there is no property
defined by this name, then the var will use its default value or remain
unbound.#config/file
Tags a string, interpreted as a path to a file, and yields the
string contents of the file. If the file does not exist, then the var will use
its default value or remain unbound.#config/edn
Tags a string, interpreted as a single EDN-formatted object, and
yields the read object. When composed with #config/env
, #config/file
, or
#config/property
, if the external value is not provided, then the var will
use its default value or remain unbound.Custom data-readers
whose tag namespace is config
will be automatically loaded during config
initialization. See outpace.config/read-env
for an example of how to properly
implement a custom data-reader.
It is possible to use #config/or
to be able to use multiple sources of
external configuration values. It tags a vector where the first external value
will be used.
In the following example, the value from the environment variable APP_HOME
will be used. However, if it is not provided, it will fall back to the
app.home
system property. If neither external value is availabe, the var
will use its default value or remain unbound.
#config/or [#config/env "APP_HOME"
#config/property "app.home"]
The outpace.config.generate
namespace exists to generate a config.edn
file
containing everything one may need to know about the state of the config vars in
the application and its dependent namespaces. If a config.edn
file is already
present, its contents will be loaded, and thus preserved by the replacing file.
To generate a config.edn
file, invoke the following in the same directory as
your project.clj
file:
lein run -m outpace.config.generate
Alternately, one can just invoke lein config
by adding the following to
project.clj
:
:aliases {"config" ["run" "-m" "outpace.config.generate"]}
The generator can take an optional :check
flag (e.g., lein config :check
)
that causes it to print unbound and unused config vars to stdout. It will not
generate a config file.
The generator can also take a :strict
flag (e.g., lein config :strict
)
that will result in an exception after file generation if there are any config
vars with neither a default value nor configured value. This can be used to
provide feedback to automated build systems.
The following is an example of a generated config.edn
file:
{
;; UNBOUND CONFIG VARS:
; This is the docstring for the 'foo' var. This
; var does not have a default value.
#_com.example/foo
;; UNUSED CONFIG ENTRIES:
com.example/bar 123
;; CONFIG ENTRIES:
; The docstring for aaa.
com.example/aaa :configured-aaa
; The docstring for bbb. This var has a default value.
com.example/bbb :configured-bbb #_:default-bbb
; The docstring for ccc. This var has a default value.
#_com.example/ccc #_:default-ccc
}
The first section lists commented-out config vars that do not have a default value nor configured value, thus will be unbound at runtime. If a config value is provided, these entries will be re-categorized after regeneration.
The second section lists config entries that have no corresponding config var. This may happen after code change, or when a dependent library has been removed. If the config var reappears, these entries will be re-categorized after regeneration.
The third section lists all config vars used by the system, and their respective values. For reference purposes, commented-out default values will be included after the configured value. Likewise, commented-out entries will be included when their default values are used.
outpace.config-aws
as part of the outpace.config
namespace:check
flag for generate
.resources.config.edn
system property. @rafalprzywarski#config/or
outpace.config.repl/reload
to re-read config.#config/property
data-reader which sets a config var's value to the
contents of a Java property.extract
and provides?
now recursively visit data structures. This allows
something like the following in your config.edn
file (which did not work
before):
{foo.bar/aws-creds {:access-key #config/env "AWS_ACCESS_KEY_ID"
:secret-key #config/env "AWS_SECRET_ACCESS_KEY"}}
#config/edn
data-reader which can be composed with other readers to
interpret values from content.#config/file
data-reader which sets a config var's value to the contents
of a file.:validate
metadata support to defconfig
.config.edn
file nor
setting a system-property can be used (e.g., webapps).config
namespace (e.g., #config/foo
) will have the corresponding
data-reader function's namespace automatically loaded.Copyright © Outpace Systems, Inc.
Released under the Apache License, Version 2.0
Can you improve this documentation? These fine people already did:
Alexander Taggart, Paul Stadig, Daniel Solano Gómez, Devin Walters, Timothy Pratley, Jake McCrary, Rafal Przywarski & Ron TolandEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close