How to manage multiple CLI tool versions with ease: direnv + asdf on macOS

We all work on different projects with different tools and different versions. And usually, chaos arises with all sorts of incompatibility.

Let's now think about this scenario: You are working with a customer using Kubernetes 1.20, managed via a Terraform 1.0.7 project. Then, a new customer arrives, and the status of their infrastructure is Kubernetes 1.13 and Terraform 0.14.11.

A way to manage multiple versions of CLI tools is mandatory. There are a lot of tools specialized in one single CLI, for example, terraenv, tfenv, kbenv, etc, but all of them use a different logic, each with a slightly different CLI interface. We need something centralized to manage everything.

The solution

Here comes the cavalry: asdf. asdf is a tool to manage other tool versions using a plugin architecture. We will also use direnv, an extension for the shell. It augments existing shells with a new feature that loads and unloads environment variables depending on the current directory.

This combo will automatically set environment variables and change tool versions when entering and exiting the project/directory that requires them!

Let's install everything

First of all, make sure to uninstall direnv and asdf via brew - we will install everything with the git process.

brew remove asdf
brew remove direnv

We will follow the guide on this page to install asdf: http://asdf-vm.com/guide/getting-started.html#_2-download-asdf

At the time of writing, the last asdf version is v.0.8.1.
Let's clone the asdf source code:

git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.1

This command will clone the project in our home directory under the .asdf folder.

Now add the following in your .bashrc or .zshrc file:

. $HOME/.asdf/asdf.sh

Reload your CLI and now you should have the asdf command available.

Let's proceed with the installation of direnv.

asdf plugin add direnv

Now that the plugin is available, we also need to use asdf to install direnv like this:

asdf install direnv 2.28.0
asdf global direnv 2.28.0

Let's go ahead and install some other plugins:

asdf plugin add terraform
asdf plugin add kustomize
asdf plugin add helmfile
asdf plugin add kubectl
asdf plugin add helm

Now edit your .bashrc or .zshrc file and add the required hook:

eval "$(asdf exec direnv hook bash)"

Replace bash with zsh if needed.

The last thing to do is to run the following command:

asdf direnv setup --shell zsh --version 2.28.0

Reload your CLI, and now everything should be ready to be configured.

Home setup

Create two files in your home root:

cat <<EOF> .envrc
use asdf
EOF
cat <<EOF> .tool-versions
direnv 2.28.0                                                                                                                                                      
terraform 1.0.7                                                                                                                                                    
kustomize 3.5.3                                                                                                                                                     
kubectl 1.20.2 
EOF

The first line with the direnv version is mandatory, otherwise the integration will not work.

Now execute:

asdf install
asdf exec direnv allow

The first command asdf install will read the .tool-versions file and install all the versions of the programs defined, if the asdf plugin is available.

The second command asdf exec direnv allow will execute direnv to allow the execution of what we have defined in the .envrc to run.

That's it, you should be able to use all the tools defined in the .tool-versions file with the correct versions!

Setup a new project/folder

Now we have configured a global version of tools, in our shell. How do we configure on a per-project basis? In the same way we configured the home, for example if we have a project that needs terraform 0.14.11 and some environment variables exported , we can create the two files, .envrc and .tool-versions, with the following content:

cat <<EOF> .enrvc
use asdf
export AWS_REGION=eu-west-1
EOF
cat <<EOF> .tool-versions
terraform 0.14.11
EOF

Activate the environment with: asdf exec direnv allow and install missing tool versions with asdf install.

Conclusions

I hope this article has been helpful, because we have been looking for a centralized way to manage different tool versions for a long time. Thanks to Luca Novara - he was the first to find this solution that the whole SIGHUP team now uses.