Ansible Registers - Dynamic naming - ansible

I am trying to use a register in Ansible playbook to store my output. Below is the code which i am using.
I have tried below code
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: {{ item }}_service_status
with_items:
- XXX
- YYY
- ZZZ
I need different outputs to be stored in different register variables based on the items as mentioned in the code. It is failing and not able to proceed. Any help would be appreciated.

Updated answer
I think you need to put quotes around it:
register: "{{ item }}_service_status"
Or you can use set_fact (1, 2, 3, 4)
register all the output to a single static variable output and then use a loop to iteratively build a new variable service_status (a list) by looping over each item in the static variable output
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: output
with_items:
- XXX
- YYY
- ZZZ
- name: Setting fact using output of loop
set_fact:
service_status:
- rc: "{{ item.rc }}"
stdout: "{{ item.stdout }}"
id: "{{ item.id }}"
with_items:
- "{{ output }}"
- debug:
msg: "ID and stdout: {{ item.id }} - {{ item.stdout }}"
with_items:
- "{{ service_status }}"
Initial Answer
IIUC, this link from the Ansible docs shows how to use register inside a loop (see another example in this SO post).
A couple of points
it may be more convenient to assign the list (XXX, YYY, ZZZ) to a separate variable (eg. 1, 2)
I don't know if this is part of the problem, but with_items is no longer the recommended approach to loop over a variable: instead use loop - see here for an example
vars:
items:
- XXX
- YYY
- ZZZ
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: service_status
loop: "{{ items|flatten(levels=1) }}"
- name: Show the return code and stdout
debug:
msg: "Cmd {{ item.cmd }}, return code {{ item.rc }}, stdout {{ item.stdout }}"
when: item.rc != 0
with_items: "{{ service_status.results }}"

Related

Ansible - loop over multiple items in stdout_lines

I am performing a grep with multiple items.
---
- hosts: my_host
gather_facts: false
vars:
my_list:
- whatever
- something
tasks:
- name: grep for item in search path
shell: "grep -rIL {{ item }} /tmp"
register: the_grep
loop: "{{ my_list }}"
- debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ the_grep.results }}"
Depending on the result, multiple files could match.
msg:
- /tmp/something.conf
- /tmp/folder/file.txt
Q: How would I configure Ansible to loop over the items in stdout_lines?
The use case I'm solving is to delete .ini sections based on the item, but in this case, Ansible doesn't loop over the stdout_lines.
- name: remove stanza from ini file
ini_file:
path: "{{ item.stdout_lines }}"
section: "{{ item.item }}"
mode: '0600'
state: absent
loop: "{{ the_grep.results }}"
when: item.stdout_lines | length > 0
It seems that this doesn't work, but configuring item.stdout_lines[0] gives the partially expected result, since Ansible will use only the first item in that list. But ofc, not the 2nd and so on.
Perhaps there's a prettier answer, but solved it by using with_nested and creating a json_query:
- name: remove stanza from ini file
ini_file:
path: "{{ item.0 }}"
section: "{{ item.1.item }}"
mode: '0600'
state: absent
with_nested:
- "{{ the_grep | json_query('results[].stdout_lines[]') }}"
- "{{ the_grep.results }}"

Ansible: Output of 'loop' with 'register' module

I'm trying to get the output of my Ansible script using register module but the loop I'm using is probably causing some issue.
Whats a default way of using register module with loop?
Code:
---
- name:
hosts:
gather_facts:
tasks:
- name: Execute file
ansible.builtin.shell:
environment:
"setting environment"
register: output
loop:
"value"
- debug:
vars: output.std_lines
Whats a default way of using register module with loop?
It is just registering the result.
The only difference will be, that a single task will provide you just with an dictionary result (or output in your example) and registering in a loop will provide with a list result.results (or output.results in your example). To access the .stdout_lines you will need to loop over the result set .results too.
You may have a look into the following example playbook which will show some aspects of Registering variables, Loops, data structures, dicts and lists and type debugging.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create STDOUT output (single)
command: 'echo "1"'
register: result
- name: Show full result (single)
debug:
var: result
- name: Show '.stdout' (single)
debug:
msg: "The result in '.stdout': {{ result.stdout }} is of type {{ result.stdout | type_debug }}"
- name: Create STDOUT output (loop)
command: 'echo "{{ item }}"'
register: result
loop: [1, 2, 3]
loop_control:
label: "{{ item }}"
- name: Show full result (loop)
debug:
var: result
- name: Show '.stdout' (loop)
debug:
msg: "The result in '.stdout': {{ item.stdout }} is of type {{ item.stdout | type_debug }}"
loop: "{{ result.results }}"
loop_control:
label: "{{ item.item }}"
By running it and going through the output you can get familiar with the differences in your tasks.
Further Q&A
Register Variables in Loop in an Ansible Playbook
Ansible: loop, register, and stdout
Register variables in loop in Ansible playbook
... and many more here on SO.

Ansible: tasks to append number to name tag

I'm trying to append a number to the end of the instance name tag, i have the following code but there's a problem with the second task which i cannot find, and i've not been able to find an example of anyone else trying to solve this problem.
I'm also relatively new to Ansible and cannot find the relevant documentation to do certain things like how to lookup a value in a list like how i'm doing with my until: which is probably invalid syntax
Ansible is 2.9 and runs on the instance itself
The Tasks I have are setup to do the following
Get a list of running EC2 instances that belong to the same launch template
Loop the same amount of times as the instance list until the desired name based on item index is not found in the name tags of the ec2 list
Set the updated tag name
Current Code:
---
- name: "{{ role_name }} | Name Instance: Gather facts about current LT instances"
ec2_instance_info:
tags:
"aws:autoscaling:groupName": "{{ tag_asg }}"
"aws:ec2launchtemplate:id": "{{ tag_lt }}"
Application: "{{ tag_application }}"
filters:
instance-state-name: [ "running" ]
no_log: false
failed_when: false
register: ec2_list
- name: "{{ role_name }} | Name Instance: Generate Name"
with_sequence: count="{{ ec2_list.instances|length }}"
until: not "{{ tag_default_name }} {{ '%02d' % (item + 1) }}" in ec2_list.instances[*].tags['Name']
when: tag_name == tag_default_name
no_log: false
failed_when: false
register: item
- name: "{{ role_name }} | Name Instance: Append Name Tag"
ec2_tag:
region: eu-west-1
resource: "{{ instance_id }}"
state: present
tags:
Name: "{{ tag_default_name }} {{ '%02d' % (item + 1) }}"
when: tag_name == tag_default_name
no_log: false
failed_when: false
As requested here's the error I am getting:
ERROR! no module/action detected in task.
The error appears to be in '/app/deploy/Ansible/roles/Boilerplate/tasks/name_instance.yml': line 14, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: "{{ role_name }} | Name Instance: Generate Name"
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
The error is not with the name: as i constantly get that error when there's some other error within the task body
You don't appear to have a module listed in the second task. You might be able to use debug as the module, or use set_fact and skip the register line.
I think it might also be possible to combine the last two tasks with some more advanced looping, but I'd have to play around with it to give you a working example.
Thanks to bnabbs answer i realised the problem with that version was the lack of module, after fixing that i then finished creating and testing it and now have a fully working set of tasks which has resulted in the following code.
---
- name: "{{ role_name }} | Name Instance: Gather facts about current LT instances"
ec2_instance_info:
filters:
"tag:aws:autoscaling:groupName": "{{ tag_asg }}"
"tag:aws:ec2launchtemplate:id": "{{ tag_lt }}"
"tag:Application": "{{ tag_application }}"
instance-state-name: [ "pending", "running" ]
register: ec2_list
- name: "{{ role_name }} | Name Instance: Set Needed Naming Facts"
set_fact:
tag_name_seperator: " "
start_index: "{{ (ec2_list.instances | sort(attribute='instance_id') | sort(attribute='launch_time') | map(attribute='instance_id') | list).index(instance_id) }}"
name_tag_list: "{{ (ec2_list.instances | map(attribute='tags') | list) | map(attribute='Name') | list }}"
# Generate Name from starting index of array and mod to the length of the amount of instances to help prevent conflicts when multiple instances are launched at the same time
- name: "{{ role_name }} | Name Instance: Generate Name"
set_fact:
desired_tag_name: "{{ tag_default_name }}{{ tag_name_seperator }}{{ '%02d' % (((item|int) + (start_index|int) % (name_tag_list|length)) + 1) }}"
loop: "{{ name_tag_list }}"
until: not "{{ tag_default_name }}{{ tag_name_seperator }}{{ '%02d' % (((item|int) + (start_index|int) % (name_tag_list|length)) + 1) }}" in name_tag_list
- name: "{{ role_name }} | Name Instance: Append Name Tag"
ec2_tag:
region: eu-west-1
resource: "{{ instance_id }}"
state: present
tags:
Name: "{{ desired_tag_name }}"

Run a task first time we execute the script but not after that

Background:
We are providing an Ansible utility for the admins to add or remove comments in motd file. We want to restrict any direct edits to motd file. Since there can be previous comments we want to retain them. This means that we parse the file only once and capture existing comments. After which the admins have to use the tool to add/delete comments. Any comments directly added to the file will be discarded.
Requirement:
I have this block which needs to run only once. Not once per execution but once only for many executions. In other words, it should run the first time we execute the script but not after that.
Approach:
To accomplish this, I defined a flag variable and initialized it to 0 like this common_motd_qsc_flag: 0 in defaults/mail.yml. Once I executed a particular task I am trying to update the variable to 1 like this common_motd_qsc_flag: 1. Within the task, I am making sure that the task is executed only when the flag variable is 0 in using the when condition.
Problem:
Every time the script executes it is still running the task that shouldn't be run. I understand why this is happening. It is because during the start of the script it is reading common_motd_qsc_flag: 0 in defaults/main.yml.
Question:
Is there a way to update common_motd_qsc_flag: 1 in defaults/main.yml without using lineinfile module? Any alternative approaches are also appreciated if this an ugly way to handle this requirement.
tasks/main.yml:
- name: Parse all existing comments from /etc/motd
shell: tail --lines=+10 "{{ common_motd_qsc_motd_file }}"
register: existing_comments
when:
- motd_file.stat.exists == True
- common_motd_qsc_flag == 0 # defaults
- name: Update flag variable
set_fact:
common_motd_qsc_flag: 1
when: common_motd_qsc_flag == 0
- name: Add existing comments to the array
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_existing_entry]) }}"
loop: "{{ existing_comments.stdout_lines }}"
when:
- not t_existing_entry is search('Note:')
- not t_existing_entry is search('APPTYPE:')
- not t_existing_entry is search('Comments:')
- t_existing_entry not in common_motd_qsc_comments_array
vars:
t_existing_entry: "{{ item | trim }}"
defaults/main.yml:
common_motd_qsc_flag: 0
I was able to fix this using local facts as per your advice. Thanks much for the pointer. Here is the working code:
- name: Parse all existing comments from /etc/motd
shell: tail --lines=+10 "{{ common_motd_qsc_motd_file }}"
register: existing_comments
when:
- t_common_motd_qsc_check_qsc_file.stat.exists == True
- ansible_local['snps'] is defined
- ansible_local['snps']['cache'] is defined
- ansible_local['snps']['cache']['common_motd_qsc_flag'] is not defined
changed_when: false
- name: Add existing comments to the array
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_existing_entry]) }}"
loop: "{{ existing_comments.stdout_lines }}"
when:
- ansible_local['snps'] is defined
- ansible_local['snps']['cache'] is defined
- ansible_local['snps']['cache']['common_motd_qsc_flag'] is not defined
- not t_existing_entry is search('Note:')
- not t_existing_entry is search('APPTYPE:')
- not t_existing_entry is search('Comments:')
- t_existing_entry not in common_motd_qsc_comments_array
vars:
t_existing_entry: "{{ item | trim }}"
- name: Set common_motd_qsc_flag to facts file
ini_file:
dest: "/etc/ansible/facts.d/snps.fact"
section: 'cache' # [header]
option: 'common_motd_qsc_flag' # key
value: "1" # value
- name: Add a new comment if it does not exist
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_new_entry]) }}"
loop: "{{ common_motd_qsc_add_comment }}"
when:
- t_new_entry not in common_motd_qsc_comments_array
- t_new_entry|length > 0
vars:
t_new_entry: "{{ item | trim }}"
- name: Delete an existing comment
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | difference([t_new_entry]) }}"
loop: "{{ common_motd_qsc_delete_comment }}"
when:
- t_new_entry in common_motd_qsc_comments_array
- t_new_entry|length > 0
vars:
t_new_entry: "{{ item | trim }}"
- name: Save comments to snps.fact file
ini_file:
dest: "/etc/ansible/facts.d/snps.fact"
section: 'motd' # [header]
option: 'common_motd_qsc_comment_array' # key
value: "{{ common_motd_qsc_comments_array }}" # value

Ansible: Save registered variables to file

How could I save a registered variables to a file using Ansible?
Goal:
I would like to gather detailed information about all PCI buses and devices in the system and save the result somewhere (Ex. using lspci. Ideally, I should have results of command in my local machine for further analysis).
Save results also somewhere with the given criterion.
My playbook looks like this:
tasks:
- name: lspci Debian
command: /usr/bin/lspci
when: ansible_os_family == "Debian"
register: lspcideb
- name: lspci RedHat
command: /usr/sbin/lspci
when: ansible_os_family == "RedHat"
register: lspciredhat
- name: copy content
local_action: copy content="{{ item }}" dest="/path/to/destination/file-{{ item }}-{{ ansible_date_time.date }}-{{ ansible_hostname }}.log"
with_items:
- lspcideb
- aptlist
- lspciredhat
But saves only item_name
Good Q&A with saving 1 variable there - Ansible - Save registered variable to file.
- local_action: copy content={{ foo_result }} dest=/path/to/destination/file
My question:
How can I save multiple variables and transfer stdout to my local machine?
- name: copy content
local_action: copy content="{{ vars[item] }}" dest="/path/to/destination/file-{{ item }}-{{ ansible_date_time.date }}-{{ ansible_hostname }}.log"
with_items:
- lspcideb
- aptlist
- lspciredhat
Explanation:
You must embed variable names in Jinja2 expressions to refer to their values, otherwise you are passing strings. So:
with_items:
- "{{ lspcideb }}"
- "{{ aptlist }}"
- "{{ lspciredhat }}"
It's a universal rule in Ansible. For the same reason you used {{ item }} not item, and {{ foo_result }} not foo_result.
But you use {{ item }} also for the file name and this will likely cause a mess.
So you can refer to the variable value with: {{ vars[item] }}.
Another method would be to define a dictionary:
- name: copy content
local_action: copy content="{{ item.value }}" dest="/path/to/destination/file-{{ item.variable }}-{{ ansible_date_time.date }}-{{ ansible_hostname }}.log"
with_items:
- variable: lspcideb
value: "{{ lspcideb }}"
- variable: aptlist
value: "{{ aptlist }}"
- variable: lspciredhat
value: "{{ lspciredhat }}"

Resources