Solved: ansible-lint on macOS “FATAL: Ansible CLI and python module versions do not match.”

My Problem

While attempting to run ansible-lint on macOS, I received the error:

FATAL: Ansible CLI (2.10.8) and python module (2.11.5) versions do not match. This indicates a broken execution environment.

My Solution

I ran the following destructive command, but before you even think about running it yourself, please familiarize yourself with what it will do:

brew link --overwrite ansible

The Long Story

While attempting to lint some Ansible .yml files, I realized that the specific macOS machine I was on didn’t have ansible-lint. I begrudgingly use Homebrew on it to manage packages formulae. I ran brew install ansible-lint in a hurry, and attempted to immediately use it. I then received this error:

FATAL: Ansible CLI (2.10.8) and python module (2.11.5) versions do not match. This indicates a broken execution environment.

My first thought was to check if the ansible command corroborated that it was indeed 2.10.8 and it did:

ansible --version
ansible 2.10.8
config file = None
configured module search path = ['/Users/username/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
executable location = /usr/local/bin/ansible
python version = 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)]

Then, I checked pip list and saw:

ansible-base 2.10.8

I even checked the release.py file:

grep __version__ /usr/local/lib/python3.9/site-packages/ansible/release.py __version__ = '2.10.8'

Clearly there’s a different version of Ansible somewhere, so I brute-forced the situation by searching for every file named release.py that was in a path with the word ansible in it:

mdfind "kMDItemDisplayName == 'release.py'c"
/usr/local/Cellar/ansible/4.6.0/libexec/lib/python3.9/site-packages/ansible/release.py
/Users/wdecesare/gitstuff/sre/inventory/release.py
/usr/local/lib/python3.9/site-packages/ansible/release.py

🤔

Hold up. There appears to be two Ansibles installed. One is in the standard filesystem paths, and another is in Homebrew’s Cellar path. Let’s check the Homebrew version:

grep __version__ /usr/local/Cellar/ansible/4.6.0/libexec/lib/python3.9/site-packages/ansible/release.py
__version__ = '2.11.5'

Because I used Homebrew to install ansible-lint, it also installed ansible in Homebrew’s path. In the past, I had installed Ansible using pip which installed binaries in standard OS paths.

If I had paid attention when I was installing ansible-lint I would have noticed this very informative error:

==> Pouring ansible--4.6.0.big_sur.bottle.tar.gz
Error: The brew link step did not complete successfully
The formula built, but is not symlinked into /usr/local
Could not symlink bin/ansible
Target /usr/local/bin/ansible
already exists. You may want to remove it:
rm '/usr/local/bin/ansible'

To force the link and overwrite all conflicting files:
brew link --overwrite ansible

To list all files that would be deleted:
brew link --overwrite --dry-run ansible

Possible conflicting files are:
/usr/local/bin/ansible
/usr/local/bin/ansible-config
/usr/local/bin/ansible-connection
/usr/local/bin/ansible-console
/usr/local/bin/ansible-doc
/usr/local/bin/ansible-galaxy
/usr/local/bin/ansible-inventory
/usr/local/bin/ansible-playbook
/usr/local/bin/ansible-pull
/usr/local/bin/ansible-test
/usr/local/bin/ansible-vault

When considering the situation, I realized that I’d rather be managing all of Ansible’s binaries in Homebrew, so I was comfortable with running brew link --overwrite ansible. The problem was solved, and I could continue on until the next problem.

Adding Simple base64 Decoding to Your Shell

I had a need to repeatedly decode some base64 strings quickly and easily. Easier than typing out openssl base64 -d -in -out, or even base64 --decode file.

The simplest solution that I found and prefer is a shell function with a here string. Crack open your preferred shell’s profile file. In my case, .zshrc. Make a shell function thusly:

decode() {
  base64 --decode <<<$1
}

Depending on your shell and any addons, you may need to echo an extra newline to make the decoded text appear on its own line and not have the next shell prompt append to the decoded text.