Ansible items.key in when condition - ansible

I am trying to write an Ansible script which will compress files on multiple hosts, based on inventory file. The code and inventory file has given below.
tasks:
- name: Create a tar.gz archive of a single file.
community.general.archive:
path:
- "{{items.path}}"
dest:
- "{{items.dest}}"
format: gz
owner: ubuntu
become: true
when: "{{items.key}} in inventory_hostname"
with_items:
- {path: "/var/log/adobe/adobe.log", dest: "/tmp/adobe.log.gz", key: "adobe"}
- {path: "/usr/local/app/applc.log", dest: "/tmp/applc.log.gz", key: "applc"}
inventory file (hosts.ini) contains the hostname.
vm-stg-adobe-201
vm-stg-adobe-202
vm-stg-applc-101
But when I execute the code am getting the below error.
FAILED! => {"msg": "The conditional check '{{items.key}} in inventory_hostname' failed. The error was: error while evaluating conditional ({{items.key}} in inventory_hostname): 'items' is undefined
I tried changing items.key as follows but failed.
when: "'items.key' in inventory_hostname"
when: "'{{items.key}}' in inventory_hostname" [when I use this the host key skipped]
Any will be much appreciated. Thank you.

After fixing the variable name issue, next task is to make the when condition work based on the hostname and the item.key.
Since the hostnames in your inventory have a pattern, the ideal way would be to split the hostname on the - delimiter and try to match the 3rd column to the item.key. In code, you could do these by:
when: inventory_hostname.split('-')[2] == item.key
UPDATE:
If you want to just search if the inventory_hostname contains the item.key, you can use:
when: inventory_hostname is search(item.key)
cheers

Related

Ansible - Set Playbook level Environment variable, but after defining some tasks

I want to set a playbook level environment, but after I execute a couple of tasks. I have found that I could define a playbook level environment variable before definition of any tasks or task level environment variables. But, I haven't found how can I set-up an environment variable that can be used by all tasks following a task.
- name: server properties
hosts: kafka_broker
vars:
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
ansible_host_key_checking: false
date: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
copy_to_dest: "/export/home/kafusr/kafka/secrets"
server_props_loc: "/etc/kafka"
secrets_props_loc: "{{ server_props_loc }}/secrets"
environment:
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 }}"
tasks:
- name: Create a directory if it does not exist
file:
path: "{{ copy_to_dest }}"
state: directory
mode: '0755'
- name: Find files from "{{ server_props_loc }}"
find:
paths: /etc/kafka/
patterns: "server.properties*"
# ... the rest of the task
register: etc_kafka_server_props
- name: Find files from "{{ secrets_props_loc }}"
find:
paths: /etc/kafka/secrets
patterns: "*"
# ... the rest of the task
register: etc_kafka_secrets_props
- name: Copy the files
copy:
src: "{{ item.path }}"
dest: "{{ copy_to_dest }}"
remote_src: yes
loop: "{{ etc_kafka_server_props.files + etc_kafka_secrets_props.files }}"
- name: set masterkey content value
set_fact:
contents: "{{ lookup('file', '/export/home/kafusr/kafka/secrets/masterkey.txt') }}"
extract_key2: "{{ contents.split('\n').2.split('|').2|trim }}"
I want to set CONFLUENT_SECURITY_MASTER_KEY after the set_facts task
Is it possible to set playbook level environment variable, but after defining some tasks
Thank you
UPDATE
Initially, when I was executing the playbook as originally defined, I was getting the error
fatal: [kafkaserver1]: FAILED! => {"msg": "The field 'environment' has an invalid value,
which includes an undefined variable. The error was: 'extract_key2' is undefined"}
which was expected as the variable extract_key2 was not set - before copying the files to desired directory.
After #Zeitounator's suggestion, when I added default to the environment variable's definition,
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 | default('') }}"
I now get a different error
The new error is
TASK [set masterkey content value] ******************** fatal: [kafkaserver1]: FAILED! =>
{"msg": "The task includes an option with an undefined variable. The error was: 'contents' is undefined\n\n
The error appears to be in '/export/home/kafuser/tmp/so-71538207-question.yml': line 43, column 7, but may\n
be elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n
- name: set masterkey content value\n ^ here\n"}
Getting this on all 3 brokers in the console and I checked the file it exists
I did do a cat on that file, copying the path from error to make sure there is no typo, and the contents of that file are displayed on console.
Update 2
I am trying to figure out how to use slurp to get the info, with the same approach as #Zeitounator's example about using lookup.
This is what I am trying. The current definition, is of course, erroneous. Just wanted to show what I am trying to do. But, can it be done for slurp and am I on the right path?
environment:
CONFLUENT_SECURITY_MASTER_KEY: >-
{{
(
((slurp: src: /export/home/z8tpush/kafka/secrets/masterkey.txt)['content'] | b64decode ).split('\n').2.split('|').2|trim
)
}}
#Zeitounator - Will you be able to direct me to an example where a slurp or fetch module is defined to set-up an environment variable and where the value will get updated after the tasks that create the file are executed, similar to what you have shown with lookup filter? I would really appreciate it.
Note:
Ultimately, I want to use ansible to create a new kafka user using confluents CLI commands ( using shell or command module ), verify it in my directory and once satisfied, I will encrypt the security.properties file using the masterkey and copy it to the appropriate location where confluent is installed.
As already mentioned, you can
With Ansible Configuration Settings set environment variables globally
Setting the remote environment in a task
Regarding your question
I haven't found how can I set-up an environment variable that can be used by all tasks following a task.
You can set the environment on Block level, a logical groups of tasks too
Setting the remote environment: "When you set a value with environment: at the play or block level, it is available only to tasks within the play or block that are executed by the same user."
This means you would need to define a block for the next tasks
- name: Block of next task(s)
block:
- name: Next task
...
environment:
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 }}"
Regarding your question
Is it possible to set playbook level environment variable, but after defining some tasks?
No, not on that level in that run as the playbook is already running.
Another option might be to distribute the tasks in question into an other role, playbook or task file and include_* it.
You cannot set_fact a var depending on an other var in the same block. Moreover, there is absolutely no need to set_fact here as long as your relevant tasks can live with an empty environment var until it is fully defined. The following environment declaration (untested) should work and return the key for every task running after your file exists.
environment:
CONFLUENT_SECURITY_MASTER_KEY: >-
{{
(
(
lookup('file', '/export/home/kafusr/kafka/secrets/masterkey.txt', errors='ignore')
| default('')
).split('\n').2
| default('')
).2
| default('')
| trim
}}

Is there a way to skip a task/play when files are not found in ansible?

Below I posted an example of what I currently have but it doesn't resolve the issue.
ignore_errors still outputs the errors from the play but doesnt stop the tasks from completing. Is there a way to skip the play all together and move on to the next?
- name: replace static with delta file
copy:
src: "/home/docs/delta.{{ inventory_hostname }}"
dest: "/usr/share/static"
backup: yes
ignore_errors: yes
You could use a fileglob to prevent the task from running if the source file does not exist:
- name: replace static with delta file
copy:
src: "{{ item }}"
dest: "/usr/share/static"
backup: yes
loop: "{{ query('fileglob', '/home/docs/delta.%s' % inventory_hostname) }}"
This fileglob will return either 0 or 1 results, so the task will be
skipped if there is no matching file.
So the first thing which comes to my mind is to create task which will check if the directory exist:
- name: Playbook name
hosts: all
tasks:
- name: Task name
stat:
path: [path to the file or directory you want to check]
register: register_name
And the second task to work if directory exists:
- name: Task name
debug:
msg: "The file or directory exists"
when: register_name.stat.exists

win_copy giving error when using with_items

I am trying to use win_copy in a following fashion:
win_copy:
src: {{item}}_outputfile.txt
dest: c:\temp{{ item }}_outputfile.csv
with_items:
{{ AnArrayOfValues }}
Now if the file does not exist Ansible giving error: FIleNotExist and it fails the script, but I want the Ansible script to continue and ignore the file which doesn't exist.
Any help in this regard?
You can use ignore_errors: True with the ansible modules.
Note:- ignore_errors: True will ignore all the error but in case of win_copy we can use this as there won't be many error except for FIleNotExist
- hosts: localhost
tasks:
- win_copy:
src: "{{item}}_outputfile.txt"
dest: c:\temp{{ item }}_outputfile.csv
with_items:
- "{{ AnArrayOfValues }}"
ignore_errors: true

Ansible Task: with_dict statement got triggered, even if the when-clause should prevent this

I'am trying to automate the creation of the smart_[diskdevice] links to
/usr/share/munin/plugins/smart_
during the installation of the munin node via ansible.
The code here works partially, except there is no diskdevice to link on the target machine. Then I got a fatal failure with
{"msg": "with_dict expects a dict"}
I've review the ansible documentation and tried to search the problem in the web. For my understanding, the whole "file" directive should not be executed if the "when"-statement fails.
---
- name: Install Munin Node
any_errors_fatal: true
block:
...
# drives config
- file:
src: /usr/share/munin/plugins/smart_
dest: /etc/munin/plugins/smart_{{ item.key }}
state: link
with_dict: "{{ ansible_devices }}"
when: "item.value.host.startswith('SATA')"
notify:
- restart munin-node
On targets with a SATA-Drive, the code works. Drives like "sda" are found and the links are created. Loop- and other soft-Devices are ignored (as intended)
Only on a Raspberry with no SATA-Drive at all i got the fatal failure.
You are using the with_dict option to set the loop. This sets the value of the item variable for each iteration as a dictionary with two keys:
key: The name of the current key in the dict.
value: The value of the existing key in the dict.
You are then running the when option that checks the item variable on each iteration. So check if that is the behavior you want.
Regarding your error, it is being thrown because for some reason, ansible_devices is not a dict as the error says. And Ansible checks for the validity of the with_dict type before resolving the when condition.
Check the following example:
---
- name: Diff test
hosts: local
connection: local
gather_facts: no
vars:
dict:
value: True
name: "dict"
tasks:
- debug: var=item
when: dict.value == False
with_dict: '{{ dict }}'
- debug: var=item
when: dict.value == True
with_dict: '{{ dict }}'
- debug: var=item
when: dict.value == False
with_dict: "Not a dict"
The first two task will succeed because they have a valid dict on the with_dict option and a correct condition on the when option. The last one will fail because the with_dict value has the wrong type, even though the when condition resolves correctly and should guarantee to skip the task.
I hope it helps.

Use lineinfile to fill ansible inventory file

I have trivial task to fill particular ansible inventory file with the new deployed VMs names under their roles.
Here is my playbook:
- hosts: 127.0.0.1
tasks:
- name: Add host to inventory file
lineinfile:
dest: "{{inv_path}}"
regexp: '^\[{{role}}\]'
insertafter: '^#\[{{role}}\]'
line: "{{new_host}}"
Instead of adding new line after [role], ansible replace the string with the hostname. My aim is to naturally insert after it. If there is no matched role it should skip add new line.
How to achieve this? Thanks!
[role] is getting replaced because the regexp parameter is the regular expression Ansible will look for to replace with the line parameter. Just move that value to insertafter and get rid of regexp.
As for the other requirement you will need another task that will check if [role] exists first. Then your lineinfile task will evaluate the result of the checker task to determine if it should run.
- name: Check if role is in inventory file
shell: grep [{{ role }}] {{ inv_path }}
ignore_errors: True
register: check_inv
- name: Add host to inventory file
lineinfile:
dest: "{{ inv_path }}"
insertafter: '^\[{{role}}\]$'
line: "{{new_host}}"
when: check_inv.rc == 0

Resources