Using vars_files conditionally in ansible playbook - ansible

What I am trying to achieve is something like following
vars_files:
- "{{ 'vars/vars.yml' if condition==True else 'vars/vars1.yml' }}"
Can someone help me with the correct syntax?

vars_files is a playbook keyword. The conditions can't be applied to keywords. Instead, an option would be using include_vars which is a task. For example
- hosts: all
tasks:
- include_vars: vars/vars1.yml
when: condition|bool
Notes
The directory vars in the path is not necessary. See The magic of ‘local’ paths
See CONDITIONAL_BARE_VARS
Make sure there is no problem with the higher precedence. If there is a problem with the higher precedence of include_vars open a new question with mcve details. There are many options for how to solve it depending on the use-case.

You can do something like this:
vars_files:
- vars/{{ 'vars' if condition==True else 'vars1' }}.yml

Related

How to access group variables from inventory file inside ansible yaml

I have an inventory file with one group like below:
[example1]
H1 ip1 username1
H2 ip2 username2
H3 ip3 username3
and I have defined group variable like below to make that variable common to all hosts in that group:
[example1:vars]
temp='H2'
I am trying to access this variable inside my ansible yml under hosts field like below:
---
- name: temp playbook to practice hostvars
hosts: "{{ hostvars['example1']['temp'] }}"
tasks:
.....
.....
....
But while executing this playbook, I am getting the hostvars undefined error like below:
"ERROR! The field 'hosts' has an invalid value, which includes an undefined variable. The error was: 'hostvars' is undefined"
My ansible file and inventory file in same folder, could anyone help me to rectify what I am missing to access the variable from inventory file?
Please, don't do it like that. It's bad way. Use groups with 'children' property for host grouping.
Nevertheless, here is a fix for your problem. You've tried to access hostvars for a group. There is no host 'example1' in your inventory. And hostvars have variables for host, as you can see.
Following code will do the trick:
- hosts: localhost
gather_facts: no
tasks:
- debug:
- hosts: '{{ hostvars[groups.example1[0]].temp }}'
tasks:
...
I use groups.example1 to get a list of hosts in a group example1, then I take first member of that group ([0]) then I peek into its variables through hostvars.
The strange task with debug at the begin need to help Ansible to initialize hostvars. Without it, it will fail with undefined variable error.
But, again, don't do it like that. It's really, really mess up way to reinvent groups.
I know it's an old question, but just found the solution for this problem. Just put this before using hostvars in the next task or role and somehow you will be able to use the hostvars variables into hosts: definition.
- name: Do nothing
hosts: all
gather_facts: no
become: no
it seems that there is no need for run_once: yes (maybe only for some performance improvment, if any)
I can't say I understand what's under the hood, but it seems that ansible (2.9, 2.10) need something bogus in order to use hostvars variable in the next tasks/roles.

Pass variable to included playbook?

I would like to have a master playbook, which include's other playbooks. Is it possible to pass a variable to that included playbook?
The normal syntax which is used for passing variables to included tasks doesn't work (see below)
- include: someplaybook.yml variable=value
and
- include: someplaybook.yml
vars:
variable: value
I'm running v2.0.2.0.
The only thing i see missing is quotes.
- include: someplaybook.yml variable='value'
It works for me and should work for you too. If not share the error you face.
Make sure you have this variable "variable" defined in the task of the role as well and from here you are just passing the value to that variable.
Tested on ansible 2.4
- import_playbook: any_playbook.yml variable='value'
Also, I suggest you read this,
http://docs.ansible.com/ansible/latest/playbooks_reuse.html
and try using roles in this case, it'll help in a case like this, where you're trying to include/import multiple playbooks in a single main playbook.
And about passing a value to the include statement you can add it to the vars main.yml of the role.
Or, if the variable you want to pass is the result of a previous task in the single main playbook use 'register' and save the output in a varible.
- debug: msg="{{result.stdout_lines}}"
here, result is the registered variable.
Use the debug module to know exactly what you want to pass to the playbook.
Hope this helps.
In my opinion most consistent way to pass variable to included playbook and to another play in the current playbook as well is using:
set_fact:
global_var_name: "{{ your_var }}"
before
- import_playbook: your_playbook.yml
After you set the fact it's is accessible from any play in the playbook and imported playbooks, for instance inside "your_playbook" you can call it like so:
debug:
var: global_var_name
If you use:
- import_playbook: someplaybook.yml variable='value'
you can only pass fixed 'value'(in include as well) if you try to pass var value:
- import_playbook: someplaybook.yml internal_var="{{ your_var }}"
you will get NOT DEFINED when you call internal_var inside 'someplaybook.yml'
All of this is true for the beginning of 2021, ansible 2.9*, it's quite possible they will 'fix' import_playbook, I would like this, by the way.

Specifying variables in master Ansible playbook

I am writing a master Ansible playbook which is including playbooks. Now, I want to set variables that all the playbooks specified within it use. How do I specify variables in this playbook?
I know that one of the options is to include vars_files and use it with each of the playbook. Example: - include: abc.yml
vars_files: vars.yml
I am using Ansible 1.9.3.
First I would really recommend you to update your Ansible to latest version. It is very easy to do so, no reason to stay behind.
Having said that, there are many ways on how to specify variables in your master playbook. All these are more or less the same with any other playbook. Briefly mentioning:
a. Define them in your playbook itself
- hosts: webservers
vars:
http_port: 80
b. Separating into a variable file, as you already said:
- hosts: all
remote_user: root
vars:
favcolor: blue
vars_files:
- /vars/external_vars.yml
vars/external_vars.yml
somevar: somevalue
password: magic
Other possibilities include:
c. Using facts
d. Registering output into variables
Additionally, which may be important for your case:
d. You can pass variables into includes:
tasks:
- include: wordpress.yml wp_user=timmy
- include: wordpress.yml wp_user=alice
- include: wordpress.yml wp_user=bob
e. Passing variables in command line:
ansible-playbook release.yml -k "version=1.23.45 other_variable=foo"
-k is shorthand for --exra-vars.
There might be other ways too that I may be missing at the moment.

Ansible: How can I Include variable file with conditional statement

I have three sets of servers. Depending on the hostname, I want the client to grab the correct file. I tested the playbook on a server with the word "batch" but it picks up both files group_vars/perl and group_vars/batch
pre_tasks:
- include_vars: group_vars/web
when: "'web' in ansible_hostname"
- include_vars: group_vars/batch
when: "'web' not in ansible_hostname"
- include_vars: group_vars/perl
when: "'web' not in ansible_hostname" or "'batch' not in ansible_hostname"
Ansible will handle group_vars for you. For any hosts in the group web, the vars at group_vars/web (or group_vars/web.yml) will be automatically included. See Splitting Out Host and Group Specific Data in the Ansible docs.
To answer your question about conditional variable includes, the examples you gave are the correct syntax. Here's another example, not using group_vars:
- include_vars: vars/{{ ansible_distribution}}_packages.yml
when: install_extra_packages is defined and
install_extra_packages != ''
Note that include_vars accepts expressions, so you can use variables to determine the vars files to pull in dynamically.

Ansible include task only if file exists

I'm trying to include a file only if it exists. This allows for custom "tasks/roles" between existing "tasks/roles" if needed by the user of my role. I found this:
- include: ...
when: condition
But the Ansible docs state that:
"All the tasks get evaluated, but the conditional is applied to each and every task" - http://docs.ansible.com/playbooks_conditionals.html#applying-when-to-roles-and-includes
So
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
Will fail if the file being included doesn't exist. I guess there might be another mechanism for allowing a user to add tasks to an existing recipe. I can't let the user to add a role after mine, because they wouldn't have control of the order: their role will be executed after mine.
The with_first_found conditional can accomplish this without a stat or local_action. This conditional will go through a list of local files and execute the task with item set to the path of the first file that exists.
Including skip: true on the with_first_found options will prevent it from failing if the file does not exist.
Example:
- hosts: localhost
connection: local
gather_facts: false
tasks:
- include: "{{ item }}"
with_first_found:
- files:
- /home/user/optional/file.yml
skip: true
Thanks all for your help! I'm aswering my own question after finally trying all responses and my own question's code back in today's Ansible: ansible 2.0.1.0
My original code seems to work now, except the optional file I was looking was in my local machine, so I had to run stat through local_action and set become: no for that particular tasks, so ansible wouldn't attempt to do sudo in my local machine and error with: "sudo: a password is required\n"
- local_action: stat path=/home/user/optional/file.yml
register: optional_file
become: no
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
In Ansible 2.5 and above, it can be done using tests like this:
- include: /home/user/optional/file.yml
when: "'/home/user/optional/file.yml' is file"
More details: https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-paths
I using something similar but for the file module and what did the trick for me is to check for the variable definition, try something like:
when: optional_file.stat.exists is defined and optional_file.stat.exists
the task will run only when the variable exists.
If I am not wrong, you want to continue the playbook even the when statement false?
If so, please add this line after when:
ignore_errors: True
So your tasks will be look like this:
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
ignore_errors: True
Please let me know, if I understand your question correctly, or can help further. Thanks
I could spend time here to bash ansible's error handling provisions, but in short, you are right and you can't use stat module for this purpose due to stated reasons.
Simplest solution for most ansible problems is to do it outside ansible. E.g.
- shell: test -f /home/user/optional/file.yml # or use -r if you're too particular.
register: optional_file
failed_when: False
- include: /home/user/optional/file.yml
when: optional_file.rc == 0
- debug: msg="/home/user/optional/file.yml did not exist and was not included"
when: optional_file.rc != 0
* failed_when added to avoid host getting excluded from further tasks when the file doesn't exist.
Using ansible-2.1.0, I'm able to use snippets like this in my playbook:
- hosts: all
gather_facts: false
tasks:
- name: Determine if local playbook exists
local_action: stat path=local.yml
register: st
- include: local.yml
when: st.stat.exists
I get no errors/failures when local.yml does not exist, and the playbook is executed (as a playbook, meaning it starts with the hosts: line, etc.)
You can do the same at the task level instead with similar code.
Using stat appears to work correctly.
There's also the option to use a Jinja2 filter for that:
- set_fact: optional_file="/home/user/optional/file.yml"
- include: ....
when: optional_file|exists
The best option I have come up with so far is this:
- include: "{{ hook_variable | default(lookup('pipe', 'pwd') ~ '/hooks/empty.yml') }}"
It's not exactly an "if-exists", but it gives users of your role the same result. Create a few variables within your role and a default empty file. The Jinja filters "default" and "lookup" take care of falling back on the empty file in case the variable was not set.
For convenience, a user could use the {{ playbook_dir }} variable for setting the paths:
hook_variable: "{{ playbook_dir }}/hooks/tasks-file.yml"

Resources