Ansible set_fact type cast - ansible

I have encountered a problem and hope to get your help, thank you!
Example:
- hosts: localhost
tasks:
- name: "set variable"
set_fact:
test1:
a: 1
- name: "assignment"
set_fact:
test2:
a: "{{test1.a}}"
- name: "print test2"
debug:
var: test2
Output is :
"test2":{ "a": "1"}
What i expect is :
"test2":{ "a": 1}

Q: Output is : "test2":{ "a": "1"}
A: This is correct. An output of Jinja is a string (it's a template engine). You declared test1.a as an integer (without quotes)
- set_fact:
test1:
a: 1
- debug:
msg: "{{ test1.a|type_debug }}"
gives
msg: int
But, the evaluation of "{{ test1.a }}" resulted in a string
- set_fact:
test2:
a: "{{ test1.a }}"
- debug:
var: test2
- debug:
msg: "{{ test2.a|type_debug }}"
give
test2:
a: '1'
msg: str
There are more options on how to preserve types.
Use configuration option DEFAULT_JINJA2_NATIVE (default: False).
"This option preserves variable types during template operations."
This will influence the whole playbook. For example
shell> ANSIBLE_JINJA2_NATIVE=True ansible-playbook pb.yml
gives
test2:
a: 1
msg: int
Next option is a combination of the dictionaries. For example
- set_fact:
test2: "{{ test2|default({})|combine({'a': test1.a}) }}"
- debug:
var: test2
- debug:
msg: "{{ test2.a|type_debug }}"
give
test2:
a: 1
msg: int
Single (bare) variables behave differently and preserve the types. For example
- set_fact:
v2: 1
- debug:
msg: "{{ v2 }}"
- debug:
msg: "{{ v2|type_debug }}"
- set_fact:
v3: "{{ v2 }}"
- debug:
msg: "{{ v3 }}"
- debug:
msg: "{{ v3|type_debug }}"
give
msg: 1
msg: int
msg: 1
msg: int
(I've used yaml callback plugin "ANSIBLE_STDOUT_CALLBACK=yaml ansible-playbook pb.yml")

This is default behaviour. I am unsure why you would expect the value to display without quotes.
Perhaps you're thinking the value is converted to a string, but it is not.
- name: set variable
set_fact:
test1:
a: 1
- name: check if variable is integer
assert:
that:
- test1.a | type_debug == "int"
- test1.a is regex("^[0-9]+$")
- name: assignment
debug:
var: "{{ test1.a }}"

Related

(solved) Ansible loop (subelements) in users>name, users>authorized_key_file and users>authorized_keyS

i tried this test code :
- hosts: localhost
gather_facts: no
vars:
users:
- name: user1
authorized_keys_file: authorized_keys_file_1.log
authorized_keys:
- key1
- key2
- name: user2
authorized_keys_file: authorized_keys_file_2.log
authorized_keys:
- key1
tasks:
- name: List keys to add to authorized_key_file
debug:
msg: "User={{ item.0.name }} File={{ item.0.authorized_keys_file }} key={{ item.1 }}"
loop:
- "{{ users | subelements('authorized_keys') }}"
but i got the error :
The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'name'
expected result (to be used in ansible.posix.authorized_key) :
User=user1 File=authorized_keys_file_1 key=key1
User=user1 File=authorized_keys_file_1 key=key2
User=user2 File=authorized_keys_file_2 key=key1
Do you have an idea of what to do !?
Regards.
I found the solution ! "loop" must be on one line.
This is because i did a conversion from the old "with_subelements"
old version :
with_subelements:
- "{{ users }}"
- authorized_keys
New version (oneline...) :
loop: "{{ users | subelements('authorized_keys') }}"

Ansible merge dictionaries using with_items and vars stores only last item

Trying to create a dictionary per item and merge them
---
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: create empty dict
set_fact:
ids: {}
- name: Merge all
vars:
single_entry: "{ '{{ item }}': {{ item }} }"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
I thought I followed the right guidelines but I seem to be getting only the last item afterwards:
ok: [localhost] => {
"msg": {
"2": 2
} }
I tried replacing the single_entry with the expression in vars but it does not run.
Is there a different syntax to get this done?
EDIT: version info
ansible-playbook 2.5.1
python version = 2.7.17 [GCC 7.5.0]
Try the filters dict and zip. The zip is available since 2.3, e.g.
- set_fact:
d2: "{{ dict(two_nums|zip(two_nums)) }}"
- debug:
var: d2
- debug:
var: d2|type_debug
gives
d2:
1: 1
2: 2
d2|type_debug: dict
If this does not work try Jinja and the filter from_yaml, e.g.
- hosts: localhost
vars:
two_nums:
- 1
- 2
l1: |-
{% for i in two_nums %}
{{ i }}: {{ i }}
{% endfor %}
tasks:
- set_fact:
d1: "{{ l1|from_yaml }}"
- debug:
var: d1
- debug:
var: d1|type_debug
gives the same result
d1:
1: 1
2: 2
d1|type_debug: dict
If you need the keys to be strings quote it, e.g.
l1: |-
{% for i in two_nums %}
"{{ i }}": {{ i }}
{% endfor %}
gives
d1:
'1': 1
'2': 2
In the first case, map the list's items to string, e.g.
- set_fact:
d2: "{{ dict(two_nums|map('string')|zip(two_nums)) }}"
gives the same result
d2:
'1': 1
'2': 2
I can't reproduce the behavior you're describing. Running your
playbook verbatim, I get as output:
TASK [Print Result] **************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"1": 1,
"2": 2
}
}
I'm using Ansible core 2.11.2, but I've also tested your playbook with Ansible 2.9.20 and I get the same output.
I would probably drop the set_fact task, and also change how you're
setting single_entry:
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: Merge all
vars:
ids: {}
single_entry: "{{ {item: item} }}"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
In this version, the template expression is returning a dictionary,
and only requires a single set of Jinja template markers. I'm curious
if this version behaves any differently for you.

Ansible - check variable type

Apparently, according to several hours of searching nobody has encountered this use-case:
Its simple - I would like to execute some ansible logic depending on variable type. Basically equivalent of e.g. instanceof(dict, var_name) but in Ansible:
- name: test
debug:
msg: "{{ instanceof(var_name, dict) | ternary('is a dictionary', 'is something else') }}"
Is there any way this can be done?
Q: "Execute some ansible logic depending on the variable type."
A: The tests including mapping work as expected. For example, the tasks below
- set_fact:
myvar:
key1: value1
- debug:
msg: "{{ (myvar is mapping)|
ternary('is a dictionary', 'is something else') }}"
give
msg: is a dictionary
Q: "Ansible - check variable type"
A: An option would be to discover the data type and dynamically include_tasks. For example, the tasks below
shell> cat tasks-int
- debug:
msg: Processing integer {{ item }}
shell> cat tasks-str
- debug:
msg: Processing string {{ item }}
shell> cat tasks-list
- debug:
msg: Processing list {{ item }}
shell> cat tasks-dict
- debug:
msg: Processing dictionary {{ item }}
with this playbook
- hosts: localhost
vars:
test:
- 123
- '123'
- [a,b,c]
- {key1: value1}
tasks:
- include_tasks: "tasks-{{ item|type_debug }}"
loop: "{{ test }}"
give (abridged)
msg: Processing integer 123
msg: Processing string 123
msg: Processing list ['a', 'b', 'c']
msg: 'Processing dictionary {''key1'': ''value1''}'
If you want to simulate the switch statement create a dictionary
case:
int: tasks-int
str: tasks-str
list: tasks-list
dict: tasks-dict
default: tasks-default
and use it in the include
- include_tasks: "{{ case[item|type_debug]|d(case.default) }}"
loop: "{{ test }}"
Since Ansible version 2.3 there is type_debug:
- name: test
debug:
msg: "{{ (( var_name | type_debug) == 'dict') | ternary('is a dictionary', 'is something else') }}"
Note that the docs state a preference for 'type tests'.
Older question, but you can do this easily with a Python custom filter plugin. Granted it would give you Python specific types, but that may be fine for your use case.
This could work. It just needs to be placed in a folder named filter_plugins alongside your playbook or role.
import sys
if sys.version_info[0] < 3:
raise Exception("Must be using Python 3")
def get_type(var, **kwargs):
return type(var)
class FilterModule(object):
def filters(self):
return {
"get_type": get_type
}
Then in your playbook:
- name: test
debug:
msg: "{{ var_name | get_type }}"

Ansible: Sequence range using loop variable

I need to create a certain number of systemd unit files based on name and included variable with number of files to create, like:
app-name#1.service
app-name#2.service
app-name#3.service
script-name#1.service
script-name#2.service
script-name#3.service
script-name#4.service
It works with a nested loop using range function, but I not understand how to use loop variable (item.1.workers) into range() parameters.
- hosts: localhost
vars:
apps:
- name: app-name
workers: 3
- name: script-name
workers: 5
connection: local
tasks:
- name: test
debug:
msg: "{{ item.1.name }}#{{ item.0 }}.service"
loop: "{{ range(1, 3) | product(apps) | list }}"
Let's create the lists of workers in the first task and loop with subelements in the second task
- set_fact:
apps1: "{{ apps1|default([]) +
[{'name': item.name,
'workers': range(1, item.workers + 1)|list}] }}"
loop: "{{ apps }}"
- debug:
msg: "{{ item.0.name }}#{{ item.1 }}.service"
loop: "{{ apps1|subelements('workers') }}"
gives
msg: app-name#1.service
msg: app-name#2.service
msg: app-name#3.service
msg: script-name#1.service
msg: script-name#2.service
msg: script-name#3.service
msg: script-name#4.service
msg: script-name#5.service

VARIABLE IS NOT DEFINED when trying to register output in playbook

I'm trying to register a variable with the output to a query of a F5 pool and I'm getting this error:
"<type 'list'>": "VARIABLE IS NOT DEFINED!",
What is that I'm doing wrong?
Any help appreciated.
Thanks!
---
- name: GRAB F5 FACTS
hosts: f5
connection: local
gather_facts: no
tasks:
- name: Collect BIG-IP facts
bigip_device_facts:
gather_subset: ltm-pools
provider: "{{ prov }}"
register: bigip_device_facts
- name: FACTS OUTPUT
debug:
var: "{{ item.members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
register: jkout
with_items: "{{ bigip_device_facts.ltm_pools }}"
when: item.full_path == "/Common/mypool"
- name: Set a variable
debug:
msg: "jkvar={{ jkout }}"
You are using the debug: module with the option var: and this expects a variable, not a jinja2 template.
So either change it to:
debug:
var: item.members
or
debug:
msg: "{{ item.members }}"
Like said by #dgw, the problem is with the var option of debug module.
https://docs.ansible.com/ansible/latest/modules/debug_module.html#parameters
This playbooks works:
- name: test rejectattr
hosts: localhost
gather_facts: no
vars:
members:
- { name: "one", state: "present" }
- { name: "two", state: "absent" }
- { name: "three", state: "present" }
tasks:
- name: FACTS OUTPUT
debug:
msg: "{{ members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
Thanks for your responses. I'll investigate it further.
Apart from that, I think I've been able to solve it another way.
- name: FACTS OUTPUT
set_fact:
listado: "{{ item.members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
with_items: "{{ bigip_device_facts.ltm_pools }}"
when: item.full_path == "/Common/mypool"
- debug: msg={{ listado }}
register: jkout
- name: Set a variable
debug:
msg: "jkvar={{ jkout }}"
Is that a right way to do it?
Thanks!!

Resources