How to use ansible blockinfile on multiple files? - ansible

hi I would like to use the blockinfile function on multiple files going to change blocks of text in each of them.
unfortunately blockinfile does not support this function. can someone help me?

To use blockinfile on multiple files to change blocks of text ...
you might want to create templates and loop the blockinfile module.
- blockinfile:
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.template }}"
create: yes
path: "{{ item.file }}"
block: "{{ lookup('template', item.template) }}"
loop: "{{ files_templates }}"

You can do it like this:
- name: Add same block of text in multiple files/paths
blockinfile:
path: "{{ item.path }}"
marker: "###### {mark} Ansible Config #####"
insertafter: EOF
state: present
block: |
# Some random text comment
Some random command1
Some random command2
with_items:
- {path: '/your/path/one'}
- {path: '/your/path/two'}

Related

ansible lineinfile how add multiple lines with multiple dest?

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

Use Ansible to ensure a file exists, ignoring any extra lines

I'm trying to update the sssd.conf file on about 200 servers with a standardized configuration file, however, there is one possible exception to the standard. Most servers will have a config that looks like this:
[domain/domainname.local]
id_provider = ad
access_provider = simple
simple_allow_groups = unixsystemsadmins, datacenteradmins, sysengineeringadmins, webgroup
default_shell = /bin/bash
fallback_homedir = /export/home/%u
debug_level = 0
ldap_id_mapping = false
case_sensitive = false
cache_credentials = true
dyndns_update = true
dyndns_refresh_interval = 43200
dyndns_update_ptr = true
dyndns_ttl = 3600
ad_use_ldaps = True
[sssd]
services = nss, pam
config_file_version = 2
domains = domainname.local
[nss]
[pam]
However, on some servers, there's an additional line after simple_allow_groups called simple_allow_users, and each server that has this line has it configured for specific users to be allowed to connect without being a member of an LDAP group.
My objective is to replace the sssd.conf file on all servers, but not to remove this simple_allow_users line, if it exists. I looked into lineinfile and blockinfile, but neither of these seems to really handle this exception. I'm thinking I'm going to have to check the file for the existance of the line, store it to a variable, push the new file, and then add the line back, using the variable afterwards, but I'm not entirely sure if this is the best way to handle it. Any suggestions on the best way to accomplish what I'm looking to do?
Thanks!
I would do the following
See if the simple_allow_users exists in the current sssd.conf file
Change your model configuration to add the current value of the line simple_allow_users is exists
overwrite the sssd.conf file with the new content
You can use jinja2 conditional to achieve step 2 https://jinja2docs.readthedocs.io/
I beleive the above tasks will solve what you need, just remember to test on a simngle host and backup the original file just for good measure ;-)
- shell:
grep 'simple_allow_users' {{ sssd_conf_path }}
vars:
sssd_conf_path: /etc/sssd.conf
register: grep_result
- set_fact:
configuration_template: |
[domain/domainname.local]
id_provider = ad
access_provider = simple
simple_allow_groups = unixsystemsadmins, datacenteradmins, sysengineeringadmins, webgroup
{% if 'simple_allow_users' in grep_result.stdout %}
{{ grep_result.stdout.rstrip() }}
{% endif %}
default_shell = /bin/bash
..... Rest of your config file
- copy:
content: "{{ configuration_template }}"
dest: "{{ sssd_conf_path }}"
vars:
sssd_conf_path: /etc/sssd.conf
I used Zeitounator's tip, along with this question Only check whether a line present in a file (ansible)
This is what I came up with:
*as it turns out, the simple_allow_groups are being changed after the systems are deployed (thanks for telling the admins about that, you guys... /snark for the people messing with my config files)
---
- name: Get Remote SSSD Config
become: true
slurp:
src: /etc/sssd/sssd.conf
register: slurpsssd
- name: Set simple_allow_users if exists
set_fact:
simpleallowusers: "{{ linetomatch }}"
loop: "{{ file_lines }}"
loop_control:
loop_var: linetomatch
vars:
- decode_content: "{{ slurpsssd['content'] | b64decode }}"
- file_lines: "{{ decode_content.split('\n') }}"
when: '"simple_allow_users" in linetomatch'
- name: Set simple_allow_groups
set_fact:
simpleallowgroups: "{{ linetomatch }}"
loop: "{{ file_lines }}"
loop_control:
loop_var: linetomatch
vars:
- decode_content: "{{ slurpsssd['content'] | b64decode }}"
- file_lines: "{{ decode_content.split('\n') }}"
when: '"simple_allow_groups" in linetomatch'
- name: Install SSSD Config
copy:
src: etc/sssd/sssd.conf
dest: /etc/sssd/sssd.conf
owner: root
group: root
mode: 0600
backup: yes
become: true
- name: Add simple_allow_users back to file if it existed
lineinfile:
path: /etc/sssd/sssd.conf
line: "{{ simpleallowusers }}"
insertafter: "^simple_allow_groups"
when: simpleallowusers is defined
become: true
- name: Replace simple allow groups with existing values
lineinfile:
path: /etc/sssd/sssd.conf
line: "{{ simpleallowgroups }}"
regexp: "^simple_allow_groups"
backrefs: true
when: simpleallowgroups is defined
become: true

Ansible conditionally loop through with_items?

Is it possible to loop through a list of items if a string is defined in a variable i will specify.
Essentially i want to have a list of variables defined and utilized the aws_s3 module to download the files only if they are defined when running the playbook
e.g
say i have the list "var1,var2"
and I have the following variables defined:
apps_location:
- { name: 'vars1', src: 'vars1.tgz', dest: '/tmp/vars1_file.tgz' }
- { name: 'vars2', src: 'vars2.tgz', dest: '/tmp/vars2_file.tgz' }
- { name: 'vars3', src: 'vars3.tgz', dest: '/tmp/vars3_file.tgz' }
Task:
- name: "Splunk Search Head | Download Splunk Apps from S3"
aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: get
with_items: "{{ apps_location }}"
I want to run the command:
ansible-playbook -i inventory -e "var1,var2"
and download only var1 and var2 on that specific run.
I tried utilizing "lookups" but couldnt get the syntax right. Im not entirely sure if this best way of doing this, but i want to have a predefined list of file locations and only download the ones that i'm passing during runtime.
Note the only reason "name" exists in apps_location is to see if i can do a lookup and only install that one but i couldnt get the syntax right.
Define a variable containing a list of defined apps. I'm trying:
- name: "Set Fact"
set_fact:
dict: "{{ apps_location[item].dest }}"
with_items: "{{ my_vars|default([]) }}"
However whenever I output dict I only get the last value.
Any help would be appreciated :)
The extra-vars must be an assignment of a variable and value. For example
shell> ansible-playbook -i inventory -e "my_vars=['vars1','vars2']"
A more convenient structure of the data would be a dictionary for this purpose. For example
apps_location:
vars1:
src: 'vars1.tgz'
dest: '/tmp/vars1_file.tgz'
vars2:
src: 'vars2.tgz'
dest: '/tmp/vars2_file.tgz'
vars3:
src: 'vars3.tgz'
dest: '/tmp/vars3_file.tgz'
Then the loop might look like
- aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ apps_location[item].src }}"
dest: "{{ apps_location[item].dest }}"
mode: get
loop: "{{ my_vars|default([]) }}"
Q: "Define a variable containing a list of defined apps."
A: Try this
- set_fact:
my_list: "{{ my_list(default([]) +
[apps_location[item].dest] }}"
loop: "{{ my_vars|default([]) }}"
(not tested)

Ansible nested loops, how to set inner loop variable based on outer variable

I'm having problems with a nested loop on ansible.
I'm using ansible 2.5.2 with the following config files:
file hosts:
[group1]
host1
host2
host3
[group2]
hostA
file host_vars/host{N} (where N is the number for each host on group1):
variable:
- { line: "keyB" , file: "keyc"}
- { line: "key2" , file: "key3"}
I need to execute on hostA one task for each line in host_vars/host{N}.
in pseudo code, need something like this:
- name: modify file
for host in groups['group1']:
for item in host['variables']:
lineinfile: "path={{ host }}/{{ item.file }} line={{ item.line }}"
using jinja2 loops does not work:
- name: Modify files
lineinfile: "{% for linea in hostvars[item]['variables'] %}
path={{ item }}/{{ linea.file }}
line={{ linea.line }}
{% endfor %}"
loop: "{{ groups['group1'] }}"
normal nested loops does not work either because the inner loop deppends on the hostname:
- name: Modify files
lineinfile: "path={{ item[0] }}/{{ item[1]['file'] }} line={{ item[1]['line'] }}"
with_nested:
- "{{ groups['group1'] }}"
- "{{ hostvars[item[0]]['variables'] }}"
There is another way to cicle nested loops?
I solved my problem using loop_control,
I add a new file: inner.yml
- name: Modify files
lineinfile: "path={{ outer_item }}/{{ item.file }} line={{ item.line }}"
loop: "{{ hostvars[outer_item]['variables'] }}"
And defined my task file as follows
- include_tasks: inner.yml
loop: "{{ groups['group1'] }}"
loop_control:
loop_var: outer_item
which solves my problem of using two nested variables on loops.

Ansible blockinfile re-print

I am using Ansible's blockinfile feature to insert user's public keys into authorized_keys file. Keys are stored as variable in group_vars.
Inserting works fine, but is there a way to ask blockinfile to print blocks from the beginning every time? What I mean is, if I remove one key from variable, and run playbook it still exists in authorized file, because blockinfile is only printing once.
Can I make it print whole variable again everytime?
Playbook:
- name: Add root authorized keys
blockinfile:
state: present
path: /root/.ssh/authorized_keys
block: |
{{ item.key }} {{ item.label }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.label }}"
backup: yes
with_items: "{{ root_keys }}"
This is how variable looks like:
root_keys:
- { label: somelabel, key: somekey }
- { label: somelabel2, key: somekey2 }
So what I am trying to achieve is that when I remove somekey2 from root_keys variable, it will disappear from authorized_keys file.
Can this be done?
You should use a native Ansible authorized_key for these operations.
As for the question itself if you plan to reorganise root_keys in the following way:
root_keys:
- { label: somelabel, key: somekey }
- { label: somelabel2 }
You can redefine the task to:
- name: Add root authorized keys
blockinfile:
state: "{{ (item.key is defined ) | ternary('present', 'absent') }}"
path: /root/.ssh/authorized_keys
block: |
{{ item.key | default(omit) }} {{ item.label }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.label }}"
backup: yes
with_items: "{{ root_keys }}"
If you want to remove the whole element { label: somelabel2, key: somekey2 }, then you should store all possible label-values in a separate list and iterate over that list checking if an element is present in a union or difference of that all-attributes-list and root_keys|map(attribute='label') to determine if a value should be included or not;
A bad practice-variation would be to parse the file to create this list by parsing the destination file in search for ANSIBLE MANAGED BLOCK. But that, on the other hand would be the only reason you might want to use blockinfile instead of authorized_key module.

Resources