Ansible regex_findall is not giving expected results with negative search - ansible

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

Related

Using Jinja filter with two variables in Ansible

I am trying to get a random host in a group that contains a list of IP's. We are running into some issues as our group name uses a variable, but the line is already in {{'s due to the Jinja random call.
- hosts: "{{ {{ env }}_as_group | random }}"
ERROR! template error while templating string: expected token 'end of print statement', got '{'. String: {{ {{ env }}_as_group | random }}
The env variable is passed into the playbook on execution with ansible-playbook "-e env=stage" myplaybook.yml
Looking around online, most people solve this with vars[env], however that doesnt seem to quite work for us -
- hosts: "{{ vars[env]_as_group | random }}"
ERROR! template error while templating string: expected token 'end of print statement', got '_as_group'. String: {{ tag_aws_autoscaling_groupName_[ env ]_as_group | random }}
If I make it - hosts: "{{ stage_as_group | random }}", it works as expected.
How can I set the host to pull a random value in the group {{ env }}_as_group?
In Ansible, moustache should not be stacked.
So you should use the proper Jinja concatenation operator: ~.
Ending with:
- hosts: "{{ vars[env ~ '_as_group'] | random }}"
This said it is not totally clear to me what stage_as_group really is: is it a list or a hosts group?
If this is a group in you inventory, this won't work because random works on a sequence, and a host group is a string, not a sequence.
For that use case, you probably want this instead:
- hosts: "{{ groups[env ~ '_as_group'] | random }}"

Return IP addresses from a list in quotes - Ansible

Hoping someone can assist. I'm trying to extract IP addresses from a list so that they may be used in a URI call in Ansible, so the basic code I have is
Vars:
servers:
- name: Backups
ipaddr:
- "192.168.10.10"
- "192.168.20.10"
- "192.168.30.10"
Then I run this code
- name: list vars
debug:
msg: "{{ item.ipaddr | flatten | join (',')}}"
loop: "{{servers}}"
But the output is
"msg": "192.168.40.10,192.168.50.10,192.168.60.10"
Whereas I need this returned with the quotes
"msg": "192.168.40.10","192.168.50.10","192.168.60.10"
Sure I am missing something obvious but any help would be gratefully received
Thanks to Jinja_dude but managed to fix this with
{{ item.ipaddr | to_json | join() }}
Which produced the result
"msg": [
"192.168.40.10",
"192.168.50.10",
"192.168.60.10"
]
And this was accepted in my URI call
Hello steve I think a way of achieving something similar is using this :
"{{ item.ipaddr | map('quote') | flatten | join(', ') }}"
Otherwise there is also this already implemented filter in ansible that will help you have each ip between quotes :
"{{ item.ipaddr | map("regex_replace","(.+)","\'\\1\'") | flatten | join(',')}}"

Ansible - Json_Path does not seem to work

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.

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)') }}"

Ansible random UUID generation

In my Ansible script, I want to generate UUIDs on the fly and use them later on.
Here is my approach:
- shell: echo uuidgen
with_sequence: count=5
register: uuid_list
- uri:
url: http://www.myapi.com
method: POST
body: "{{ item.item.stdout }}"
with_items: uuid_list.result
However I get the following error:
fatal: [localhost] => One or more undefined variables: 'str object' has no attribute 'stdout'
How can I solve this issue?
In ansible 1.9 there is a new filter : to_uuid , which given a string it will return an ansible domain specific UUID,you can find the usage in here https://docs.ansible.com/playbooks_filters.html#other-useful-filters
As metioned by Xingxing Gao, there is to_uuid which can be used with a large enough number and the random filter to produce a random UUID. The larger the number the greater the randomness. eg:
{{ 99999999 | random | to_uuid }}
or
{{ 9999999999999999999999 | random | to_uuid }}
Generate a random UUID from a 20 char string with upper/lower case letters and digits:
{{ lookup('password', '/dev/null chars=ascii_letters,digits') | to_uuid }}
This is very close. I only had to change a few things. I figured this out by using the task "debug: var=uuid_list" and iterating.
- shell: uuidgen # note 1
with_sequence: count=5
register: uuid_list
- uri:
url: http://www.myapi.com
method: GET
body: "{{ item.stdout }}" # note 2
timeout: 1 # note 3
with_items: uuid_list.results # note 4
Notes:
echo caused the string uuidgen to be printed. removed echo, kept uuidgen.
item.item.stdout needed to be item.stdout
I used a short timeout so I could test this without having a rest endpoint available. This gives failure error messages but it's clear that it is correct.
uuid_list.stdout needed to be uuid_list.results
Be aware if you use Willem's solution, Jinja and Ansible will cache the result for multiple executions of the same filter, so you must change the source number each time
api_key_1: "{{ 999999999999999999995 | random | to_uuid }}"
api_key_2: "{{ 999999999999999999994 | random | to_uuid }}"
and for situations where you need a normal md5 instead of to_uuid, hash('md5') does not take an integer. The most convenient way to convert the random to a string I've found is to use to_uuid:
api_key_3: "{{ 999999999999999999999 | random | to_uuid | hash('md5') }}"
api_key_4: "{{ 999999999999999999998 | random | to_uuid | hash('md5') }}"
One solution which should be immune to caching/stale fact gathering and give you a reasonably random UUID each time you use it is:
{{ (999999999999999999999 | random | string + (lookup('pipe', 'date +%s%N'))) | to_uuid() }}
It concatenates a random number between 0 and 999999999999999999999 with current nanoseconds since Unix epoch and feeds that through Ansible's to_uuid() filter (available since version 1.9). Fact caching should not cause a problem as lookups are always evaluated each and every time they are invoked.
If you want a UUID that remains constant throughout the plays in a playbook (but doesn't persist between multiple invocations of the playbook - even with fact caching turned on) then use:
set_fact: uuid={{ (999999999999999999999 | random | string + (lookup('pipe', 'date +%s%N'))) | to_uuid() }}
I tried to avoid using the filter to_uuid, because it does not only take numbers as input but also characters. So, if I would only give it numbers as input, I would not be able to create every uuid possible.
I came up with this solution:
- name: Generate UUID
vars:
# This creates a random 128-bit integer and translates it to hexadecimal string.
# Zeros are put infront of the hex string, to make it always 32 hex-digits long.
__uuid: "{{ '%032x' % ((2**128) | random) }}"
# Format the hexadecimal string to a UUID format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
uuid: "{{ __uuid[0:8] }}-{{ __uuid[8:12] }}-{{ __uuid[12:16] }}-{{ __uuid[16:20] }}-{{ __uuid[20:32] }}"
debug:
msg: "{{ uuid }}"

Resources