Jeff Li

Be another Jeff

Notes on Ansible Include

Introduction

Include mechanism is one of my favorite features about Ansible because it is the key to adhere to the principle of DRY that stands for Don’t Repeat Yourself which is of great importance to write reusable and maintainable Ansible code.

This post won’t cover every piece of the include feature. Please go through the docs from Ansible docs site before continuing if you are not familiar with Ansible include. link 1, link 2.

There are 2 kinds of includes, namely dynamic and static. Essentially, at the moment. Any include in Ansible is a form of tasks expanding. When a file A includes file B, tasks defined in B will be added to file A. The difference between the two is that included tasks will be expanded during parsing time in static include while in the other case tasks will be expanded when playbooks are running.

There is a repo in GitHub intended for demonstration and readers are supposed to be familiar with that. The idea comes from OpenStack’s release convention through which each major release will have a numeric version and a associated code name such as kilo and ocata. The convention is similar to Ubuntu’s. Assume that the OpenStack infrastructure is managed by Ansible and sometimes the gap between different major release is so huge that you have to create dedicated tasks for each major release.

Awesomeness of Include

We know that include could be applied to tasks, plays and handlers. We also know that include could be dynamic or static. So far there are 2 features exciting me.

  • Filename variable substitution
  • Include with complex controls such as for loop which is impossible without dynamic include

What makes them awesome is that they result in more clean Ansible code and thus more maintainable. Instead of being shipped when Ansible was born, they were added through the evolution of Ansible.

  • Filename variable substitution has been added since 1.7
  • Dynamic include was introduced for the first time in 2.0

Filename Variable Substitution

In the old days, variables in names of included files were not allowed. Take the demo repo as an example, anyone who wants to switch tasks based on the codename, he will have to write follow Ansible code.

1
2
3
4
5
6
---
- include: ocata.yml
  when: codename == 'ocata'

- include: kilo.yml
  when: codename == 'kilo'

Not bad so far since there are only 2 codenames. But what if you have 10 codenames? This definitely violates the DRY principle. With filename variable substitution, the code could become much more clean.

1
2
---
- include: "{{ codename }}.yml"

Now every one is happy with filename variable substitution because there is much less code to maintain. And as we know, there are multiple places to define variable in Ansible such as inventory vars, playbooks’ vars or vars_files sections, roles’ defaults or vars directories, command lines’ --extra-vars parameters and so on. Are the variables used in filename allowed to be defined anywhere? The short answer is NO to static include and YES to dynamic include. More detail will be provided in following sections, just keep in mind that variable substitution is applied to both static and dynamic include.

Static Include

Refer to the doc to check how to identify a static include. You should learn when an include will be considered as static.

Variable Substitution

As mentioned before, static include is parsed during compile time at which some variables such as those defined as inventory ones are not available, thus in static includes, variables used in names are not allowed to be defined in the places other than the following 3 places.

  1. Extra vars in the command line. example
  2. vars section in playbook. example
  3. Files which will be included in the vars_files section of playbook. example

Handler Include

The doc points out that it is impossible to notify trigger handlers from dynamic includes. The correct usage of handler include would looks like this.

1
2
3
---
- include: "{{ codename }}.yml"
  static: yes

The static option introduced in 2.1 deserves more explanation.

  • Before 2.0, all includes were static, there is no need to add the static option.
  • In 2.0, all includes were considered as dynamic, the code will not works even the variables are defined following the rules described in previous section. No errors occurs but the handlers will never be triggered.
  • After 2.0, since handler includes must be static, the static modifier has to be added because includes with filename variables are considered dynamic unless forced to be static. Of course, task_includes_static and handler_includes_static could be leveraged. IMHO, it is not a good idea to override the default behavior of Ansible unless you have to.

Dynamic Include

Dynamic include is relatively trivial because it is just a form of task which means it is possible to combine include and other complex control facilities such as for loop.

There are several differences between static and dynamic include.

  • The time to expand the tasks. Dynamically included tasks will not get expanded until the include task is reached.
  • Play and handler includes must be static.

It is worth noting that except for the circumstances under which the includes must be static, the gap between the two is really tiny. If both forms are applicable, they should result in the same state. In other words, they are equivalent in that case. If anyone find an exception, please drop me a comment.

Conclusion

  • Filename variable substitution is applicable for both static and dynamic include.
  • Play and handler includes must be static.
  • Static and dynamic includes are equivalent if they are applicable at the same time.
  • Ansible is not 100% stable so far, pay attention to the the change log of every releases.

Comments