/role_unit

Ansible role testing framework

Primary LanguageShellGNU General Public License v3.0GPL-3.0

pipeline

role_unit

The goal of this project is to test your ansible system automation. Its goal is basically to test ansible roles, but role_unit’s own tests are an example of an other usage that can be made.

foo.gif

Role_unit is based on bash_unit, a bash unit-testing framework. The first provides the second a controlled environment for reproductible testing over multiple environments.

prerequisites

After a long time using only bash and standard Unix commands (like sed, grep, tr…​), jq has been added as a pre-requisite. It is not a strong constraint and is used to manipulate docker and podman json outputs.

None of the following are really required to use role_unit. It only depends on your use-case.

  • docker, podman or podman-compose installed and configured to test using containers

  • nocloud if you want to test using nocloud virtual machines

  • ansible to test ansible roles

installation

There is no installation process. Valid way to use role_unit is to clone repository somewhere and link role_init to a place where it will be in your PATH environment variable.

usage

standard use

To create a new ansible role tested with role_unit, place yourself in the directory where you want the role and run role_init:

cd /my/ansible/dir/roles
role_init my_role

This will create an empty role with the testing tools in the tests directory. A default tests file is created. To run the tests, you will have to specify the distribution by setting the RU_ENV_IMAGE variable (see below). Standard use is the following:

cd my_role
RU_ENV_IMAGE=debian9 tests/bash_unit tests/tests_my_role

Full exemple of a role creation using role_init:

/tmp role_init app
/tmp cd app
/tmp/app RU_ENV_IMAGE=debian9 tests/bash_unit tests/tests_app
Running tests in tests/tests_app
starting container ru_role_unit_00
applying ansible playbook
Running test_that_succeeds... SUCCESS ✓
Running test_that_succeeds_in_failure... SUCCESS ✓
stopping container ru_role_unit_00
/tmp/app RU_BACKEND=nocloud RU_ENV_IMAGE=debian9 tests/bash_unit tests/tests_app
Running tests in tests/tests_app
applying ansible playbook
Running test_that_succeeds... SUCCESS ✓
Running test_that_succeeds_in_failure... SUCCESS ✓
stopping machines

The tests directory is a standard ansible structure : if you place a group_vars directory it will be used.

You have a list of functions you can call in your test file, described below.

debug mode

In the standard usage, role_unit executes the following sequence:

  • containers creation

  • application of automation to the container(s)

  • execution of tests through bash_unit

  • containers destruction

if you set RU_DEBUG environment variable to 1, the sequence will be:

  • search for an existing debug environment

  • containers creation if none found

  • application of automation to the container(s)

  • execution of tests through bash_unit

containers are then not removed and instructions are given to enter the container to do actual debugging and dispose of the environment when debugging is done.

if tests are run without debug, any existing debug environment matching test file and backend will be destroyed.

available functions

here is a list of functions available in the test file as a complement of bash_unit to manipulate the test environment. For some examples see the tests_tuto file.

ru_init

Initiates testing environment, i.e. creates containers or virtual machines depending on configuration.

ru_frontend_init

Initiates ansible inventory and playbook necessary to test your ansible role.

Note that if your testing directory contains a requirements.yml file, ansible-galaxy will be used to fetch the roles in the roles directory. It is then up to you to use that role (rewrite playbook, include tasks in your role…​).

ru_run_frontend

Actually run ansible-playbook on your testing environment.

ru_run [ -d ] [ -n N ] cmd …​

Runs the specified command on all of the servers or just on the one specified. N is the number of the server you want to run the command on.

If you specify -d option, this will launch command in a detached mode. There may be evolution in the future about that but for now those options must be sepcified in this order only.

ru_run_with_timeout -t T [ -n N ] cmd

Similar to ru_run but will retry cmd every second until timeout T is expired or command returns a 0 exit code (whatever comes first). Return code will be the exit code of the last run of the command.

This comes handy for instance if you start a service on the server and you want to wait until the service is effectively operational.

ru_fact [ -n N ] fact_name…​

Returns the specified ansible fact for all of the servers or just on the one specified. N is the number of the server you want to get the fact from.

ru_fact_filter [ -n N ] fact_filter

Filters facts with the given expression and return the matching part of the facts in json format.

For instance, running ru_fact_filter anisble_default_ipv4 will return a similar parsable output :

192.168.13.53 | SUCCESS => {
    "ansible_facts": {
        "ansible_default_ipv4": {
            "address": "192.168.13.53",
            "alias": "ens3",
            "broadcast": "192.168.13.255",
            "gateway": "192.168.13.1",
            "interface": "ens3",
            "macaddress": "00:50:56:7a:3e:d9",
            "mtu": 1500,
            "netmask": "255.255.255.0",
            "network": "192.168.13.0",
            "type": "ether"
        }
    },
    "changed": false
}

ru_group group_name N…​

Calling ru_group will rearange inventory by creating a new inventory group containing servers which indexes are past as argument.

This function comes with two others:

  • ru_groups will list all existing groups in inventory

  • ru_group_servers will list all servers in a group passed as argument

ru_server and ru_servers

Those functions return either a single server name from index, or the list.

ru_server_nums

Returns the list of indexes to address the server through functions like ru_server or ru_run.

ru_uuid

Returns an identifier, using what is avilable on the host (uuidgen, uuid…​) and if no other option is available, fallbacks to using the date.

parameters

There is only one mandatory environment variable, which is RU_ENV_IMAGE. It defines the linux flavor you will run your tests against. Default configuration pulls docker images from role_unit containers repository (registry.gitlab.com/role_unit/role_unit_containers). Actually, you can choose among :

  • debian 11 (bullseye)

  • debian 10 (buster)

  • debian 9 (stretch)

  • ubuntu 22.04

  • ubuntu 20.04

  • ubuntu 18.04

  • centos 8

  • archlinux

This list is completed by some additional containers. For instance the '_cached' containers where package manager has a cache buitin to install package whithout extra steps (cache is being cleaned in standard container to reduce image size). Full list of those containers can be found in the container registry of role_unit_containers repository.

role_unit behaviour can be changed using environment variables:

  • RU_ENV_NAME defines the testing environment name. It is the name of the role you will test.

  • RU_BACKEND defines the the backend you are using. Can be docker, podman, podman-compose or nocloud, defaults to docker.

  • RU_ENV_DOCKER_REPO defines the docker repository to pull images from.

  • RU_ENV_IMAGE defines the system image used to create test environment.

  • RU_COUNT sets the number of containers or virtual machines

  • RU_DEBUG when set to 1, will make role_unit not to stop containers after the run, so you can enter them to check things.

  • RU_NOTMPFS defines wheter /tmp and /run are mounted as tmpfs. As a default they are, but there may be cases where, for instance, you want /tmp not to be mounted /noexec.

Following variable, only available with docker backend:

  • RU_FIXED_NAMES if set to 1, will have containers names based on RU_ENV_NAME instead of UUIDs. This will break the hability to run tests in a conccurent way, but is needed when container names must be predictable.

other role_unit variables may be used, but only to read values. Overwriting them may produce unexpected behaviours:

  • ru_inventory is an absolute path to the inventory that will be used by ru_run_frontend

  • ru_playbook is an absolute path to the playbook that will be used by ru_run_frontend

  • ru_dir is the temporary working directory for the tests. You will for instance find the group_vars in it.

For example of the usage you can made of these variables, have a look at the tests_tuto file in your tests directory.

about namespace

Role unit functions are prefixed by ru_. Role_unit environment variables for configuration are prefixed by RU_. Internal variables are prefixed with ru_. We keep it that way to minimize impact on tested environment.

tests

prerequisites

Role_unit is tested with role_unit, so preprequisites are the same.

run

The tests are described in the .gitlab-ci.yml file.

To run the tests, you will have to launch the commands in the "script" part of the .gitlab-ci.yml file.