An example Ansible role ####################### :date: 2016-05-19 :summary: An example Ansible role. A few weeks ago I started a new job and a lot of time was spent on refactoring as well as adding to an existing Ansible automation code base. For me this was a chance to work more with `Molecule `_ for testing. Molecule is a infrastructure-as-code testing tool that is inspired by Test-kitchen and the tests can be written using `Testinfra `_ which in turn is using `pytest `_. The reasons for me to choose this combination is that the tools are written in Python and that they're focused on Ansible. However I quickly grew tired of copying files from role to role or making the same changes to files again and again. So in that spirit I created a new Git repo with an empty Ansible role (no tasks, variables, handlers etc.) but has all of my changes and tweaks already applied and working tests out of the box. Usage ----- To work on the role install VirtualBox and Vagrant (I use the versions in Debian's repos) and from PyPI Ansible, Molecule and Testinfra. Now, fork the repo. As you can see there are already README and LICENSE files. If you ever ran :code:`ansible-galaxy init` or :code:`molecule init` you'll notice that indeed the repo was created with those tools. Dependencies ------------ There's an example dependency present in :code:`meta/main.yml` but instead of the declaring the dependencies in :code:`meta/main.yml` and the sources of the dependencies in :code:`requirements.yml` which leads to repeating yourself, the example shows how to declare the source of the dependent role directly in :code:`meta/main.yml` (which I haven't seen mentioned clearly in the Ansible documentation. For repositories with playbooks I'd still add a :code:`requirements.yml` file since there's no meta directory. Pulling the dependencies took some thought and what I came up with is: .. code:: shell ansible-galaxy install git+file://$(pwd),$(git rev-parse --abbrev-ref HEAD) This is a workaround for installing the dependencies as it actually uses ansible-galaxy to install the git repo of the role and the dependencies as well. Testing ------- First of all, I configured `pre-commit `_ hooks that check, among other things, the validity of the YAML files and the does a syntax check of the Ansible playbook. As for Molecule, the configuration of the test environment is mainly under :code:`molecule.yml`. That is were you'd go to change the Vagrant box to test. You can add multiple boxes and specify which box to test like so :code:`molecule test --platform `. Also worth mentioning is the Ansible configuration in :code:`ansible.cfg`. This is some what of a workaround as well because many of the options can be configured in :code:`molecule.yml` which is used to generate its own :code:`ansible.cfg`. However since Testinfra runs the tests over Ansible and Molecule doesn't pass the configuration along to it, the configuration isn't honored during testing. This caused me some grief as tests were constantly failing because Ansible would the host SSH key and fail as it was not known. The way I did is create an :code:`ansible.cfg` at the root of the repo where Testinfra would look and passed that as the template to Molecule. The playbook that is run in at :code:`tests/playbook.yml` and the tests are under :code:`tests/` as well. There's an simple example test but the Testinfra documentation quite good. Just remember to that both the filename and function name should start with :code:`test_` and you won't have tests that aren't found. A word on CI ------------ Now you have all of the different pieces and workflow to run complete tests on roles the next obvious step is setting up a CI pipeline. In my tests and as I know the various CI services (I personally tried Travis-CI and CircleCI) disable the option to run any hypervisor. For me it's a deal breaker because I depend on VirtualBox (I need to test on different OSes, not just Linux). If LXC serves your needs than you should be able to run Vagrant with the LXC provider and therefore Molecule. For me it's a deal breaker. A final word on boiler-plate ---------------------------- In a previous post I mentioned that I have several repositories that have the same boiler-plate and how I plan on dealing with that. Now, this is the first attempt at this. The idea is having a base repo that I clone, add another remote and voilĂ , a new project with the scaffloding already there. For bonus points, I can update the base repo and pull those changes in all projects. Here's how I do it: .. code:: shell git clone https://www.shore.co.il/git/ansible-role-example ansible-role-name cd ansible-role-name for file in $(git grep -l ansible-role-example); do sed -i 's/ansible-role-example/ansible-role-name/g' $file; done git add . git commit -m"- Renamed ansible-role-example to ansible-role-name." git remote rename origin ansible-role-example git remote add origin git@example.com/path/to/repo git push -u origin master And in case I update the ansible-role-example repo than I pull the updates by running :code:`git pull ansible-role-example master`.