DEV Community

Ihor Dotsenko
Ihor Dotsenko

Posted on

The lesson learned from using ansible_user variable

TL;DR

Use ansible_user ONLY in inventory / group_vars / host_vars files


Problem: ansible_user in my playbooks

I wanted Ansible to setup Docker with the geerlingguy/ansible-role-docker:

playbook.yml👇

...

roles:
  - role: geerlingguy.docker
    docker_users: ["{{ ansible_user }}"]

...
Enter fullscreen mode Exit fullscreen mode

Ran vagrant provision: no errors 🎉

Connected to a VM with vagrant ssh:

  • docker ps: no permission denied errors 🎉
  • docker run hello-world: no errors 🎉

git commit, git push, done... ✅


I have wanted to try out dev containers for a long time, and my repository with Ansible stuff was a great choice.

While dev containers have a lot of benefits, there are also some limitations. The one I faced was broken Vagrant integration.

Before migration to dev containers: Ansible and Vagrant live on a host and integrate perfectly.

After the migration: Ansible lives in a dev container and Vagrant lives on the host. If you run vagrant provision, you will see the error about missing ansible-playbook executable on your PATH.

While installing Vagrant in a Docker container is easy, it's not with VirtualBox. I found some threads on StackOverflow about getting VirtualBox up and running, but...

  • It requires sticking to an image with systemd. On Docker Hub you can find some systemd-* images provided by jrei, but...

    • I have no idea who jrei is and how long they will maintain the images (e. g. rebasing onto the latest Debian/Ubuntu/etc. to grab all the security patches). I wouldn't like it resting on my shoulders one day.
    • Just using one of these images in .devcontainer/devcontainer.json is not enough; I would also need to create a Dockerfile and copy everything from the Microsoft's Python 3 one to make it suitable for development. We need a dev container, remember? This way it starts resting on my shoulders immediately: periodically I need to check the changes commited to the Microsoft's Python 3 Dockerfile and apply them to my Dockerfile.
  • The fact that there is almost no info on the web about the setup like this says it's uncommon practice, and if I have any problems, there is nobody to help me.

OK, but except the Ansible provisioner Vagrant has the Ansible Local one!

In a Vagrantfile I changed one line:

-  config.vm.provision 'ansible' do |ansible|
+  config.vm.provision 'ansible_local' do |ansible|
Enter fullscreen mode Exit fullscreen mode

... and thought it was a quick win 🏆.

I ran vagrant provision and got saddened by the error:

... {{ ansible_user }}: 'ansible_user' is undefined ...
Enter fullscreen mode Exit fullscreen mode

Hmm, where is ansible_user? 🤔

Let's take a step back and look at how Ansible Local provisioning works under the hood. Vagrant generates an inventory and passes it to ansible-playbook:

<your-project>/.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory
Enter fullscreen mode Exit fullscreen mode
# Generated by Vagrant

default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='<your-project>/.vagrant/machines/default/virtualbox/private_key'
                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
Enter fullscreen mode Exit fullscreen mode

The ansible_ssh_user you can see here is the ansible_user at the time of Ansible <2. You can find the deprecation notice here.

I wish this deprecation didn't happen. So now I would not write these lines 😂

Let's get back to the Ansible Local provisioning. Ansible doesn't connect to any machine with SSH ➡️ there is no need for an inventory ➡️ there is no ansible_ssh_user/ansible_user 🙃

OK, googling for undefined ansible_user gave me the solution:

...

roles:
  - role: geerlingguy.docker
-   docker_users: ["{{ ansible_user }}"]
+   docker_users: ["{{ ansible_env.USER }}"]

...
Enter fullscreen mode Exit fullscreen mode

that makes my vagrant provision successful again 🎉

git commit, git push, done... ✅


It's time for production. I run vagrant provision to provision my Raspberry Pi 4 (where my pet projects are hosted) and see how docker_users variable evaluates to [root] instead of [pi] 🤦‍♂️

The easy solution that can be found in some open-sourced Ansible roles:

...

roles:
  - role: geerlingguy.docker
-   docker_users: ["{{ ansible_env.USER }}"]
+   docker_users: ["{{ ansible_user | default(ansible_env.USER) }}"]

...
Enter fullscreen mode Exit fullscreen mode

The root cause

I specified ansible_user only in the inventory and used {{ ansible_user }} to refer to it in my playbooks, in my roles, in variables to 3rd party roles (like in the one described above)... everywhere.

Let's say the user is ubuntu. Use cases:

  1. Ansible used ubuntu to SSH for provisioning.
  2. Ansible was configuring ubuntu user according to my instructions, e.g. changing its shell preference from bash to zsh and configuring .zshrc so when you SSH, you get connected to the running tmux session or a new one created for you 👍 Did you get it? Ansible is configuring the user it's using to SSH... What if it gets broken as a result of provisioning? 🙃
  3. I used ubuntu user to SSH...

At that time I thought: wow, how flexible it is 💪
Now I think: how stupid and fragile it is 🤦‍♂️😂

The root cause is that I forgot about the single-responsibility principle, overloaded the user and thought it was cool. ansible_user is just the possible implementation. The way I will re-write this Ansible setup and will use for all my future ones:

  • vagrant/ubuntu/pi/whatever is used only by Ansible for SSHing, installing packages, blacklisting ports, etc., but it stays pristine because it creates and configures 👇
  • dev user to have zsh by default, tmux hook described above, etc.

vars/main.yml 👇

---
dev_user: dev
Enter fullscreen mode Exit fullscreen mode

I am using the variable for the case I find a better name one day... before the very first provisioning on a new project of course... changing this in-between would be too risky 💣 Also it simplifies searching across the project.

playbook.yml 👇

...

roles:
  - role: geerlingguy.docker
-   docker_users: ["{{ ansible_user | default(ansible_env.USER) }}"]
+   docker_users: ["{{ dev_user }}"]

...
Enter fullscreen mode Exit fullscreen mode

This way you are in a safe place. No matter you are running Ansible Remote or Ansible Local provisioner. Also migration from one OS to another is an easy task:

inventory👇

-some-host ansible_user=ubuntu
+some-host ansible_user=ec2-user
Enter fullscreen mode Exit fullscreen mode

... with everything else being familiar: the same ssh dev@some-host command, the same dev user, etc.

The less complex interpolations, conditions, loops you have - the better. These guys must be tested. Is it easy to test Ansible code? Would you like to test your Ansible code? I guess, no 🙂

Top comments (0)