How can I access variables from within an ansible module? - ansible

I'd like to be able to set configuration variables for my ansible modules as standard host/group vars. How can I access them from within the module?
I'm aware I can pass all the values in the actual call, but that seems really tedious.
--
tasks:
- name: tell everyone
foo_announce: msg="tell everyone" token=x091232 uri=https://api.com
Versus having an appropriate set of default configuration variables and referencing those:
--
tasks:
- name: tell everyone
foo_announce: msg="tell everyone"

easiest to use an include file and reference it - you can specify default variables or just hard-code the defaults that way.
Personally I use variables and pass them as part of the include:
tasks:
- include: include-notification.yaml
vars:
themessage: "Starting on {{ inventory_hostname }}"
As long as your include file uses a variable called 'themessage' in the above example, it should work fine...

Related

Is it possible to use dynamic host_vars files?

I'm aware I can add host variables dynamically via an inventory script. I'm wondering if I can scripts in the host_vars directory which will be executed instead of simple read.
I have tried to create a simple script that outputs some variables. It seems only .json and .yml or no extension are read by the ansible-playbook. Since these are not executed the raw source will result in an error.
So, hence the question. Is this even possible and if not, would you be aware of a method to achieve the same results: Query a (local) dynamic source for variables of a particular host.
I'm pretty sure lookup("pipe") will do what you want, provided the script is available on the target host:
- set_fact:
my_vars: '{{ lookup("pipe", "./my_script.py") | from_json }}'
(substituting from_json with from_yaml or whatever to coerce the textual output from the script into a python datastructure; it's possible that ansible would coerce it automagically, but explicit is better than implicit)
If you want the script that is on the control machine to run, you'll likely have to do some hoopjumpery with delegate_to: and some hostvars ninjary to promote the set_fact: off of the control host over to all playbook hosts

Save vars back in a file automatically after including and modifying it

Is there anything like save_vars in Ansible that would automatically save modified var/fact imported with include_vars?
The following code serves the purpose but it is not an elegant solution.
# vars/my_vars.json
{
"my_password": "Velicanstveni",
"my_username": "Franjo"
}
# playbook.yaml
---
- hosts: localhost
gather_facts: no
tasks:
- name: fetch vars
include_vars:
file: my_vars.json
name: playbook_var
- name: change var
set_fact:
playbook_var: "{{ playbook_var|combine({'my_username': 'Njofra' }) }}"
- name: save var to file
copy:
content: "{{ playbook_var | to_nice_json }}"
dest: vars/my_vars.json
I was wondering if there is an option in Ansible we can turn on so that whenever the var/fact is changed, during the playbook execution, this is automatically updated in the file var was imported from. The background problem I am trying to solve is having global variables in Ansible. The idea is to save global vars in a file. Ansible has system of hostavars which is collection of per-hostname variables but not all data is always host-related.
I think a fact cache will plus or minus do that, although it may have some sharp edges to getting it right, since that's not exactly the problem it is trying to solve. The json cache plugin sounds like it is most likely what you want, but there is a yaml cache plugin, too.
The bad news is that, as far as I can tell, one cannot configure those caching URIs via the playbook, rather one must use either the environment variable or an ansible.cfg setting.

Pass through a variable to an included playbook

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.

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.

Using a variable from one Ansible var file in a second var file

In using Ansible, I'm trying to use a vaulted vars file to store private variables, and then using those in another vars file, in the same role. (The idea from 'Vault Pseudo leaf encryption' here.)
e.g. I have one standard vars file, roles/myrole/vars/main.yml:
---
my_variable: '{{ my_variable_vaulted }}'
and then one which is encrypted, roles/myrole/vars/vaulted_vars.yml:
---
my_variable_vaulted: 'SECRET!'
But when I run the playbook I always get '"ERROR! ERROR! 'my_variable_vaulted' is undefined"'.
I've tried it without encrypting the second file, to make sure it's not an issue with encryption, and I'm getting the same error.
The reason why my_variable_vaulted wasn't available was because I hadn't included the variable file. I'd assumed that all files in a role's vars/ directory were picked up automatically, but I think that's only the case with vars/main.yml.
So, to make the vaulted variables available to all tasks within the role, in roles/myrole/tasks/main.yml I added this before all the tasks:
- include_vars: vars/vaulted_vars.yml
That is not the best way to handle vault in ansibles. Much better approach is outlined in vault documentation for ansible. So you would create your basic variable for environment in group_vars/all.yml like that:
my_variable: {{ vault_my_variable }}
And then in your inventories/main you decide which hosts should load which vault file to satisfy this variable. As example you can have that in your inventories/main:
[production:children]
myhost1
[development:children]
myhost2
[production_vault:children]
production
[development_vault:children]
development
Then ansible will automatically fetch production_vault.yml or development_vault.yml respectively from group_vars depending on which environment box belongs to. And then you can use my_variable in your roles/playbooks as before, without having to worry about fetching it from the right place.

Resources