Liking cljdoc? Tell your friends :D

config

Loads resources/conf/<env>/config.edn through Aero and exposes the settings libraries need. Assembling an Integrant system from those settings is your application’s job, not this library’s.

Coordinates

{:deps {com.wagoe/wagoe-config {:mvn/version "1.1.0-rc-1"}}}

Key namespaces

NamespacePurpose

wagoe.config

load-config plus typed accessors for the database, HTTP, observability and user-validation sections.

wagoe.config.secrets

The #secret reader, the ISecretSource protocol, and redaction for anything that displays config.

Usage

(require '[wagoe.config :as config])

(def cfg (config/load-config))          ; reads conf/<WAG_ENV>/config.edn
(config/db-spec cfg)                    ; => {:adapter :sqlite :database-path "app.db" ...}
(config/http-config cfg)
(config/logging-config cfg)

load-config resolves the profile from WAG_ENV, accepting both the long and short spellings — production and prod name the same directory.

Accessors

FunctionReads

db-adapter / db-spec

The active database: the first of :wagoe/sqlite, :wagoe/h2, :wagoe/postgresql and :wagoe/mysql present under :active. None is an error rather than a default, because defaulting would boot against a database nobody chose.

http-config

Port, host and server settings.

app-config / default-tenant-id

Application identity and the development tenant.

user-validation-config

Password and profile rules for the user library.

logging-config / metrics-config / tracing-config / error-reporting-config

Observability adapters. Each defaults rather than throwing — an absent optional section is normal.

Secrets

#secret reads a value through a secret source instead of straight from the environment:

{:password #secret "DB_PASSWORD"                             ; the default source, :env
 :api-key  #secret [:file "stripe-key"]                      ; /run/secrets/stripe-key, trimmed
 :token    #secret {:name "API_TOKEN" :default "dev-token"}} ; a default for dev

Two sources are built in. :env reads an environment variable. :file reads a file in /run/secrets, where Docker and Kubernetes mount secrets; set WAGOE_SECRETS_DIR to read another directory. Leading and trailing whitespace is trimmed.

A secret that no source has, or that is blank, fails load-config with a :configuration-error naming the secret and its source. The value never appears in an error. The map form takes :name, and optionally :source and :default; the default is used when the secret is missing. #or does not supply one: a missing #secret inside it fails like any other.

bb doctor reports every #secret without a default that the :env or :file source does not have, by name. It cannot ask other sources.

Pass :default-secret-source to make bare names read another source:

(config/load-config {:default-secret-source :file})

Redaction

Every value a #secret resolved to is redacted by value, whatever key it sits under: (wagoe.config.secrets/redact x) replaces it with <secret source:name>. The devtools config page and the REPL (config :section) helper show placeholders, and Apply on the config page writes the real value back.

Adding a source

A source is anything implementing ISecretSource. fetch-secret returns the value, or nil when the source does not have it. Throw when the source cannot be reached, so boot fails instead of running without the secret. Wagoe ships no Vault, SOPS or cloud adapter; a Vault one is a few lines:

(ns my.app.vault
  (:require [wagoe.config.secrets :as secrets]))

(defrecord VaultSource [client mount]
  secrets/ISecretSource
  (fetch-secret [_ secret-name]
    ;; my-vault stands in for your Vault client
    (get-in (my-vault/read-secret client (str mount "/" secret-name)) [:data :value])))

(defn source []
  (->VaultSource (my-vault/client (System/getenv "VAULT_ADDR")) "secret/my-app"))

Declare it at the top level of config.edn and name it with #secret [:vault "db-password"]:

{:secret-sources {:vault my.app.vault/source}
 :active         {:wagoe/postgresql {:password #secret [:vault "db-password"]}}}

The symbol names an ISecretSource, or a function of no arguments that returns one. The map is read before the rest of the file, so it must be literal: not inside #profile, and not in an included file. Because the file declares it, every reader of the config sees it, including the migration and database CLIs, which run no application code first. A bare aero/read-config does not: read through config/load-config or config/read-config-resource. A source under :env or :file replaces the built-in one.

Sources passed to load-config under :secret-sources win over declared ones:

(config/load-config {:secret-sources {:vault (->VaultSource client "secret/my-app")}})

Why it is a library

Four published libraries read configuration. Until it was extracted (#412) they did it with requiring-resolve against a wagoe.config namespace that the monorepo and wagoe new both happened to generate — a convention nothing declared and nothing checked, resting in one case on a private var. Anyone assembling a Wagoe application by hand had no such namespace at all.

Can you improve this documentation? These fine people already did:
thijscreemers & Thijs Creemers
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close