set path when file exists in Ansible yml code - ansible

I'm trying to set a var only when a file exists, here is one of my attempts
---
- hosts: all
tasks:
- stat:
path: '{{ srch_path_new }}/bin/run'
register: result
- vars: srch_path="{{ srch_path_new }}"
when: result.stat.exists
This also didn't work
- vars: srch_path:"{{ srch_path_new }}"

The task you are looking for is called set_fact: and is the mechanism ansible uses to declare arbitrary "host variables", sometimes called "hostvars", or (also confusingly) "facts"
The syntax would be:
- set_fact:
srch_path: "{{ srch_path_new }}"
when: result.stat.exists
Also, while vars: is a legal keyword on a Task, its syntax is the same as set_fact: (or the vars: on the playbook): a yaml dictionary, not a key:value pair as you had. For example:
- debug:
msg: hello, {{ friend }}
vars:
friend: Jane Doe
and be aware that vars: on a task exist only for that task

Related

Ansible: Get Variable with inventory_hostname

I have the following passwords file vault.yml:
---
server1: "pass1"
server2: "pass2"
server3: "pass3"
I am loading these values in a variable called passwords:
- name: Get Secrets
set_fact:
passwords: "{{ lookup('template', './vault.yml')|from_yaml }}"
delegate_to: localhost
- name: debug it
debug:
var: passwords.{{ inventory_hostname }}
The result of the debugging task shows me the result I want to get: The password for the specific host.
But if I set the following in a variables file:
---
ansible_user: root
ansible_password: passwords.{{ inventory_hostname }}
This will not give me the desired result. The ansible_password takes "passwords" literally and not as a variable.
How can I achieve the same result I got when debugging the passwords.{{ inventory_hostname }}?
Regarding the part
... if I set the following in a variables file ...
I am not sure since I miss some information about your use case and data flow. However, in general the syntax ansible_password: "{{ PASSWORDS[inventory_hostname] }}" might work for you.
---
- hosts: localhost
become: false
gather_facts: false
vars:
PASSWORDS:
SERVER1: "pass1"
SERVER2: "pass2"
SERVER3: "pass3"
localhost: "pass_local"
tasks:
- name: Debug var
debug:
var: PASSWORDS
- name: Set Fact 'ansible_password'
set_fact:
ansible_password: "{{ PASSWORDS[inventory_hostname] }}"
- name: Debug var
debug:
var: ansible_password
In that way you can access a element by name.

How to pass variable value from command parameter and use it in with_items:?

For me the below code is working -
with_items: "{{ groups['mlpoc'] }}" but instead of hardcoded mlpoc I want to pass it in a variable as a parameter.
Say the command parameter is mlhosts=mlpoc and I want to use the variable instead of hardcoded value, something like -
with_items: "{{ groups['{{ mlhosts }}'] }}" but it throws error. Is it even possible to pass a dynamic value to groups ?
Here is my code -
- hosts: dbsrd3510
user: '{{ mluser }}'
gather_facts: no
no_log: false
tasks:
- name: Fetch source list from clients
with_items: "{{ groups['mlpoc'] }}"
shell: rsync -av /MLbackup/{{ pkg }} {{ mluser }}#{{ item }}:/tmp/
Try something like this,
---
- hosts: all
gather_facts: no
tasks:
- name: Add a line to a file if the file does not exist, without passing regexp
debug:
msg: "{{ item }}"
with_items: " {{ groups[group_name] }} "
And you can test the above changes using,
ansible-playbook -i hosts main.yml -e group_name="all"

how can i loop over a variable that might have single value?

I'm writing a playbook and want to loop a role over a variable that gets its value from the user. however that value might not always be a list of items, it might be a single value and whenever that happens it throws an error.
My Task:
- name: task name
include role:
name: role name
vars:
cluster_name: '{{ item }}'
loop: "{{ list_or_not }}"
loop_control:
loop_var: item
error:
...Invalid data passed to 'loop', it requires a list...
Have you tried the: "| list" filter?
Sorry cannot test at the moment.
You could test if the variable is a string, and if so, transform it into a single-item list. Something like this:
---
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
list_or_not: ["{{ list_or_not }}"]
when: list_or_not is string
- debug:
msg: "{{ item }}"
loop: "{{ list_or_not }}"

How to fallback to a default value when ansible lookup fails?

I was a little bit surprised to discover that his piece of code fails with an IOError exception instead of defaulting to omitting the value.
#!/usr/bin/env ansible-playbook -i localhost,
---
- hosts: localhost
tasks:
- debug: msg="{{ lookup('ini', 'foo section=DEFAULT file=missing-file.conf') | default(omit) }}"
How can I load a value without raising an exception?
Please note that the lookup module supports a default value parameter but this one is useless to me because it works only when it can open the file.
I need a default value that works even when the it fails to open the file.
As far as I know Jinja2 unfortunately doesn't support any try/catch mechanism.
So you either patch ini lookup plugin / file issue to Ansible team, or use this ugly workaround:
---
- hosts: localhost
gather_facts: no
tasks:
- debug: msg="{{ lookup('first_found', dict(files=['test-ini.conf'], skip=true)) | ternary(lookup('ini', 'foo section=DEFAULT file=test-ini.conf'), omit) }}"
In this example first_found lookup return file name if file exists or empty list otherwise. If file exists, ternary filter calls ini lookup, otherwise omit placeholder is returned.
In case people like me stumble upon this question in 2022,
Ansible now supports rescue blocks, which is similar to try-catch-finally in programming languages.
Examples can be found in the official documentation Error handling with blocks.
You can use block/rescue as follows:
- hosts: localhost
tasks:
- block:
- debug: msg="{{ lookup('ini', 'foo section=DEFAULT file=missing-file.conf') }}"
rescue:
- debug: msg="omit"
You can also convert your input file with a from_yaml filter before using the default filter
- name: "load a yaml file or a default value"
set_fact:
myvar: "{{ lookup('file', 'myfile.yml', errors='ignore') | from_yaml | default(mydefaultObject, true) }}"
To avoid the error when the path doesn't exist, use a condition to check for the path before attempting the lookup:
---
- hosts: localhost
tasks:
- debug: msg="{{ lookup('ini', 'foo section=DEFAULT file=missing-file.conf') }}"
when: missing-file.conf | exists
You can use this with set_fact as well, then omit the undefined var when using it if required:
- hosts: localhost
tasks:
- set_fact:
foo: "{{ lookup('ini', 'foo section=DEFAULT file=missing-file.conf') }}"
when: missing-file.conf | exists
- debug:
var: foo # undefined
msg: "{{ foo | default(omit) }}" # omitted
Note that lookups and Jinja2 tests run on the controller. If you need to check the path on the host, use the stat and either slurp or fetch modules:
- stat:
file: missing-remote-file-with-text-i-want
register: file
- slurp:
src: missing-remote-file-with-text-i-want
register: slurp
when: file.stat.exists
- set_fact:
foo: "{{ slurp.content | b64decode }}"
when: file.stat.exists
- fetch:
src: missing-file.conf
dest: /tmp/fetched
fail_on_missing: False
- set_fact:
bar: "{{ lookup('ini', 'foo section=DEFAULT file=/tmp/fetched/' + inventory_hostname + '/missing-file.conf') }}"
when: ('/tmp/fetched/' + inventory_hostname + '/missing-file.conf') | exists
Second note, in Ansible v2.5 the grammar for using the path tests was changed, the format is now:
- set_fact:
foo: "{{ lookup('ini', 'foo section=DEFAULT file=missing-file.conf') }}"
when: missing-file.conf is exists

Adding field to dict items

Consider the following play. What I am trying to do is add a field, tmp_path which is basically the key and revision appended together to each element in the scripts dict.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
# - with_items: "{{ scripts }}"
# set_fact: {{item.value.tmp_path}}="{{item.key}}_{{item.value.revision}}"
# - with_items: "{{ scripts }}"
# debug:
# msg: "{{ item.value.tmp_path }}"
...
Obviously the commented code doesn't work, any idea how I can get this working? Is it possible to alter the scripts dict directly, or should I somehow be creating a new dict to reference instead?
By the way welcome to correct the terminology for what I am trying to do.
OK, I think I got a solution (below), at least to let me move forwards with this. Disadvantages are it has removed the structure of my dict and also seems a bit redundant having to redefine all the fields and use a new variable, If anyone can provide a better solution I will accept that instead.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
- with_dict: "{{ scripts }}"
set_fact:
new_scripts: "{{ (new_scripts | default([])) + [ {'name': item.key, 'revision': item.value.revision, 'tmp_path': item.key ~ '_' ~ item.value.revision}] }}"
# - debug:
# var: x
# - with_dict: "{{ scripts }}"
- with_items: "{{ new_scripts }}"
debug:
msg: "{{ item.tmp_path }}"
...
BTW credit to the following question which pointed me in the right direction:
Using Ansible set_fact to create a dictionary from register results

Resources