Solving AWX Error “unexpected EOF make: *** [Makefile:508: docker-compose-build] Error 1”

My Problem

Attempting to build the containers for a Docker Compose installation of AWX fails with the error:

unexpected EOF
make: *** [Makefile:508: docker-compose-build] Error 1

My Solution

Give up on trying to clone a specific tag / release and simply clone master. As of the writing of this blog post, there’s both an error in the documentation concerning using tagged releases as well as a disconnect between the tagged release version numbering vs the official Docker image naming scheme.

If you really want to use a specific image, check out the published images here and then find a specific release that you want. For example, release 4.2. Edit the AWX Makefile and change the line that assigns a value to the variable COMPOSE_TAG towards the top of the file. Assign it the value of the tag that you desire. For example COMPOSE_TAG ?= release_4.2 which will then pull image ghcr.io/ansible/awx_devel:release_4.2

The Long Story

I’m attempting to create a test installation of Ansible’s AWX project following the Docker Compose instructions. After installing the required prerequisite packages, I follow the official documentation and clone the latest stable branch (at the time of this blog post):

git clone https://github.com/ansible/awx.git --branch 21.4.0

From there I modify the inventory file using settings appropriate to my environment:

localhost ansible_connection=local ansible_python_interpreter="/usr/bin/env python"

[all:vars]

# pg_password="mega_Secret"
# broadcast_websocket_secret="giga_Secret"
# secret_key="tera_Secret"

Then I run make docker-compose-build and wait for a few minutes before hitting the error:

unexpected EOF
make: *** [Makefile:508: docker-compose-build] Error 1

Let’s examine the relevant lines around 508:

    506 docker-compose-build:
    507         ansible-playbook tools/ansible/dockerfile.yml -e build_dev=True -e receptor_image=$(RECEPTOR_IMAGE)
    508         DOCKER_BUILDKIT=1 docker build -t $(DEVEL_IMAGE_NAME) \
    509             --build-arg BUILDKIT_INLINE_CACHE=1 \
    510             --cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) .

As we can see, the docker-compose-build target starts on 506, and the whole target only lasts until line 510. It’s really just two commands. The first being a call to run the dockerfile.yml playbook that builds the Dockerfile, and then docker build to actually create the Docker image. Simple enough, and yet something is causing it to bomb out.

I decided to run these commands manually in my shell, but to do so meant I also had to manually populate the variables. Some variables were themselves created from a combination of other variables. Ultimately, this is what I had to create:

  • RECEPTOR_IMAGE is assigned on line 30: RECEPTOR_IMAGE ?= quay.io/ansible/receptor:devel
  • DEVEL_IMAGE_NAME is assigned on line 28: DEVEL_IMAGE_NAME ?= $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG)
  • DEV_DOCKER_TAG_BASE is assigned on line 27: DEV_DOCKER_TAG_BASE ?= ghcr.io/ansible
  • COMPOSE_TAG is assigned on line 12: COMPOSE_TAG ?= $(GIT_BRANCH)
  • GIT_BRANCH is assigned on line 5: GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)

Ultimately I assigned the variables these values:

RECEPTOR_IMAGE="quay.io/ansible/receptor:devel"
DEV_DOCKER_TAG_BASE="ghcr.io/ansible"
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
COMPOSE_TAG="$GIT_BRANCH"
DEVEL_IMAGE_NAME="$DEV_DOCKER_TAG_BASE/awx_devel:$COMPOSE_TAG"

Let’s double check to make sure they look right:

echo -e "RECEPTOR_IMAGE = $RECEPTOR_IMAGE \nDEV_DOCKER_TAG_BASE = $DEV_DOCKER_TAG_BASE \nGIT_BRANCH = $GIT_BRANCH \nCOMPOSE_TAG = $COMPOSE_TAG \nDEVEL_IMAGE_NAME = $DEVEL_IMAGE_NAME"
RECEPTOR_IMAGE = quay.io/ansible/receptor:devel
DEV_DOCKER_TAG_BASE = ghcr.io/ansible
GIT_BRANCH = HEAD
COMPOSE_TAG = HEAD
DEVEL_IMAGE_NAME = ghcr.io/ansible)/awx_devel:HEAD

Now I can continue on by manually executing the next command that the docker-compose-build target performs:

ansible-playbook tools/ansible/dockerfile.yml -e build_dev=True -e receptor_image=$RECEPTOR_IMAGE

The four tasks complete successfully and a Dockerfile and some config files are created. Next up, it’s time to build the Docker image:

DOCKER_BUILDKIT=1 docker build -t $DEVEL_IMAGE_NAME --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from=$DEV_DOCKER_TAG_BASE/awx_devel:$COMPOSE_TAG .

And the build process kicks off. After a few minutes, we bomb out with:

------
 > importing cache manifest from ghcr.io/ansible/awx_devel:HEAD:
------
importing cache manifest from ghcr.io/ansible/awx_devel:HEAD:
unexpected EOF

Maybe for the first time in this process I really start to think. More importantly, I start to look up at the tidal wave of Docker build logs in my terminal. Towards the top, I see this:

=> ERROR importing cache manifest from ghcr.io/ansible/awx_devel:HEAD

Trying to manually pull that image receives a slightly more helpful error:

docker pull ghcr.io/ansible/awx_devel:HEAD
Error response from daemon: manifest unknown

Maybe if we don’t use a tag and just let docker use the default tag of latest?

docker pull ghcr.io/ansible/awx_devel
Using default tag: latest
Error response from daemon: manifest unknown

Looking at the latest published container images for the AWX project and I see what’s wrong:

There’s no HEAD tag, there’s no latest. Instead there’s devel among other release and version tags.

Apparently we’re not getting what the Makefile authors expected when we populate the GIT_BRANCH variable with git rev-parse --abbrev-ref HEAD.

Let’s refresh our memories from a few minutes ago when I explained how I cloned the AWX repository. The official documentation very clearly states:

We generally recommend that you view the releases page and clone the latest stable tag, e.g., git clone -b x.y.z https://github.com/ansible/awx.git

Please note that deploying from HEAD (or the latest commit) is not stable, and that if you want to do this, you should proceed at your own risk.

https://github.com/ansible/awx/blob/devel/tools/docker-compose/README.md#clone-the-repo

The documentation has simultaneously told us to check out a a specific branch, and also scared us away from using HEAD. This I dutifully did:

git clone https://github.com/ansible/awx.git /awx --branch 21.4.0

However, cloning a tag puts you in a detached HEAD state. Thus when you git rev-parse --abbrev-ref HEAD you get back… well… HEAD. The logic of the Makefile then searches for a base Docker image named ghcr.io/ansible/awx_devel:HEAD which does not exist.

If we were bad users and just cloned MASTER like we were told not to, what would we get as the value of `COMPOSE_TAG“?

git rev-parse --abbrev-ref HEAD
devel

And that would ‘solve’ the issue. Except, would it? Now we’re using the HEAD docker image, which we were told not to. If we look at all of the tagged images we see something curious. None of the image tags appear to be named in any way similar to the branch tags. What’s release_4.2? What’s release_3.8.7? Looking at tagged releases shows a completely different numbering system. Even if I did get the tagged release version with something like git show -s --pretty=%d HEAD, there would be no docker image to correlate it to. I can’t even find correlations between the SHAs for tags vs images.

The AWX project uses GitHub Actions to build official images. One action builds and pushes the Docker images, and we can see that it’s only triggered on pushes to branches named devel or release_*. Furthermore, it tags the image with GITHUB_REF##, which now explains why images are named release_4.2 for example and not anything to do with the tagged release names like the documentation would indicate.

It was then that I began to realize something humorous. Every blog and forum post I’ve encountered in the last month which explained how an individual installed the Docker Compose version of AWX always cloned from master. No one seems to have read or cared about the documentation enough to follow directions to deploy only tagged releases. When I cloned master and ran the docker-compose-build target, everything completed perfectly.

Further proof that reading the documentation is overrated and one should just wing it whenever possible.

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.

Solving ModuleNotFoundError: No module named ‘ansible’

My Problem

When running any ansible command, I see a stack trace similar to:

Traceback (most recent call last):
File "/usr/local/bin/ansible", line 34, in
from ansible import context
ModuleNotFoundError: No module named 'ansible'

My Solution

pip install ansible or brew install ansible or yum install ansible or…

Somehow your Ansible Python modules were removed, but the Ansible scripts in your $PATH remained. Install Ansible’s python package however makes the most sense for your platform and preferences. E.g. via pip directly or Homebrew or your package manager of choice.

The Long Story

Let’s break the error down line by line:

File "/usr/local/bin/ansible", line 34, in

Ansible is just a Python script, so let’s check out line 34:

[...]
31 import sys
32 import traceback
33
34 from ansible import context
35 from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
36 from ansible.module_utils._text import to_text
[...]

The second line in the stack trace shows that from ansible import context is just another module import in the larger context of the Python application. With that larger context clarified, this error may snap a bit more into focus:

ModuleNotFoundError: No module named 'ansible'

It’s just a Python application that can’t find a module. If there’s no module, let’s check with Python to see what packages it knows about:

$ pip list

Package    Version
---------- -------
gpg        1.14.0
pip        20.1.1
protobuf   3.13.0
setuptools 49.2.0
six        1.15.0
wheel.     0.34.2

There’s no Ansible package listed. Wait, which version of Python did I just check?

$ which pip
pip: aliased to pip3

Let’s check pip2 just to make sure there’s no version weirdness going on:

$ pip2 list

Package      Version
------------ -------
altgraph     0.10.2
asn1crypto   0.24.0
bdist-mpkg   0.5.0
bonjour-py   0.3
boto         2.49.0
cffi         1.12.2
cryptography 2.6.1
enum34       1.1.6
future       0.17.1
[...]

Nope, no Ansible. Since I’m on a Mac, let’s check Brew just to see what comes back:

brew list ansible
Error: No such keg: /usr/local/Cellar/ansible

I’m not really sure what happened. I’ve got the Ansible scripts in my path, but I don’t have the python modules. I prefer to install Ansible via pip so I simply pip install ansible and everything was right with the world.