Ansible environment variable or default - ansible

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

Related

Not picking up variable in expression correctly - Ansible

I'm trying the following -
---
- name: Test
hosts: "{{ hosts }}"
vars:
before: "groups.{{ hosts[0] }}_group_name"
after: "{{ before }}" # This equals {{ groups.test_group_name }}
roles:
- test-check
Just an explanation: I'm feeding hosts in when executing the playbook as a 'var'. In this case, var = test. The expected var string for before would be groups.test_group_name which is a group that contains multiple hosts in my inventory. However, when I execute this, after remains as groups.test_group_name instead of the expected array of hosts.
Does anybody know how I can remedy this? If I hard-code the host_name (test) into the after var, it picks it up, but if I don't, it doesn't. Thanks.
It appears you are trying to do pseudocode: {{ eval(before) }} but that is not how ansible, or jinja2, work. Thankfully, groups is a normal python dict and thus is subject to the __getitem__ syntax [] to dynamically look up keys
Thus, you likely want:
- hosts: "{{ hosts }}"
vars:
after: "{{ groups[ hosts[0]+'_group_name' ] }}"
tasks:
- debug: var=after

How to check in ansible if a SHELL (/bin/sh) environment variable is defined

In my playbook I'd like to detect if a certain environment variable is defined. If it is defined then I'd like to carry a certain action otherwise carry out certain other action.
Question: How do I detect in ansible if a certain shell environment variable is defined?
Let's say you want the current user:
- name: Get current user.
set_fact:
whoami: "{{ lookup('env','USER') }}"
That will not tell you whether the variable is defined. If the variable is not defined in the environment, your set_fact variable will be empty. If you want to use some default if the variable is not set...
- name: See if environment variable MYVAR is set
shell: "env | grep '^MYVAR='"
failed_when: 1 == 0
register: result
- name: Get MYVAR or use 'DEFAULT'
set_fact:
myvar: "{% if result.rc == 0 %}{{ lookup('env','MYVAR') }}{% else %}DEFAULT{% endif %}"
Have a read up on Ansible Facts and checkout:
- debug:
var: ansible_env
And therefore for conditional stuff, you can do things like:
- set_fact:
env_var: <SOME ENV VAR TO CHECK FOR>
- debug:
msg: "ENV {{ env_var }} is {{ ansible_env[env_var] is defined | ternary('defined', 'not defined') }}"
which results in:
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"msg": "ENV USER is defined"
}
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"msg": "ENV USR is not defined"
}
For env_var set to 'USER & 'USR'.
Bear in mind that parts of the env will look different, dependent on which user you are connected to the remote host as.

How to set variables for all subsequent hosts in one place without redundant code?

TL/DR: I have "src_host" and "dest_host" variables that I want to use to set the "- hosts:" object in a play. However, I have to set them again for each play under "vars:" of each "- hosts:" section e.g. src_host="{{ hostvars['localhost']['src_host'] }}" how do I set these two variables at the beginning and not have to reset them?
My hosts file looks like this
[wordpress]
localhost ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_localhost ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
---snip---
server2.net ansible_host="server2.net" ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_server2.net ansible_host="server2.net" ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
The beginning of my playbook looks like this:
- hosts: localhost, server2.net, root_server2.net #always include "localhost" in this list because it is needed to store the variables for the src_host and dest_host
vars:
src_host: localhost #modify these and the host will be changed for all subsequent plays/tasks
dest_host: server2.net #modify these and the host will be changed for all subsequent plays/tasks
src_dump_path: /home/user/cvrt9_dump.sql #set vars for copying file
roles:
- set_facts_for_db_copy
- hosts: "{{ src_host }}"
vars:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
---snip---
roles:
- dump_db
- copy_file
etc . . .
for "- set_facts_for_db_copy" I have "main.yml" as this where I set the "src_host" and "dest_host" variables:
---
# tasks file for set_facts_for_db_copy
- name: create variables that equal src_dump_path and set src_host/dest_host
set_fact:
---snip---
src_host: "{{ src_host }}"
dest_host: "{{ dest_host }}"
So I need to set the "src_host" and "dest_host" for all subsequent "- hosts:" that use them by getting the values from one of the host variables that "set_fact_for_db_copy" set. I randomly picked "localhost" as you may have noticed:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
If I don't have that line there I get:
user#localhost:/home/maintainer/ansible-play$ ansible-playbook -i hosts_tat-kay playbook.yml
PLAY [localhost, server2.net, root_server2.net] **************
TASK [setup] *******************************************************************
ok: [server2.net]
ok: [root_server2.net]
ok: [localhost]
TASK [set_facts_for_db_copy : create variables that equal src_dump_path] *******
ok: [localhost]
ok: [server2.net]
ok: [root_server2.net]
ERROR! the field 'hosts' has an invalid value, which appears to include a variable that is undefined. The error was: 'src_host' is undefined
The error appears to have been in '/home/maintainer/ansible-play/playbook.yml': line 14, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- hosts: "{{ src_host }}"
^ here
. . .
Now I can set the these variables in my host file:
[wordpress:vars]
src_host=localhost
dest_host=server2.net
But then I still have to reference them from the subsquent "-hosts:" objects in my playbook with "{{ hostvars['localhost']['src_host'] }}" etc . . . So my question is how do I get rid of this redundant code in all my subsequent "-hosts:" objects (shown below) while still letting me change the "src_host" and "dest_host" variables once at the beginning and have those changes affect the rest of the plays? Thanks.
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
For this use your inventory file, make a parent group of the host you need the variable as follow.
[desireenv:children]
wordpress
otherhost
etc
and then assigne th vars value to the new parent group created
[desireenv:vars]
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
One solution I found with the help of https://stackoverflow.com/users/4716639/bryan-calvo-benoit is to put this in my hosts file (inventory file)
[wordpress]
localhost
server2.net
[testenv:children]
wordpress
[testenv:vars]
src_host=localhost
dest_host=server2.net
And then in the ansible playbook and the roles that it calls I had to replace
"{{ src_host }}"
with
"{{ hostvars['localhost']['src_host'] }}"
and likewise for "{{ dest_host }}"
However, I could delete this redundant code in my ansible playbook:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
It would be nice if I didn't have to change the src_host and dest_host to hostvars['localhost']... because it seems arbitrary to use localhost and also what if I want to run several ansible scripts one right after the other with different src_host and dest_host? Using the inventory file locks it down so this is not ideal. If no one else answers I'll accept this answer because it is the only one that works and it technically does what my question asked.

Ansible vars using lookups

I have an Ansible playbook that populates some variables, here is a snippet:
#myTest playbook
---
- hosts: localhost
connection: local
become: False
vars:
- APP_NAME: "{{lookup( 'env', 'name')| mandatory }}"
I'd like to use another lookup first, and take that value if its been populated. Is this achievable in one line? I'm figuring something like Javascript's ||:
- APP_NAME: "{{lookup( 'env', 'customName') || lookup( 'env', 'name')| mandatory }}"
You can use the default filter with an option to trigger it if the value of the preceding expression is an empty string (as in the case of an undefined environment variable):
- APP_NAME: "{{ lookup('env', 'customName') | default(lookup('env', 'name'), true) | mandatory }}"

Ansible check if variables are set

I want to automate the installation process of our software for our client. Therefore I wrote an Ansible playbook which has a task which should check if all the mandatory variables are set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail: msg="Variable '{{ item }}' is not defined"
when: item not in hostvars[inventory_hostname]
with_items:
- required_vars
The required_vars.yml looks like this:
required_vars:
- APPHOME: /home/foo/bar
- TMPDIR: /home/foo/bar/tmp
When I execute the playbook via ansible-playbook -i inventory/dev.yml playbook.yml I get the following error:
TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************ok: [localhost]
TASK [fail] ************************************************************************************************************************************************************************************************************************************failed:
[localhost] (item=required_vars) => {"changed": false, "failed": true, "item": "required_vars", "msg": "Variable 'required_vars' is not defined"}
It is obvious that I am doing something wrong, but I cannot point to the error. Can you help me please?
Edit: the accepted answer helped me out. Thank you.
But I have two more questions:
The executed task says:
TASK [fail]
skipping: [some_ip] => (item=/root)
skipping: [some_ip] => (item=TMPDIR: /home/foo/bar/tmp)
It is getting skipped because all variables are set, correct?
I think I figured out how to print the correct message, if the variable is not set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail:
msg: "Variable '{{ item }}' is not defined"
with_items: "{{ required_vars }}"
when: item is undefined
Correct? Or is there a better solution?
Two problems here:
You want to iterate over the value of required_vars variable value, so you need to provide it as an argument to with_items: "{{ required_vars }}":
with_items: "{{ required_vars }}"
Currently you are providing a list of a single element with a statically defined string required_vars.
You need to change the data type of the elements in your required_vars list to strings:
required_vars:
- "APPHOME: /home/foo/bar"
- "TMPDIR: /home/foo/bar/tmp"
Currently (because of : followed by space) you defined dictionaries, so for example in the first iteration item will have a value of { "APPHOME": "/home/foo/bar" }, which will then always fail on the when condition.
Bonus problem:
you defined a message in the form "Variable '{{ item }}' is not defined";
Ansible reports Variable 'required_vars' is not defined;
the above is not an error, as you think ("I get the following error"), but a correct result of the fail module with the message you defined yourself.
Since you have only one value for 'with_items' I think it should look like this:
with_items: "{{ required_vars }}"
On one line and with the brackets and quotation marks. Once you have more then one item, you can use the list like you did:
with_items:
- "{{ one }}"
- "{{ two }}"

Resources