Insert a loop variable into json_query in a when condition (ansible) - ansible

I have a json query that returns this result when run using set_fact and displaying using debug:
'''
Json_query: hits | community.general.json_query('results[?item==`xyz`].count')
debug output: TASK [debug] **********************************************************************************************************************************************
ok: [control] => {
"new_list8": "0"
}
Variable used in when conditional
matched: ["0"]
'''
In the JSON_query, "xyz" needs to be substituted by a loop variable in when conditional:
```
**- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ server|upper }}"
_:
- agent:
_:
- name: "{{ server }}"
with_items:
- "{{ servers }}"
when: matched[0] in (hits | community.general.json_query('results[?item==`\" + server + \"`].count'))
loop_control:
loop_var: server**
```
Loop variable has been mentioned as seen in
<https://stackoverflow.com/questions/46038985/ansible-pass-a-variable-in-a-json-query-
filter>
When condition always evaluates to false.Task is to add a server to file if it doesn't already exist in it. Mentioned JSON_QUERY is to extract the count value from another task that checks number of occurrences. Query in JPTERM works fine too.`

fo your problem i rewrite your task like this:
```
**- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ item|upper }}"
_:
- agent:
_:
- name: "{{ item }}"
when: matched[0] in (hits | community.general.json_query('results[?item==`\" ~ item ~ \"`].count'))
with_items:
- "{{ servers }}"

you cannot use an ansible variable inside the json_query.
specify the jmespath statement in a variable,
then use that in the json_query
- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ server|upper }}"
_:
- agent:
_:
- name: "{{ server }}"
loop: "{{ servers }}"
when: matched[0] in (hits | community.general.json_query(my_query)
loop_control:
loop_var: server
vars:
my_query: 'results[?item==`{{ server }}`].count'

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 when condition registered from csv

I'm using csv file as ingest data for my playbooks, but im having trouble with my when condition. it's either both task will skipped or both task will be ok, my objective is if ansible see the string in when condition it will skipped for the specific instance.
here is my playbook
- name: "Read ingest file from CSV return a list"
community.general.read_csv:
path: sample.csv
register: ingest
- name: debug ingest
debug:
msg: "{{ item.AWS_ACCOUNT }}"
with_items:
- "{{ ingest.list }}"
register: account
- name: debug account
debug:
msg: "{{ account.results | map(attribute='msg') }}"
register: accountlist
- name:
become: yes
become_user: awx
delegate_to: localhost
environment: "{{ proxy_env }}"
block:
- name: "Assume role"
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: "pm"
with_items:
- "{{ ingest.list }}"
register: assumed_role
when: "'aws-account-rnd' not in account.results | map(attribute='msg')"
here is the content of sample.csv
HOSTNAME
ENVIRONMENT
AWS_ACCOUNT
ROLE_ARN
test1
dev
aws-account-rnd
arn:aws:iam::XXXX1
test2
uat
aws-account-uat
arn:aws:iam::XXXX2
my objective is to skipped all items in the csv file with aws-acount-rnd
Your condition does not mention item so it will have the same result for all loop items.
Nothing you've shown requires the weird abuse of debug + register that you're doing, and it is in fact getting in your way.
- name: Read CSV file
community.general.read_csv:
path: sample.csv
register: ingest
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
loop: "{{ ingest.list }}"
when: item.AWS_ACCOUNT != 'aws-account-rnd'
register: assumed_role
If you'll always only care about one match you can also do this without a loop or condition at all:
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ ingest.list | rejectattr('AWS_ACCOUNT', '==', 'aws-account-rnd') | map(attribute='ROLE_ARN') | first }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
register: assumed_role
my objective is to skipped all items in the csv file with aws-acount-rnd
The multiple debug you have with register, seems to be a long-winded approach IMHO.
A simple task to debug the Role ARN, only if the account does not match aws-acount-rnd.
- name: show ROLE_ARN when account not equals aws-account-rnd
debug:
var: item['ROLE_ARN']
loop: "{{ ingest.list }}"
when: item['AWS_ACCOUNT'] != 'aws-account-rnd'
This results in:
TASK [show ROLE_ARN when account not equals aws-account-rnd] **********************************************************************************************************************
skipping: [localhost] => (item={'HOSTNAME': 'test1', 'ENVIRONMENT': 'dev', 'AWS_ACCOUNT': 'aws-account-rnd', 'ROLE_ARN': 'arn:aws:iam:XXXX1'})
ok: [localhost] => (item={'HOSTNAME': 'test2', 'ENVIRONMENT': 'uat', 'AWS_ACCOUNT': 'aws-account-uat', 'ROLE_ARN': 'arn:aws:iam:XXXX2'}) => {
"ansible_loop_var": "item",
"item": {
"AWS_ACCOUNT": "aws-account-uat",
"ENVIRONMENT": "uat",
"HOSTNAME": "test2",
"ROLE_ARN": "arn:aws:iam:XXXX2"
},
"item['ROLE_ARN']": "arn:aws:iam:XXXX2"
}
The same logic can be used to pass the item.ROLE_ARN to community.aws.sts_assume_role task.

How to register and capture only the output that meets a failed condition criteria in ansible playbook?

I am trying to set up a playbook as follows where I need to send an email notification when my task fails. The alert with email notification as such works as expected, however I am unable to capture the output just for the failed conditions and currently the way I am registering gives me all results including the variables (file paths in this example) where the condition does not fail.
---
- hosts: test-box
tasks:
- name: alert on failure
block:
- name: generate a list of desired file paths
find:
paths: /base/sdir
recurse: yes
file_type: file
patterns: "abrn.*.dat"
use_regex: yes
register: file_paths
- name: check if file stopped updating
vars:
msg: |
"{{ item }}"
"{{ ansible_date_time.epoch }}"
"{{ item.stat.mtime|int }}"
"{{ ( (ansible_date_time.epoch|int - item.stat.mtime|int) / 60 ) | int }} min"
with_items: "{{ ts.results }}"
fail:
msg: |
"{{ msg.split('\n') }}"
register: failed_items ### -> HOW TO REGISTER ONLY THE FILE PATHS (RESULTS) WHERE THIS FAIL CONDITION IS MET??
when: ( (ansible_date_time.epoch|int - item.stat.mtime|int) / 60 ) | int > 2
rescue:
- name: email notification
mail:
host: localhost
port: 25
from: A
to: B
subject: TASK FAILED
body: |
Failed subdirs: {{ failed_items }} ## This gives me all results including those where the failed condition is not met
delegate_to: localhost
...
In the body of the email, I want to capture only the file paths where the mtime condition is met but currently I get all file paths.
Any suggestions on how I can filter to capture the output only for matching condition?
Thanks.
You should use set_fact, this way you will assign a list variable only if your when condition is true.
---
- hosts: localhost
become: false
vars:
path: "{{ '%s/bin' | format(lookup('env', 'HOME')) }}"
time: "{{ 2 * 60 }}" # 2 mins
tasks:
- name: Find files in a defined path
find:
paths: "{{ path }}"
register: _result
- name: Add files not modified for longer time than defined to list
set_fact:
stale_files: "{{ stale_files | default([]) + [item.path] }}"
loop: "{{ _result.files }}"
when: ((ansible_date_time.epoch | float) - item.mtime) > (time | float)
- name: Show stale files
debug:
msg: "{{ stale_files }}"
Well you could use also another approach, ie. make loop with filtering (for me selectattr filter did not work with lt test, thus json_query filter should work, see Can't compare attribute to a number. Error: "not supported between instances of 'AnsibleUnsafeText' and 'int'").

ansible - Incorrect type. Expected "object"

site.yaml
---
- name: someapp deployment playbook
hosts: localhost
connection: local
gather_facts: no
vars_files:
- secrets.yml
environment:
AWS_DEFAULT_REGION: "{{ lookup('env', 'AWS_DEFAULT_VERSION') | default('ca-central-1', true) }}"
tasks:
- include: tasks/create_stack.yml
- include: tasks/deploy_app.yml
create_stack.yml
---
- name: task to create/update stack
cloudformation:
stack_name: someapp
state: present
template: templates/stack.yml
template_format: yaml
template_parameters:
VpcId: "{{ vpc_id }}"
SubnetId: "{{ subnet_id }}"
KeyPair: "{{ ec2_keypair }}"
InstanceCount: "{{ instance_count | default(1) }}"
DbSubnets: "{{ db_subnets | join(',') }}"
DbAvailabilityZone: "{{ db_availability_zone }}"
DbUsername: "{{ db_username }}"
DbPassword: "{{ db_password }}"
tags:
Environment: test
register: cf_stack
- name: task to output stack output
debug: msg={{ cf_stack }}
when: debug is defined
Error at line debug: msg={{ cf_stack }} saying:
This module prints statements during execution and can be useful for debugging variables or expressions without necessarily halting the playbook. Useful for debugging together with the 'when:' directive.
This module is also supported for Windows targets.
Incorrect type. Expected "object".
Ansible documentation allows the above syntax, as shown here
$ ansible --version
ansible 2.5.1
....
How to resolve this error?
You still need to remember quotes for lines starting with a {, even when using short-hand notation:
- debug: msg="{{ cf_stack }}"
This would be more obvious using full YAML notation:
- debug:
msg: "{{ cf_stack }}"
Also, given this is a variable, you could just do:
- debug:
var: cf_stack

How to create a 'null' default in Ansible

I want 'lucy' to follow the user module creators' default behaviour which is to create and use a group matching the user name 'lucy'. However for 'frank' I want the primary group to be an existing one; gid 1003. So my hash looks like this:
lucy:
comment: dog
frank:
comment: cat
group: 1003
And my task looks like this:
- name: Set up local unix user accounts
user:
name: "{{ item.key }}"
comment: "{{ item.value.comment }}"
group: "{{ item.value.group | default(undef) }}"
loop: "{{ users|dict2items }}"
This doesn't work, as undef is not recognised. Nor is anything else I can think of. 'null', 'None' etc. all fail. '' creates an empty string which is not right either. I can't find out how to do it.
Any ideas?
default(omit) is what you are looking for. For example,
- name: Set up local Unix user accounts
user:
name: "{{ item.key }}"
comment: "{{ item.value.comment }}"
group: "{{ item.value.group | default(omit) }}"
loop: "{{ users|dict2items }}"
Comments
Comment by Lucas Basquerotto: "... omit only works correctly when used directly in a module, it won't work in a set_fact ..."
A: You're wrong. For example, default(omit) works both in set_fact and in the module. The first item in the list defaults to false with the result "VARIABLE IS NOT DEFINED!". The second item defaults to omit. Omitted parameter get_checksum defaults to true with the checksum in the results
shell> cat pb.yml
- hosts: localhost
tasks:
- set_fact:
test:
- "{{ gchk|default(false) }}"
- "{{ gchk|default(omit) }}"
- stat:
path: /etc/passwd
get_checksum: "{{ item }}"
loop: "{{ test }}"
register: result
- debug:
var: item.stat.checksum
loop: "{{ result.results }}"
gives
shell> ansible-playbook pb.yml | grep item.stat.checksum
item.stat.checksum: VARIABLE IS NOT DEFINED!
item.stat.checksum: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
In addition to this, default(omit) works as expected also in some expressions. For example
- debug:
msg: "{{ {'a': item}|combine({'b': true}) }}"
loop: "{{ test }}"
gives
msg:
a: false
b: true
msg:
b: true
See the results without default values
shell> ansible-playbook pb.yml -e "gchk={{ true|bool }}"

Resources