Blog of Rob Galanakis (@robgalanakis)

Makefile Application Presets

We saw in the last post how to use Makefile wildcards to write targets like this:

migrate-to-%:
    @bundle exec rake migrate[$(*)] 
guard-%:
    @if [ -z '${${*}}' ]; then echo 'ERROR: variable $* not set' && exit 1; fi
logs: guard-STACK
    @awslogs get -w /ecs/$(STACK)_MyService

So that we can build CLIs like this:

$ make migrate-to-50
Migrating to version 50...

$ make logs
ERROR: variable STACK not set
$ STACK=qa make logs
...

(Note the @ prefix on commands in the Makefile, it avoids the line being echoed to stdout)

This is neat but it only works well for user-supplied values, like "50". There are cases where we want the user to supply an argument, but not the value. Say, for example, users want to specify ‘production’ to ‘staging’ but we don’t want them to remember the URL to the server.

We can use wildcards to dynamically select a Make variable:

staging_url:=https://staging-api.lithic.tech
production_url:=https://api.lithic.tech

ping-%:
    curl "$($(*)_url)"

And we can use it as so:

$ make ping-staging
curl "https://staging-api.lithic.tech"

Okay, this example isn’t incredibly useful. But for some clients, we have multiple deployed versions of the same application, and we can use these variables to avoid having to remember where applications are deployed.

For example, let’s say we have 3 versions of a codebase deployed in Heroku: one staging and two production apps. In the Make snippet below, each _app variable refers to the name of a Heroku app. We can use that app name to get the database string using the Heroku CLI, and pass that to psql (Postgres CLI).

staging_app:=lithic-api-staging
production-pdx_app:=lithic-api-production
production-nyc_app:=lithic-api-production-nyc

psql-%:
    psql `heroku config:get DATABASE_URL --app=$($(*)_app)`

Now to connect to staging, it’s as simple as:

$ make psql-staging

If we use Heroku’s Review Apps, we should also support an environment-variable version of these sorts of commands, since the app names are ephemeral. Instead of a wildcard, we’ll require the APP environment variable is set:

psql-app: guard-APP
    psql `heroku config:get DATABASE_URL --app=${APP}`

Putting It All Together

The example above has a couple small error cases that may confuse users: if psql or heroku are not on the PATH, the command will error with a sort of cryptic error:

$ make psql-staging
psql `heroku config:get DATABASE_URL --app=lithic-api-staging`
/bin/sh: heroku: command not found
could not connect to server: No such file or directory

Yuck! Can we use something like our lovely guard-% target to declare our executable dependencies?

You bet we can!

cmd-exists-%:
    @hash $(*) > /dev/null 2>&1 || \
        (echo "ERROR: '$(*)' must be installed and available on your PATH."; exit 1)

guard-%:
    @if [ -z '${${*}}' ]; then echo 'ERROR: environment variable $* not set' && exit 1; fi

psql-%: cmd-exists-heroku cmd-exists-psql
    psql `heroku config:get DATABASE_URL --app=$($(*)_app)`

psql-app: guard-APP cmd-exists-heroku cmd-exists-psql
    psql `heroku config:get DATABASE_URL --app=${APP}`

Now if you’re missing heroku or psql, you get a nice message:

$ make psql-staging
ERROR: 'heroku' must be installed and available on your PATH.

Who knew Make could be so fun to use?

Not done with Make yet

We’ll have one more blog post involving Make next week, along with a dump of a bunch of useful Makefile helpers. Stay tuned!

This was originally posted on my consultancy’s blog, at https://lithic.tech/blog/2020-05/makefile-apps. If you have any questions, please leave a comment or get in touch!

Leave a Reply