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.
Related
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
I'm attempting to install NVS via Ansible but not having much luck. Following the instructions on the repo, it seems that I should first make sure to set the NVS_HOME environment variable, then clone the repo, then run the installer.
Thus, here is how I have it set up in my playbook:
- hosts: all
remote_user: "{{ new_user }}"
gather_facts: true
environment:
NVS_HOME: ~/.nvs
tasks:
- name: See if NVS is already installed
lineinfile:
path: ~/.zshrc
line: export NVS_HOME="$HOME/.nvs"
state: present
check_mode: true
register: zshrc
- name: Clone the NVS repo
git:
repo: https://github.com/jasongin/nvs
dest: "$NVS_HOME"
version: v1.6.0
ignore_errors: true
when: (zshrc is changed) or (zshrc is failed)
- name: Ensure NVS is installed
shell: ". \"$NVS_HOME/nvs.sh\" install"
when: (zshrc is changed) or (zshrc is failed)
With this, it first checks to see if NVS is installed and won't bother with the following two tasks if it is. Cloning is not a problem, so that leaves the final step of installing.
When Ansible gets to the final task, it outputs an error. Here's the result of that task from the Ansible debugger:
[localhost] TASK: Ensure NVS is installed (debug)> p result._result
{'_ansible_no_log': False,
'_ansible_parsed': True,
'changed': True,
'cmd': '. "$NVS_HOME/nvs.sh" install',
'delta': '0:00:00.002482',
'end': '2020-11-24 12:53:52.983307',
'failed': True,
'invocation': {'module_args': {'_raw_params': '. "$NVS_HOME/nvs.sh" install',
'_uses_shell': True,
'argv': None,
'chdir': None,
'creates': None,
'executable': None,
'removes': None,
'stdin': None,
'stdin_add_newline': True,
'strip_empty_ends': True,
'warn': True}},
'msg': 'non-zero return code',
'rc': 127,
'start': '2020-11-24 12:53:52.980825',
'stderr': "/bin/sh: 1: .: Can't open ~/.nvs/nvs.sh",
'stderr_lines': ["/bin/sh: 1: .: Can't open ~/.nvs/nvs.sh"],
'stdout': '',
'stdout_lines': []}
So I don't know what to do at this point. Any ideas?
The issue is with your dest: "$NVS_HOME" in git task.
I suggest to declare a variable and use it in your environment and in your task
- hosts: all
remote_user: "{{ new_user }}"
gather_facts: true
vars:
nvs_home: "~/.nvs"
environment:
NVS_HOME: "{{ nvs_home }}"
And in your git task:
- name: Clone the NVS repo
git:
repo: https://github.com/jasongin/nvs
dest: "{{ nvs_home }}"
version: v1.6.0
ignore_errors: true
when: (zshrc is changed) or (zshrc is failed)
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 }}"
I am trying to unmount filesystems with the below playbook.
vars:
unmountlist:
- "/DATA1"
- "/DATA2"
tasks:
- name: unmount
mount:
path: "{{ item }}"
state: unmounted
with_items:
- "{{ unmountlist }}"
register: output
ignore_errors: true
- debug:
msg: "{{ output }}"
- name: YE unmount persistant
mount:
path: "{{ item }}"
state: absent
with_items:
- "{{ unmountlist }}"
- name: Lazy unmount
command: umount -l "{{ item }}"
when: output.changed == false
with_items: "{{ unmountlist }}"
The debug section looks like this:
ok: [host001] => {
"msg": "error is {'msg': u'All items completed', 'failed': True, 'changed': False, 'results': [{'_ansible_parsed': True, 'changed': False, '_ansible_no_log': False, 'item': u'/DATA1', '_ansible_item_result': True, u'failed': True, u'invocation': {u'module_args': {u'src': None, u'dump': None, u'boot': u'yes', u'fstab': None, u'passno': None, u'fstype': None, u'state': u'unmounted', u'path': u'/DATA1', u'opts': None}}, u'msg': u'Error unmounting /DATA1: umount.nfs: /DATA1: device is busy\\n'}, {'_ansible_parsed': True, 'changed': False, '_ansible_no_log': False, 'item': u'/DATA2', '_ansible_item_result': True, u'failed': True, u'invocation': {u'module_args': {u'src': None, u'dump': None, u'boot': u'yes', u'fstab': None, u'passno': None, u'fstype': None, u'state': u'unmounted', u'path': u'/DATA2', u'opts': None}}, u'msg': u'Error unmounting /DATA2: umount.nfs: /DATA2: device is busy\\n'}]}"
I am trying to achieve the below.
lazy unmount the only the filesystem that returns error "device is busy". I cant see how to read the variable from above debug that contains string "device is busy" and how to unmount only the filesystem that returns this error.
What you can do is put the tasks in a tasks file and loop over the tasks file using unmountlist.
This is how your tasks file (umounts_tasks.yml) will look like - tasks file
Then within your play you can use include_tasks to include the above tasks file and loop over it with the unmountlist. So your play will look like this - play.yml
Let me know if this worked. :)
I have a playbook that looks like this:
- hosts: host1
gather_facts: false
tasks:
- name: "Loop"
command: "echo {{ item }}"
with_items: [ 0, 2, 4, 6, 8, 10 ]
register: hello
- debug: "msg={{ hello.results }}"
Everything works correctly, and the output is returned, but there is tons and tons of output. It turns out that this:
- debug: "msg={{ hello.results.1.stdout }}"
does exactly what I want -- just grab the stdout from the command -- but only for one of the six times through the loop.
What I really want/need to do is this:
- debug: "msg={{ hello.results.*.stdout }}"
where it goes into the hello structure, accesses the results entry, goes to each member of that array, and pulls out the stdout value.
Is this possible?
UPDATE
- hosts: host1
gather_facts: false
tasks:
- name: "Loop"
command: "echo {{ item }}"
with_items: [ 0, 2, 4, 6, 8, 10 ]
register: hello
- debug:
msg: "{{item.stdout}}"
with_items: "{{hello.results}}"
is no less verbose than my original example.
TASK [debug] *******************************************************************
ok: [host1] => (item={'_ansible_parsed': True, 'stderr_lines': [], u'cmd': [
u'echo', u'0'], u'end': u'2018-01-02 20:53:08.916774', '_ansible_no_log': False
, u'stdout': u'0', '_ansible_item_result': True, u'changed': True, 'item': 0,
u'delta': u'0:00:00.002137', u'stderr': u'', u'rc': 0, u'invocation': {u'module_
args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params
': u'echo 0', u'removes': None, u'creates': None, u'chdir': None, u'stdin': Non
e}}, 'stdout_lines': [u'0'], u'start': u'2018-01-02 20:53:08.914637', 'failed':
False}) => {
"item": {
"changed": true,
"cmd": [
"echo",
"0"
],
"delta": "0:00:00.002137",
"end": "2018-01-02 20:53:08.916774",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo 0",
"_uses_shell": false,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"warn": true
}
},
"item": 0,
"rc": 0,
"start": "2018-01-02 20:53:08.914637",
"stderr": "",
"stderr_lines": [],
"stdout": "0",
"stdout_lines": [
"0"
]
},
"msg": "0"
}
I get 6 copies of the above construct.
It feels like I'm close but I'm still doing something wrong. I see "msg": "0" at the bottom, which is what I want. I just don't want the rest of it.
Solution:
- debug: "msg={{ hello.results | map(attribute='stdout') | join('\n') }}"
Remark:
By default, Ansible will print visible \n two-character sequences instead of wrapping the lines, so either use a callback plugin for a human readable output (example) or verify the method with:
- copy:
content: "{{ hello.results | map(attribute='stdout') | join('\n') }}"
dest: ./result.txt
and check the contents of the result.txt.
I have used the keyword loop to get stdout from all iterations of the previous loop:
loop: "{{ hello | json_query('results[*].stdout') }}"
I find json_query easiest to use in such register-loop situations. Official documentation can be found here ==> json-query-filter
Sure. The ansible website has documentation that explains how to use register in a loop. You just need to iterate over the hello.results array, as in:
- debug:
msg: "{{item.stdout}}"
with_items: "{{hello.results}}"
What about:
- debug: "msg={{ item.stdout }}"
with_items: "{{ hello.results }}"
I think this construct works well enough for my needs.
- hosts: localhost
gather_facts: false
vars:
stuff: [ 0,2,4,6,8,10 ]
tasks:
- name: "Loop"
command: "echo {{ item }}"
with_items: "{{ stuff }}"
register: hello
- debug: "var=hello.results.{{item}}.stdout"
with_sequence: "0-{{stuff|length - 1}}"
I was looking at a similar problem and was confused by getting lots of output when I was expecting a relatively small msg or var from debug:. Turns out most of that output was the 'label' with which Ansible was prefixing each of those small outputs. It being a few years after this question was originally asked I've been using loop rather than with_items; this also has a label: option in loop_control:, so in my case for a similar problem - getting any /etc/passwd entries for users 'alice' or 'bob',
- hosts: all
gather_facts: false
serial: 1 # output easier to read when grouped by host
tasks:
- name: Look for users in /etc/passwd
command: grep {{ item }} /etc/passwd
register: res
ignore_errors: true
loop:
- alice
- bob
- debug:
msg: "{{ item.stdout_lines }}"
when: not item.failed
loop: "{{ res.results }}"
loop_control:
label: "{{ item.item }}"