Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: accessing values of variables as they are being used for provisioning an instance inside Testinfra tests #151

Closed
tjanez opened this issue Apr 5, 2016 · 5 comments

Comments

@tjanez
Copy link

@tjanez tjanez commented Apr 5, 2016

I want to use Testinfra tests to test my role.

Inside an Testinfra test I would like to access the values of variables as they are being used for provisioning the machine when playbook.yml is converged for some instance.
I need this since the instance's state, which I want to check, depends on the chosen values of the variables defined in role's default/main.yml or vars/main.yml file.

I tried using the following 'trick' suggested by Testinfra maintainer, but it only works for Ansible facts and group_vars/host_vars, not for variables defined within a role, either in default/main.yml or vars/main.yml.

Here is an example test for PostgreSQL service:

def test_postgresql_running_and_enabled(Ansible, Service):
    postgresql_unit_name = Ansible("debug", "msg={{ postgresql_unit_name }}")["msg"]
    postgresql = Service(postgresql_unit_name)
    assert postgresql.is_running
    assert postgresql.is_enabled

Variable postgresql_unit_name is defined in role's vars/main.yml, but apparently the invocation of Ansible through Testinfra is unable to find it. Here is the error:

    def test_postgresql_running_and_enabled(Ansible, Service):
        postgresql_unit_name = Ansible("debug", "msg={{ postgresql_unit_name }}")["msg"]
        postgresql = Service(postgresql_unit_name)
>       assert postgresql.is_running
E       assert <service 'postgresql_unit_name' is undefined>.is_running

Any ideas how I could achieve this?

@melodous

This comment has been minimized.

Copy link

@melodous melodous commented Apr 5, 2016

Hi tjanez,

We load ansible variables files (defaults and vars) with the following functions, maybe it works for you:

@pytest.fixture(scope="module")
def AnsibleDefaults(Ansible):
return Ansible("include_vars","./defaults/main.yml")["ansible_facts"]

@pytest.fixture(scope="module")
def AnsibleVars(Ansible):
return Ansible("include_vars","./vars/main.yml")["ansible_facts"]

Regards

Raúl Melo

@retr0h

This comment has been minimized.

Copy link
Contributor

@retr0h retr0h commented Apr 5, 2016

Also, molecule executes ansible-playbook with an inventory path of -i .molecule/ansible_inventory. However, in molecule's case this is host inventory. Only the contents of this file, are available to testinfra. I would have expected Ansible to provide defaults as part of this inventory. However, @melodous has come up with a rather creative solution.

@tjanez

This comment has been minimized.

Copy link
Author

@tjanez tjanez commented Apr 6, 2016

@melodous, thanks for sharing your code, it was very valuable.

I had to adapt your approach a bit since I want to create a role that loads different variables depending on value of ansible_distribution and another variable (postgresql_install_source in the example code below):

import os.path

import pytest

@pytest.fixture()
def AnsibleDistribution(Ansible):
    return Ansible("setup")["ansible_facts"]["ansible_distribution"]

@pytest.fixture()
def AnsiblePostgresqlInstallSource(Ansible):
    return Ansible("debug", "msg={{ postgresql_install_source }}")["msg"]

@pytest.fixture()
def AnsibleVars(Ansible, AnsibleDistribution, AnsiblePostgresqlInstallSource):
    if AnsibleDistribution == "fedora":
        vars_file = "fedora.yml"
    elif AnsibleDistribution in ["CentOS", "RedHat"]:
        if AnsiblePostgresqlInstallSource == "centos_scl_repo":
            vars_file = "centos_scl_repo.yml"
        else:
            vars_file = "RedHat.yml"
    else:
        raise ValueError("Unsupported distribution: " + AnsibleDistribution)
    return Ansible("include_vars", os.path.join("./vars/", vars_file))["ansible_facts"]

def test_postgresql_running_and_enabled(Service, AnsibleVars):
    postgresql = Service(AnsibleVars["postgresql_unit_name"])
    assert postgresql.is_running
    assert postgresql.is_enabled

I had to modify .molecule/ansible_inventory and define the postgresql_install_source variable there since this is what gets passed to Testinfra (as @retr0h explained in the previous comment).

However, this is not very usable yet since .molecule/ansible_inventory is over-written after performing a cycle of commands:

molecule destroy
molecule create
molecule converge

Is it possible to specify additional variables to put into the inventory file for an instance at the level of molecule.yml?
Or is there another approach that would be able to control the values of variables of a role on a particular instance and the values could also be accessed from Testinfra tests?

@retr0h

This comment has been minimized.

Copy link
Contributor

@retr0h retr0h commented May 13, 2016

Is it possible to specify additional variables to put into the inventory file for an instance at the level of molecule.yml?
Or is there another approach that would be able to control the values of variables of a role on a particular instance and the values could also be accessed from Testinfra tests?

Not currently, but welcome PRs.

@retr0h retr0h closed this May 13, 2016
@Perdjesk

This comment has been minimized.

Copy link

@Perdjesk Perdjesk commented Apr 3, 2019

I had an use case in which I wanted to inspect variables set with set_fact during the role's tasks. In order to have access to those variables in TestInfra code, the variables are dumped to the instance filesystem as a post_tasksof the converge playbook and loaded from filesystem during TestInfra test.

Molecule converge playbook:

---
- name: Converge
  hosts: all
  roles:
    - role: <role>

  post_tasks:
    - name: dump
      template:
        src: templates/ansible-vars.yml.j2
        dest: /tmp/ansible-vars.yml
      changed_when: False

Note: changed_when: False is required since the the vars being dumped contains variables that change at each Ansible run (timestamp, run duration, ..). Without it the idempotence step will fail.

templates/ansible-vars.yml.j2:

{{ vars | to_yaml }}

test_default.py:

import os

import yaml

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')


def test_ansible_vars(host):

    stream = host.file('/tmp/ansible-vars.yml').content
    ansible_vars = yaml.load(stream)
    assert ansible_vars['var_to_test'] == 'expected_value_of_var'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.