I have an ansible inventory with groups as follows:
+hosts/
+all/
+group_vars/
- linux.yml
- webserver.yml
- dbserver.yml
...
And I have a playbook that sets monitoring for hosts; and the kind of monitoring is done by plugins. So in each group y set a list monitoring_plugins that contain the plugin to be able t monitor each service.
Inside each group yml I try to "append" to the list:
monitoring_plugins: {{ monitoring_plugins|default([]) + [ 'whatever_plugin_correspond_to_group' ] }}
But it doesn't work as expected, being expected that if a host belongs to more than one group, it should have the plugins corresponding to those groups.
Is there a way to acommplish this?
Thanks in advance.
What you're describing should work as expected from within a task, but you cannot have executable code in a vars or group_vars yaml or json file -- those are static
So you will need to set a distinct name at the low level and then roll them up at the top level:
group_vars/
dbserver.yml # sets monitoring_plugins_dbserver: ["a", "b"]
linux.yml # sets monitoring_plugins_linux: ["c", "d"]
and then in your tasks, something like this (but be forewarned I haven't tested this specific example):
- set_fact:
monitoring_plugins: >-
{% set results = [] %}
{% for g in groups.keys() %}
{% set _ = results.extend(vars['monitoring_plugins_'+g]|d([])) %}
{% endif %}
{{ results }}
Related
I have a yaml file like this:
models:
- name: test_view
description: "test"
config:
meta:
database_tags:
ACCOUNT_OBJECTS.TAGS.ENV: DEV`
I am trying automatically change 'DEV' to PROD when it's in that environment. I have a macro that gets the variable from targets.name
This is the jinja code:
{% macro test_macro(target) %}
{%- if target.name == "dev" -%} DEV
{%- elif target.name == "prod" -%} PROD
{%- else -%} invalid
{%- endif -%}
{% endmacro %}`
However, when I try to use the macro I get 'test_macro is undefined'
eg. ACCOUNT_OBJECTS.TAGS.ENV: {{ test_macro(target)}}
Is it that custom macros still cannot be used in yaml files?
Macros are for templating the SQL queries DBT compiles from its models. The YAML files are for configuration; they are not themselves models and do not support macros.
I went looking, and there is an active discussion about whether this could be supported in future, but as of the end of 2022 it is not possible.
Have you considered using a config block inside your model in order to set these metadata?
I'm pretty new to Ansible, coming from Puppet I really like it.
I'm trying to get a string compiled from server admin_port.
Vars file:
webservers:
ws1:
listen_address: "webserver1.mydomain"
admin_port: "7779"
http_port: "7777"
ssl_port: "4443"
ws2:
listen_address: "webserver2.mydomain"
admin_port: "7779"
http_port: "7777"
ssl_port: "4443"
I'm templating quite a customised set of config files so I'm trying to get the two listen ports into this format:
ports=7779,7779
I've tried:
vars:
webserver_admin_ports: "{{lookup('subelements', webservers, 'admin_port', {'skip_missing': True})}}"
But I run into the issue:
Error was a <class 'ansible.errors.AnsibleError'>, original message: the key admin_port should point to a list, got '7779'"}
I'm sure this isn't too tricky and the data structure is simple enough, there could be 1 or 10 servers.
In Puppet I'd do this in the template, but it seems with ansible its better to pass a var.
Thanks,
For anyone with a similar question, this is my solution.
In the template, do the lookup, it keeps the code cleaner:
{%- set serverNames = [] -%}
{%- set adminPorts = [] -%}
{%- set listenAddresses = [] -%}
{%- set httpPorts = [] -%}
{%- for server in webserver_meta -%}
{{- serverNames.append(server) -}}
{{- adminPorts.append(webserver_meta[server].admin_port) -}}
{{- listenAddresses.append(webserver_meta[server].listen_address) -}}
{{- httpPorts.append(webserver_meta[server].http_port) -}}
{%- endfor %}
serverNames={{ serverNames|join(',') }}
serverAdminPorts={{ adminPorts|join(',') }}
serverListenAddress={{ listenAddresses|join(',') }}
serverHttpPorts={{ httpPorts|join(',') }}
I need help with converting a Unicode variable to a string in order for the below Ansible construct to work.
In this particular case, I want to use the item.keys() method in order to get the current env name (i.e. uat) but I get [u'uat'] instead. I have been searching the Internet but could not find a way to convert [u'uat'] to a simple uat.
defaults/main.yml:
blablabla:
env:
- uat:
accounts:
- david:
email: david#example.com
- anna:
email: anna#example.com
- develop:
accounts:
- john:
email: john#example.com
tasks/main.yml:
- include_tasks: dosomething.yml
with_items:
- "{{ blablabla.env }}"
tasks/dosomething.yml:
- name: Get accounts
set_fact:
accounts: "{%- set tmp = [] -%}
{%- for account in item[item.keys()].accounts -%}
{{ tmp.append(account) }}
{%- endfor -%}
{{ tmp }}"
error message:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<failed value="True"/>
<msg value="The task includes an option with an undefined variable. The error was: dict object has no element [u'uat']
The error appears to have been in 'dosomething.yml': line 9, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Get accounts
^ here
exception type: <class 'ansible.errors.AnsibleUndefinedVariable'>
exception: dict object has no element [u'uat']"/>
</root>
Alternatively, I would also welcome alternative approaches, as long the data structure (i.e. the defaults/main.yml file) remains unchanged.
I get [u'uat']
This is not a "Unicode string", this is a list ― pay attention to [ ].
As item.keys() returns a list, but you want to use it as an index to item[], you must select the element. So either use first filter or [0]:
- name: Get accounts
set_fact:
accounts: "{%- set tmp = [] -%}
{%- for account in item[item.keys()|first].accounts -%}
{{ tmp.append(account) }}
{%- endfor -%}
{{ tmp }}"
So, the scenario is I have producers and consumers in ratio 7:1, and I want to have a consistent and deterministic multilple mapping b/w the producers and consumers in my service. List of consumers is provided in the config to each of the producer, which is done via ansible. So, I try to implement the mapping logic in the ansible itself, rather than passing the entire list of consumers, and doing it inside producer service. So, I thought of using a custom filter to filter out from the list of consumers, and assign it to producer. Below is the custom filter I wrote:
#!/usr/bin/python
class FilterModule(object):
def filters(self):
return { 'map_producer_to_consumer': self.map_producer_to_consumer }
# consumer_servers: complete list of consumers servers
# producer_id: provided to each producer for mapping purpose
# producer_count: total no. of producers
# map_consumer_count: no. of consumers need to be mapped to each producer
# consumer_count: total no. of consumers
def map_producer_to_consumer(self, consumer_servers, producer_id, producer_count, map_consumer_count):
consumer_count = len(consumer_servers)
index_offset = 0 if producer_count%consumer_count else 1
rotation_count = (producer_id/consumer_count) % (map_consumer_count-1) # used for left rotation of mapped servers
map_consumer_indexes = [ (producer_count*i + producer_id + index_offset*i) % consumer_count for i in xrange(map_consumer_count)]
mapped_consumer_servers = [consumer_servers[map_consumer_indexes[0]]]
for i in xrange(1, map_consumer_count):
index = (i + rotation_count) % map_consumer_count
if i + rotation_count >= map_consumer_count:
mapped_consumer_servers.append( consumer_servers[map_consumer_indexes[index] + 1] )
else:
mapped_consumer_servers.append( consumer_servers[map_consumer_indexes[index]] )
return (',').join(mapped_consumer_servers)
This filter is working as expected, when using with static arguements like this:
"{{ tsdb_boxes | map_producer_to_consumer(2,3,3) }}"
but I want to make it use dynamic arguments via jinja2 templating, something like:
"{{ groups['producers'] | map_producer_to_consumer ({{ consumer_servers }}, {{ producer_id }}, {{ producer_count }}, {{ map_consumer_count }}) }}"
but its resulting in errors due to nesting of variables, which is not allowed in Jinja2. If I try something like this:
"{{ groups['producers'] }} | map_producer_to_consumer ({{ consumer_servers }}, {{ producer_id }}, {{ producer_count }}, {{ map_consumer_count }})"
it results in printing out the string like this:
['ip-1', 'ip-2'...] | map_producer_to_consumer (1000, 10, 150, 3)
Can someone please suggest what should be the best ansible way to achieve this. Should I use script module and convert the logic in bash, or it will be better to keep this inside the service only.
Answer from comments:
Why not try {{ groups['producers'] | map_producer_to_consumer(consumer_servers, producer_id, producer_count, map_consumer_count) }}
And link from #techraf about nesting.
I am using ansible to template a jinja2 file.
IP:{{ ansible_eth0.ipv4.address }}
IP:{{ ansible_docker0.ipv4.address }}
IP:{{ ansible_{{ ka_interface }}.ipv4.address }}
there is a var named ka_interface for network adapter.
but you will get error in 3rd var
(IP:{{ ansible_{{ ka_interface }}.ipv4.address }} )
It seems that var in jinja2 template can be nested.
It's not possible to construct a dynamic variable with Jinja2 syntax.
However, you can access any play-bound variables via the builit-in vars hash object:
{{ vars['ansible_' + ka_interface]['ipv4']['address] }}
Edit: Fixed hash syntax
follow Chris Lam 's advice,
It works
- name: test
shell: echo {{ vars['ansible_' + ka_interface]['ipv4']['address'] }}
tags: test