takelage-var provides the pytest plugin pytest-takeltest for the takelage devops workflow. The takelage devops workflow helps devops engineers build, test and deploy os images.
The pytest plugin pytest-testinfra allows to write unit tests in python to test your servers configured by the management tool ansible. testinfra is a verifier of the molecule testing environment.
The pytest plugin pytest-takeltest provides helper functions and fixtures to facilitate the use of molecule and testinfra. It provides access to variables and secrets and helps to not only unit test your ansible roles but to integration and system test your whole ansible project.
pytest-testinfra wraps cli calls to the ansible executable. pytest-takeltest uses the ansible runner to leverage the ansible python api and run playbooks.
Project | Artifacts |
---|---|
Project | Pipelines |
---|---|
Install the pytest-takeltest pytest plugin using pip:
pip install pytest-takeltest
Run pytest unit tests and molecule system tests by invoking rake:
rake test
Have a look at anarchism and env system test directories for examples of molecule projects using ansible, pytest-testinfra and pytest-takeltest.
As a boilerplate for testinfra tests it is enough to do:
import pytest-takeltest
testinfra_hosts = takeltest.hosts()
Arguably the most useful feature of the pytest-takeltest plugin are the multitestvars and testvars fixtures. The fixtures resolve and expose ansible variables as a python dict:
def test_mytest(multitestvars):
my_python_variable = multitestvars['my_host']['my_ansible_variable']
testvars is a list containing the ansible variables of the first molecule host. Use this if you only have one host:
def test_mytest(testvars):
my_python_variable = testvars['my_ansible_variable']
multitestvars runs a playbook against the molecule hosts using the ansible runner.
multitestvars creates a symbolic link to the roles directory of your
ansible project in the ephemeral playbook environment which molecule sets up.
It then runs a playbook with gather_facts:true
and a debug task
to get the ansible variables and the ansible facts of the play and host.
multitestvars uses the ansible runner so the usual ansible variable precedence rules apply. Internally, the fixture uses the ansible debug module to resolve templates. Thus, it can resolve any kind of template which the debug module can resolve including jinja2 code and calls to lookup plugins.
The TESTVARS_EXTRA_VARS
environment variable can be set in molecule.yml
.
It can contain dirpaths or filepaths relative to the
MOLECULE_SCENARIO_DIRECTORY
separated by colons:
verifier:
name: testinfra
env:
TESTVARS_EXTRA_VARS: "../../vars:../../extra_vars/extra_vars.yml"
The vars files will be included in moleculebook playbooks by adding
the paths to vars_files
(as opposed to adding include_vars
tasks).
Which roles are included is determined in the following order, the first match wins:
- List of roles separated by colon specified in the
TESTVARS_ROLES_EXCLUSIVE
environment variable. - List of roles specified in playbooks separated by colon specified in
TESTVARS_ROLES_PLAYBOOKS
environment variable. - List of roles specified in playbook specified in
molecule.yml
- List of roles specified in default playbook
converge.yml
- List of roles specified in old default playbook
playbook.yml
- All roles in
roles
directory in project directory
Roles included in TESTVARS_ROLES_INCLUDE
will be included.
Roles blocked in TESTVARS_ROLES_BLOCK
won't be included.
You may want to include roles which are imported by task and not by playbook.
You can find the source code in the function _configure_roles_
in
moleculeenv.py.
By specifying playbooks via TESTVARS_ROLES_PLAYBOOKS
you are able to test your packer images with molecule.
Let's assume your local docker image is called
packer_local/my_image
.
Then you can start this image within a molecule scenario:
platforms:
- name: molecule-my-image
image: packer_local/my_image
Make the group_vars (and host_vars, if you wish so) available to pytest-takeltest:
provisioner:
name: ansible
inventory:
links:
group_vars: ../../group_vars/
Now you don't want to run your playbook on that packer image again.
But you still want to have access to all the role defaults variables
of those roles defined in your playbook so that your tests will pass.
Either you do not run molecule converge
and molecule idempotence
on your image or you set the TESTVARS_ROLES_PLAYBOOKS
environment variable:
verifier:
name: testinfra
env:
TESTVARS_ROLES_PLAYBOOKS: ../../site.yml:../../my_layer.yml
Code examples are the
takelbuild_converge scenario
and the
takelbuild_custom scenario
of the anarchism project.
Omitting molecule converge
and molecule idempotence
has the
advantage that your pytests are automagically included.
Both scenarios achieve the same but use different methods. The takelbuild server happens to be built with the same role takel_anarchism whose unit tests are applied in these scenarios to the takelbuild server docker image.
multitestvars and testvars are session scope fixtures
so they are configured in
molecule.yml
by using pytest command line options.
You can add a couple of options in the options dictionary
of the verifier section:
verifier:
name: testinfra
options:
testvars-no-gather-facts: true
By default, multitestvars runs a playbook to gather ansible variables and facts. It then runs a playbook to resolve the variables.
You can change the default behaviour with these options:
testvars-no-gather-facts
Run playbook to gather variables withgather_facts: false
. You won't be able to accessansible_facts
but your tests will run faster.testvars-no-gather-molecule
Do not resolve molecule variables. You probably won't need these variables but it won't take much time to resolve them, either.testvars-no-extra-vars
Do not add extra variables specified inTESTVARS_EXTRA_VARS
. Ignores the environment variable.
Hopefully the multitestvars fixture allows fast test-driven development.
It has session
scope so variables are collected and resolved only once
per testrun as pytest caches the result.
If this is still too slow for you then you can enable the pytest
cache plugin
in molecule.yml
:
verifier:
name: testinfra
options:
p: cacheprovider
You should use the pytest-takeltest boilerplate code to be able to run pytest directly. Otherwise testinfra will complain about missing environment variables.
Remember to clear the cache when you add or change an ansible variable::
pytest --cache-clear; molecule verify
The cache will use the molecule ephemeral directory as the cache key which is unique for each molecule instance. When using the boilerplate you can inspect the cache by running::
pytest --cache-show
The pytest-takeltest plugin provides two main pytest fixtures:
- multitestvars – resolves and exposes ansible vars and facts of all molecule hosts
- testvars – resolves and exposes ansible vars and facts of one molecule host
- moleculebook – api to run playbooks against a molecule host
The multitestvars and testvars fixtures use the moleculebook fixture which uses the ansible runner. The moleculeenv fixture handles the sysadmin tasks of setting the right symlinks, creating and deleting files. moleculebook might be handy in those situations where you know you shouldn't implement a hackaround. ;-)
Here is how you could run an ansible playbook programmatically from a test (or even better: from a fixture) using dependency injection.
def test_takeltest_moleculebook(host, moleculebook):
playbook = moleculebook.get()
task_touch = {
'name': 'touch this file',
'ansible.builtin.file': {
'path': '/tmp/moleculebook_did_this',
'state': 'touch'
}
}
play = playbook[0]
play['tasks'].append(task_touch)
moleculebook.run([play])
assert host.file('/tmp/moleculebook_did_this').exists
You can inspect the ansible logs in ~/.cache/molecule
.
See
takel-gem
for a real-world example in which moleculebook is used
to avoid a molecule prepare.yml
playbook
which otherwise needs to be copied
to the project's molecule default scenario.