Ansible random UUID generation - ansible

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

Related

How to force a variable in Ansible to be only 3 CHARS or only numbers?

I have the following task in Ansible:
- name: Run multiple queries
community.general.hana_query:
sid: "{{ sid | default('HDB') | upper }}"
instance: "{{ instance | default('01') }}"
Variable sid must be 3 CHARS.
Variable instance can be any number between 01 and 99.
How can i force it in script?
Do you want the task to run only when the variable is 3 chars or only numbers and skip otherwise?
If that's the case then you can simply do the checks within a when conditional and use the length and int filters.
- name: Run multiple queries
community.general.hana_query:
sid: "{{ sid | upper }}"
instance: "{{ instance }}"
when:
- sid | length == 3
- instance | int
Do you want the playbook to fail when the variable is neither 3 chars or only numbers?
If that's the case, then you can do a assert before the task.
- name: Check if variables are legit
assert:
that:
- sid | length == 3
- instance | int
Or do you want to trim the variable so that it fits your requirements?
You can choose the first 3 characters of sid with [:3].
And do a regex_replace to remove any characters that are not numbers in instance, and then choose the first 2 with [:2].
- name: Run multiple queries
community.general.hana_query:
sid: "{{ (sid | upper)[:3] }}"
instance: "{{ (instance | regex_replace('[^0-9]+', ''))[:2] }}"

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

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

ansible how to get last 2 digit of number

I have question about how to convert a digit to string and then get last 2 number using filter
For string, it's easy to use like:
vars:
string_1: 'abcd'
number_1: 1234
For string, it's easy:
"{{ string_1[-2:] }}"
But for number then i have to convert to string first but it failed while templating.
"{{ number_1 | string | [-2:] }}
How can I achieve this in single line code?
Close the conversion in parenthesis. The index has higher precedence compared to the filter. (And index can't be used as a filter, of course).
msg: "{{ (number_1|string)[-2:] }}"
The only difference is that modulus % returns an integer. The tasks
- debug:
msg: "{{ (number_1 % 100)|type_debug }}"
- debug:
msg: "{{ (number_1|string)[-2:]|type_debug }}"
give
"msg": "int"
"msg": "str"
You may use a modulus trick here:
{{ number_1 % 100 }}
The modulus dividing by 100 should yield the final two digits of any number input.

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

Resources