Creating a folder and a filename for the Ansible output failing - ansible

I'm trying to get an output to a file using a Jinja2 template. However the last task 'Write to the file' fails. The idea is to create a dynamic folder based on m-d-yyyy--Hr:Min as the task is running on the same set of devices. Therefore they will be saved to folders based on date and time. The folder gets created. But the file is not. Also if I go to a static folder and use it in a "dest" it also works. Can someone please provide a direction? Thank you in advance.
- name: Create directory
file:
path: ./config-backups/{{ lookup('pipe', 'date +%m-%d-%Y--%H:%M') }}
mode: 0755
recurse: yes
register: folder_name_with_date
- name: Write to the file
ansible.builtin.template:
src: ./templates/show_run.j2
dest: ./{{folder_name_with_date}}/{{ inventory_hostname }}-running-config-{{ lookup('pipe', 'date +%m-%d-%Y--%H:%M') }}.txt
newline_sequence: '\r\n'
Error
fatal: [10.6.66.66]: FAILED! => {"changed": false, "checksum": "699bd34165f85658300254daf40e5322cf7faaa", "msg": "Destination directory ./{'path': './config-backups/03-17-2021--09:58', 'changed': True, 'diff': {'before': {'path': './config-backups/03-17-2021--09:58', 'state': 'absent', 'mode': '0775'}, 'after': {'path': './config-backups/03-17-2021--09:58', 'state': 'directory', 'mode': '0755'}}, 'uid': 1000, 'gid': 1000, 'owner': 'xxxx', 'group': 'xxxx', 'mode': '0755', 'state': 'directory', 'size': 4096, 'failed': False} does not exist"}

Q: "Destination directory ./{'path': './config-backups/03-17-2021--09:58' ... does not exist"}"
A: If you want to create a directory it's necessary to set the attribute state=directory. The default state is file. Create the variable with the name of the directory first. Then use it in the playbook. Given the template
shell> cat templates/show_run.j2
This is show_run.j2
the playbook
- hosts: localhost
tasks:
- name: Set name of the directory
set_fact:
folder_name_with_date: "{{ lookup('pipe', 'date +%m-%d-%Y--%H:%M') }}"
- name: Create directory
file:
state: directory
path: ./config-backups/{{ folder_name_with_date }}
mode: 0755
- name: Write to the file
ansible.builtin.template:
src: ./templates/show_run.j2
dest: "./config-backups/{{ folder_name_with_date }}/{{ inventory_hostname }}-running-config-{{ lookup('pipe', 'date +%m-%d-%Y--%H:%M') }}.txt"
newline_sequence: '\r\n'
gives
shell> tree config-backups
config-backups
└── 03-17-2021--16:42
└── localhost-running-config-03-17-2021--16:42.txt
shell> cat config-backups/03-17-2021--16\:42/localhost-running-config-03-17-2021--16\:42.txt
This is show_run.j2

First, the variable you are registering as folder_name_with_date is a list of items, not just a string. You need to get just the path variable from your folder_name_with_date list. Call it via folder_name_with_date.path to get the path you are looking for.
Also, because you are trying to concatenate variables with a string, I would recommend encapsulating your entire statement with "'s, like so: dest: "./{{folder_name_with_date.path}}/{{ inventory_hostname }}-running-config-{{ lookup('pipe', 'date +%m-%d-%Y--%H:%M') }}.txt"
Not entirely sure whether adding the "'s or not is necessary, but be sure to get the folder_name_with_date's .path instead.
EDIT: For the future, I would recommend outputting what your variables look like after you create them, so you can see how to use them in future tasks. Technically, Ansible will output what the variable is when it is registered, but I also like to include a quick debug like this to view my variables:
name: what is my variables structure??
debug:
msg: "{{ folder_name_with_date }}"
Debug documentation: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/debug_module.html

Related

Ansible win_file is not deleting dirs when looping over item

I am running into a silly issue when i try to delete some folders with win_file
first i copy some folders on the remote itself from one dir to another
- name: copy folders first
win_copy:
src: '{{ item }}'
dest: 'C:\folders\to\copy'
remote_src: yes
loop: '{{ paths_to_copy }}'
register: copied_folders
then i filter only the 'path' of those folders to be deleted later in the play after executng some other tasks.
- name: filter paths to be deleted after some tasks
set_fact:
paths_to_delete: "{{ copied_folders | json_query('results[*].dest') }}"
i get this results:
ok: [<computer>] => {
"ansible_facts": {
"paths_to_delete": [
"C:\\folders\\to\\copy\\1",
"C:\\folders\\to\\copy\\2",
"C:\\folders\\to\\copy\\3",
"C:\\folders\\to\\copy\\4"
]
},
"changed": false
}
all seems good but the playbook is failing when i loop over 'paths_to_delete' because it returns with all those 4 paths as ONE path.
- name: clean up temporary copied directories
win_file:
path: '{{ item }}'
state: absent
loop:
- '{{ paths_to_delete }}'
"msg": "Get-AnsibleParam: Parameter 'path' has an invalid path '['C:\\\\folders\\\\to\\\\copy\\\\1','C:\\\\folders\\\\to\\\\copy\\\\2','C:\\\\folders\\\\to\\\\copy\\\\3','C:\\\\folders\\\\to\\\\copy\\\\4'] specified."
why it is not looping over this list and deletes them one by one?
i am using the same mechanism in the first copy task, looping over a list and it DOES copy the folder one by one without any issue.
Any help would be much appreciated.
Your loop syntax is incorrect.
loop:
- '{{ paths_to_delete }}'
This nests the list inside another list with a single element. What you want to do is loop over the original list:
loop: '{{ paths_to_delete }}'

Find filename in Ansible and Save to a file

How can I take filename from a specific directory and use it as a variable?
Then I'll use this variable to copy a new file with variable filename
For example: In the directory /home/user1/test/ there is always only one file named test1 or some other name.
I need to extract the filename (test1) from this path to some variable
- hosts: linux
become: yes
tasks:
- name: Ansible find file examples
find:
paths: /home/user1/test/
register: files_matched
- debug:
msg: "{{ files_matched.files }}"
- name: "Save find results to file"
copy:
content: "{{ files_matched.files }}"
dest: "/tmp/find_result.txt"
Then I've should get "test1" as variable and use it in the new filename in this code:
copy:
src: /home/myuser/myfile
dest: "{{ item.dest }}"
owner: root
group: root
mode: 0666
with_items:
- { dest: '/home/user2/test/{{ files_matched }}' }
As result of first script i've got:
: 0, "ischr": false, "wusr": true, "xoth": false, "islnk": false, "nlink": 1, "issock": false, "rgrp": true, "gr_name": "root", "path": "/home/user1/test/test1", "xusr":
false, "atime": 1564553299.6092095, "isdir": false, "ctime": 1564553304.7172158, "isblk": false, "xgrp": false, "dev": 2050, "wgrp": false, "isfifo": false, "mode": "0644
", "rusr": true}]
But i need only test1 part as result, not this all.
Thank you!
Q: "How can I take filename from a specific directory and use it as a variable?'
A: Given the tree
$ tree /home/user1/test/
/home/user1/test/
├── test1
├── test2
└── test3
the tasks below
- find:
paths: /home/user1/test/
register: files_matched
- debug:
msg: "{{ '/home/user2/test/' ~ item|basename }}"
loop: "{{ files_matched.files|json_query('[*].path') }}
give
"msg": "/home/user2/test/test1"
"msg": "/home/user2/test/test3"
"msg": "/home/user2/test/test2"
I hope you are expecting something like this. this will save the list of files under the specified directory into the /tmp/find_result.txt. I hope from here you can proceed.
---
- name: find file
hosts: linux
tasks:
- name: Ansible find file examples
find:
paths: /home/user1/test/
register: files_matched
- name: "Save find results to file"
lineinfile:
line: "{{ item.path }}"
path: "/tmp/find_result.txt"
create: yes
loop: "{{ files_matched.files | flatten }}"
note: make sure that you delete the /tmp/find_result.txt once your task completed.
if you want to store only the filename not the entire path, then replace line: "{{ item.path }}" with line: "{{ item.path | basename }}"

How can I use the mv module Ansible

I am trying to use the mv module on Ansible but I am not having luck.
In my initial attempt I did the following:
- name: changing the name of the file
shell: mv /tmp/bundle /opt/Rocket.Chat
And I get the following error:
FAILED! => {"changed": true, "cmd": "mv /tmp/bundle /opt/Rocket.Chat", "delta": "0:00:00.033553", "end": "2019-02-11 06:06:43.273787", "msg": "non-zero return code", "rc": 1, "start": "2019-02-11 06:06:43.240234", "stderr": "mv: cannot move ‘/tmp/bundle’ to ‘/opt/Rocket.Chat/bundle’: File exists", "stderr_lines": ["mv: cannot move ‘/tmp/bundle’ to ‘/opt/Rocket.Chat/bundle’: File exists"], "stdout": "", "stdout_lines": []}
So, I changed it to:
- name: create directory
file:
state: directory
path: "/opt/Rocket.Chat"
- name: copy the files
copy:
src: "/tmp/bundle"
dest: "/opt/Rocket.Chat"
remote_src: yes
- name: delete the other files
file: path=/tmp/bundle state=absent
My new error is:
FAILED! => {"changed": false, "msg": "Remote copy does not support recursive copy of directory: /tmp/bundle"}
Seems that the "copy module to work with recursive and remote_src" does not work yet, but will be supported from May 2019
Here is a workaround, edit the folder names to your setup.
# Copy all files and directories from /usr/share/easy-rsa to /etc/easy-rsa
- name: List files in /usr/share/easy-rsa
find:
path: /usr/share/easy-rsa
recurse: yes
file_type: any
register: find_result
- name: Create the directories
file:
path: "{{ item.path | regex_replace('/usr/share/easy-rsa','/etc/easy-rsa') }}"
state: directory
mode: "{{ item.mode }}"
with_items:
- "{{ find_result.files }}"
when:
- item.isdir
- name: Copy the files
copy:
src: "{{ item.path }}"
dest: "{{ item.path | regex_replace('/usr/share/easy-rsa','/etc/easy-rsa') }}"
remote_src: yes
mode: "{{ item.mode }}"
with_items:
- "{{ find_result.files }}"
when:
- item.isdir == False

Filter Ansible output from lineinfile

I'm attempting to audit my systems via files copied to a single host. The default output is very verbose. I would like to see just the pertinent fields of Ansible log output; that way, over 1000 hosts, I can zero into my problems more quickly. . When my play is successful, I'd like to just see:
ok: u'/mnt/inventory/hostname999'
I have a playbook that looks like this:
- hosts: 'localhost'
name: Playbook for the Audit for our infrastructure.
gather_facts: False
become: no
connection: local
roles:
- { role: network, tags: network }
My network role main.xml file looks like this:
---
- name: find matching pattern files
find:
paths: "/mnt/inventory"
patterns: "hostname*"
file_type: directory
register: subdirs
- name: check files for net.ipv4.ip_forward = 0
no_log: False
lineinfile:
name: "{{ item.path }}/sysctl.conf"
line: "net.ipv4.ip_forward = 0"
state: present
with_items: "{{ subdirs.files }}"
register: conf
check_mode: yes
failed_when: (conf is changed) or (conf is failed)
- debug:
msg: "CONF OUTPUT: {{ conf }}"
But I get log output like this:
ok: [localhost] => (item={u'uid': 0, u'woth': False, u'mtime': 1546922126.0,
u'inode': 773404, u'isgid': False, u'size': 4096, u'roth': True, u'isuid':
False, u'isreg': False, u'pw_name': u'root', u'gid': 0, u'ischr': False,
u'wusr': False, u'xoth': True, u'rusr': True, u'nlink': 12, u'issock':
False, u'rgrp': True, u'gr_name': u'root', u'path':
u'/mnt/inventory/hostname999', u'xusr': True, u'atime': 1546930801.0,
u'isdir': True, u'ctime': 1546922126.0, u'wgrp': False, u'xgrp': True,
u'dev': 51, u'isblk': False, u'isfifo': False, u'mode': u'0555', u'islnk':
False})
Furthermore, my debug message of CONF OUTPUT never shows and I have no idea why not.
I have reviewed https://github.com/ansible/ansible/issues/5564 and other articles but they just seem to refer to items like shell commands that send stuff to stdout, which lineinfile does not.
But I get log output like this:
Then you likely want to use loop_control: with a label: "{{ item.path }}" child:
- lineinfile:
# as before
with_items: "{{ subdirs.files }}"
loop_control:
label: "{{ item.path }}"
which will get you closer to what you want:
ok: [localhost] => (item=/mnt/inventory/hostname999)
Furthermore, my debug message of CONF OUTPUT never shows and I have no idea why not.
The best guess I have for that one is that maybe the verbosity needs to be adjusted:
- debug:
msg: "CONF OUTPUT: {{ conf }}"
verbosity: 0
but it works for me, so maybe there is something else special about your ansible setup. I guess try the verbosity: and see if it helps. Given what you are actually doing with that msg:, you may be much happier with just passing conf directly to debug:
- debug:
var: conf
since it will render much nicer because ansible knows it is a dict, rather that just effectively calling str(conf) which (as you saw above) does not format very nicely.

ansible fetch is not working as expected

On remote host I have many files under /tmp with name like EM_Prereq*, I want to copy all those to my ansible server under current ansible working directory or /tmp/results directory .
I am using below code and is working fine but its creating files in different path than I expected.
ansbile creating file in path /tmp/EM_Prereq_testbafffmqygx_root_Warning_20180311202123.txt/test.host.com/tmp/<actual file name>
But i want file to be created as /tmp/results/<file name>
---
- name: 'vij'
hosts: 'all'
gather_facts: 'false'
tasks:
- name: 'ls files'
shell: "ls -l /tmp/EM_Prereq_*|awk '{print $(NF)}'"
register: 'filetocopy'
- name: 'fetch files'
fetch :
src: '{{ item }}'
dest: '{{ item }}'
with_items: '{{ filetocopy.stdout_lines }}'
Output is below
changed: [test.host.com] => (item=/tmp/EM_Prereq_testbafffmqygx_root_Warning_20180311202123.txt) => {
"changed": true,
"checksum": "1f7edc7c9704add9f3b191c70a6eb81aa4ff3e14",
"dest": "/tmp/EM_Prereq_testbafffmqygx_root_Warning_20180311202123.txt/oc-129-158-67-48.compute.oraclecloud.com/tmp/EM_Prereq_testbafffmqygx_root_Warning_20180311202123.txt",
"item": "/tmp/EM_Prereq_testbafffmqygx_root_Warning_20180311202123.txt",
"md5sum": "de1bcca72d0c391f203d2956e672f51d",
"remote_checksum": "1f7edc7c9704add9f3b191c70a6eb81aa4ff3e14",
"remote_md5sum": null
}
Appreciate your inputs
See: http://docs.ansible.com/ansible/latest/fetch_module.html
You need: flat: yes as the documentation says: Allows you to override the default behavior of appending hostname/path/to/file to the destination. If dest ends with '/', it will use the basename of the source file, similar to the copy module. Obviously this is only handy if the filenames are unique.
- name: 'fetch files'
fetch :
src: '{{ item }}'
dest: '/tmp/results/'
flat: yes
with_items: '{{ filetocopy.stdout_lines }}'
edit: you also want '/tmp/results/' for the destination directory, and not {{ item }}

Resources