issue with ansible fetch module - ansible

I am using the below Ansible code to get the file system details (NAME,MOUNTPOINT,FSTYPE,SIZE) from node servers to control server. I am not getting any issues while running the playbook. But the CSV file is not copied to control machine.
Can anyone please help me on this?
tasks:
- name: Fsdetails
shell: |
lsblk -o NAME,MOUNTPOINT,FSTYPE,SIZE > $(hostname).csv
register: fsdetails_files_to_copy
- name: Fetch the fsdetails
fetch:
src: "{{ item }}"
dest: /data3/deployments/remediation
flat: yes
with_items: "{{ fsdetails_files_to_copy.stdout_lines }}"
Output:
PLAY [all] ************************************************************************************************
TASK [Gathering Facts] ************************************************************************************
ok: [10.xxx.xxx.xx]
TASK [Fsdetails] ******************************************************************************************
changed: [10.xxx.xxx.xx]
TASK [Fetch the fsdetails] ********************************************************************************
PLAY RECAP ************************************************************************************************
10.xxx.xxx.xx : ok=2 changed=1 unreachable=0 failed=0

Your shell command is not returning anything, since it is writing the output to the CSV file. Because of this, your fetch task has nothing to loop on (stdout_lines is an empty list).
What you could do is make your shell task echo the CSV name $(hostname):
- name: Fsdetails
shell: |
lsblk -o NAME,MOUNTPOINT,FSTYPE,SIZE > $(hostname).csv && echo $(hostname).csv
register: fsdetails_files_to_copy
This way, your fetch task will pick the correct filename to download.

Related

How to loop multiple tasks in Ansible? [duplicate]

I need to check if a file named deploy.db exists. If it does not exist, I need to perform a set of tasks for which I am using a block.
Below is how I run the playbook
ansible-playbook test.yml \
-e Layer=APP \
-e BASEPATH="/logs" \
-e Filenames="file1,file2,file3"
Here is the playbook test.yml:
---
- name: "Play 1"
hosts: localhost
gather_facts: false
tasks:
- name: Construct
debug:
msg: "Run"
- block:
- stat: path="{{ BASEPATH }}/deploy.db"
register: currdb
- file: path="{{ BASEPATH }}/deploy.db" state=touch recurse=no
when: currdb.stat.exists == False
- shell: "echo done>>{{ BASEPATH }}/deploy.db"
when: currdb.stat.exists == False
when: Layer == 'APP'
with_items:
- "{{ Filenames.split(',') }}"
I am getting the below error running the playbook:
ERROR! 'with_items' is not a valid attribute for a Block
The error appears to be in '/app/test.yml': line 9, column 6, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- block:
^ here
After researching a bit, I understand that neither with_items nor loop is supported by a block and the solution is to include a tasks file.
I am, however, not sure how to get that to work. Can you suggest what tweaks I need in order to make my playbook work?
Considering I am on the latest version of Ansible, are there other solutions?
TL;DR
'with_items' is not a valid attribute for a Block
The error message says it all: you cannot loop over a block.
If you need to loop over a set of tasks, put them in a separate file and use include_tasks
Implementation (and some good practice...)
Below is an implementation based on your example illustrating the solution.
Since your question and code lacks some precision and since I pointed out some bad practices, please note that:
I fixed the looped code to effectively use the filenames you loop on (I inferred it was supposed to the deploy.db file). Note the use of loop_control to disambiguate the variable name in the included file (i.e. db_filename).
I made the code idempotent as much as possible by using the ansible module copy in place of shell and dropped the touch phase.
I transformed the var names to all lowercase and underscore separator.
To make sure the copy task works on all occasion, I replaced the removed tasks with a single making sure the basepath dir exists.
I added a unique filter after filenames.split(',') as well as a trim filter on each value to remove possible duplicates and eventual spaces added by error in the coma separated list.
I used not keyword and bool filter (for extra security) rather than a bare compare to a boolean False value.
Here is the included file create_db_each.yml
---
- name: Check if file exists
stat:
path: "{{ basepath }}/{{ db_filename }}"
register: currdb
- name: Create the file with "done" line if not present
copy:
content: "done"
dest: "{{ basepath }}/{{ db_filename }}"
when: not currdb.stat.exists | bool
used in the following create_db.yml playbook
---
- name: "Create my dbs"
hosts: localhost
gather_facts: false
tasks:
- name: Make sure the base directory exists
file:
path: "{{ basepath }}"
state: directory
- name: load each db
include_tasks: "create_db_each.yml"
when: layer == 'APP'
loop: "{{ filenames.split(',') | unique | map('trim') }}"
loop_control:
loop_var: db_filename
which gives
notes:
first run only, run it again on your side to witness it reports OK everywhere
see the filenames parameter value to illustrate the use of unique and trim
$ ansible-playbook -e basepath=/tmp/my/base/path -e "filenames='a.bla, b.toto, c , z.txt,a.bla'" -e layer=APP create_db.yml
PLAY [Create my dbs] ************************************************
TASK [Make sure the base directory exists] **************************
changed: [localhost]
TASK [load each db] *************************************************
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=a.bla)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=b.toto)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=c)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=z.txt)
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
PLAY RECAP **********************************************************
localhost: ok=13 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ tree /tmp/my/base/path/
/tmp/my/base/path/
├── a.bla
├── b.toto
├── c
└── z.txt
$ for f in /tmp/my/base/path/*; do cat $f; echo; done
done
done
done
done

Execute all yaml files from different directory

I have a directory I have created with several sub-tasks but I'm having trouble in making Ansible run all tasks from inside the specified directory.
The script looks like this:
---
- hosts: localhost
connection: local
tasks:
# tasks file for desktop
- name: "LOADING ALL TASKS FROM THE 'SUB_TASKS' DIRECTORY"
include_vars:
dir: sub_tasks
extensions:
- 'yml'
And this is the output:
plbchk main.yml --check
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not
match 'all'
PLAY [localhost] ****************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************
ok: [localhost]
TASK [LOADING ALL TASKS FROM THE 'SUB_TASKS' DIRECTORY] *************************************************************
fatal: [localhost]: FAILED! => {"ansible_facts": {}, "ansible_included_var_files": [], "changed": false, "message": "/home/user/Documents/ansible-roles/desktop/tasks/sub_tasks/gnome_tweaks.yml must be stored as a dictionary/hash"}
PLAY RECAP **********************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
I've tried all sorts of ways to make it run the sub-tasks but to no avail.
I'd like to do it this way instead of creating one big file containing all the tasks. Is this possible?
include_vars is not for including tasks , this is to include vars (as its name suggest). Also If you check the error message it says "must be stored as a dictionary/hash.
fatal: [localhost]: FAILED! => {"ansible_facts": {}, "ansible_included_var_files": [], "changed": false, "message": "/home/user/Documents/ansible-roles/desktop/tasks/sub_tasks/gnome_tweaks.yml must be stored as a dictionary/hash"}
Solution:
You need to use include_taskfor what you are trying. Check out here.
Here is a complete/minimal working example, here we are making a list of yaml or yml files present in a provided directory and then running include_tasks over loop for all the files.
---
- name: Sample playbook
connection: local
gather_facts: false
hosts: localhost
tasks:
- name: Find all the yaml files in the directory
find:
paths: /home/user/Documents/ansible-roles/desktop/tasks
patterns: '*.yaml,*.yml'
recurse: yes
register: file_list
- name: show the yaml files present
debug: msg="{{ item }}"
loop: "{{ file_list.files | map(attribute='path') | list }}"
- name: Include task list in play
include_tasks: "{{ item }}"
loop: "{{ file_list.files | map(attribute='path') | list }}"

Ansible: Save host names and results of linux command in dictionary format

I want to save the hosts name and results of linux command in the dictionary format. The problem is I cannot successfully get the dictionary format, and the new line stored in the results.txt file will replace the previous lines.
---
- hosts: "{{variable_host | default('lsbxdmss001')}}"
tasks:
- name: Check Redhat version for selected servers
shell:
cmd: rpm --query redhat-release-server
warn: False
register: myshell_output
- debug: var=myshell_output
- name: set fact
set_fact: output = "{{item.0}}:{{item.1}}"
with_together:
- groups['{{variable_host}}']
- "{{myshell_output.stdout}}"
register: output
- debug: var=output
- name: copy the output to results.txt
copy:
content: "{{output}}"
dest: results.txt
delegate_to: localhost
Looks like you have four issues:
Your dictionary creation seems odd, a dictionary in python is enclosed in curly bracers { ... }
So you line dictionary
"{{item.0}}:{{item.1}}"
Should rather be
"{{ {item.0: item.1} }}"
Adding to a file, in Ansible, is done via the module lineinfile rather than the copy one.
Your loop doesn't really makes sense, as Ansible is already executing each task on all hosts, so the reason you have only one information is also coming from this: you keep on overriding the output fact, in your trial to do it.
For the same reason as above, instead of your with_together loop, you should use the special variable inventory_hostname, as this is
The inventory name for the ‘current’ host being iterated over in the play
Source: https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html#special-variables
So after your shell command, here should be the next task:
- lineinfile:
path: results.txt
line: "{{ {inventory_hostname: myshell_output.stdout} | string }}"
create: yes
delegate_to: localhost
Mind that the string filter was added in order to convert the dictionary into a string to write it into the file without Ansible issuing a warning.
An example of playbook would be:
- hosts: all
gather_facts: no
tasks:
- name: Creating a fake shell result
shell:
cmd: echo 'Linux Vlersion XYZ {{ inventory_hostname }}'
register: shell_output
- lineinfile:
path: results.txt
line: "{{ {inventory_hostname: shell_output.stdout} | string }}"
create: yes
delegate_to: localhost
Which gives the recap:
PLAY [all] ******************************************************************************************************************************************
TASK [Creating a fake shell result] *****************************************************************************************************************
changed: [host2]
changed: [host1]
TASK [lineinfile] ***********************************************************************************************************************************
changed: [host1 -> localhost]
changed: [host2 -> localhost]
PLAY RECAP ******************************************************************************************************************************************
host1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
And fill the results.txt file with:
{'host1': 'Linux Vlersion XYZ host1'}
{'host2': 'Linux Vlersion XYZ host2'}

Using wildcards in ansible playbooks

I'm running a script remotely on an ansible client which creates a filename HOSTNAME.html.
I plan to run this on numerous clients in one go, so I need to be able to copy all the files *.html from all the clients.
I'm new to this so have a little playbook.
---
- hosts: tariq-test
tasks:
- name: run script
script: /usr/bin/cfg2html-linux
- name: fetch file
fetch:
src: /root/*.html
dest: /tmp/
flat: yes
And get this:
[root#ansible playbooks]# ansible-playbook scp.yml
PLAY [tariq-test] *******************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************
ok: [tariq-test]
TASK [run script] *******************************************************************************************************************************************
changed: [tariq-test]
TASK [fetch file] *******************************************************************************************************************************************
fatal: [tariq-test]: FAILED! => {"changed": false, "msg": "file not found: /root/*.html"}
to retry, use: --limit #/etc/ansible/playbooks/scp.retry
PLAY RECAP **************************************************************************************************************************************************
tariq-test : ok=2 changed=1 unreachable=0 failed=1
(Not tested)
Option 1:
If the filename is hostname.html and there is only one file
fetch:
src: "/root/{{ansible_hostname}}.html"
dest: /tmp/
flat: yes
Option 2:
Find the files with *.html pattern and then use fetch within loop.
- name: Find files
find:
path: "{{folder}}"
pattern: "*.html"
register: html_files
- name: Fetch files
fetch:
src: "{{item.path}}"
dest: /tmp/
flat: yes
with_items: "{{html_files.files}}"

Unable to get complete output of command module in Ansible

I need to store in variable & print the output of "ls -ltr" for a set of files on remote host. This variable with be used by another play in the same yml.
I tried the following however, it just prints the filename and not the complete results of "ls -ltr" command.
My lstest.yml looks like below:
- name: Play 4- Configure nodes
hosts: remotehost
tasks:
- name: "Collecting APP file information"
command: ls -ltr /app/{{ vars[item.split('.')[1]] }}/{{ item | basename }}
register: fdetails_APP
when: Layer == 'APP'
with_fileglob:
- "{{ playbook_dir }}/tmpfiles/*"
- debug: var=fdetails_APP.stdout_lines
- set_fact: fdet_APP={{ fdetails_APP.stdout_lines }}
- name: Printing fpath
debug:
var: fdet_APP
Output:
TASK [Collecting APP file information]
************************************************************************************ changed: [localhost] =>
(item=/app/Ansible/playbook/tmpfiles/filename1.exe) changed:
[localhost] => (item=/app/Ansible/playbook/tmpfiles/33211.sql)
changed: [localhost] =>
(item=/app/Ansible/playbook/tmpfiles/file1.mrt) changed: [localhost]
=> (item=/app/Ansible/playbook/tmpfiles/filename1.src)
PLAY RECAP
************************************************************************************************************************************************** localhost : ok=3 changed=1 unreachable=0
failed=0 skipped=0 rescued=0 ignored=0
Can you please suggest.
Note. In near future I would also like to add the checksum of the files stored in the same variable.
As per fileglob documentation- "Matching is against local system files on the Ansible controller. To iterate a list of files on a remote node, use the find module." For remote host fileglob cannot be used.

Resources