takelage-var
takelage-var provides the pytest plugin takeltest for the takelage devops workflow. The takelage devops workflow helps devops engineers build, test and deploy os images.
The pytest plugin 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 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.
testinfra wraps cli calls to the ansible executable. takeltest uses the ansible python api to run ansible playbooks.
Framework Versions
App | Artifact |
---|---|
takelage-doc | |
takelage-dev | |
takelage-cli | |
takelage-var | |
takelage-bit | |
takelage-img-takelslim | |
takelage-img-takelbase |
Framework Status
App | Deploy project | Test project | Test roles |
---|---|---|---|
takelage-dev | |||
takelage-cli | |||
takelage-var | |||
takelage-bit | |||
takelage-img-takelslim | |||
takelage-img-takelbase |
Installation
Install the takeltest pytest plugin using pip:
pip install takeltest
Tests
Run pytest unit tests and molecule system tests by invoking rake:
rake test
Example
Have a look at anarchism and env system test directories for examples of molecule projects using ansible, testinfra and takeltest.
Boilerplate
As a boilerplate for testinfra tests it is enough to do:
import takeltest
testinfra_hosts = takeltest.hosts()
Fixture testpass
You can access gopass secrets by using the testpass fixture:
def test_mytest(testpass):
my_password = testpass('my_project/my_password')
Fixtures multitestvars and testvars
Arguably the most useful feature of the 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 python api.
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 VariableManager so the usual ansible variable precedence rules apply. Internally, the fixture uses the ansible debug module to resolve templates which have not been resolved by the setup module through the gather facts task. Thus, it can resolve any kind of template which the debug module can resolve including jinja2 code and calls to lookup plugins.
extra vars
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).
roles
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.
example: testing packer images
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 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
bitboard_provisioner scenario
and the
bitboard_verifier 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 thing but they use different methods. The bitboard server happens to be built with the same role takel-anarchism whose unit tests are applied in this scenarios to the bitboard server docker image.
options
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.
caching
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 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
Ansible Python API
The takeltest plugin provides four main pytest fixtures (and a couple of command line, environment variables and helper fixtures):
- testpass – exposes the ansible passwordstore plugin
- 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
- moleculeplay – api to leverage the ansible python api
The multitestvars, testvars and testpass fixtures use the moleculebook fixture which in turn uses the moleculeplay fixture. moleculeplay makes low-level calls to the ansible python api and uses the moleculeenv fixture to handle the sysadmin tasks of setting the right symlinks. moleculeplay and moleculeenv will probably not be very useful on their own but 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()
args = dict(path='/tmp/moleculebook_did_this', state='touch')
task_touch = dict(action=dict(module='file', args=args))
playbook['tasks'].append(task_touch)
moleculebook.set(playbook)
moleculebook.run()
assert host.file('/tmp/moleculebook_did_this').exists
See
takel-gem
for a real-world example where moleculebook is used
to avoid a molecule prepare.yml
playbook
which otherwise needs to be copied
to the project's molecule default scenario.