Ansible playbook loop with with_items - linux

I have to update sudoers.d multiple user files with few lines/commands using ansible playbook
users.yml
user1:
- Line1111
- Line2222
- Line3333
user2:
- Line4444
- Line5555
- Line6666
main.yml
- hosts: "{{ host_group }}"
vars_files:
- ../users.yml
tasks:
- name: Add user "user1" to sudoers.d
lineinfile:
path: /etc/sudoers.d/user1
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ user1 }}"
The above one is working only for user1..
If I want to also include user2 --> How to change the file name : path: /etc/sudoers.d/user1
I tried below and its not working :
Passing below users as variable to main.yml while running
users:
- "user1"
- "user2"
- name: Add user "{{users}}" to sudoers.d
lineinfile:
path: /etc/sudoers.d/{{users}}
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
So, basically I want to pass in users to a variable {{users}} as user1 and user2 and wanted to use the lines for each user from users.yml and add it to respective user files (/etc/sudoers.d/user1 and /etc/sudoers.d/user2).
So /etc/sudoers.d/user1 should look like
Line1111
Line2222
Line3333
and /etc/sudoers.d/user2 should look like
Line4444
Line5555
Line6666

Try to add quotes:
users:
- "user1"
- "user2"
- name: "Add user {{users}} to sudoers.d"
lineinfile:
path: "/etc/sudoers.d/{{users}}"
line: "{{ item }}"
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
As per Ansible Documentation on Using Variables:
YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be sure you aren’t trying to start a YAML dictionary. This is covered on the YAML Syntax documentation.
This won’t work:
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
Do it like this and you’ll be fine:
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"

cat users.yml
---
users:
- user1:
filename: user1sudoers
args:
- Line1111
- Line2222
- Line3333
- user2:
filename: user2sudoers
args:
- Line4444
- Line5555
- Line6666
I use template here, instead of lineinfile
---
cat sudoers.j2
{% if item.args is defined and item.args %}
{% for arg in item.args %}
{{ arg }}
{% endfor %}
{% endif %}
the task content
---
- hosts: localhost
vars_files: ./users.yml
tasks:
- name: sync sudoers.j2 to localhost
template:
src: sudoers.j2
dest: "/tmp/{{ item.filename }}"
loop: "{{ users_list }}"
when: "users_list is defined and users_list"
after run the task.yml, generate two files under /tmp directory.
cat /tmp/user1sudoers
Line1111
Line2222
Line3333
cat /tmp/user2sudoers
Line4444
Line5555
Line6666

Related

Multiple with_items in an Ansible module block

I want to create multiple logical volumes with a variable file but it return a sintax error found character that cannot start any token, I have tried in different ways but still doesn't work
main.yml
---
- name: playbook for create volume groups
hosts: localhost
become: true
tasks:
- include_vars: vars.yml
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item.var1 }}"
size: "{{ item.var2 }}"
with_items:
- { var1: "{{ var_lv_name }}", var2: "{{ var_lv_size }}" }
vars.yml
var_lv_name:
- lv05
- lv06
var_lv_size:
- 1g
- 1g
Use with_together. Test it first. For example,
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
with_together:
- "{{ var_lv_name }}"
- "{{ var_lv_size }}"
gives (abridged)
msg: 'Create lv: lv05 size: 1g'
msg: 'Create lv: lv06 size: 1g'
Optionally, put the declaration below into the file vars.yml
var_lv: "{{ var_lv_name|zip(var_lv_size) }}"
This creates the list
var_lv:
- [lv05, 1g]
- [lv06, 1g]
Use it in the code. The simplified task below gives the same results
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
loop: "{{ var_lv }}"
The previous answer it's totally correct but In my humble opinion we should be getting into the new way to do the things with loop and filters.
Here's my answer:
---
- name: playbook for create volume groups
hosts: localhost
gather_facts: no
become: true
vars_files: vars.yml
tasks:
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item[0] }}"
size: "{{ item[1] }}"
loop: "{{ var_lv_name | zip(var_lv_size) | list }}"
In this answer you're using the new way to use loops with keyword loop and using filters like zip and turning the result into a list type for iteration in the loop.

Fetch files and remove them from source if succesful

I've been using Ansible to fetch files from Windows nodes to a Linux node for some time with good results.
I would now like the nodes to remove fetched files once they have uploaded successfully.
However, since I'm fetching from lots of endpoints in various states, some files occasionally fail to transfer - and I'm having trouble using Ansible to skip those files, and those files only.
Here's what I have so far:
- name: Find .something files
ansible.windows.win_find:
paths: 'C:\Some\Path'
patterns: [ '*.something' ]
recurse: yes
age_stamp: ctime
age: -1w
register: found_files
- name: Create destination directory
file:
path: "/some/path/{{inventory_hostname}}/"
state: directory
delegate_to: localhost
- name: Fetch .something files
fetch:
src: "{{ item.path }}"
dest: "/some/path/{{inventory_hostname}}/"
flat: yes
validate_checksum: no
with_items: "{{ found_files.files }}"
register: item.sync_result
ignore_errors: yes
msg: "Would remove {{ item.path }}"
when: sync_result is succeeded
with_items: "{{ found_files.files }}"
The problem is, the sync_result variable seems to apply to each node instead of each file - that is, if one file has failed to transfer, no files will be deleted.
I've tried various loops and lists and could not get it to work.
Any pointers would be greatly appreciated.
In a nutshell:
- name: Find .something files
ansible.windows.win_find:
paths: 'C:\Some\Path'
patterns: [ '*.something' ]
recurse: yes
age_stamp: ctime
age: -1w
register: find_something
- name: Create destination directory
file:
path: "/some/path/{{ inventory_hostname }}/"
state: directory
delegate_to: localhost
- name: Fetch .something files
fetch:
src: "{{ item.path }}"
dest: "/some/path/{{ inventory_hostname }}/"
flat: yes
validate_checksum: no
loop: "{{ find_something.files }}"
register: fetch_sync
ignore_errors: yes
- name: Delete successfully fetched files
file:
path: "{{ item.file }}"
state: absent
loop: "{{ fetch_sync.results | select('succeeded') }}"
# If you are using ansible < 2.10 you need to cast to list i.e.
# loop: "{{ fetch_sync.results | select('succeeded') | list }}"

Ansible - load multiplne local yml files and merge their content

my host_vars file has about 5k lines of yml code. So I would like to have separate yml files - one file per one service.
Simplified example:
user#test $ cat production/split_configs/a.example.net.yml
my_array:
- a.example.net
user#test $ cat production/split_configs/b.example.net.yml
my_array:
- b.example.net
user#test $ cat webhosts.yml
- hosts: myservers
pre_tasks:
- name: merge ansible arrays
tags: always
delegate_to: localhost
block:
- name: find config files
find:
paths: production/configs/
patterns: '*.yml'
register: find_results
- name: aaa
debug:
msg: "{{ find_results.files }}"
- name: bbb
debug:
msg: "{{ item.path }}"
with_items: "{{ find_results.files }}"
- name: ccc
debug:
msg: "{{ lookup('file', 'production/configs/a.example.net.yml') }}"
- name: ddd
debug:
msg: "{{ lookup('file', item.path) }}"
loop: "{{ find_results.files }}"
tasks:
- name: eee
debug:
msg: "{{ my_array }}"
The goal is to merge content of both arrays an print the merged content in task eee:
my_array:
- a.example.net
- b.example.net
Task aaa print informations about files (path, mode, uid, ...) - it works.
Tasks bbb, and ddd print nothing. I do not understand why.
Task ccc print content of file. But the path is written in playbook :-(
After load files I need to merge them. My idea is to use something like set_fact: my_array="{{ my_array + my_array }}" in task with with_items: "{{ find_results.files }}". Is it good idea? Or how better I can do it?
For example, the tasks below do the job
- include_vars:
file: "{{ item }}"
name: "my_prefix_{{ item|regex_replace('\\W', '_') }}"
with_fileglob: production/split_configs/*
- set_fact:
my_vars: "{{ my_vars|d({})|
combine(lookup('vars', item),
recursive=True,
list_merge='append') }}"
loop: "{{ q('varnames', 'my_prefix_.*') }}"
give
my_vars:
my_array:
- a.example.net
- b.example.net
You can use the simple cat commnd to merge the files into the one file and later include var file for example -
- raw: "cat production/split_configs/* >my_vars.yml"
- include_vars: file=my_vars.yml name=my_vars
will give you the result -
my_vars:
my_array:
- a.example.net
- b.example.net

Ansible to execute a task only when multiple files exist

I want to execute a task only when multiple files exist . if only single file is exist i need to ignore this task. How can i achieve this.
Am unable to achieve with the below playbook
---
- name: Standardize
hosts: test
gather_facts: false
vars:
file_vars:
- {id: 1, name: /etc/h_cm}
- {id: 2, name: /etc/H_CM}
tasks:
- block:
- name: Check if both exists
stat:
path: "{{ item.name }}"
with_items: "{{ file_vars }}"
register: cm_result
- name: Move both files
shell: mv "{{ item.item }}" /tmp/merged
with_items: "{{ cm_result.results }}"
when: item.stat.exists
After check if both exist task, you can add a set fact task like this one:
- name: set facts
set_fact:
files_exist: "{{ (files_exist | default([])) + [item.stat.exists] }}"
with_items: "{{ cm_result.results }}"
And you change your move files task to:
- name: Move both files
debug:
msg: "{{ item.stat.exists }}"
with_items: "{{ cm_result.results }}"
when: false not in files_exist
You have to specify shell: mv "{{ item.item.name }}" /tmp/merged insted of shell: mv "{{ item.item }}" /tmp/merged
Check the below works?:
- name: Standardize
hosts: test
gather_facts: false
become: yes ## If needed
vars:
file_vars:
- {id: 1, name: /etc/h_cm}
- {id: 2, name: /etc/H_CM}
tasks:
- block:
- name: Check if both file exists
stat:
path: "{{ item.name }}"
with_items: "{{ file_vars }}"
register: cm_result
- debug:
var: item.stat.exists
loop: "{{ cm_result.results }}"
- name: Crate a dummy list
set_fact:
file_state: []
- name: Add true to list if file exists
set_fact:
file_state: "{{ file_state }} + ['{{ item.stat.exists }}']"
loop: "{{ cm_result.results }}"
when: item.stat.exists == true
- name: Move both files
shell: mv "{{ item.item.name }}" /tmp/merged
loop: "{{ cm_result.results }}"
when: file_state|length > 1

How to rename a file in ansible

I need to rename hosts_example or hosts_Example to be named as hosts_real if any of the file exists
- name: Playbook to Standardize Hosts
hosts: test
vars:
destpath: /etc/hosts_real
filename: [ /etc/hosts_example,/etc/hosts_Example ]
tasks:
- name: Check if file exists
stat:
path: "{{ item }}"
with_items:
- "{{ filename }}"
register: check_file_name
- debug:
msg: "{{check_file_name}}"
- name: Rename file
command: mv "{{ item }}"{{destpath}}"
with_items:
- "{{ check_file_name.results }}"
when: item.stat.exists == true
I tried this am getting errors and not able to achieve the desired result
name: replace file
shell: mv /local/oracle/12.2/oldora.ora /local/oracle/12.2/tnsnames.ora
become: appuser
Works for us. Update your path in the shell command. I dont think the file module has a rename command. If you have a variable in your shell line put the whole line in quotes like:
shell: "mv {{ path_1 }} {{ path_2 }}"

Resources