The Wizard: Scenarios

This is another episode of the “The Wizard” series. If you haven’t read the previous episodes, you can find them here and here.

You just don’t know how to do it.

You’ve been thinking about it for a while too. It’s an important issue. The crafting of the spell is the most important part. The journey is usually more important than the destination itself. But you don’t feel like you are doing it right.

The purpose is simple, yet it has a duality of character. You want to enable the transmission of information between all realms into a single library of knowledge. A task you’ve already accomplished many times in the past.


# filebeat/tasks/main.yml

- name: install Filebeat


name: filebeat={{ elk_version }}

state: present

update_cache: true

notify: restart Filebeat

- name: copy configuration file


src: filebeat.yml.j2

dest: /etc/filebeat/filebeat.yml

owner: root

group: root

mode: 0600

notify: restart Filebeat

- name: start Filebeat


name: filebeat

state: started

enabled: true

You have crafted this spell properly, following the same principles you have taught many people across the years, the same ones you’ve used for your own. The same ones that Merlin taught you the first time he showed you how to create a playground to refine your creations, thousand of years ago.

import os

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(


def test_filebeat_is_installed(host):

filebeat = host.package('filebeat')

assert filebeat.is_installed

def test_filebeat_is_running(host):

filebeat = host.service('filebeat')

assert filebeat.is_running

def test_filebeat_is_enabled(host):

filebeat = host.service('filebeat')

assert filebeat.is_enabled

def test_filebeat_configuration_exists(host):

filebeat = host.file('/etc/filebeat/filebeat.yml')

assert filebeat.exists

Nevertheless, you want to add an extension to the spell, depending on whether you want to receive information about all the portals you’ve spawned into this world or not. Visualisation is key.


# filebeat/tasks/main.yml


- name: include module tasks

import_tasks: nginx_module.yml

when: activate_filebeat_nginx_module


# filebeat/tasks/nginx_module.yml

- name: copy nginx module file


src: nginx.yml.j2

dest: /etc/filebeat/modules.d/nginx.yml

owner: root

group: root

mode: 0644

register: nginx_module

notify: restart Filebeat

- name: setup filebeat

shell: filebeat setup -e && touch /tmp/filebeat_configured

when: nginx_module.changed


- skip_ansible_lint

You know how to craft the general part of the spell, but not the extension. If only…

N appears out of thin air.

  • Hey! What are you up to?
  • Fighting with an Ansible role.
  • Can I help?
  • Sure. I’m trying to conditionally add some tasks to my role, but only if I want the nginx logs to be forwarded to my Elasticsearch cluster. I use the nginx Filebeat module, which also happens to create pretty cool dashboards.
  • Great. Just use an include_tasks conditioned by a boolean variable defined somewhere. You could name it `activate_filebeat_nginx_module`, for example.

He’s good.

  • Yeah, that’s what I did. Then again, my problem isn’t that part, but rather testing the conditional part. I don’t activate the module by default. The `activate_filebeat_nginx_module` is set to false in the role’s defaults. So my Molecule test only tests the default case.
  • Yes. That’s because that’s the default scenario. Just create another scenario.

You’re confused.

  • What?

N pulls up his sleeves and steps forward.

  • Remember how you taught me how to create a role with tests using Molecule? The principle is rather simple. You type `molecule init role --role-name filebeat` and Molecule creates all the resources you’re going to need in order to test your role. That includes the test files and all the other playbooks you may need: the prepare playbook, the side_effects playbook…
  • Indeed. I remember.
  • The thing is that Molecule creates a `default` scenario in order to do that. That’s why all your resources are under the `molecule/default` directory on your role.

N waves his hands around in the air. Light appears everywhere, and you can see the structure of your current spell:



├── defaults

│   └── main.yml

├── handlers

│   └── main.yml

├── meta

│   └── main.yml

├── molecule

│   └── default

└── tasks

├── main.yml

└── nginx_module.yml

  • You’re right.
  • If you examine the playbook on the default directory, you will see that there are no variables specified for the role.

N waves his hands once again in the air and the light and the images change:


# filebeat/molecule/default/playbook.yml

- name: Converge

hosts: all


- role: filebeat

  • This is because you are actually testing the `default` scenario: the one where you only use the default variables that are defined in the `default` directory of your role.
  • I see.

You have seen this before, but you’ve never really understood the abstractions or their meaning, even if you’ve already been doing this for a while. You feel on the verge of enlightenment.

  • So you just need to create a new scenario.

N claps his hands, and energy starts flowing through him as a new component of the enclosure you used in order to craft your test appears.

`molecule init scenario --scenario-name activate_filebeat_nginx_module --role-name filebeat`

--> Initializing new scenario activate_filebeat_nginx_module...

Initialized scenario in filebeat/molecule/activate_filebeat_nginx_module successfully.


├── activate_filebeat_nginx_module

│   ├── Dockerfile.j2

│   ├── INSTALL.rst

│   ├── create.yml

│   ├── destroy.yml

│   ├── molecule.yml

│   ├── playbook.yml

│   ├── prepare.yml

│   └── tests

└── default

├── Dockerfile.j2

├── INSTALL.rst

├── create.yml

├── destroy.yml

├── molecule.yml

├── playbook.yml

├── prepare.yml

└── tests

You both walk to the new part of the playground.

  • In this new directory, you can specify everything you need in order to properly test your new scenario. What do you need first?
  • I need to specify that I want to include the conditional part of my playbook.
  • Where would you do that?
  • I’d use the group_vars at the playbook level.
  • That’s good. Then again, there are no group_vars here. You only have a small playbook that specifies everything you do in order to test your role.
  • So I’ll do it in the playbook.yml file.
  • Correct.
  • Isn’t that a bit dirty? I’ve always thought that you’re not supposed to use variables on a playbook, but rather put them somewhere else.
  • That’s a style thing, but yes, I would say that generally it is a good practice. Nevertheless, it is precisely what you need right now. As I just said, your playbook defines every parameter used to test your role. It’s easy to see how are you testing your role just by looking at it. You know precisely which variables are defined and how. Even if the name of the scenario should be explicit enough already in order to understand it. Try it.

You mold the spell accordingly.


# filebeat/molecule/activate_filebeat_nginx_module/playbook.yml

- name: Converge

hosts: all


- role: filebeat

activate_filebeat_nginx_module: true

  • Good. Now you can test everything you wanted. Just remember that in order to test every scenario, you need to use `molecule test --all`. This will execute every scenario, which is probably what you want. If you want to speed up the red/green/refactor loop, you can use the `create`, `converge` and `verify` actions like you taught me before, but you need to specify the scenario name when executing every action, like this: `molecule create --scenario-name activate_filebeat_nginx_module`

You are impressed with N. It’s hard to believe that you were the one teaching him things once. Maybe he’s been the one teaching you all from the start.

  • That’s precisely what I needed.
  • I’m glad I could be of service. Do you need any more help?
  • No, I’m all set. I just…

You’re having a déjà-vu. N is outside your field of vision. You slowly turn your head around...

N is still there.

  • What?
  • Nothing.