goodplay builds upon playbooks -- Ansible's configuration, deployment, and orchestration language.
Quoting from Ansible's documentation:
At a basic level, playbooks can be used to manage configurations of and deployments to remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions to other hosts, interacting with monitoring servers and load balancers along the way.
A pseudo playbook -- written as a YAML file -- may look like this:
## playbook_name.yml
# play #1
- hosts: host1:host2
tasks:
# play #1, task #1
- name: first task name
module1:
arg1: value1
arg2: value2
# play #1, task #2
- name: second task name
module2:
arg1: value1
arg2: value2
tags: specialtag
# play #2
- hosts: host3
tasks:
# play #2, task #1
- name: another task name
module1:
arg1: value1
Each playbook is composed of one or more plays.
Each play basically defines two things:
- on which hosts to run a particular set of tasks, and
- what tasks to run on each of these hosts.
A task refers to the invocation of a module which can be e.g. something like creating a user, installing a package, or starting a service. Ansible already comes bundled with a large module library.
After we have briefly introduced the basic terminology of the Ansible language, it is now time to define what a test playbook looks like in the goodplay context.
A test playbook is as the name implies a playbook with the following contraints:
- The filename is prefixed with
test_
. - The filename extension is
.yml
. - Right beside the test playbook a file or directory named
inventory
exists. See :ref:`environment` for details. - If you want to test against Docker containers you may optionally put a
docker-compose.yml
file right beside the test playbook. - The test playbook contains or includes at least one task tagged with
test
, also called test task. - Within a test playbook all test task names must be unique.
An example test playbook that verifies that two hosts (host1
and host2
created as Docker containers, each one running centos:centos6
platform
image) are reachable:
## docker-compose.yml
version: "2"
services:
host1:
image: "centos:centos6"
tty: True
host2:
image: "centos:centos6"
tty: True
## inventory
host1 ansible_user=root
host2 ansible_user=root
## test_ping_hosts.yml
- hosts: host1:host2
tasks:
- name: hosts are reachable
ping:
tags: test
The name of the single test task is hosts are reachable
.
The test task only passes when the task runs successful on both hosts
i.e. both hosts are reachable.
A slightly more complicated example making use of more advanced Ansible features, like defining host groups or registering variables and using Ansible's assert module:
## install_myapp.yml
- hosts: myapp-hosts
tasks:
- name: install myapp
debug:
msg: "Do whatever is necessary to install the app"
## tests/docker-compose.yml
version: "2"
services:
host1:
image: "centos:centos6"
tty: True
host2:
image: "centos:centos6"
tty: True
## tests/inventory
[myapp-hosts]
host1 ansible_user=root
host2 ansible_user=root
## tests/test_myapp.yml
- include: ../install_myapp.yml
- hosts: myapp-hosts
tasks:
- name: config file is only readable by owner
file:
path: /etc/myapp/myapp.conf
mode: 0400
state: file
tags: test
- name: fetch content of myapp.log
command: cat /var/log/myapp.log
register: myapp_log
changed_when: False
- name: myapp.log contains no errors
assert:
that: "'ERROR' not in myapp_log.stdout"
tags: test
To keep playbooks organized in a consistent manner and make them reusable, Ansible provides the concept of Ansible Roles. An Ansible role is defined as a directory (named after the role) with subdirectories named by convention:
role/
defaults/
files/
handlers/
meta/
tasks/
templates/
vars/
When writing tests for your role, goodplay expects another subdirectory by convention:
role/
...
tests/
By following this convention, goodplay takes care of making the Ansible role available on the Ansible Roles Path, so you can use them directly in your test playbook.