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
Related
$ ansible --version
ansible 2.10.8
I believe this is a different issue from Ansible's set_fact not working at all.
I have the following variable defined in group_vars/all.yml
nexus_repo = "dev_builds"
nexus_url = "https://nexus.company.com/{{ nexus_repo }}"
Then on one of my tasks, I do
- name: Adjust nexus_repo value for release builds
set_fact:
nexus_repo: "release_builds"
nexus_url: "https://nexus.company.com/{{ nexus_repo }}"
- debug: msg="{{ nexus_url }}"
When I run my playbook (my-playbook.yml runs my-role.yml), I just see
$ ansible-playbook -i inverntories/hosts.yml -e var_hosts=my-host my-playbook.yml
TASK [my-role : Adjust nexus_repo value for release builds] ****************
ok: [10.227.x.x]
TASK [my-role : debug] *****************************************************
ok: [10.227.x.x] => {
"msg": "https://nexus.mycompany.com/repository/dev_builds"
}
Why is that?
The problem is that you can't re-use variables declared in the same set_fact. For example, if the variables nexus_repo and nexus_url are declared for the first time in the set_fact below. The task
- set_fact:
nexus_repo: release_builds
nexus_url: "https://nexus.company.com/{{ nexus_repo }}"
will fail because nexus_url can't use next_repo declared at the previous line:
The task includes an option with an undefined variable. The error was: 'nexus_repo' is undefined
This explains the 'strange' behavior you see when the group_vars/all.yml is used
shell> cat group_vars/all.yml
nexus_repo: dev_builds
nexus_url: "https://nexus.company.com/{{ nexus_repo }}"
The set_fact below will use the value dev_builds of the variable nexus_repo from group_vars/all.yml when evaluating nexus_url
- set_fact:
nexus_repo: release_builds
nexus_url: "https://nexus.company.com/{{ nexus_repo }}"
- debug:
var: nexus_url
gives
nexus_url: https://nexus.company.com/dev_builds
There are more options on how to fix it. For example, don't declare the same variable nexus_url twice
- set_fact:
nexus_repo: release_builds
- debug:
var: nexus_url
gives
nexus_url: https://nexus.company.com/release_builds
If you have to declare nexus_url new put it into the separate set_fact. For example, the tasks below give the same result
- set_fact:
nexus_repo: release_builds
- set_fact:
nexus_url: "https://nexus.company.com/{{ nexus_repo }}"
- debug:
var: nexus_url
See the Ansible issue Can't reference a dict key inside the same dict #50280.
I have a hosts file myEnv with:
[myEnv:children]
app0
app1
app2
And a group file myEnv with:
env: "myEnv"
In the following playbook task I'm attempting to pass environment (value of env key) and list of apps in that environment (myEnv:children) to a shell script as parameters. Only the environment ('myEnv') is getting passed to the script. I'm unable to figure out correct jinja2 syntax to pass the list of apps associated with this key which is in my hosts file.
- name: Run createFacts.sh in bin directory
command: ./createFacts.sh {{ env }} {{ hostvars[env] }}
register: createPuppetFacts
args:
chdir: "{{binHome}}"
What jinja2 syntax do I require for this? I've scoured ansible docs and stack overflow and just not finding right format...other than errors in syntax the best I can do is get an empty string back!
Much appreciate help on this.
Q: "Pass environment (value of env key) and list of apps in that environment (myEnv:children) to a shell script as parameters."
A: Given the inventory
$ cat hosts
test_01
test_02
test_03
[app0]
test_01
test_02
test_03
[app1]
test_01
test_02
[app2]
test_02
test_03
[myEnv:children]
app0
app1
app2
and the group_vars
$ cat group_vars/myEnv
env: "myEnv"
The playbook
$ cat playbook.yml
- hosts: test_01
tasks:
- command: "/home/admin/createFacts.sh {{ env }}"
register: result
- debug:
var: result.stdout
- command: "/home/admin/createFacts.sh {{ groups[env] }}"
register: result
- debug:
var: result.stdout
with the script
$ cat /home/admin/createFacts.sh
#!/bin/sh
echo $#
exit 0
gives
ok: [test_01] => {
"result.stdout": "myEnv"
}
ok: [test_01] => {
"result.stdout": "[utest_01, utest_02, utest_03]"
}
Notes
myEnv, app0, app1, app2 are groups. See Inheriting variable values: group variables for groups of groups
The value of env is myEnv. This is the name of a group. It's posible to list the members of the group groups[env]. But hostvars[env] must fail, because hostvars expects the name of a host as a parameter.
Ansible does not provide the runtime with the information that the group myEnv is parent of the groups app0, app1, app2.
There is a special variable group_names: "List of groups the current host is part of"
The task below
- debug:
var: group_names
gives
ok: [test_01] => {
"group_names": [
"app0",
"app1",
"myEnv"
]
}
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..."
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
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"