Conditional Playbook Import in Ansible

Mihail Milev
4 min readFeb 10, 2021

Today I struggled some time with actually a very simple problem in Ansible. At the end I found a solution to it, so I wanted to write some words about it — for my feature self and for other people, who’ve stumbled upon this problem.

The problem itself can be formulated very simple: “import a playbook, if and only if a variable is set”.

Basics

Now, Ansible has two ways to “import” something — via “import_*” statements and via “include_*” statements. The difference between an “import” and “include” is, that the former is static and the latter is dynamic.

A dynamic include is done during runtime. This means, Ansible starts processing a playbook and when it hits an “include” statement, will evaluate it and decide how to proceed. Of course, if there is a false conditional associated with the “include” statement, the actions will not be carried out and the object of inclusion will not be further analyzed. Ansible will continue with the next actions.

A static import is done when the playbook is started. Everything which is “imported” gets analyzed by Ansible in the beginning and is … let’s call it “injected” into the workflow instead of the “import” statements. There is no evaluation of conditionals in this early phase, since these conditionals may depend on variables, which will come into the workflow during execution. This means, that everything which is “imported” will be analyzed by Ansible.

Simple master playbook

Let’s have a look on a simple playbook:

If we leave the master playbook like this, both sub-playbooks will be executed. We want to make their execution dependent on a variable called “where_var” inside a variables file called “vars.yaml”:

First try

From the basics above, the simple solution would be to replace “import_playbook” with “include_playbook” (remember, that “includes” are analyzed during runtime) and add some conditionals:

There are two problems with this approach:
1. The variable “where_var” is nowhere set. So I have to pass it over at command line and it won’t be read from the file itself;
2. The Ansible playbook executable will throw a very clear error:

There is no “include_playbook” in Ansible.

Second try

Ok, what will happen if we keep the conditionals, but replace the “include” with “import”, like this:

Well, it kind of works, but not exactly what we wanted.
1. We still pass the variable over the command line;
2. Although not being executed, the first imported playbook is still being analyzed and Ansible goes through it — it just skips all its task, as if it is setting our conditional for all the tasks inside the playbook. This has two negative impacts (in my opinion):
2.1.: if you have a lot of such imported playbooks, your output will be filled with skipped events. This makes debugging and analyzing problems very hard;
2.2.: depending on how many levels of imports you’re doing, at some point you would lose track of what is being conditionally imported where and unexpected conditions could happen causing your scripts to have unexpected behavior.

The solution

Let’s modify our master playbook a bit:

I wrapped the string on a new line to improve reading. Although the string looks somewhat “crazy”, it does some interesting stuff.

  1. The “{{“ and “}}” open and close the jinja2 template in Ansible, so the code between them is jinja2 code;
  2. The “|” mean “filter”, which means — perform some action on the previous part;
  3. “lookup(‘file’, ‘vars.yaml’)” tries to read the contents of the specified file using relative path. Essentially this part returns a string representation of the contents of the file “vars.yaml”;
  4. The first filter — “from_yaml” — tries to parse the string given to it as YAML. Upon success these two parts return an object with all our variables inside the “vars.yaml” file and their values;
  5. We wrap the previous two parts in brackets and ask jinja2 to fetch only one specific variable out of the created object: “(…).where_var”;
  6. If it doesn’t exist, the last filter sets the default value to “here”.

When we run this playbook, we get:

Mission accomplished — only one playbook gets analyzed and executed!

--

--

Mihail Milev

Electronics and DevOps engineer, Linux geek, passionate about cyber security