Composable Configuration
A way of composing configuration values across multiple dimensions and using them in arbitrary applications. Probably not original.
I recently found myself trying to manage the configuration of a bunch of utility applications. These apps were extracting data from a database, massaging it, then uploading to to a Graphql server.
I had three databases: local development, staging, and production, and three servers: test, staging and production. I needed to run any of the apps, passing in appropriate URLS and credentials based on the database and server.
It seems that the conventional way is to use config files with approriate names and somehow knit them together. One example of this is .dotenvx, where you could run
$ dotenvx run -f .env.db-staging -f .env.gql-test -- ruby ....
Using a tool such as this adds yet more dependencies and a bit more cognative load to an already complex project. So I went primitive and wrote something that lets me do this using a command such as:
# use staging database and test gql server
$ db-staging gql-test ruby ....
# or the test database and the staging server
$ db-test gql-staging ruby ...
Embarassing Simplicity
db-staging
and gql-test
are simply shell scripts that set environment variables. In this case I’m using the fish shell, but this’ll likely work in bash and zsh.1
#!/opt/homebrew/bin/fish
set -x DB_HOST localhost
set -x DB_PORT 3306
set -x DB_NAME pip
set -x DB_USERNAME dave
set -x DB_PASSWORD "secret"
exec $argv
#!/opt/homebrew/bin/fish
set -x GQL_SERVER "https://my.server"
set -x GQL_ACCESS_TOKEN .....
exec $argv
The only special thing is the last line. After the script sets the configuration into environment variables, it calls exec
to invoke the next command on the command line. This command will inherit the environment variables just set. If it’s another configuration script, it can set more variables, and then chain to the next command.
For example, when you run
$ db-staging gql-test ruby ....
the db-staging
script receives the arguments gql-test ruby ...
. When it calls exec
, the gql-test
script is run, receiving ruby ...
as an argument. This is our application, and it runs with all the configuation we just set up.
And, when it finishes and returns us to our top-level prompt, the variables are no longer in the environment.
Footnotes
You’ll need to change
$argv
to whatever your shell uses.↩︎