Executable ansible playbooks

Ansible playbooks are a mighty tool for automation and scaling of your server infrastructure. Playbooks are executed using ansible-playbook. Often the execution lines become complex and not easily rememberable by themselves. By adding ansible-playbook to the shebang (#!) of the playbook file, you can make them effectively standalone executables with complex program arguments.

Wait … What?!? Let me show you how this works in practice:

#!/usr/bin/env -S ansible-playbook -K
# NOTE: Don't provide the playbook filename in the shebang. It is added automatically.
---

- name: Phoenixify mjoelnir
  hosts: mjoelnir.home
  user: phoenix
  become: yes
  become_user: root
  
  roles:
    - role: phoenixify
        bash_configure: true
        gui_config: true
        # even more stuff ....

Those are the contents of my mjoelnir.yml file. Making this file an executable (chmod +x mjoelnir.yml) allows me now to execute this file, which will just run it as an ansible playbook.

./mjoelnir.yml              # more conventient than `ansible-playbook -K mjoelnir.yml`

Important note: In teh shebang the filename is added automatically at the end. So don’t provide the playbook yaml filename there otherwise the playbook will be executed twice.

I find this awesome!

A more complex example

In the above example it was a convenience feature, but for some complex real-world scenarios this is a real life-improvement.

Let’s assume a complex line like the following for an imaginary rauchkofel server:

ansible-playbook -i inventory --ask-vault-pass -e @vault.yml rauchkofel.yml

This is something that you encounter often on real-world production systems. Indeed, this can become much more complex! You keep some of the server variables in an encrypted vault file and this makes already the line to type in non-trivial.

There is however no need to create a separate bash script or to email each other the commands that needs to be run or remember each time what you have to type, because we can just add this to our shebang:

#!/usr/bin/env -S ansible-playbook -i inventory --ask-vault-pass -e @vault.yml
---
- name: Setting up rauchkofel
  hosts: rauchkofel
  user: root
  
  roles:
    - role: skeleton
    - role: grafana
    - role: nginx
    # ....

Now all what we need to do is make the file executable and then run it.

chmod +x rauchkofel.yml
./rauchkofel.yml

This is awesome!! 🚀

Adding more program parameters

If you need to add more custom program parameters, that also works. e.g.

./rauchkofel.yml --tags=nginx

Will do exactly what you expect, it will result in a run of

ansible-playbook -i inventory --ask-vault-pass -e @vault.yml rauchkofel.yml --tags=nginx

Common pitfalls

“Unrecognized arguments: XYZ.yaml”

The playbook terminates with

usage: ansible-playbook [-h] [--version] [-v] [-k]
                        [--private-key PRIVATE_KEY_FILE] [-u REMOTE_USER]
                        [-c CONNECTION] [-T TIMEOUT]
                        [--ssh-common-args SSH_COMMON_ARGS]
                        [--sftp-extra-args SFTP_EXTRA_ARGS]
                        [--scp-extra-args SCP_EXTRA_ARGS]
                        [--ssh-extra-args SSH_EXTRA_ARGS] [--force-handlers]
                        [--flush-cache] [-b] [--become-method BECOME_METHOD]
                        [--become-user BECOME_USER] [-K] [-t TAGS]
                        [--skip-tags SKIP_TAGS] [-C] [--syntax-check] [-D]
                        [-i INVENTORY] [--list-hosts] [-l SUBSET]
                        [-e EXTRA_VARS] [--vault-id VAULT_IDS]
                        [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES]
                        [-f FORKS] [-M MODULE_PATH] [--list-tasks]
                        [--list-tags] [--step] [--start-at-task START_AT_TASK]
                        playbook [playbook ...]```
ansible-playbook: error: unrecognized arguments: ./mjoelnir.yml

Solution: The playbook filename needs to be the last argument.

WRONG: #!/usr/bin/env -S ansible-playbook mjoelnir.yml -K

RIGHT: #!/usr/bin/env -S ansible-playbook -K mjoelnir.yml

Also: Check if you really need the playbook filename because it’s added automatically (See the next topic).

The playbook is executed twice

Probably you have provided the playbook filename in the shebang. Don’t do that, it is added automatically.

So, e.g. assuming you have the playbook rauchkofel.yml.

WRONG:

#!/usr/bin/env -S ansible-playbook rauchkofel.yml
---
...

RIGHT:

#!/usr/bin/env -S ansible-playbook
---
...

The reason is that the filename is added automatically, so if you provide it manually, it will be executed twice.