Access ansible.cfg variable in task - ansible

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"

Related

Ansible accessing Nested Variables

In foo.yml, the dev.foo variable contains a value of bar.
---
- hosts: all
vars:
dev:
foo: bar
I set the env variable to contain a value of dev on the command line.
ansible-playbook foo.yml --extra-vars "env=dev"
If I attempt to debug env.foo . . .
tasks:
- debug:
msg: "{{ env.foo }}"
The following is returned.
TASK [debug]
fatal: [server1.example.com]: FAILED! => {
"msg": "The task includes an option with an undefined variable.
The error was: 'str object' has no attribute 'foo'"
}
I am not sure how to resolve env to dev in jinja2 and then access nested variable dev.foo.
Indirect addressing is not available in Ansible. You can use vars lookup instead. See ansible-doc -t lookup vars e.g.
- debug:
msg: "{{ lookup('vars', env).foo }}"
gives
msg: bar

Ansible - passing dynamic variables into Jinja2 template

I'm having a problem accessing dynamically named Ansible variables in a Jinja2 template. I have a list of tenants like this:
tenants:
- liamtest1
- liamtest2
In my playbook I create terraform configuration files for each of these tenants like this:
- name: Generate a .tf file for each tenant in list
template:
src: templates/tenant.tf.j2
dest: "{{ enviro }}/terraform/{{ item }}.tf"
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
Later in the playbook I use the terraform module to apply my configuration and register the outputs to a variable:
- name: Run terraform
terraform:
project_path: "{{ enviro }}/terraform/"
state: present
register: tf_result
I've prefixed my terraform outputs with the tenant name so that I don't get duplicates. This bit is all working fine and I can I can display these outputs with a debug task, for example tenant_domain:
- debug:
var: tf_result.outputs.{{ item + '_domain' }}.value
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
Produces this output:
ok: [localhost] => (item=liamtest1) => {
"ansible_loop_var": "item",
"item": "liamtest1",
"tf_result.outputs.liamtest1_domain.value": "liamtest1.mydomain.com"
}
ok: [localhost] => (item=liamtest2) => {
"ansible_loop_var": "item",
"item": "liamtest2",
"tf_result.outputs.liamtest2_domain.value": "liamtest2.mydomain.com"
}
The bit I can't seem to do is generate another set of files (this time javascript files for mongodb) from another Jinja2 template.
I've tried this:
- name: Generate a .js file for each tenant in list
vars:
domain: tf_result.outputs.{{ item + '_domain' }}.value
template:
src: templates/tenant.js.j2
dest: "{{ enviro }}/mongodb/{{ item }}.js"
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
If I reference that in my Jinja2 template using {{ domain }} it ends up with just a string e.g. tf_result.outputs.liamtest1_domain.value in the first file and tf_result.outputs.liamtest2_domain.value in the second file.
I also tried using lookup in the Jinja2 template like this:
{{ lookup('vars', domain) }}
Which gives me:
"AnsibleUndefinedVariable: No variable found with this name: tf_result.outputs.liamtest1_domain.value"
I've also tried some other variations such as:
{{ lookup(hostvars[inventory_hostname], domain) }}
I've tried a few other things as well, I'm not sure they're all worth mentioning as none of them worked but for example I tried setting the variable inside the Jinja template instead of at the task level like this for example:
{% set domain = lookup('vars', 'tf_result.outputs.' + item + '_domain' %}
You simply have a syntax problem in your yaml.
# Wrong
vars:
domain: tf_result.outputs.{{ item + '_domain' }}.value
This is declaring a var which value is a concatenation of (literally) "tf_result.outputs." followed by the value of the current item and "_domain.value". What you want is the actual value contained in that full variable. This is the correct syntax:
# Correct
vars:
domain: "{{ tf_result.outputs[item + '_domain'].value }}"

Ansible environment variable or default

How do I get a value from an environment variable, but use a default if the environment variable is unset?
This is an example that does not work
---
- name: a playbook
hosts: all
vars:
build_dir: "{{ lookup('env','BUILD_DIR') | default('builds/1.0.0/LATEST') }}"
tasks:
- debug: msg="{{ build_dir }}"
Running this playbook returns an empty string instead of the default.
$ ansible-playbook build.yml
TASK [debug] ********************
ok: [amber] => {
"msg": ""
}
However, it works as expected to obtain the environment variable.
$ BUILD_DIR=LOL ansible-playbook build.yml
TASK [debug] ****************
ok: [amber] => {
"msg": "LOL"
}
Discovered this that is more concise and easier to read than some other options I have seen
"{{ lookup('env','BUILD_DIR') or 'builds/1.0.0/LATEST' }}"
The last parameter to Jinja's default template built-in function should be true, like this:
vars:
build_dir: "{{ lookup('env','BUILD_DIR')|d('builds/1.0.0/LATEST', true) }}"
Better not to have too many sources of truth, but I always try to set intelligent defaults in defaults/main.yml. I also make frequent use of the default() filter, like this:
db_url : "{{ DB_HOST }}:{{ db_port | default(1521) }}:{{ DB_SVC | default(SID|default('')) }}"
Then a playbook can always overwrite a role's variable with a lookup that defaults to a literal -
vars:
db_port: "{{ lookup('env','db_port')|default('9999') }}"
or with a value dynamically written into a vars_file before the play begins, or into the hosts file or groups file, or on the ansible command-line with --extra-vars, etc.
Look at the variable precedence rules, but be careful not to get too complex if it can be avoided. Flexibility is good, but KISS, else "that way lies madness..."

Ansible: How can I access a variable of other host?

How can I access a variable of other host? I'd like to access the slack_token varaiable of my localhost on the working_host.
- hosts: localhost
vars:
slack_token: 123123123
tasks:
- block:
- name: test
debug: msg="{{ slack_token }}"
- hosts: "{{ working_host }}"
vars:
slack_token: "{{ hostvars['localhost']['slack_token'] }}"
tasks:
- block:
- name: test2
debug: msg={{ slack_token }}
The error message:
fatal: [localhost]: FAILED! => {"failed": true, "msg": "the field
'args' has an invalid value, which appears to include a variable that
is undefined. The error was: {{ hostvars['localhost']['slack_token']
}}: 'dict object' has no attribute 'slack_token'
Any idea?
Just answered a somewhat same question in my previous post.
Here's what I used:
set_fact:
myVar: "{{ hostvars[groups['all'][0]]['slack_token'] | default(False) }}"
But you're using two plays in a playbook.
You can also try to copy a file to a machine stating the fact.
To access slack_token from everywhere, either:
pass it as extra variable with -e slack_token=zzzz
define it in your inventory under all group

Ansible: "this module requires key=value arguments" when key=value args supplied

I'm getting this error from Ansible 1.9.4:
TASK: [rabbitmq | add rabbitmq vhost] *****************************************
failed: [prod-sensu01] => (item={'name': u'/sensu'})
=> {"failed": true, "item": {"name": "/sensu"}}
msg: this module requires key=value arguments
(['name:', '/sensu', 'node:', 'rabbit',
'tracing:', 'no', 'state:', 'present'])
But as you can see from the error message, the item is actually a key=value argument (hash).
Here are the variables:
# playbooks/roles/rabbitmq/vars/main.yml
# Set the rabbitmq vhost
rabbitmq_vhost_definitions:
- name: "{{ sensu_server_rabbitmq_vhost }}"
And the role's task:
# playbooks/roles/rabbitmq/tasks/vhost.yml
- name: add rabbitmq vhost
rabbitmq_vhost: >
name: "{{ item.name }}"
node: "{{ item.node | default('rabbit') }}"
tracing: "{{ item.tracing | default('no') }}"
state: present
with_items: rabbitmq_vhost_definitions
What is going on here?
Key/value pairs of arguments for Ansible module passed as string should be separated by =, not by :. Correct task should look like this:
# playbooks/roles/rabbitmq/tasks/vhost.yml
- name: add rabbitmq vhost
rabbitmq_vhost: >
name="{{ item.name }}"
node="{{ item.node | default('rabbit') }}"
tracing="{{ item.tracing | default('no') }}"
state=present
with_items: rabbitmq_vhost_definitions
Why they're passed as string? Because you're using folded block scalar > after name of the module, in this line:
rabbitmq_vhost: >
You could also try to remove > scalar (and leave colons) if this module supports both ways of passing arguments.

Resources