Accessing ansible variables in filters - filter

I have been trying to create a filter, which prefix some input. The prefix should consist of some ansible variable in my case inventory_dir and role_name.
I tried to implement following code:
from ansible import errors
def role_file(self):
try:
return inventory_dir + "/roles/" + role_name
except Exception, e:
raise errors.AnsibleFilterError(
'role_file plugin error: {0}, self={1},'.format(str(e), str(self)))
class FilterModule(object):
''' prefix a file resource to the inventory directory '''
def filters(self):
return {
'role_file': role_file
}
and my playbook looks as follows:
---
- hosts: messagebus
tasks:
- debug:
msg: "Hello World {{ 'abc' | role_file }}"
I get following error message:
fatal: [localhost]: FAILED! => {"msg": "role_file plugin error: global name 'inventory_dir' is not defined, self=abc,"}
Can anybody see what the issue is with the implementation
Thanks in advance

Answer from the comments:
You can define variable inv_prefix: "{{ inventory_dir + '/roles/' + role_name }}" and use it in copy/template as src: "{{ inv_prefix }}/myfile"

You can also pass along the variable to the python filter like:
{{ 'abc' | role_file(param2,param3) }}"
def role_file(self, param1,param2,param3):
try:
return param2 + "/roles/" + param3

Related

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

How to use a dictionary of registered ansible variables in vars?

I want to pass multiple variables to a task using vars. Currently, I am doing it like below
vars:
var1_name: "var1_value"
var2_name: "var2_value"
As the number of variables can grow in size, I'd rather prefer to pass the dictionary of variables to the task using vars. I have constructed a dictionary of variables like below
- name: set fact
hosts: localhost
tasks:
- set_fact:
variables: "{{ variables|default({}) | combine( {item.variable: item.value} ) }}"
with_items:
- variable: var1_name
value: "var1_value"
- variable: var2_name
value: "var2_name"
Dictionary looks something like this:
"variables": {
"var1_name": "var1_value",
"var2_name": "var2_value",
}
Now, I want to make variables in this dictionary available to roles executing on other hosts.
But, when I tried to pass dictionary to vars like below
vars: "{{ variables }}"
Ansible throws the error:
ERROR! Vars in a Play must be specified as a dictionary, or a list of dictionaries
How to pass a dictionary variable in vars?
After doing some searching through the Ansible source code, it looks like this is an issue even the developers of Ansible face. In some integration tests, there are specific tests that are commented out because of this same error.
https://github.com/ansible/ansible/blob/devel/test/integration/targets/include_import/role/test_include_role.yml#L96
## FIXME Currently failing with
## ERROR! Vars in a IncludeRole must be specified as a dictionary, or a list of dictionaries
# - name: Pass all variables in a variable to role
# include_role:
# name: role1
# tasks_from: vartest.yml
# vars: "{{ role_vars }}"
I also found out that this is the underlying function that is being called to include the variables:
https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/base.py#L681
def _load_vars(self, attr, ds):
'''
Vars in a play can be specified either as a dictionary directly, or
as a list of dictionaries. If the later, this method will turn the
list into a single dictionary.
'''
def _validate_variable_keys(ds):
for key in ds:
if not isidentifier(key):
raise TypeError("'%s' is not a valid variable name" % key)
try:
if isinstance(ds, dict):
_validate_variable_keys(ds)
return combine_vars(self.vars, ds)
elif isinstance(ds, list):
all_vars = self.vars
for item in ds:
if not isinstance(item, dict):
raise ValueError
_validate_variable_keys(item)
all_vars = combine_vars(all_vars, item)
return all_vars
elif ds is None:
return {}
else:
raise ValueError
except ValueError as e:
raise AnsibleParserError("Vars in a %s must be specified as a dictionary, or a list of dictionaries" % self.__class__.__name__,
obj=ds, orig_exc=e)
except TypeError as e:
raise AnsibleParserError("Invalid variable name in vars specified for %s: %s" % (self.__class__.__name__, e), obj=ds, orig_exc=e)
Seems as if since "{{ }}" is actually just a YAML string, Ansible doesn't recognize it as a dict, meaning that the vars attribute isn't being passed through the Jinja2 engine but instead being evaluated for what it actually is.
The only way to pass YAML objects around would be to use anchors, however this would require that the object be defined in whole instead of dynamically.
var: &_anchored_var
attr1: "test"
att2: "bar"
vars:
<<: *_anchored_var
I recommend using a structured way of managing variables like:
File myvars1.yml
myvars:
var1_name: "var1_value"
var2_name: "var2_value"
Then read the variables like
- name: Read all variables
block:
- name: Get All Variables
stat:
path: "{{item}}"
with_fileglob:
- "/myansiblehome/vars/common/myvars1.yml"
- "/myansiblehome/vars/common/myvars2.yml"
register: _variables_stat
- name: Include Variables when found
include_vars : "{{item.stat.path}}"
when : item.stat.exists
with_items : "{{_variables_stat.results}}"
no_log : true
delegate_to: localhost
become: false
After that, use like:
- name: My Running Module
mymodule:
myaction1: "{{ myvars.var1_name }}"
myaction2: "{{ myvars.var2_name }}"
Hope it helps

Ansible, list object has no attribute

I am using Ansible 2.3.0.0 and I have tested in Ansible 2.4.0.0, obtaining the same result. My problem is very simple, but I cannot see the problem.
I have defined a list of objects in Ansible as follows:
vars:
password_text_to_encrypt:
- { line: "{{truststore_pass }}" , regexp: '\${TRUSTSTORE_PASS}*'}
- { line: "{{ keystore_pass }}" , regexp: '\${KEYSTORE_PASS}*'}
- { line: "{{ gp_pass }}" , regexp: '\${GP_PASS}*'}
- { line: "{{ datasource_password }}" , regexp: '\${DATASOURCE_PASS}*'}
- { line: "{{ server_password }}" , regexp: '\${SERVER_PASSWORD}*'}
- { line: "{{ sftp_password }}" , regexp: '\${SFTP_PASSWORD}*'}
- { line: "{{ db_userpassword }}" , regexp: '\${DB_PASSWORD}*'}
roles:
- basic_role
My Basic_role just prints the items, and I would like to obtain the content of each line:
---
- name: "print password"
debug:
msg: "The content of the line is: {{ item.line}}"
with_nested:
- "{{password_text_to_encrypt}}"
But the result that I obtain is:
FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'list object' has no attribute 'line'\n\nThe error appears to have been in.....
If I change item.line to just item, it works but it prints:
ok: [localhost] => (item=[u'regexp', u'line']) => {
"item": [
"regexp",
"line"
],
"msg": "The content of the line is: [u'regexp', u'line']"
}
.
.
.
To summarize, Ansible does not consider the content of line o regexp. I have been doing tests and the variable which are used to init line and regexp are not empty.
Use with_items instead of with_nested.
I think you want loops and includes because you're getting a flattened list which is expected (as per the documentation).

Ansible - is it possible to register multiple variable?

I have a python script which is returning / print two lists.
test.py
def getHosts():
Pool1=[x.x.x.x, x.x.x.x]
Pool2=[x.x.x.x, x.x.x.x]
Print pool1,pool2
Return pool1,pool2
getHosts()
My playbook looks like:
-task:
name: get the hosts
command: /test.py
register: result
Now, is it possible to fetch out the pool1 and pool2 seperately from the registered variable result ? If yes, please show me an example.
Any help or suggestions will be highly appreciated.
Produce JSON and feed it to Ansible. It will automatically create an appropriate data structure:
---
- hosts: localhost
gather_facts: no
connection: local
tasks:
- command: 'echo { \"Pool1\": \"[x.x.x.x, x.x.x.x]\", \"Pool2\": \"[x.x.x.x, x.x.x.x]\" }'
register: my_output
- set_fact:
my_variable: "{{ my_output.stdout | from_json }}"
- debug:
msg: "Pool1 is {{ my_variable.Pool1 }} and Pool2 is {{ my_variable.Pool2 }}"
Result:
ok: [localhost] => {
"msg": "Pool1 is [x.x.x.x, x.x.x.x] and Pool2 is [x.x.x.x, x.x.x.x]"
}
Depending on how you later use the variable, you might/might not need to from_json filter (see this).

Access ansible.cfg variable in task

How can I refer remote_tmp (or any other) value defined in ansible.cfg in my tasks? For example, in the my_task/defaults/main.yml:
file_ver: "1.5"
deb_file: "{{ defaults.remote_tmp }}/deb_file_{{ file_ver }}.deb"
produces an error:
fatal: [x.x.x.x]: FAILED! => {"failed": true,
"msg": "the field 'args' has an invalid value,
which appears to include a variable that is undefined.
The error was: {{ defaults.remote_tmp }}/deb_file_{{ file_ver }}.deb:
'defaults' is undefined\... }
You can't do this out of the box.
You either need action plugin or vars plugin to read different configuration parameters.
If you go action plugin way, you'll have to call your newly created action to get remote_tmp defined.
If you choose vars plugin way, remote_tmp is defined with other host vars during inventory initialization.
Example ./vars_plugins/tmp_dir.py:
from ansible import constants as C
class VarsModule(object):
def __init__(self, inventory):
pass
def run(self, host, vault_password=None):
return dict(remote_tmp = C.DEFAULT_REMOTE_TMP)
Note that vars_plugins folder should be near your hosts file or you should explicitly define it in your ansible.cfg.
You can now test it with:
$ ansible localhost -i hosts -m debug -a "var=remote_tmp"
localhost | SUCCESS => {
"remote_tmp": "$HOME/.ansible/tmp"
}
You can use lookup.
file_ver: "1.5"
deb_file: "{{ lookup('ini', 'remote_tmp section=defaults file=ansible.cfg' }}/deb_file_{{ file_ver }}.deb"
EDIT
In case you don't know the path to the configuration file, you can set that to a fact by running the following tasks.
- name: look for ansible.cfg, see http://docs.ansible.com/ansible/intro_configuration.html
local_action: stat path={{ item }}
register: ansible_cfg_stat
when: (item | length) and not (ansible_cfg_stat is defined and ansible_cfg_stat.stat.exists)
with_items:
- "{{ lookup('env', 'ANSIBLE_CONFIG') }}"
- ansible.cfg
- "{{ lookup('env', 'HOME') }}/.ansible.cfg"
- /etc/ansible/ansible.cfg
- name: set fact for later use
set_fact:
ansible_cfg: "{{ item.item }}"
when: item.stat is defined and item.stat.exists
with_items: "{{ ansible_cfg_stat.results }}"
You can then write:
file_ver: "1.5"
deb_file: "{{ lookup('ini', 'remote_tmp section=defaults file=' + ansible_cfg) }}/deb_file_{{ file_ver }}.deb"

Resources