Ansible variable override default in another role - ansible

I'm unsure how to override variables between roles in Ansible.
To simplify the setup a little, I have two roles applied to the same host. The first role defines a variable in its default/main.yml:
do_some_task: yes
And looks for that variable in its tasks:
- name: Some Task
when: do_some_task
The second role overrides that in its vars/main.yml, which is supposed to take precedence over the defaults:
do_some_task: no
However, the task is still being run, indicating that the variable wasn't overridden. It seems that the override is scoped to the tasks of the second role. I tested that by adding a debug task to both roles:
- name: Test some task
debug: "msg='do_some_task = {{ do_some_task }}'"
This confirms that the first role sees a different value of the variable than the second.
TASK: [role1 | Test some task]
ok: [myhost] => {
"msg": "do_some_task = True"
}
...
TASK: [role2 | Test some task]
ok: [myhost] => {
"msg": "do_some_task = False"
}
The common answer to this appears to be to set the variables in the inventory or the host vars. However this isn't particularly DRY: if you have many hosts in different inventories, you'd have to set the same variables in lots of places.
So is there some way of overriding a variable from another role?

If you use Ansible >= 2.2, you maybe able to use include_roles:
- name: Pass variables to role
include_role:
name: role1
vars:
do_some_task: "{{ role2_do_some_task }}"
You can also use dependencies of roles for Anslbie >= 1.3. Write the role2/meta/main.yml like:
---
dependencies:
- { role: role1, do_some_task: yes }
If you want neither, given that you don't want to put the variable definition in playbook, the only sensible place to define them is in inventory.
You can define the ones that need to overwrite the role1 default in the same group:
[overwrite_do_some_task]
host1
host3
[overwrite_do_some_task:vars]
do_some_task=yes
If you don't give the variable value before running role1, then the expected behavior is, use the role default, which is what OP observed.
Note: It is more readable to prefix role variables. i.e. use role1_do_some_task instead of do_some_task. Doing so, you are less likely to confuse yourself. See Ansible Best Practices: The Essentials.

If you want to wrap a role with another role then you can override the inner role's variable by specifically passing variables from the outer role.
So as per your example you would have an inner role task of:
- name: Some Task
when: do_some_task
This inner role would have a variable set like this:
do_some_task: true
And then you can override the inner role when you are calling your outer role in a playbook like this:
- name: outer-role
hosts: outer-role-nodes
roles:
- role: outer-role
- { role: inner role,
do_some_task: "{{ do_some_task }}" }
This should take the do_some_task variable from the outer role and override the inner role's do_some_task.

TLDR : to be able to inherit defaults/ config, you have to separate it into other roles and include it where it's going to be used
One way to apparently work around this is to create a separate role which will hold the defaults, and then import/include it in your main role. The roles you create with the default configurations can import each other as a way of "inheriting" configuration from defaults, and then the main role would load the top-most configuration role and take all the configs as expected..
For example, in my case I want to have a templating system which has a "stacked" level of configuration.. all of them inheriting from a base set of config in defaults, and not vars, otherwise you can't override it from inventory.
these are the roles:
template-base
template-config-standard
template-config-performance
-> Template-base has the whole logic for deploying templates based on the selected configuration.. this has some logic to include the selected config role on the tasks files
-> template-config-standard has the standard set of config defined in defaults/main.yml, and nothing else
-> template-config-performance has some additional tweaking on the defaults/main.yml file, but does not repeat anything from template-config-standard, and instead simply uses it on "import_role" on the tasks/main.yml file.. probably dependencies still work
In a bit more examplified way:
role template-base:
- files/ (contains all the template files)
- tasks/
|---- main.yml (contains the code below)
# Includes the template configuration based on the selected template
- include_role:
name: "template-config-{{ selected_template}}"
public: true # Important so that configs are exposed here
- ** Does templating work **
role template-config-std:
- defaults/
|---- main.yml (contains a bunch of configuration)
role template-config-perf:
- defaults/
|---- main.yml (contains just a few config items, overriding stuff from the role above)
- tasks/
|---- main.yml (contains below code)
#Simply includes the standard configuration.. all this does is take defaults in the defaults/main.yml file from the below role
- import_role:
name: template-config-std
The approach above seems to work just fine, respecting the desired inheritage of variables and precedence. It looks like you cannot have the role that will use the variables competing with the roles that contain the default variables, unfortunately, because it will always win.. but in any case the example above seems okay :).. if you need to add more things other than just configuration to the supporting roles, you have to create a kind of mechanism on the main role, but hopefully nothing very complex..
Good luck and have a good one!
PS: one of the most important aspects of this approach is that we keep the default/ precedence on the configuration, meaning the inventory can override it if needed!

Related

How to have multiple tasks under one role in Ansible?

I am setting up Ansible as a newbie. I would like to group a few tasks under an Nginx role.
See my folder structure
The only way I could get this to work is to use include statements in my playbooks... but that means I needed to start changing everything to relative paths since Ansible stopped being able to find things.
My playbook currently:
- name:
Install Nginx, Node, and Verdeccia
hosts:
all
remote_user:
root
tasks:
- include: ../roles/nginx/tasks/install.yml
- include: ../roles/nginx/tasks/create_node_config.yml
vars:
hostname: daz.com
How can I reference the sub task in the playbook to be something like
tasks:
- nginx.install
and still be within best practices.
Your use of roles so far is not in line with the norm or the idea of Ansible. From a purely technical point of view, it is quite possible that you have multiple task bundles in yml files that you include in the playbook.
If you want to include a specific task file from a role, you should better do this via the module include_role with the parameter tasks_from.
However, the workflow with roles usually looks different.
in the folder tasks should always be a file main.yml, this is called automatically, if simply the role is included, no matter over which way.
in this main.yml you can then add further control logic to include your yml files as required.
As it looks to me, for example, you always need to install, but depending on the use case you want to have different configurations.
create in your nginx role a file defaults/main.yml.
---
config_flavor: none
This initializes the config_flavor variable with the string none.
create in your nginx role a file tasks/main.yml.
---
- name: include installation process
include_tasks: install.yml
- name: configure node
include_tasks: create_node_config.yml
when: config_flavor == "node"
- name: configure symfony
include_tasks: create_symfony_config.yml
when: config_flavor == "symfony"
- name: configure wordpress
include_tasks: create_wordpress_config.yml
when: config_flavor == "wordpress"
The main.yml will be included by default when the role is applied.
This is where the installation is done first, then the proper configuration is done.
Which configuration should be done is defined by the variable config_flavor. In the listed example the values are node, symfony and wordpress. In all other cases the installation will be done, but no configuration (so in the default case with none).
include your role in the playbook as follows
---
- name: Install Nginx, Node, and Verdeccia
hosts: all
remote_user: root
vars:
hostname: daz.com
roles:
- role: nginx
config_flavor: node
At this point you can set the value for config_flavor. If you set wordpress instead, the tasks create_wordpress_config.yml will be included automatically, using the logic from tasks/main.yml.
You can find more about roles in the Ansible Docs.
There are many more possibilities and ways, you will get to know them in the course of your learning. Basically I would recommend you to read a lot in the Ansible docs and to use them as a reference book.
Please refer to bellow document. you can use "include_role" with "tasks_from" attribute. you don't need to provide full path of task files, Just file names.
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/include_role_module.html
Example:
- name: Run tasks/other.yaml instead of 'main'
ansible.builtin.include_role:
name: myrole
tasks_from: other

ansible handler not found when used when included as play within pre_tasks

In my pre_task of master playbook(site.yml),
I have included one of the playbooks from a my_role
(located in my_role/tasks/my_playbook)
This my_playbook calls a handler, which is located in the role directory(my_role/handlers/main.yml)
and I am getting handler not found error.
What are the types I can use inside the pre_task??
Handlers defined inside my_role are for use by that role only.
If you include some tasks from your role like my_role/tasks/tasklist1.yml inside pre_tasks block of your master playbook and expect it to use some handlers also defined inside that role, you should also include its handlers as separate statement, like this:
---
- hosts: localhost
gather_facts: false
pre_tasks:
- include: roles/role1/tasks/main.yml
handlers:
- include: roles/role1/handlers/main.yml

Run Ansible role without running dependencies defined in the role's meta

I have a role wp-vhost that has one role it depends on:
// roles/wp-vhost/meta/main.yml
---
dependencies:
- { role: nginx }
Each time I run wp-vhost, the nginx role will also run. I understand that this is fine and it's a desired behavior.
However, during my local development, time is unnecessarily lost on running the nginx role, when I want to run only the tasks defined in wp-vhosts since I know that nginx had run before and set-up the necessary environment for wp-vhost.
Is there a way to execute a playbook with roles, without executing roles' dependencies?
The way I would do this is to use Ansible tags and apply them to your "wp-vhost" specific code.
Assuming your wp-vhost role's main playbook is in main.yml, a good pattern is to spin out the actual tasks into a sub-playbook called something like wp-vhost.yml, included from main.yml, so the non-nginx code gets a tag that doesn't get applied to the nginx role. In this case:
- include: wp-vhost.yml
tags: wp-vhost
In order to use a tag for every chunk of Ansible code (whether an included tasks file or a role), try writing every role mentioned in dependencies like this:
- role: nginx
tags: nginx
When in testing mode, you can run just the wp-vhost specific parts like this:
$ ansible-playbook --tags wp-vhost main.yml
Or you can run the whole playbook including any dependencies like this - default is to run everything ignoring tags:
$ ansible-playbook main.yml
This makes it easy to quickly run just parts of a complex set of cascading roles and include files when testing, and also use the wp-vhost role normally in other roles' dependencies.
Impact on role structure
Careful use of tags doesn't affect role structure or use at all, and you would typically use tags only for testing.
For more complex roles, it's common to structure the tasks into separate files in any case, keeping the main.yml simple, like this:
- name: Set up base OS
include: base_os.yml
tags: base_os
- name: Ensure logs are rotated
include: logrotate.yml
tags: logrotate
- name: Create users and groups
include: users_groups.yml
tags: users_groups
Solution without include files
If you don't want to change the wp-vhosts use of include files, you would need to use blocks in the playbook (Ansible 2.0+):
- hosts: all
roles:
- role: nginx
tags: nginx
tasks:
- block:
- debug: msg=hello
- someaction: ...
tags: wp-vhosts
Note that the final tags: is aligned with the block: so applies to all tasks in that block. This is cleaner than splitting the playbook into multiple plays.
Non-tag alternative
You can use a when: condition on the role invocation in the wp-vhost role dependencies, and define a variable such as debug_mode to control this. However, such debug/test logic will clutter your codebase compared to defining a tag per role invocation or task file.

Define `become=yes' per role with Ansible

In my system provisioning with Ansible, I don't want to specify become=yes in every task, so I created the following ansible.cfg in the project main directory, and Ansible automatically runs everything as root:
[privilege_escalation]
become = True
But as the project kept growing, some new roles should not be run as root. I would like to know if it is possible to have some instruction inside the role that all tasks whithin that role should be run as root (eg. something in vars/), instead of the global ansible.cfg solution above!
I have found a solution, although I think a better solution should be implemented by the Ansible team. Rename main.yml to tasks.yml, and then write the following to main.yml:
---
- { include: tasks.yml, become: yes }
Another solution is to pass the parameter directly in site.yml, but the main idea of the question was reusing the role in other projects without forgetting it needs root:
---
- hosts: localhost
roles:
- { role: name, become: yes }
You can also wrap your tasks in a block and put become: yes on the block. So, inside your roles/role_name/tasks/main.yml, you'd do this:
- block:
- name: Tasks go here as normal
...
become: yes
This will run all the tasks inside the block as root. More details of Ansible's blocks here (latest docs).
Not really a fundamentally different answer, rather a cosmetic reformatting of what's already been said. Looks the shortest, cleanest and YAML-ishest to me:
- name: My play
hosts: myhosts
roles:
- role: role1
become: yes
- role: role2
Role1 will be run as root while role2 won't.
In Ansible documentation for 2.4, you can find a way to define connection variables, such as ansible_become and ansible_user. They are defined as usual variables. Below is a snippet.
The first role prepare_user connects to hosts using user root without elevation rights. The second role register connects to hosts using the remote_user set via ansible.cfg (looked for in a defined order; search for "in the following order").
---
- hosts: all
name: Prepare VMs for cluster
roles:
- role: prepare_user
vars:
- ansible_become: false
- ansible_user: root
- role: register
...
There is a way to do what you are asking, but you need to be careful with how you use it, because Ansible evaluates most vars before running any tasks. If you use this trick, you must be sure to use it consistently or you could unintentionally use become where you don't want to.
Under the hood, Ansible uses the variable ansible_become to determine whether to use become for that task. Within your role, you can create a defaults/main.yml and set ansible_become: [true/false] This will cause that entire role to accept that value, unless overwritten by a higher-precedence definition (important to understand variable precedence)
The critical "gotcha" here is that if you use a role where this is defined, it will affect all other roles called below it in the play, unless they also have it defined.
Examples:
role_default_become_true has ansible_become: true defined as true in defaults
role_default_become_false has ansible_become: false defined as true in defaults
role_no_default has no default ansible_become value
---
- name: test1
hosts: localhost
connection: local
roles:
- role_default_become_true
- role_default_become_false
- role_no_default
- name: test2
hosts: localhost
connection: local
roles:
- role_default_become_false
- role_default_become_true
- role_no_default
- name: test3
hosts: localhost
connection: local
roles:
- role_default_become_false
- role_default_become_true
- { role: role_no_default, become: false }
In test1, role_no_default will run without become, because the previous role defined it as false, and it does not have its own definition.
In test2, role_no_default will run with become, because the previous role defined it as true, and it does not have its own definition.
In test3, role_no_default will run without become, because it has its own definition.
This is also possible using the include_task module
Create a main.yaml which includes the yaml with the tasks
---
- name: main
include_tasks:
file: "my_tasks_that_needs_become_true.yaml"
apply:
become: true

ansible: include role in a role?

Is it possible to reuse a role in a role? I do not mean via defining a dependency in the meta/main.yml file of a role but by including the role in the tasks/main.yml of another role directly?
For example, I define a couple of basic roles in rolebooks and some more high level roles in roles.
I want the high level roles to include some of the basic roles in addition to some specific tasks.
playbooks/
rolebooks/
some_role/
roles/
webtier/
tasks/
main.yml
In playbooks/roles/webtier/tasks/main.yml:
- shell: echo 'hello'
- { role: rolebooks/some_role }
- shell: echo 'still busy'
Thanks
Old question BUT for the record: use Ansible 2.2+ and you're good to go with include_role. Exactly for this very purpose... see documentation here.
Check out import_role as well... see documentation here
AFAIK, you can't. This is what dependencies are for.
If you want to avoid dependencies (because, for instance, you want 'role X' to run between two tasks), you can do this in the playbook itself if you think the tasks are related :
roles/webtier/tasks/main.yml:
- shell: echo 'hello'
- include: webtier.yml
- shell: echo 'role done'
All in all, it depends on what you're trying to do exactly. But in your example, 'still busy' seems to imply that the rolebooks/some_role is still running, which is not possible (there is no concurrency here).
Obviously, you can also sequence roles in a master playbook (which is probably what you do already) :
- name: Polite foo stuff
hosts: foo_hosts
roles:
- say_hello
- rolebooks/some_role
- say_bye
- name: Unpolite foo stuff
hosts: !foo_hosts
roles:
- rolebooks/some_role
You can't, but you can do something kind of similar.
For a layout of:
roles/
...
common/tasks/main.yml
nginx/tasks/main.yml
...
In nginx/tasks/main.yml, you can call your common task:
- name: Call the 'common' role to do some general setup
include: ../../common/tasks/main.yml
Note that because you're not using the typical import structure, you might run into some "weirdness" like role default variables not being accessible unless you included the role in the standard fashion earlier.

Resources