Ansible - problem with ansible_facts for packages in jinja template - ansible

I try to get all installed packages with Ansible and write them in a "pretty" way to a file.
Calling the module works:
- name: Gather the rpm package facts
package_facts:
manager: auto
In a Jinja template I am using a loop, what works too:
{% for item in ansible_facts.packages %}
{{ item }}
{% endfor %}
Unfortunately the simple output creates this "mess":
"yum": [
{
"arch": "noarch",
"epoch": null,
"name": "yum",
"release": "4.el8",
"source": "rpm",
"version": "4.2.23"
}
],
"zlib": [
{
"arch": "x86_64",
"epoch": null,
"name": "zlib",
"release": "16.el8_2",
"source": "rpm",
"version": "1.2.11"
}
]
Some of these elements are unnecassry for the current job, so the first call coming in mind was this:
{% for item in ansible_facts.packages %}
{{ item.name }} {{ item.version }}
{% endfor %}
But this ended just in an error:
fatal: [somehost.example.org]: FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'unicode object' has no attribute 'name'"}
Searched through the internet, looked into the documentation of Ansible, tried various notations and nothing worked:
vars[item].name
item[0].name
item["name"]
As last option I tried it with iteritems:
{% for (key,value) in ansible_facts.packages.iteritems() %}
{{ value }}
{% endfor %}
But this ended I an error to:
fatal: [somehost.example.org]: FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'list object' has no attribute 'name'"}
It seems I am not smart enough to figure out the solution, can someone lend me a hand?
Sincerely,
a frustrated Ansible user

In the data sample you posted, package name is the key and value is a list of 1 element comprising of a dictionary.
To get the version you have to access the first element. Like so:
{% for key, value in ansible_facts.packages.iteritems() %}
{{ key }} {{ value[0].version }}
{% endfor %}
Should render the file with package list like:
...
zlib 1.2.11
...

Related

Ansible Nested variables and Jinja2 templates

I'm trying to figure out why my jinja2 template (and ansible for that matter) cannot find my variables in my inventory file.
Here is my inventory file:
all:
hosts:
test05:
ansible_host: 192.168.x.x
filebeat:
version: 7.15.2
applog:
- title: Separate Application Log Path with Tags
type: log
paths:
- /var/log/something/moresomething/current
tags: '["something", "application"]'
- title: Separate Application Log Path, with Tags, and "decode_json_fields" processor.
type: log
paths:
- /var/log/something/moresomething/blah-shell.log
tags: ["application", "something"]
fields: ["message"]
depth: 2
- title: Separate Application Log Path, with Tags, and Multiline fields
type: log
paths:
- /var/log/something/moresomething/production.log
tags: ["application", "something"]
multiline_type: pattern
multiline_patern: 'Started'
multiline_negate: true
multiline_match: after
Then attempting to get the first title. I'm doing the following:
- name: debugging
debug:
var: filebeat.applog.title
when I run this I end up getting filebeat.applog.title: VARIABLE IS NOT DEFINED! which I think is good since it doesn't know what title I want. So changing this to
- name: debugging
debug:
var: filebeat.applog.0.title
I end up getting what I want filebeat.applog.0.title: Separate Application Log Path with Tags. However, how do I use this in a jinja2 template?
I have the following for a template, I know I need to update this to loop through the different items in my inventory. That's a different problem on how to loop through this.
title: {{ filebeat.applog.title }}
- type: {{ filebeat.applog.type }}
enabled: true
paths:
- {{ filebeat.applog.path }}
tags: {{ filebeat.applog.tags }}
{% if filebeat.applog.fields is defined %}
processors:
- decode_json_fields:
fields: {{ filebeat.applog.fields }}
max_depth: {{ filebeat.applog.depth }}
target: {{ filebeat.applog.target | default "" }}
{% endif %}
{% if filebeat.applog.multiline_pattern is defined %}
multiline.type: {{ filebeat.applog.multiline_type }}
multiline.pattern: {{ filebeat.applog.multiline_pattern }}
multiline.negate: {{ filebeat.applog.multiline_negate }}
multiline.match: {{ filebeat.applog.multiline_match }}
{% endif %}
each time I get the following, even when I do use {{ filebeat.applog.0.logtitle }} in the template:
fatal: [test05]: FAILED! => changed=false
msg: |-
AnsibleError: template error while templating string: expected token 'end of print statement', got 'string'. String: title: {{ filebeat.applog.title }}
- type: {{ filebeat.applog.type }}
enabled: true
paths:
- {{ filebeat.applog.path }}
tags: {{ filebeat.applog.tags }}
{% if filebeat.applog.fields is defined %}
processors:
- decode_json_fields:
fields: {{ filebeat.applog.fields }}
max_depth: {{ filebeat.applog.depth }}
target: {{ filebeat.applog.target | default "" }}
{% endif %}
{% if filebeat.applog.multiline_pattern is defined %}
multiline.type: {{ filebeat.applog.multiline_type }}
multiline.pattern: {{ filebeat.applog.multiline_pattern }}
multiline.negate: {{ filebeat.applog.multiline_negate }}
multiline.match: {{ filebeat.applog.multiline_match }}
{% endif %}
I'm not sure what I'm missing or doing wrong. I'm thinking I'm doing something wrong since this the first time doing something like this.
So the template you have should either:
have a for loop to iterate over filebeat.applog
OR
reference n'th element of filebeat.applog
Aside from that, there are some errors like below:
1.
target: {{ filebeat.applog.target | default "" }}
This is the main one, and this is what the error message is complaining about, i.e. got 'string'. The correct usage for default filter is {{ some_variable | default("") }}.
2.
{% if filebeat.applog.multiline_pattern is defined %}
In the inventory this variable is mis-spelled, i.e. multiline_patern (missing one "t"). Fix this in your inventory.
3.
when I do use {{ filebeat.applog.0.logtitle }} in the template
This should be {{ filebeat.applog.0.title }} to work.
Considering the above fixes, a template that loops over filebeat.applog such as below should work:
{% for applog in filebeat.applog %}
title: {{ applog.title }}
- type: {{ applog.type }}
enabled: true
paths: {{ applog.paths }}
tags: {{ applog.tags }}
{% if applog.fields is defined %}
processors:
- decode_json_fields:
fields: {{ applog.fields }}
max_depth: {{ applog.depth }}
target: {{ applog.target | default("") }}
{% endif %}
{% if applog.multiline_pattern is defined %}
multiline.type: {{ applog.multiline_type }}
multiline.pattern: {{ applog.multiline_pattern }}
multiline.negate: {{ applog.multiline_negate }}
multiline.match: {{ applog.multiline_match }}
{% endif %}
{% endfor %}

Shopify Help Returning Line Item Properties

I am having an issue retrieving the line tem properties of an order. The problem is that the code I am using is not displaying anything.
I am able to get the order line items, but the properties of the line item (like if I have a form field name properties[SomeText] or properties[Color])
Here is a simplified version of what I am using:
{% for item in order.line_items %}
Sku: {{item.item.sku}}
Product Title: {{item.title}}
{% for prop in item.properties %}
Properties: {{ prop.first }} = {{ prop.last }}
{% endfor %}
{% endfor %}
In the example above, the values for Sku and Product Title are working, but I am not getting any values returned for the Properties. I know they exist because they show when I go an view an order.
So, I'm not sure what I've done incorrectly. Any help will be greatly appreciated.
After looking at the Order's raw XML, I noticed that instead of using prop.first and prop.last, I changed it to prop.name and prop.value and it works.
{% for item in order.line_items %}
Sku: {{item.item.sku}}
Product Title: {{item.title}}
{% for prop in item.properties %}
Properties: {{ prop.name }} = {{ prop.value }}
{% endfor %}
{% endfor %}

Ansible, pg_hba.conf configuration with IPs from hosts file

I have problem and i don't did i even start correctly.
i have hosts file that looks like
all:
children:
application1:
children:
application1-webserver:
hosts:
host1.domain.net:
host2.domain.net:
application1-database:
hosts:
dbhost1.domain.net:
application2:
children:
application2-webserver:
hosts:
host3.domain.net:
host4.domain.net:
application1-database:
hosts:
dbhost2.domain.net:
app-servers:
hosts:
host1.domain.net:
host2.domain.net:
host3.domain.net:
host4.domain.net:
I have created template file. I know it is not pg_hba.conf, but it is no mater now, if i get IPs out it will be easy
{% for i in groups['app-servers'] %}
{{ hostvars[i]['ansible_default_ipv4_address'] }}
{% endfor %}
So maybe for time to time i have to run this script to create "new" enviroment, and i don't want to change IPs from app-servers manually. What i want is to get IP from FQDN.
Need this so i can limit access to db from network, just to those servers.
Thanks for help.
The variable holding the global info is ansible_default_ipv4. It is only available if you have gathered facts on your host (make sure you did not use gather_facts: false on your play).
It's a hash containing several keys among which address. As an example (obfuscated), this is what I get on my localhost:
$ ansible localhost -m setup -a gather_subset=network -a filter=*default_ipv4*
localhost | SUCCESS => {
"ansible_facts": {
"ansible_default_ipv4": {
"address": "x.y.z.a",
"alias": "interface",
"broadcast": "x.y.z.255",
"gateway": "x.y.z..1",
"interface": "interface",
"macaddress": "xx:xx:xx:xx:xx:xx",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "x.y.z..0",
"type": "ether"
}
},
"changed": false
}
So the name of the var you are looking for in your template is ansible_default_ip4.address
Your full fixed template:
{% for i in groups['app-servers'] %}
{{ hostvars[i].ansible_default_ipv4.address }}
{% endfor %}
Possible alternative notations (mixing dot and array notation):
{{ hostvars[i]['ansible_default_ipv4']['address'] }}
{{ hostvars[i].ansible_default_ipv4['address'] }}
etc.
So i got it. It is little complicated.
Playbook looks like this:
---
- hosts: app-servers
tasks:
- ping:
- hosts: dbhost1.domain.net
tasks:
- include_role:
name: test-for
...
Hosts file like above:
all:
children:
application1:
children:
application1-webserver:
hosts:
host1.domain.net:
host2.domain.net:
application1-database:
hosts:
dbhost1.domain.net:
application2:
children:
application2-webserver:
hosts:
host3.domain.net:
host4.domain.net:
application1-database:
hosts:
dbhost2.domain.net:
app-servers:
hosts:
host1.domain.net:
host2.domain.net:
host3.domain.net:
host4.domain.net:
Only after i ping hosts i get IPs i need
Then in template:
{% for i in groups['app-servers'] %}
{{ hostvars[i].ansible_default_ipv4.address }}
{% endfor %}
And it works.
Thanks

concatenate variables in Jinja2

I'm struggling with the following example data from Satellite server when templating with Jinja2 in Ansible:
"results": {
"test.example.com": {
"interfaces": "eth0,lo",
"ipaddress_eth0": "10.251.0.45",
"ipaddress_lo": "127.0.0.1",
"netmask_eth0": "255.255.255.0",
"netmask_lo": "255.0.0.0",
"network_eth0": "10.251.0.0",
"network_lo": "127.0.0.0",
This piece of code will return: 127.0.0.1, but I want to replace 'ipaddress_lo' with the variable interface.
I've tried a million combinations... without any luck... please advice.
{% for items in my_host_facts.json.results %}
{% for interface in my_host_facts.json.results[items].interfaces.split(",") %}
{{ interface }}:
address: "{{ my_host_facts.json.results[items].ipaddress_lo }}"
netmask: "{{ my_subnet.json.mask }}"
network: "{{ my_subnet.json.network }}"
gateway: "{{ my_subnet.json.gateway }}"
{% endfor %}
dns: [ "{{ my_subnet.json.dns_primary }}", "{{ my_subnet.json.dns_secondary }}" ]
{% endfor %}
Kind regards,
Try {{ my_host_facts.json.results[items]['ipaddress_'+interface] }}.

Ansible template adds 'u' to array in template

I have the following vars inside of my ansible playbook I got the following structure
domains:
- { main: 'local1.com', sans: ['test.local1.com', 'test2.local.com'] }
- { main: 'local3.com' }
- { main: 'local4.com' }
And have the following inside of the my conf.j2
{% for domain in domains %}
[[acme.domains]]
{% for key, value in domain.iteritems() %}
{% if value is string %}
{{ key }} = "{{ value }}"
{% else %}
{{ key }} = {{ value }}
{% endif %}
{% endfor %}
{% endfor %}
Now when I go in the VM and see the file I get the following:
Output
[[acme.domains]]
main = "local1.com
sans = [u'test.local1.com', u'test2.local.com']
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
Notice the u inside of the sans array.
Excpeted output
[[acme.domains]]
main = "local1.com"
sans = ["test.local1.com", "test2.local.com"]
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
Why is this happening and how can I fix it?
You get u' ' because you print the object containing the Unicode strings and this is how Python renders it by default.
You can filter it with list | join filters:
{% for domain in domains %}
[[acme.domains]]
{% for key, value in domain.iteritems() %}
{% if value is string %}
{{ key }} = "{{ value }}"
{% else %}
{{ key }} = ["{{ value | list | join ('\',\'') }}"]
{% endif %}
{% endfor %}
{% endfor %}
Or you can rely on the fact, that the string output after sans = is a JSON and render it with to_json filter:
{{ key }} = {{ value | to_json }}
Either will get you:
[[acme.domains]]
main = "local1.com"
sans = ["test.local1.com", "test2.local.com"]
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
But the first one is more versatile.

Resources