Ansible: overriding dictionary variables in extra-vars [duplicate] - ansible

This question already has answers here:
Ansible. override single dictionary key [duplicate]
(4 answers)
Closed 4 years ago.
In my Ansible playbook I have a nested variable declaration as shown below in a variable file.
repo:
branch: int
url: git#github:user/repo.git
dest: "/var/code"
How would I override the branch param in extra-vars? I tried something like this below but it didn't work.
--extra-vars "repo.branch=exec_refactor"
neither this
--extra-vars "repo[branch]=exec_refactor"
using JSON representation like below results in overriding the entire repo node and hence repo.branch is successfully overridden but both repo.url and repo.dest becomes undefined.
--extra-vars '{"repo":{"branch":"exec_refactor"}}'

To merge dicts, you need to set hash_behaviour=merge in your ansible.cfg. But it is not recommended to do this since pretty much all roles you find on Ansible Galaxy do expect the default value replace and might run crazy.
See hash_behaviour in the docs.
I once had a similar problem and wrote an action plugin to solve it: include_vars_merged. It is no out-of-the-box solution for your problem because Ansible in any case will override the dict with the one from --extra-vars and using my plugin, you would again override that single value you passed in --extra-vars. But it should not be too hard to modify the plugin and only add new values instead of overriding values. I think switching parameters in line 34 & 40 in include_vars_merged.py should already do it.

Related

Fail to use yaml reference in ansible inventory plugin

I would like to use this config with an inventory plugin
# test_inventory_xxx.yml
plugin: cloudscale # or openstack or ...
inventory_hostname: &inventory_hostname_value uuid
compose:
setting_of_inventory_hostname: *inventory_hostname_value
I get no error, but the value is not set. And it is valid yaml. (At least my checker nor myself see an error.
So I decided to simplify it by using the constructed plugin, which is standard:
# inventory_constructed.yaml
plugin: constructed
# add variables to existing inventory
keyed_groups:
- key: from_inventory
prefix: inventory
parent_group: &common_parent_group test_group_1
compose:
var_from_constructed: 1233456789
also_from_constr: "'also'" # must be in quotes 2x!
some_from_constr: &ref1 1234567777
ref_from_constr: *ref1 # this works fine
ref_to_test: *common_parent_group # <--- this line returns an error
strict: yes
Now I get the error: Could not set ref_to_test for host my_host: 'test_group_1' is undefined
But it passes when I uncomment the marked line. (the ref &common_parent_group is still defined, but not used with *common_parent_group.) Why is test_group_1 undefined in one case, but not in the other?
How to reproduce: ansible -i some_of/your_inventory -i inventory_constructed.yaml -m debug -a var=vars
What do I do wrong? Or what else is the problem?
(I tought it is an missing feature, so original info in https://github.com/ansible/ansible/issues/69043)
It seems like parent_group takes a literal string while ref_to_test takes a Jinja2 expression (because it's under compose). It should fail the same way if you write
ref_to_test: test_group_1
because test_group_1 simply isn't a Jinja2 variable. You'll have to write
ref_to_test: "'test_group_1'"
just like above so Jinja2 sees 'test_group_1' which is a literal string. This also means you can't use an alias because parent_group does not evaluate its content with Jinja2 and therefore shouldn't include quotes in its content.

Is there any way to check a Yml/Yaml file is valid ansible Playbook?

It should check all cases of imports, includes and custom variables. As of now I can see that ansible-playbook playbook.yml --list-tasks fails in few cases when we have custom variables. For example a yml having tasks only should not be a valid play. yml importing those tasks should be treated as valid ansible play.
Because of the way templating etc works, the only way to know for sure beyond basic syntax checking (eg --syntax-check or --list-tasks) is to execute it. --check-mode can tell you some things if your playbook is written correctly to support it, and there are other tools around like ansible-lint that might help, but nothing short of executing the playbook will tell you 100%.

Ansible nested variables in inventory

I've been using Ansible for a while now, and in general have no trouble with variables in inventories. However, for the first time I have to override a nested variable in the inventory, and the I'd expect it to work... doesn't.
The default/main.yml of the role looks like this:
archiver_config:
archiver_folder: "/opt/archiver"
source_folder: "/var/tmp/images"
archive_folder: "/var/tmp/imagearchive"
min_diskspace: 1e6
logfile: "/var/log/archiver.log"
I need to override the default archive folder for some hosts because some of them have an external filesystem attached for this purpose, so I did this in the inventory:
[tdevices]
10.8.0.38 adeploy_name=16014c archiver_config.archive_folder=/media/ext
I have also tried putting the value in double and single quotes, like e.g.
archiver_config.archive_folder='/media/ext'
But it doesn't work. Ansible doesn't throw any errors, but the default value does not get overridden. What's the correct syntax to do this?
There are no "nested variables" in your example. There is only one variable archiver_config which is a dictionary (hash).
You cannot assign a value to a dictionary key in the inventory file.
What you can do is add a variable in the defaults/main.yml, use it as a value for the key (now, this can be called a nested variable):
archive_folder: "/var/tmp/imagearchive"
archiver_config:
archiver_folder: "/opt/archiver"
source_folder: "/var/tmp/images"
archive_folder: "{{archive_folder}}"
min_diskspace: 1e6
logfile: "/var/log/archiver.log"
and assign value to it in the inventory file:
[tdevices]
10.8.0.38 adeploy_name=16014c archive_folder=/media/ext

Extract server IP from the group in the inventory file

I have the following inventory file:
[group_01]
g01_h01 ansible_ssh_host='10.1.0.1'
g01_h01 ansible_ssh_host='10.1.0.2'
[group_02]
g02_h01 ansible_ssh_host='10.2.0.1'
g02_h01 ansible_ssh_host='10.2.0.2'
[group_03:children]
group_01
group_02
[group_03:vars]
fst_group2={{groups['group_02'][0]}}
snd_group1={{groups['group_01'][1]}}
I would like that in my playbook variables had the following values:
fst_group2=10.2.0.1
snd_group1=10.1.0.2
Instead I get:
fst_group2=g02_h01
snd_group1=g01_h02
Any ideas, a workaround?
Very strange task indeed... Anyway,
groups variable – is a list of hosts, which are g01_h01, g01_h02, etc.
To achieve what you expect, you may use this:
[group_03:vars]
fst_group2={{hostvars[groups['group_02'][0]]['ansible_ssh_host']}}
snd_group1={{hostvars[groups['group_01'][1]]['ansible_ssh_host']}}
And keep in mind that ansible_ssh_host is deprecated in favor of ansible_host.

Specifying a particular callback to be used in playbook

I have created different playbooks for different operations in ansible.
And I have also created different Callback Scripts for different kinds of Playbooks (And packaged them with Ansible and installed).
The playbooks will be called from many different scripts/cron jobs.
Now, is it possible to specify a particular callback script to be called for a particular playbook? (Using a command line argument probably?)
What's happening right now is, all the Callback scripts are called for each playbook.
I cannot put the callback script relative to the location/folder of the playbook because it's already packaged inside the ansible package. Also, all the playbooks are in the same location too.
I am fine with modifying a bit of ansible source code to accommodate it too if needed.
After going through the code of Ansible, I was able to solve it with the below...
In each callback_plugin, you can specify self.disabled = True and the callback wont be called at all...
Also, which calling a playbook, there's an option to parsing extra arguments as key=value pairs. It will be part of the playbook object as extra_vars field.
So I did something like this in my callback_plugin.
def playbook_on_start(self):
callback_plugins = self.playbook.extra_vars.get('callback_plugin', '') // self.playbook is populated in your callback plugin by Ansible.
if callback_plugins not in ['email_reporter', 'all']:
self.disabled = True
And while calling the playbook, I can do something like,
ansible-playbook -e callback_plugin=email_reporter //Note -e is the argument prefix key for extra vars.
If with callback scripts you mean callback plugins, you could decide in those plugins if any playbook should trigger some action.
In the playbook_on_play_start method you have the name of the play, which you could use to decide if further notifications should be processed or not.
playbook_on_stats then is called at the end of the playbook.
SHOULD_TRIGGER = false
class CallbackModule(object):
def playbook_on_play_start(self, name):
if name == "My Cool Play":
SHOULD_TRIGGER = true
def playbook_on_stats(self, stats):
if SHOULD_TRIGGER == true:
do something cool
Please note, playbook_on_play_start is called for every play in your playbook, so it might be called multiple times.
If you are simply running a playbook via script you can do something like this
ANSIBLE_STDOUT_CALLBACK="json" ansible-playbook -i hosts play.yml
You are setting the callback as an environment variable prior to ansible-playbook command running.

Resources