ansible lineinfile how add multiple lines with multiple dest? - ansible

I have many lines need to add, like
today
is
a
good
day
if just one dest, will be
- name: add line
lineinfile:
dest: "/tmp/aaa.txt"
line: "{{ item }}"
with_items:
- "toady"
- "is"
- "a"
- "good"
- "day"
and then, also have many files need to add, like
aaa.txt
bbb.txt
ccc.txt
if just one line, will be
- name: add line
lineinfile:
dest: "{{ item }}"
line: "today"
with_items:
- "/tmp/aaa.txt"
- "/tmp/bbb.txt"
- "/tmp/ccc.txt"
Now I need mix them, both have all dest and all line, but I can't try it success.
Both of them are an array or object, I try many method still fail.
Helppppppp please :(
Thanks everyone

Althought this looks a bit weird to me and that I think you should probably consider using blockinfile or even better a template, there is a solution to your exact question. One possibility here with the product filter:
- name: Add several lines to several files
vars:
lines:
- today
- is
- a
- good
- day
files:
- a.txt
- b.txt
- c.txt
lineinfile:
line: "{{ item.0 }}"
dest: "{{ item.1 }}"
loop: "{{ lines | product(files) }}"

Use nested. For example
- hosts: localhost
vars:
files: [aaa.txt, bbb.txt, ccc.txt]
lines: [today, good, day]
tasks:
- lineinfile:
create: true
dest: "/tmp/{{ item.0 }}"
line: "{{ item.1 }}"
with_nested:
- "{{ files }}"
- "{{ lines }}"
gives
shell> cat /tmp/aaa.txt
today
good
day
shell> cat /tmp/bbb.txt
today
good
day
shell> cat /tmp/ccc.txt
today
good
day

Related

Ansible - loop over multiple items in stdout_lines

I am performing a grep with multiple items.
---
- hosts: my_host
gather_facts: false
vars:
my_list:
- whatever
- something
tasks:
- name: grep for item in search path
shell: "grep -rIL {{ item }} /tmp"
register: the_grep
loop: "{{ my_list }}"
- debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ the_grep.results }}"
Depending on the result, multiple files could match.
msg:
- /tmp/something.conf
- /tmp/folder/file.txt
Q: How would I configure Ansible to loop over the items in stdout_lines?
The use case I'm solving is to delete .ini sections based on the item, but in this case, Ansible doesn't loop over the stdout_lines.
- name: remove stanza from ini file
ini_file:
path: "{{ item.stdout_lines }}"
section: "{{ item.item }}"
mode: '0600'
state: absent
loop: "{{ the_grep.results }}"
when: item.stdout_lines | length > 0
It seems that this doesn't work, but configuring item.stdout_lines[0] gives the partially expected result, since Ansible will use only the first item in that list. But ofc, not the 2nd and so on.
Perhaps there's a prettier answer, but solved it by using with_nested and creating a json_query:
- name: remove stanza from ini file
ini_file:
path: "{{ item.0 }}"
section: "{{ item.1.item }}"
mode: '0600'
state: absent
with_nested:
- "{{ the_grep | json_query('results[].stdout_lines[]') }}"
- "{{ the_grep.results }}"

How to add multiple lines in all files present in a directory using Ansible

In Ansible script, First I'm using find_module to find all files in a directory, and then I'm using set_fact to mention all commands that I want to add in all files and then I am using lineinfile module to add multiple lines in all the files, but it is adding all commands in list format ['line1','line2','line3'] instead of this I want these lines to be added one after another in all files.
Below mentioned is the script
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- set_fact:
lines:
- "line1"
- "line2"
- "line3"
- lineinfile:
path: "{{ item.path }}"
line: "{{ lines}}"
state: present
create: yes
backup: yes
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2
This works for me:
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- blockinfile:
path: "{{ item.path }}"
state: present
create: yes
backup: yes
block: |
line1
line1
line1
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2
So... the old way, which I prefer, is with_nested:
set_fact:
lines:
- line1
- line2
- line3
lineinfile:
...
...
with_nested:
- "{{ c1.files }}"
- "{{ lines }}"
The new way, and I have no idea what escapee from a Ph.D. CS program came up with this, is to combine the lists:
set_fact:
lines:
- line1
- line2
- line3
lineinfile:
...
...
loop: "{{ c1.files | product | lines | list }}"
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-nested-with-cartesian
If all the lines are together, you might want to look at blockinfile.
I was able to add multiple lines in all files present in a directory by using below mentioned ansible script, However the problem now is all lines are getting duplicated, if I run this script again, Please suggest any way so that lines won't get duplicated in all files
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- set_fact:
lines:
- "line1"
- "line2"
- "line3"
- lineinfile:
path: "{{ item.path }}"
line: "{{ lines[0]+'\n'+lines[1]+'\n'+lines[2]+'\n'}}"
state: present
create: yes
backup: yes
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2

How do I check a list of directories from a file, but some entries have multiple directories separated by a comma

I have a text file that looks like this:
hints_directory: /opt/cassandra/hints
data_file_directory: /opt/cassandra/data,/opt/cassandra/data2
commitlog_directory: /opt/cassandra/commitlog
cdc_raw_directory: /opt/cassandra/cdc_raw
saved_caches_directory: /opt/cassandra/saved_caches
tmp_directory: /opt/cassandra/tmp
jna_directory: /opt/cassandra/jna
dump_directory: /opt/cassandra/dump
I want to read that file and check for the existence of each directory using ansible. The tricky part comes on the second line, where the line contains 2 directory paths. How do I get ansible to read each line of the file as in the example below and check for the existence of each directory.
Here is what I have thus far, but it stumbles on the data_file_directory due to having multiple path values for the single line. I've tried to split the line entry, but that doesn't solve the issue.
Thoughts?
- name: Ensure the directories exist
file:
path: "{{item.split(':')[1].split(' ')[1].split(',')}}"
state: directory
with_lines: cat <<pathtofile>>/directories.txt
Read the variables from the file into a dictionary and convert the strings to lists, e.g.
- include_vars:
file: directories.txt
name: dirs_str
- set_fact:
dirs_list: "{{ dict(_keys|zip(_vals)) }}"
vars:
_keys: "{{ dirs_str.keys()|list }}"
_vals: "{{ dirs_str.values()|map('split', ',')|list }}"
gives
dirs_list:
cdc_raw_directory:
- /opt/cassandra/cdc_raw
commitlog_directory:
- /opt/cassandra/commitlog
data_file_directory:
- /opt/cassandra/data
- /opt/cassandra/data2
dump_directory:
- /opt/cassandra/dump
hints_directory:
- /opt/cassandra/hints
jna_directory:
- /opt/cassandra/jna
saved_caches_directory:
- /opt/cassandra/saved_caches
tmp_directory:
- /opt/cassandra/tmp
Select and flatten the values, e.g.
- set_fact:
allDirectories: "{{ dirs_list.values()|flatten }}"
gives
allDirectories:
- /opt/cassandra/hints
- /opt/cassandra/data
- /opt/cassandra/data2
- /opt/cassandra/commitlog
- /opt/cassandra/cdc_raw
- /opt/cassandra/saved_caches
- /opt/cassandra/tmp
- /opt/cassandra/jna
- /opt/cassandra/dump
, or use the expression directly in the loop
- name: Ensure the directories exist
file:
path: "{{ item }}"
state: directory
loop: "{{ dirs_list.values()|flatten }}"
For anyone else that may have a similar exercise, I have figured out how to do this:
- set_fact:
allDirectories: []
- set_fact:
allDirectories: "{{ item.split(':')[1].split(' ')[1].split(',') + allDirectories}}"
with_lines: cat <<pathtofile>>/directories.txt
- name: Ensure the directories exist
file:
path: "{{item}}"
state: directory
with_items: "{{allDirectories}}"

Is there any method to map multiple attributes in ansible

I have an output from yum list module. The thing is I want to display the output without those number (epoch attribute). The problem is I couldn't find any solutions of mapping 2 attributes (name and version). All the solutions I found are connected to only 1 attribute ( envra) in my case.
- name: check packages
become: true
yum:
list: installed
register: output
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item }}"
with_items:
- "{{ output.results | map(attribute='envra') |list }}"
delegate_to: localhost
This is the output without any mapping. As you can see there are multiple attributes. I would like to display only name and version of the package.
10.112.65.15 {u'envra': u'0:GeoIP-1.5.0-14.el7.x86_64', u'name': u'GeoIP', u'repo': u'installed', u'epoch': u'0', u'version': u'1.5.0', u'release': u'14.el7', u'yumstate': u'installed', u'arch': u'x86_64'}
The closest to expected values is envra attribute, but still has those epoch number inside...
10.112.65.15 0:GeoIP-1.5.0-14.el7.x86_64
As I mentioned at the begging I would like to get output of something like that
10.112.65.15 GeoIP 1.5.0
or at least without epoch attribute.
I've also change approach and tried this method
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item }} "
with_items:
- "{{ output | json_query(my_query) | list }}"
delegate_to: localhost
vars:
my_query: "results[].[name, version]"
but received result was with '[]' and u' which I'd like to delete but don't exactly know how.
10.112.65.15 [u'GeoIP', u'1.5.0']
Why do you extract the attribute or use json query ? Simply use the hash and print out the needed fields. The following should work out of the box.
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item.name }} {{ item.version }}"
with_items:
- "{{ output.results }}"
delegate_to: localhost

Ansible, iterate over multiple registers and make extended use of build in methods

To start off I have all my variables defined in YAML
app_dir: "/mnt/{{ item.name }}"
app_dir_ext: "/mnt/{{ item.0.name }}"
workstreams:
- name: tigers
service_workstream: tigers-svc
app_sub_dir:
- inbound
- inbound/incoming
- inbound/error
- inbound/error/processed
- name: lions
service_workstream: lions-svc
app_sub_dir:
- inbound
- inbound/incoming
- inbound/error
- inbound/error/processed
You may note app_dir: "/mnt/{{ item.name }}" and app_dir_ext: "/mnt/{{ item.0.name }}" looking odd, so I originally had my variables set as below in YAML but decided to use the above mainly due to less lines in YAML when I have a large amount of workstreams.
workstreams:
- name: tigers
service_workstream: tigers-svc
app_dir: /mnt/tigers
...
I then have Ansible code to check if the directories exists, if not create them and apply permissions (!note, have taken this approach due to a ssh timeout on operation when using the file: module on a number of very big NFS mounted shares).
- name: Check workstreams app_dir
stat:
path: "{{ app_dir }}"
register: app_dir_status
with_items:
- "{{ workstreams }}"
- name: Check workstreams app_sub_dir
stat:
path: "{{ app_dir_ext }}/{{ item.1 }}/"
register: app_sub_dir_status
with_subelements:
- "{{ workstreams }}"
- app_sub_dir
- name: create workstreams app_dir
file:
path: "/mnt/{{ item.0.name }}"
state: directory
owner: "ftp"
group: "ftp"
mode: '0770'
recurse: yes
with_nested:
- '{{ workstreams }}'
- app_dir_status.results
when:
- '{{ item.1.stat.exists }} == false'
This is a little hacky but works, however I have a 2nd, 3rd, 4th path to check..etc
My question here is how to I update/refactor the above code to use <register_name>.stat.exists == false from both app_dir_status and app_sub_dir_status to control my task ?
You don't need to make nested loop! There's all required data inside app_sub_dir_status – just strip unnecessary items.
Here's simplified example:
---
- hosts: localhost
gather_facts: no
vars:
my_list:
- name: zzz1
sub:
- aaa
- ccc
- name: zzz2
sub:
- aaa
- bbb
tasks:
- stat:
path: /tmp/{{ item.0.name }}/{{ item.1 }}
register: stat_res
with_subelements:
- "{{ my_list }}"
- sub
- debug:
msg: "path to create '{{ item }}'"
with_items: "{{ stat_res.results | rejectattr('stat.exists') | map(attribute='invocation.module_args.path') | list }}"
You can iterate over stat_res.results | rejectattr('stat.exists') | list as well, but will have to construct path again as /tmp/{{ item.item.0.name }}/{{ item.item.1 }} – note double item, because first item is an element of stat_res.results which contain another item as element of your original loop for stat task.
P.S. Also I see no reason for your first task, as subdir task can detect all missing directories.

Resources