At Stack Builders, we love doing open source software and spreading the word, so more people can get involved and use the libraries with their new features. In this post, I want to share the less common yet useful features from Dotenv and Hapistrano that have been added recently.
Dotenv
Environment variables are very useful to separate configuration from code and it can be very tedious when the app fails because environment variables are not defined or because one of them started with an incorrect value. Fortunately, dotenv provides a couple of features to prevent those mistakes. Let’s see these features in action with an example.
First, greet_friends
will be the program to be executed, which needs some environment variables before execution:
#!/bin/sh
# greet_friends
USER_EMAILS=$(psql -H $DB_HOST -U $DB_USER -p $DB_PORT $DB_NAME -c ’SELECT email FROM friends)
for user_email in $USER_EMAILS; do
curl -s \
--user “api:$MAIL_KEY” ”https://api.mailgun.net/v3/$MAIL_DOMAIN/messages” \
-F from=$MAIL_SENDER \
-F to=$user_email \
-F subject='Hello' \
-F text=’How are you?’
done
The script will fail if it starts without the necessary environment variables and a further investigation will be needed to know what environment variables are missing. The task is easy in this example but it could become a pain in the neck if the code base were larger and needed more environment variables. Dotenv helps to avoid that issue by specifying the environment variables needed before execution in a dotenv example file listing the required environment variables. A single file is an option but in case you haven’t noticed dotenv allows to have multiple example files, which can be handy to differentiate concerns, so two files will do it:
# .env.example.db
DB_HOST=
DB_USER=
DB_PORT=
DB_NAME=
# .env.example.mail
MAIL_KEY=
MAIL_SENDER
MAIL_DOMAIN=
Now, let’s run the program with dotenv
without specifying any dotenv files or setting up any env variable in the environment:
dotenv -x .env.example.db -x .env.example.mail ‘./greet_friends’
Dotenv will complain with the following message because the environment variables are not defined anywhere yet:
dotenv: The following variables are present in one of .env.example.db, .env.example.paths, but not set in the current environment, or .env: DB_HOST, DB_USER, DB_PORT, DB_NAME, MAIL_KEY, MAIL_SENDER, MAIL_DOMAIN
Copy the dotenv example files and fill them properly:
cp .env{.example,}.db
cp .env{.example,}.mail
The program will run successfully once the variables are defined either in the environment or in the dotenv files. That’s cool and Dotenv also provides a --verbose
flag that will print the environment variables and their value in standard output before running a program, which is handy to know the values that dotenv sets before running a program:
dotenv -x .env.example.db -x .env.example.mail -f .env.db -f .env.mail --verbose ‘./greet_friends’
[INFO]: Load env 'DB_HOST' with value 'server.com'
[INFO]: Load env 'DB_USER' with value 'user'
[INFO]: Load env 'DB_PORT' with value '5432'
[INFO]: Load env 'DB_NAME' with value 'db'
[INFO]: Load env ‘MAIL_KEY’ with value 'my-secret-api-key'
[INFO]: Load env ‘MAIL_SENDER’ with value 'sender@example.com'
[INFO]: Load env 'MAIL_DOMAIN' with value 'server.com'
These two features can reduce some possible issues like missing an env var or getting a typo in the environment variables and save some time. Finally, dotenv
automatic build and release have been improved and they contain a static binary file (for Linux) in the GitHub releases. The binary is less than 3 MB, so give it a try!
Hapistrano
Hapistrano supports concurrent deployments for multiple targets, which can be useful for static scaling systems that have predictable traffic. So, let’s see this feature in action. First, take a look at following script file:
deploy_path: '/var/projects/app'
repo: 'https://github.com/stackbuilders/app.git'
revision: origin/master
targets:
host: user1@app1.server.com
host: user2@app2.server.com
build_script:
cabal update
cabal build
cabal exec db-migrations -- migrate
restart_command: systemd restart my-app-service
Hapistrano will run the build script steps concurrently on the two hosts. The last line in the build script may raise eyebrows from some good observers. What if the database migrations are executed concurrently in the two hosts? What if it’s unsure that the script is idempotent? How can we restrict that command to be executed in only one lead target? The last version of Hapistrano includes an option to solve those questions by allowing a lead target, which is the first host in the list, to run specific commands during the build script.
Let’s take a look at the following change in the build script to see this in action:
build_script:
cabal update
cabal build
command: cabal exec db-migrations -- migrate
lead_target: true
In the build script above, the database migrations would be executed only in the lead server, preventing race conditions caused by multiple schema change attempts triggered from different servers to a single database instance.
Hapistrano’s automatic build and release have also been updated and now we’re uploading Hapistrano into the GitHub GHRC.io registry. Please, take that into account, so that you can use the latest version with its new features and fixes from there.
Conclusion
The Dotenv features described in this post will help developers to reduce issues with environment variables before the execution of a program. Hapistrano doesn’t stay behind and the feature presented in this post can help anyone to solve their specific escalation problems when the application access is somehow predictable. New features for these libraries are being discussed internally and they’ll be presented in the near future so stay tuned!