Ansible - Json_Path does not seem to work - ansible

I have the following set_fact task:
- set_fact:
task_uuid: "{{ task_status.json |lower |to_json | from_json |json_query('taskuuid') }}"
This is what I have for the task_status.json:
debug:
var: task_status.json
{
"task_status.json": {
"taskUuid": "e66cea71-ef33-4610-9194-0403e4bb2153"
}
}
Output:
task_uuid var is empty.
I tried any and all combination (removed the to_json,from_json,etc).
Please advice. I am basically looking to pull the value of taskUUID.
I am re-using the set_fact task for a few api endpoints - some of which give 'taskUuid' and some give 'taskuuid' and some even 'task_uuid' - i m finding a way to get the UUID from these endpoints using a common filter

This is how I would do to make sure I catch either case, whether the identifier is camel case or not:
- set_fact:
task_uuid : "{{ task_status.json.taskUuid | default(task_status.json.taskuuid | default('')) }}"
The nested default is just there to make sure the task does not fail in case neither identifiers are present. Adapt to your own need.

Related

Ansible conditionals with nested loops

I've read the ansible docs on conditionals and loops. But it's still not clear to me how it exactly works.
my yaml structure looks like this:
---
myusers:
- username: user1
homedir: 'home1'
sshkey: 'ssh-rsa bla1'
- username: user2
homedir: 'home2'
sshkey: 'ssh-rsa bla2'
process:
- transfer:
transtype: 'curl'
traname: 'ftps://targetsystem'
my playbook part looks like this:
- name: test j2
debug:
msg: |-
dest: "/var/tmp/{{ item.0.username }}/{{ item.1.traname }} {{ item.1.transtype }}"
when: item.0.process is not none
loop: "{{ myusers | subelements('process')}}"
Now I only want to loop when the sub-element process exists. I had this working at one point but don't understand what I changed to break it.
Mainly I don't understand what the effect of the sequence of 'when' and 'loop' has. It appears to me when I run it that the condition 'when' is ignored. Also when I swap the sequence of when and loop.
The error I get when running the playbook is :
FAILED! => {"msg": "could not find 'process' key in iterated item {u'username': u'user1' ...
I've also tried with different conditions like:
item.0.process is defined
myusers.username.process is not none
etc...
By default, the subelements filter (and the corresponding lookup) requires each top level element to have the subelement key (and will error with the above message if it does not exist)
You can change this behavior by setting the skip_missing parameter (note: I also fixed the index to address the traname key which was the wrong one in your question example)
- name: test j2
debug:
msg: |-
dest: "/var/tmp/{{ item.0.username }}/{{ item.1.traname }} {{ item.1.transtype }}"
loop: "{{ myusers | subelements('process', skip_missing=true) }}"

Ansible regex_findall is not giving expected results with negative search

My ansible negative regex is returning everything
So I'm getting results of dns names from JSON, of which I'm trying to sort to create an automated ansible inventory.
I have servers with naming schemes such as:
abc02vsomeserver01.subdomain1.domain.gov
abc02someserver01.subdomain1.domain.gov
xyz03votherserver11.subdomain2.domain.gov
wyz03otherserver11.subdomain2.domain.gov
qrsmainserver02.maindomain.domain.gov
I'm getting the "v" servers divided out by environment, then I want to create a catchall group that is not the "v" servers
So! I'm attempting to do:
{{ jsonOutput | json_query('json.response.results[].dnsName') | regex_findall('(?![a-z]{3}[0-9]{2}v)^.*', multiline=true) }}
Which does seem to work when I plug it into https://pythex.org.
But ansible is returning everything instead...
What the heck am I doing wrong here?
It's because that json_query emits a list[str] which when fed directly into regex_findall doesn't become a newline delimited string, it becomes the same as str(["alpha", "beta"]) (e.g. ['alpha', 'beta']) and then the multiline regex fails to do what you are expecting
There are, as with many things in life, a few ways to fix that. One is to just feed the results into |join("\n") and then you're likely back where you thought you were to begin with:
- debug:
msg: "{{ jsonOutput | json_query('json.response.results[].dnsName') | join('\n') | regex_findall('(?![a-z]{3}[0-9]{2}v)^.*', multiline=true) }}"
The other is to acknowledge that it's a list[str] and use the | select("match", ...) filter to only allow though items that match:
- debug:
msg: >-
{{ response | json_query('results[].dnsName')
| select('match', '(?![a-z]{3}[0-9]{2}v)^.*')
| list }}
vars:
response:
results:
- dnsName: abc02vsomeserver01.subdomain1.domain.gov
- dnsName: abc02someserver01.subdomain1.domain.gov
- dnsName: xyz03votherserver11.subdomain2.domain.gov
- dnsName: wyz03otherserver11.subdomain2.domain.gov
- dnsName: qrsmainserver02.maindomain.domain.gov
similarly produces:
"msg": [
"abc02someserver01.subdomain1.domain.gov",
"wyz03otherserver11.subdomain2.domain.gov",
"qrsmainserver02.maindomain.domain.gov"
]
I would guess it's personal preference which style works best in your playbook

Get the newest dictionary from a list in ansible

I have a list of dictionaries and I want to get the latest one.
reviewing jinja2 docs it seems I should be able to do this:
- set_fact:
t:
- a: 1
b: 2
- a: 3
b: 1
- debug:
msg: "{{ t | max(attribute='a') }}"
But that fails with
fatal: [localhost]: FAILED! => {
"msg": "Unexpected templating type error occurred on ({{ t | max(attribute='a') }}): max() > got an unexpected keyword argument 'attribute'"
}
what is the best whay to do this? Of course my use case is harder than that small demo.
My think looks something like this:
check_mode: no
set_fact:
tgs_info: "{{ tgs_info | default({}) | combine({ item: all_tg_info | to_json | from_json | json_query(query) | max(attribute='target_group_name') }) }}"
vars:
query: "target_groups[?contains(target_group_name, `{{ product }}`) == `true`] | [?ends_with(target_group_name, `{{ tg_suffix }}{{ item }}`) == `true`]"
loop: "{{ projects | selectattr('protocol', 'match', '^HTTP$') | map(attribute='port') | list }}"
The idea is that all_tg_info contains all the autoscaling of my aws account. I filter them and I want to get the latest one based on the name or any other parameter.
I'm kind of stuk here.
Update: As reported in #Hellseher comment below, from ansible 2.11 release, max|min(attribute='someattr') will be available. The answer below will therefore become obsolete for this version and onward. See the corresponding pull request.
reviewing jinja2 docs it seems I should be able to do this...
Even though the jinja2 builtin filters documentation mentions possible params for the max filter, there is an open bug report on ansible side to support those.
In this case you can easily acheive your requirement with the json_query filter. Here is a demo playbook with your simplistic data (as I don't have your more elaborate one...). You can probably adapt this to your actual json_query.
---
- hosts: localhost
gather_facts: false
vars:
t:
- a: 1
b: 2
- a: 3
b: 1
tasks:
- debug:
msg: "{{ t | json_query('max_by(#, &a)') }}"

What does conditional "when: var | d()" mean in Ansible 2.5

I am unable to source from Ansible documents a clear meaning of a conditional such as when: var | d(). Is someone able give a clear explanation?
E.g. Below works whether inputing extra-var value from cli or defaulting to local ENV variable value:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(False,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var | d()
But this fails:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(false,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var
What is when: my_var | d() exactly doing? How how does it interplay with the | default(false,true) part in the variable declaration?
d is an alias to the default filter. It is a Jinja2 filter, so head for the Jinja2 docs. They work the same:
default(value, default_value=u'', boolean=False)
[ ]
Aliases: d
Regarding the problem you are facing, it is because Ansible processes a condition consisting of only a variable name differently from a more complex expression (which is passed directly to Jinja2/Python) (the actual code starts here):
If the my_var variable has a value of user01, the conditional will try to find a value of user01 variable and fail because it doesn't exist.
If you just add a logical conjunction (which in common sense is redundant), Ansible will process the whole expression differently and it will work:
when: my_var and true
In your case using another default filter in the expression is also redundant, but it prevents Ansible from trying to resolve a "nested" variable value.

convert dictionary keys in playbook

I have an existing playbook variable dictionary defined like:
vars:
resource_tags: {
Name: "some name"
Service: "some service"
}
This is used in various calls to tasks in this form. But in another task, I need it in a different format, and rather than have it hard-coded, I was wondering if it could be built in a task.
I need it to look like:
{
"tag:Name": "some name"
"tag:Service": "some service"
}
I tried iterating using with_dict and setting a fact with combine:
- set_fact:
ec2_remote_facts_filter: "{{ ec2_remote_facts_filter | default({}) | combine( { 'tag:'item.name: item.val } ) }}"
with_dict: "{{ ec2_count_resource_tags }}"
And obviously that doesn't work.
Is this even possible?
If you don't mind a bit of hackery:
- debug: msg="{{ resource_tags | to_json(indent=0) | regex_replace('\n\"','\n\"tag:') }}"
This will convert your dict into JSON-formatted string with indent=0, meaning each key will start from new line; then insert tag: after first double quote on every line.
Because the result is valid JSON, Ansible template engine will convert it back into dict as the last step of variable substitution, giving you:
ok: [localhost] => {
"msg": {
"tag:Name": "some name",
"tag:Service": "some service"
}
}
I suppose there may be some corner cases if there are newlines inside your values, but in general it should be fine.
Maybe you need a custom lookup plugin in your case.
1) Edit file ansible.cfg and uncomment key 'lookup_plugins' with value './plugins/lookup'
2) Create a plugin file named 'ec2remote.py' in './plugins/lookup'
3) Use it in your playbook:
- debug:
msg: "{{ item }}"
with_ec2remote: "{{ ec2_count_resource_tags }}"
4) Implements your ec2remote.py (many examples here)
class LookupModule(LookupBase):
def run(self, terms, **kwargs):
result = {}
for k,v in terms.items():
result["tag:"+k] = v
return result
Usually, I prefer to develop plugins that are easily usable and testable and thus preserve an understandable playbook.

Resources