Pass through a variable to an included playbook - ansible

I am including another playbook from my playbook. I need to pass it a variable, called required_node_version. I am using the same variable in my playbook, so was expecting that the included playbook will inherit it. But it doesn't causing 'undefined' error. So I tried to use this not very elegant solution:
- hosts: localhost
connection: local
vars:
required_node_version: v6.3.1
...
- include: ../../base/ci/build.yml
vars:
required_node_version: "{{required_node_version}}"
Which causes {"failed": true, "msg": "ERROR! recursive loop detected in template string: {{required_node_version}}"}.
Any elegant solution for this?

Ansible maintains the state of each play separately. Variable scope of one play is also separated from other plays. you may consider variable scope defined in a play as a directed acyclic graph where each node name is unique and values will be overwritten on second write.
When you include some other playbook in a playbook (including the playbook at top level), the included playbook will have its own variable scope because its a completely different play. So you won’t be able to access the variables defined in the main play . The actual error is not recursive loop detected error, the actual error is variable undefined error because you are trying to access a variable that is defined in some other play.
Try this:
main.yml
---
- hosts: all
vars:
name: shasha
- include: included_playbook.yml
vars:
name1: "{{name}}""
Now you will get the actual error which is : name is undefined, because you are trying to access a variable from different scope(different play) while assigning name to name1.
Now change your main.yml with this:
main.yml
---
- hosts: all
vars:
name: shasha
- include: included_playbook.yml
vars:
name: shashank
Though the name is defined twice, they are different than each other because the scope for both are different (scope of main play and the scope of included play). So this works fine without any issues.
Some alternate solutions:
1. Pass the variable while invoking the playbook:
Since the playbook you are including can also be run as a main playbook, in that case how would you use that variable ? Either you will define the variable inside that playbook or you will pass it from command line.
When you pass the variable while invoking playbook,it will automatically create that variable in scope of both playbooks (the main playbook as well as the included one):
ansible-playbook -i hosts main.yml -e "required_node_version=v6.3.1"
Or maybe you can create a JSON file which contains all the common variables which are common across all the playbooks you are including and then pass that JSON file while invoking the main ansible playbook.
2. Redesign you Ansible playbooks:
In case the ansible playbook that you are including, works as a helper and not being used in standalone manner, you should then include this playbook at task level. You will still maintain the re-usability and there will be only single scope so you don't need to worry about passing variable explicitly.
A little more about the reason behind recursive loop detected error:
Ansible actually does not use the concept of rvalue when you assign a variable to a variable, It performs the binding between the two instead. For example, What do you think would be the value of variable name1 in the following code:
main.yml
---
- hosts: all
vars:
- name: old_val
- name1: "{{name}}"
- name: new_val
tasks:
- debug: msg="{{name1}}"
If your answer is new_val, you understood the logic. Consider name1 as a reference, which is referring to name and name is now referring to new_val. So the only value present is new_value and name1 and name are just the references referring to new_value.
reference to old_value is lost.
Now consider the below script:
main_recursive.yml
---
- hosts: all
vars:
- name: shasha
- name1: "{{name}}"
- name: "{{name1}}"
tasks:
- debug: msg="{{name}}"
Now let's try resoving the value of name. name is pointing to name1 and name1 is pointing to name. That's ok but how are we going to reach the actual value ??? No way, its a recursive loop !!!

A bit of documentation:
Ansible has 3 main scopes:
Global: this is set by config, environment variables and the command line
Play: each play and contained structures, vars entries, include_vars, role defaults and vars.
Host: variables directly associated to a host, like inventory, facts or registered task outputs
So variables you define for each play don't know about each other.
You have two options:
set required_node_version globally (via -e switch), so it will be defined everywhere
set required_node_version for each individual host (i.e. via ./group_vars/all.yml file), so it will be available in each task as host-bound fact.

Related

How to read a specific line from the file and register it as a variable and pass it to another role in ansible

I am creating a azure image using ansible roles, below is the sample of my play book:
- hosts: localhost
gather_facts: yes
roles:
- azure_vmcreator
- hosts: azure_vms
gather_facts: no
roles:
- vm_software
- download_Artifact
- hosts: localhost
gather_facts: yes
roles:
- azure_imagecreator
The idea is to download a f.exe file in download_Artifact role and install it in the vm, on installation f.exe will create a product.id file. I want to read the second line of that file and register it as a variable say image_prefix, so that i can use image_prefix variable in azure_imagecreator role and create a image adding it in the prefix.
I am new to ansible can any one help me out with this ?
Accessing variables set on another host in a playbook isn't very straightforward. The general workflow would look like this:
In download_Artifact use the set_fact module to save the line in question as a host_var. ( set_fact always stores variables as host vars).
You can use with_file to get the contents of a file into a variable and then use jinja to get the second line of the file:
- name: Print Second line of a File
debug:
msg: "{{ item.split("\n")[1] }}"
with_file:
- "/filepath"
What the jinja2 snippet does, is splitting the file on newlines, converting it to an array of the lines, and then accessing the second line (Array indices start with 0).
Instead of printing the contents, you would need to use the set_fact module to save that string into a variable.
Then, In your third playbook, pass the hostvar you've set as an argument to the role. Use can use hostvars.<hostname>.<varname> to access the hostvars of another host.
If you don't know the hostname beforehand, you can use ansibles special variable groups to get the name during playbook execution. The call would then look somewhat like this (haven't tested this though and assuming your role accepts a variable called image_prefix):
- hosts: localhost
gather_facts: yes
roles:
- name: azure_imagecreator
azure_imagecreator:
image_prefix: hostvars[groups.azure_vms[0]].image_prefix

How to use variables between different roles in ansible

my playbook structure looks like:
- hosts: all
name: all
roles:
- roles1
- roles2
In tasks of roles1, I define such a variable
---
# tasks for roles1
- name: Get the zookeeper image tag # rel3.0
run_once: true
shell: echo '{{item.split(":")[-1]}}' # Here can get the string rel3.0 normally
with_items: "{{ret.stdout.split('\n')}}"
when: "'zookeeper' in item"
register: zk_tag
ret.stdout:
Loaded image: test/old/kafka:latest
Loaded image: test/new/mysql:v5.7
Loaded image: test/old/zookeeper:rel3.0
In tasks of roles2, I want to use the zk_tag variable
- name: Test if the variable zk_tag can be used in roles2
debug: var={{ zk_tag.stdout }}
Error :
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout'
I think I encountered the following 2 problems:
When registering a variable with register, when condition is added, this variable cannot be used in all groups. How to solve this problem? How to make this variable available to all groups?
is my title, How to use variables between different roles in ansible?
You're most likely starting a new playbook for a new host. Meaning all previous collected vars are lost.
What you can do is pass a var to another host with the add_host module.
- name: Pass variable from this play to the other host in the same play
add_host:
name: hostname2
var_in_play_2: "{{ var_in_play_1 }}"
--- EDIT ---
It's a bit unclear. Why do you use the when statement in the first place if you want every host in the play for it to be available?
You might want to use the group_vars/all.yml file to place vars in.
Also, using add_host should be the way to go as how I read it. Can you post your playbook, and the outcome of your playbook on a site, e.g. pastebin?
If there is any chance the var is not defined because of a when condition, you should use a default value to force the var to be defined when using it. While you are at it, use the debug module for your tests rather than echoing something in a shell
- name: Debug my var
debug:
msg: "{{ docker_exists | default(false) }}"

Is there a way to reference group variables when importing in Ansible?

In my Ansible scripts, is there a way to reference a path variable when importing a playbook?
I have set up some variables with the path to different files in my Ansible file hierarchy that I would like to reference when importing and including playbook and variable files. The path variables are defined in a file in the group_vars/all/ directory so they are loaded automatically.
When I try to import a playbook and reference one of my path variables, I get an error that the variable is not defined.
Here's an example. I created a simple playbook file that I call include.yml:
---
- name: Include playbook
import_playbook: "{{ base_dir }}/foo.yml"
The playbook imports another playbook called foo.yml in a directory defined in the variable, base_dir.
Here is foo.yml:
---
- name:
hosts: localhost
gather_facts: no
tasks:
- name: Check that group_vars/all variable loaded
debug:
var: test_dir
In the group_vars/all/ directory, I define base_dir and test_dir in a file called dirs.yml:
base_dir: "{{ playbook_dir }}"
test_dir: "{{ base_dir }}/foo"
When I run:
ansible-playbook include.yml
I expect that Ansible will import and run foo.yml, which prints the value of the test_dir variable which references base_dir.
Instead, I get the error:
ERROR! 'base_dir' is undefined
If I run:
ansible-playbook include.yml --extra-vars base_dir="."
then it runs as expected.
It appears that the import occurs before the group_vars variables are loaded.
If true, this is inconvenient because I would like to define my file paths in global variables that can be referenced by multiple playbooks, instead of hardcoding them in all of my playbooks. Is there a way around this issue?
Q: "I would like to define my file paths in global variables that can be referenced by multiple playbooks, instead of hardcoding them in all of my playbooks. Is there a way around this issue?
A: It's a rational expectation that group_vars/all might have been loaded before a playbook is imported. But neither playbook nor inventory related group_vars/all works this way. There are closed issues on this topic (locked and limited conversation to collaborators on Apr 27) for example playbook_vars_root doesn't work for playbook imports #34239.
There is no workaround. The scope of such variables is the play. goup_vars can't be loaded before the hosts is known from the play.
FWIW. An option of running playbooks in a systemic and flexible way is ansible-runner. See Runner Input Directory Hierarchy and Running Playbooks.

How to access group variables from inventory file inside ansible yaml

I have an inventory file with one group like below:
[example1]
H1 ip1 username1
H2 ip2 username2
H3 ip3 username3
and I have defined group variable like below to make that variable common to all hosts in that group:
[example1:vars]
temp='H2'
I am trying to access this variable inside my ansible yml under hosts field like below:
---
- name: temp playbook to practice hostvars
hosts: "{{ hostvars['example1']['temp'] }}"
tasks:
.....
.....
....
But while executing this playbook, I am getting the hostvars undefined error like below:
"ERROR! The field 'hosts' has an invalid value, which includes an undefined variable. The error was: 'hostvars' is undefined"
My ansible file and inventory file in same folder, could anyone help me to rectify what I am missing to access the variable from inventory file?
Please, don't do it like that. It's bad way. Use groups with 'children' property for host grouping.
Nevertheless, here is a fix for your problem. You've tried to access hostvars for a group. There is no host 'example1' in your inventory. And hostvars have variables for host, as you can see.
Following code will do the trick:
- hosts: localhost
gather_facts: no
tasks:
- debug:
- hosts: '{{ hostvars[groups.example1[0]].temp }}'
tasks:
...
I use groups.example1 to get a list of hosts in a group example1, then I take first member of that group ([0]) then I peek into its variables through hostvars.
The strange task with debug at the begin need to help Ansible to initialize hostvars. Without it, it will fail with undefined variable error.
But, again, don't do it like that. It's really, really mess up way to reinvent groups.
I know it's an old question, but just found the solution for this problem. Just put this before using hostvars in the next task or role and somehow you will be able to use the hostvars variables into hosts: definition.
- name: Do nothing
hosts: all
gather_facts: no
become: no
it seems that there is no need for run_once: yes (maybe only for some performance improvment, if any)
I can't say I understand what's under the hood, but it seems that ansible (2.9, 2.10) need something bogus in order to use hostvars variable in the next tasks/roles.

Pass variable to included playbook?

I would like to have a master playbook, which include's other playbooks. Is it possible to pass a variable to that included playbook?
The normal syntax which is used for passing variables to included tasks doesn't work (see below)
- include: someplaybook.yml variable=value
and
- include: someplaybook.yml
vars:
variable: value
I'm running v2.0.2.0.
The only thing i see missing is quotes.
- include: someplaybook.yml variable='value'
It works for me and should work for you too. If not share the error you face.
Make sure you have this variable "variable" defined in the task of the role as well and from here you are just passing the value to that variable.
Tested on ansible 2.4
- import_playbook: any_playbook.yml variable='value'
Also, I suggest you read this,
http://docs.ansible.com/ansible/latest/playbooks_reuse.html
and try using roles in this case, it'll help in a case like this, where you're trying to include/import multiple playbooks in a single main playbook.
And about passing a value to the include statement you can add it to the vars main.yml of the role.
Or, if the variable you want to pass is the result of a previous task in the single main playbook use 'register' and save the output in a varible.
- debug: msg="{{result.stdout_lines}}"
here, result is the registered variable.
Use the debug module to know exactly what you want to pass to the playbook.
Hope this helps.
In my opinion most consistent way to pass variable to included playbook and to another play in the current playbook as well is using:
set_fact:
global_var_name: "{{ your_var }}"
before
- import_playbook: your_playbook.yml
After you set the fact it's is accessible from any play in the playbook and imported playbooks, for instance inside "your_playbook" you can call it like so:
debug:
var: global_var_name
If you use:
- import_playbook: someplaybook.yml variable='value'
you can only pass fixed 'value'(in include as well) if you try to pass var value:
- import_playbook: someplaybook.yml internal_var="{{ your_var }}"
you will get NOT DEFINED when you call internal_var inside 'someplaybook.yml'
All of this is true for the beginning of 2021, ansible 2.9*, it's quite possible they will 'fix' import_playbook, I would like this, by the way.

Resources