Ansible items in separate files - ansible

Is it possible to have few .yml files and then read them as separate items for task?
Example:
- name: write templates
template: src=template.j2 dest=/some/path
with_items: ./configs/*.yml

I have found pretty elegant solution:
---
- hosts: localhost
vars:
my_items: "{{ lookup('fileglob', './configs/*.yml', wantlist=True) }}"
tasks:
- name: write templates
template: src=template.j2 dest=/some/path/{{ (item | from_yaml).name }}
with_file: "{{ my_items }}"
And then in template you have to add {% set item = (item | from_yaml) %} at the beginning.

Well, yes and no. You can loop over files and even use their content as variables. But the template module does not take parameters. There is an ugly workaround by using an include statement. Includes do take parameters and if the template task is inside the included file it will have access to them.
Something like this should work:
- include: other_file.yml parameters={{ lookup('file', item) | from_yaml }}
with_fileglob: ./configs/*.yml
And in other_file.yml then the template task:
- name: write template
template: src=template.j2 dest=/some/path
The ugly part here, beside the additional include, is that the include statement only takes parameters in the format of key=value. that's what you see in above task as parameters=.... parameters here has no special meaning, it just is the name of the variable with which the content of the file will be available inside the include.
So if your vars files have a variable foo defined, you would be able to access it in the template as {{ parameters.foo }}.

Related

Use dynamic set of variable for template

My objectif is to write a generic role for nginx configuration.
I want to allow to define many snippets based on templates. Depending on templates the set of variable can be differents.
The list of templates are defined in host_vars:
nginx_templated_snippets:
- template: whitelist_open.j2
file: whitelist_iprestrict.conf
options:
allow: "{{ default_whitelist + [ansible_facts.default_ipv4.address] }}"
the role looks like:
- name: Add templated snippets
template:
src: "{{ item.template }}"
dest: "/etc/nginx/snippets/{{ item.file }}"
owner: root
group: root
mode: '0644'
vars:
"{'{{ nginx_template_options }}'}"
loop: "{{ nginx_templated_snippets }}"
notify: Test nginx configuration and reload
In this example, the variable allow is requiered in template whitelist_open.j2 but depending on template the variable name can change.
I didn't manage to set the dynamic set of variables:
ERROR! Vars in a Task must be specified as a dictionary, or a list of dictionaries
I tried many syntaxes but not sure that could be possible

How can I nest a with_filetree in Ansible?

I'm using Ansible to push config files for various apps (based on group_names) & need to loop thru the config .j2 templates from a list variable. If I use a known list of config templates I can use a standard with_nested like this...
template:
src: '{{ playbook_dir }}/templates/{{ item[1] }}/configs/{{ item[0] }}.j2'
dest: /path/to/{{ item[1] }}/configs/{{ item[0] }}
with_nested:
- ['file.1', 'file.2', 'file.3', 'file.4']
- '{{ group_names }}'
However, since each app will have its own configs I can't use a common list for a with_nested. Every attempt to somehow use with_filetree nested fails. Is there any way to nest a with_filetree? Am I missing something painfully obvious?
The most straightforward way to deal with this is probably to imbricate loops through an include. I take for granted that your app directory only contains .j2 files. Adapt if this is not the case.
In e.g. push_templates.yml
---
- name: Copy templates for group {{ current_group }}
template:
src: "{{ item.src }}"
dest: /path/to/{{ current_group }}/configs/{{ (item.src | splitext).0 | basename }}
with_filetree: "{{ playbook_dir }}/templates/{{ current_group }}"
# Or using the latest loop syntax
# loop: "{{ query('filetree', playbook_dir + '/templates/' + current_group) }}"
when: item.src is defined
Note: on the dest line, I am removing the last found extension of the file and getting its name only without the leading directory path. Check the ansible doc on filters for splitext and basename for more info
Then in your e.g. main.yml
- name: Copy templates for all groups
include_tasks: push_templates.yml
loop: "{{ group_names }}"
loop_control:
loop_var: current_group
Note the loop_var in the control section to disambiguate the possible item overlap in the included file. The var name is of course aligned with the one I used in the above included file. See the ansible loops documentation for more info.
An alternative approach to the above would be to construct your own data structure looping over your groups with set_fact and calling the filetree lookup on each iteration (see example above with the newer loop syntax), then loop over your custom data structure to do the job.

How to reference variable in files folder under roles

In one of my ansible roles, I have created a variable via the "defaults/main.yml" file. I am able to reference this variable just fine in "tasks/main.yml" file. However, the variable does not appear to work in "files/some_file.txt"
Is this expected ?
Yes that's expected. tasks/main.yml is being parsed by ansible and will replace variables as you've seen.
Generally files/some_file.txt should contain static files or scripts that should be used with the copy module. As you've discovered it will not be parsed beyond that.
If you want to use variables in a file you should use the template module. Create a templates directory and copy your files there e.g template/some_file.txt. Note that it's common to rename the file with a .j2 extension to indicate that it is a jinja template e.g some_file.j2 but this is not required.
- name: Create fact
set_fact:
my_variable: 123456
- name: Create file from template
template:
src: some_file.j2
dest: "/tmp/some_file.txt"
mode: 0755
some_file.j2 might contain:
This file contains this sentence and the number {{ my_variable }}
After the template task run /tmp/some_file.txt will looks like:
This file contains this sentence and the number 123456
Use template. For example, the role
shell> cat roles/role-17/defaults/main.yml
var1: value1
shell> cat roles/role-17/files/some_file.txt
[{{ var1 }}]
shell> cat roles/role-17/tasks/main.yml
- debug:
msg: task/main.yml [{{ var1 }}]
- debug:
msg: files/some_file.txt {{ lookup('template', 'files/some_file.txt') }}
and the playbook
shell> cat test-17.yml
- hosts: localhost
roles:
- role-17
give
"msg": "task/main.yml [value1]"
"msg": "files/some_file.txt [value1]\n"

Ansible : appending data of file using template module

Is there a way to append data using a template module in ansible. There are options for lineinfile and blockinfile in ansible. But I have to append data to the host's file. I want to preserve original hosts and add new hosts to the existing file.
When using the template module, a complete file is templated.
What you could do is dynamically build your file in a way like this in the Jinja2 template:
This is content of my file, not being replaced.
{% for item in some_variable %}
{% {{ item }} %}
{% endfor %}
And here is the end of the file, not being replaced.
However, it feels like you're trying to edit a /etc/hosts file or something similar. You shouldn't use template for this use case.
Using lineinfile is perfect for those scenarios. Try something along the lines of:
- name: Add the hosts names and IPs to /etc/hosts
lineinfile:
dest: /etc/hosts
regexp: '.*{{ item }}$'
line: "{{ hostvars[item]['ansible_default_ipv4']['address'] }} {{item}}"
state: present
when: hostvars[item]['ansible_facts']['default_ipv4'] is defined
with_items:
- "{{ groups['all'] }}"
You can use blockinfile to accomplish above as below:
- name: Add the below DNS records
blockinfile:
path: /etc/hosts
marker: "------"
insertafter: '^yourlinepattern'
state: present
block: |
lines to append

Ansible: find file and loop over paths

Using an Ansible role. I would like to loop over a list of file paths, but I get an error:
template error while templating string: unexpected '/'.
String: {{/home/xyz/download.log}}
This is the main.yml for the "list_log_files" role:
- name: "find logs"
find:
paths: /
patterns: 'download.log'
recurse: yes
register: find_logs
- name: "list log files"
debug: var="{{ item.path }}"
with_items: "{{ find_logs.files }}"
The find returns an array "files", each is a dictionary. The dictionary contains a path entry, which is what I am interested in.
I have faced same issue and above issue is same where I want list of path of each file to insert line. I use Jinja2 filter:
- name: fetch files
find: paths=/var/tmp/ patterns='*.log'
register: find_logs
- name: insert line
lineinfile: dest={{ item }} line='my line' insertafter=EOF
with_items: "{{ find_logs.files | map(attribute='path') | list }}"
{{ find_logs.files | map(attribute='path') | list }}
Helpful Link
map()
Applies a filter on a sequence of objects or looks up an attribute.
This is useful when dealing with lists of objects but you are really
only interested in a certain value of it.
The correct syntax for var argument of debug module (with the value for your use case) is:
In Ansible notation:
debug: var=item.path
In YAML notation:
debug:
var: item.path
Ansible modules' usage is fairy well documented and examples cover most users' needs. This is also true for the debug module, so refer to the examples to check the basic syntax.

Resources