I'm working on a playbook that does the following:
Goes into a specified path on each Windows server
Slurps text from a file and adds it to a variable
Performs a check on the variable to see if a string of text exists
Writes the results to a file based on the outcome.
Here is the code I have for this:
---
- name: Slurps text from file on Windows server
hosts: win
gather_facts: false
tasks:
- name: Get text
slurp:
src: D:\testsearch.ini
register: norequest
- name: Check for norequest=false in variable
lineinfile:
dest: ./norequest.csv
line: "{{ inventory_hostname }} There is a false value"
state: present
create: true
insertafter: EOF
when: '"''NoRequest = False'' in norequest.content|b64decode"|lower'
delegate_to: localhost
- name: Check for norequest=true in variable
lineinfile:
dest: ./norequest.csv
line: "{{ inventory_hostname }} There is a true value."
state: present
create: true
insertafter: EOF
when: '"''NoRequest = True'' in norequest.content|b64decode"|lower'
delegate_to: localhost
Based on my results, it looks like the playbook slurps the text from the files on both test servers and adds it all to the variable, then performs the conditional check against one of the servers (since the task itself is being delegated to localhost) and outputs the results to the file as though they all came from SERVER1 (the last part seems to be due to the delegation).
PLAY [Slurps text from file on Windows server] *******************************
TASK [Delete previous norequest file] *******************************
changed: [SERVER1 -> localhost]
TASK [Get text] ***************************************
ok: [SERVER2]
ok: [SERVER1]
TASK [Check for norequest=false in variable] ********************************
changed: [SERVER1 -> localhost]
TASK [Check for norequest=true in variable] *******************************
changed: [SERVER1 -> localhost]
PLAY RECAP *******************************
SERVER1 : ok=4 changed=3 unreachable=0 failed=0
SERVER2 : ok=1 changed=0 unreachable=0 failed=0
Here are the contents of the file after the playbook is run:
SERVER1 There is a false value
SERVER1 There is a true value.
This is what the outcome should be if the playbook worked as I want it to:
SERVER1 There is a false value
SERVER2 There is a true value.
I feel like part (or all) of my issue might be that I'm looking at this through a PowerShell lens; as in, "FOR EACH server, get the text from the file, perform a conditional check, write the output to the outfile, then move on to the next server." Is something like that possible in an Ansible playbook? I've looked into dictionaries as a way to solve this, but the only good examples I could find used pre-existing dictionaries or dictionaries populated at runtime with basic server info.
Seems to me, that the when: condition was wrong. Matching is now done via a regexp. Tested it with this playbook:
---
- name: Slurps text from file on Windows server
hosts:
- SERVER1
- SERVER2
gather_facts: false
tasks:
- name: Get text
slurp:
src: D:\testsearch.ini
register: norequest
- name: Check for norequest=false in variable
lineinfile:
dest: ./norequest.csv
line: "{{ inventory_hostname }} There is a false value"
state: present
create: true
insertafter: EOF
when: 'norequest["content"] | b64decode | lower | regex_search("norequest *= *false")'
delegate_to: localhost
- name: Check for norequest=true in variable
lineinfile:
dest: ./norequest.csv
line: "{{ inventory_hostname }} There is a true value"
state: present
create: true
insertafter: EOF
when: 'norequest["content"] | b64decode | lower | regex_search("norequest *= *true")'
delegate_to: localhost
The file testsearch.ini has the following contents on the systems:
SERVER1
NoRequest = False
SERVER2
NoRequest = True
Executing the playbook with ansible-playbook -i hosts play.yml gives the following output:
PLAY [SERVER1,SERVER2] *********************************************************
TASK [Get text] ****************************************************************
ok: [SERVER1]
ok: [SERVER2]
TASK [Check for norequest=false in variable] ***********************************
skipping: [SERVER2]
ok: [SERVER1 -> localhost]
TASK [Check for norequest=true in variable] ************************************
skipping: [SERVER1]
ok: [SERVER2 -> localhost]
PLAY RECAP *********************************************************************
SERVER1 : ok=2 changed=0 unreachable=0 failed=0
SERVER2 : ok=2 changed=0 unreachable=0 failed=0
The contents of norequest.csv after the run is
SERVER1 There is a false value
SERVER2 There is a true value
Related
I want to use content in .txt file and I tried to debug content using following code.
- name: Find .txt files
find:
paths: "{{output_path}}"
patterns: '*.txt,'
register: file_path
- name: Show content
debug:
msg: "{{lookup('file', item.path)}}"
with_items: "{{file_path.files}}"
But I got this error.
[WARNING]: Unable to find '/path/file.txt' in expected paths (use -vvvvv to see paths)
TASK [Show content] ******************************************************
fatal: [10.0.2.40]: FAILED! => {"msg": "An unhandled exception occurred
while running the lookup plugin 'file'. Error was a <class 'ansible.errors.AnsibleError'>,
original message: could not locate file in lookup: /path/file.txt"}
How I fix this error?
There's nothing inherently wrong with your playbook, except that you have a comma inside the find pattern (this might be just a typo, but you should check it out) if you are running this playbook locally. In the case that you are running this playbook on a remote server, you should try to use a different module like slurp or fetch.
slurp works great if you need to keep the contents of the txt in memory to use it on another task. Bear in mind that ansible will encode the slurp module's output in base64, so you should decode it first when you want to use it. From the module example page:.`
- name: Find out what the remote machine's mounts are
ansible.builtin.slurp:
src: /proc/mounts
register: mounts
- name: Print returned information
ansible.builtin.debug:
msg: "{{ mounts['content'] | b64decode }}"
You can verify what I am saying with the following example:
I tried replicating your situation locally. On a temporary folder, I run the following command to populate it with many .txt files:
echo {001..099} > file_{001..099}.txt
Then I wrote the same playbook that you provided:
#
# show_contents.yml
#
- hosts: localhost
gather_facts: no
tasks:
- name: Find .txt files
find:
paths: "{{ output_files }}"
patterns: "*.txt"
register: file_path
- name: Debug the file_path variable
debug:
var: file_path
- name: Get the contents of the files using debug
debug:
msg: "{{ lookup('file', item.path ) }}"
loop: "{{ file_path.files }}"
If you run this playbook, passing the appropriate output_files variable with --extra-vars, the playbook works fine.
ansible-playbook show_contents.yml --extra-vars "output_files=/tmp/ansible"
You'll see that the playbook runs without an issue. Try using this example to figure out what you are trying to achieve. And then modify the playbook to use some of the previously mentioned modules when working with remote servers.
I'm new to ansible so bear with me
I need to create a playbook that reads a file and retrieve a list of servers, then connects to each node and retrieves the value of a file in each (env), then puts together a compiled txt with the information:
node1 - version - env
node2 - version - env
node3 - version - env
the problem resides when I have to put everything together
- name: check remote file
remote_user: user
shell: cat /remote/file
register: env
- name: get version
shell: cat /local/file | -f3 | sort | uniq
register: vers
delegate_to: localhost
- name: save results
shell: echo NODE {{ hostz.stdout_lines }} VERSION {{ vers.stdout_lines }} ENVIRONMENT {{ env.stdout_lines }} >> /home/user/test.csv
delegate_to: localhost
as you can imagine, it's doing a loop per task, so I end up with a file with all the hosts, then all the versions and all the environments, which makes no sense whatsoever, what do you think would be the best way of putting this together?
I'm basically trying to do a for hosts in $hostz; do ...
For example, the inventory below
shell> cat hosts
[my_group]
test_01
test_02
test_03
this playbook
shell> cat playbook.yml
- hosts: my_group
tasks:
- shell: 'cat /etc/passwd| wc -l'
register: env
- shell: 'cat /etc/passwd| wc -l'
register: vers
delegate_to: localhost
run_once: true
- template:
src: test.csv.j2
dest: test.csv
delegate_to: localhost
run_once: true
and this template
shell> cat test.csv.j2
{% for host in groups.my_group %}
NODE {{ host }} VERSION {{ vers.stdout|trim }} ENVIRONMENT {{ hostvars[host]['env']['stdout']|trim }}
{% endfor %}
give
shell> ansible-playbook -i hosts playbook.yml
PLAY [my_group] ************************************************************
TASK [shell] ***************************************************************
changed: [test_01]
changed: [test_03]
changed: [test_02]
TASK [shell] ***************************************************************
changed: [test_01 -> localhost]
TASK [template] ************************************************************
changed: [test_01 -> localhost]
PLAY RECAP *****************************************************************
test_01: ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_02: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_03: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> cat test.csv
NODE test_01 VERSION 55 ENVIRONMENT 37
NODE test_02 VERSION 55 ENVIRONMENT 31
NODE test_03 VERSION 55 ENVIRONMENT 29
I'm able to get the timestamp of a file using Ansible stat module.
- stat:
path: "/var/test.log"
register: filedets
- debug:
msg: "{{ filedets.stat.mtime }}"
The above prints mtime as 1594477594.631616 which is difficult to understand.
I wish to know how can I put a when condition check to see if the file is less than 20 hours old?
You can also achieve this kind of tasks without going in the burden to do any computation via find and its age parameter:
In your case, you will need a negative value for the age:
Select files whose age is equal to or greater than the specified time.
Use a negative age to find files equal to or less than the specified time.
You can choose seconds, minutes, hours, days, or weeks by specifying the first letter of any of those words (e.g., "1w").
Source: https://docs.ansible.com/ansible/latest/modules/find_module.html#parameter-age
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- file:
path: /var/test.log
state: touch
- find:
paths: /var
pattern: 'test.log'
age: -20h
register: test_log
- debug:
msg: "The file is exactly 20 hours old or less"
when: test_log.files | length > 0
- file:
path: /var/test.log
state: touch
modification_time: '202007102230.00'
- find:
paths: /var
pattern: 'test.log'
age: -20h
register: test_log
- debug:
msg: "The file is exactly 20 hours old or less"
when: test_log.files | length > 0
This gives the recap:
PLAY [all] **********************************************************************************************************
TASK [file] *********************************************************************************************************
changed: [localhost]
TASK [find] *********************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"msg": "The file is exactly 20 hours old or less"
}
TASK [file] *********************************************************************************************************
changed: [localhost]
TASK [find] *********************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************
skipping: [localhost]
PLAY RECAP **********************************************************************************************************
localhost : ok=5 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
- stat:
path: "/var/test.log"
register: filedets
- debug:
msg: "{{ (ansible_date_time.epoch|float - filedets.stat.mtime ) > (20 * 3600) }}"
Does anyone knows how to get the list of remote files under a particular Dir and to iterate over them and list out the contents of each file via ansible?
For example , i have a location /var/spool/cron and it has many files which i need to cat <file> by iterate over each of them.
fileglob and lookup works locally.
Below is the play but not working as expected.
---
- name: Playbook to quick check the cron jobs for user
hosts: all
remote_user: root
gather_facts: False
tasks:
- name: cron state
shell: |
ls /var/spool/cron/
register: cron_status
- debug: var=item
with_items:
- "{{ cron_status.stdout_lines }}"
Try this as an example
---
- name: Playbook
hosts: all
become: root
gather_facts: False
tasks:
- name: run ls-
shell: |
ls -d1 /etc/cron.d/*
register: cron_status
- name: cat files
shell: cat {{ item }}
register: files_cat
with_items:
- "{{ cron_status.stdout_lines }}"
- debug: var=item
with_items:
- "{{ files_cat.results| map(attribute='stdout_lines')|list }}"
Just for the sake you someones Intrest..
Below Play will give you more cleaner stdout
---
- name: hostname
hosts: all
become: root
gather_facts: False
tasks:
- name: checking cron entries
shell: more /var/spool/cron/*
register: cron_status
- debug: var=cron_status.stdout_lines
Env is: Ansible 1.9.4 or 1.9.2, Linux CentOS 6.5
I have a role build where:
$ cat roles/build/defaults/main.yml:
---
build_user: confman
build_group: confman
tools_dir: ~/tools
$ cat roles/build/tasks/main.yml
- debug: msg="User is = {{ build_user }} -- {{ tools_dir }}"
tags:
- koba
- name: Set directory ownership
file: path="{{ tools_dir }}" owner={{ build_user }} group={{ build_group }} mode=0755 state=directory recurse=yes
become_user: "{{ build_user }}"
tags:
- koba
- name: Set private key file access
file: path="{{ item }}" owner={{ build_user }} group={{ build_group }} mode=0600 state=touch
with_fileglob:
- "{{ tools_dir }}/vmwaretools-lib-*/lib/insecure_private_key"
# with_items:
# - ~/tools/vmwaretools/lib/insecure_private_key
become_user: "{{ build_user }}"
tags:
- koba
In my workspace: hosts file (inventory) contains:
[ansible_servers]
server01.project.jenkins
site.yml (playbook) contains:
---
- hosts: ansible_servers
sudo: yes
roles:
- build
I'm running the following command:
$ ansible-playbook site.yml -i hosts -u confman --private-key ${DEPLOYER_KEY_FILE} -t koba
I'm getting the following error and for some reason, become_user in Ansible while using Ansible loop: with_fileglob is NOT using ~ (home directory) of confman user (which is set in variable {{ build_user }}, instead of that, it's picking my own user ID (c123456).
In the console output for debug action, it's clear that the user (due to become_user) is confman and value of tools_dir variable is ~/tools.
PLAY [ansible_servers] ********************************************************
GATHERING FACTS ***************************************************************
ok: [server01.project.jenkins]
TASK: [build | debug msg="User is = {{ build_user }} -- {{ tools_dir }}"] *****
ok: [server01.project.jenkins] => {
"msg": "User is = confman -- ~/tools"
}
TASK: [build | Set directory ownership] ***************************************
changed: [server01.project.jenkins]
TASK: [build | Set private key file access] ***********************************
failed: [server01.project.jenkins] => (item=/user/home/c123456/tools/vmwaretools-lib-1.0.8-SNAPSHOT/lib/insecure_private_key) => {"failed": true, "item": "/user/home/c123456/tools/vmwaretools-lib-1.0.8-SNAPSHOT/lib/insecure_private_key", "parsed": false}
BECOME-SUCCESS-ajtxlfymjcquzuolgfrrxbssfolqgrsg
Traceback (most recent call last):
File "/tmp/ansible-tmp-1449615824.69-82085663620220/file", line 1994, in <module>
main()
File "/tmp/ansible-tmp-1449615824.69-82085663620220/file", line 372, in main
open(path, 'w').close()
IOError: [Errno 2] No such file or directory: '/user/home/c123456/tools/vmwaretools-lib-1.0.8-SNAPSHOT/lib/insecure_private_key'
OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: auto-mux: Trying existing master
debug1: mux_client_request_session: master session id: 2
debug1: mux_client_request_session: master session id: 2
Shared connection to server01.project.jenkins closed.
As per the error above, the file it's trying for variable item is /user/home/c123456/tools/vmwaretools-lib-1.0.8-SNAPSHOT/lib/insecure_private_key but there's no such file inside my user ID's home directory. But, this file does exist for user confman's home directory.
i.e. the following file exists.
/user/home/confman/tools/vmwaretools-lib-1.0.7-SNAPSHOT/lib/insecure_private_key
/user/home/confman/tools/vmwaretools-lib-1.0.7/lib/insecure_private_key
/user/home/confman/tools/vmwaretools-lib-1.0.8-SNAPSHOT/lib/insecure_private_key
All, I want is to iterate of these files in ~confman/tools/vmwaretools-lib-*/.. location containing the private key file and change the permission but using "with_fileglob" become_user to set the user during an action is NOT working.
If I comment out the with_fileglob section and use/uncomment with_items section in the tasks/main.yml, then it (become_user) works fine and picks ~confman (instead of ~c123456) and gives the following output:
TASK: [build | Set private key file access] ***********************************
changed: [server01.project.jenkins] => (item=~/tools/vmwaretools/lib/insecure_private_key)
One strange thing I found is, there is no user c123456 on the target machine (server01.project.jenkins) and that's telling me that with_fileglob is using the source/local/master Ansible machine (where I'm running ansible-playbook command) to find the GLOB Pattern (instead of finding / running it over SSH on server01.project.jenkins server), It's true that on local/source Ansible machine, I'm logged in as c123456. Strange thing is, in the OUTPUT, it still shows the target machine but pattern path is coming from source machine as per the output above.
failed: [server01.project.jenkins]
Any idea! what I'm missing here? Thanks.
PS:
- I don't want to set tools_dir: "~{{ build_user }}/tools" or hardcode it as a user can pass tools_dir variable at command line (while running ansible-playbook command using -e / --extra-vars "tools_dir=/production/slave/tools"
Further researching it, I found with_fileglob is for List of local files to iterate over, described using shell fileglob notation (e.g., /playbooks/files/fooapp/*) then, what should I use to iterate over on target/remote server (server01.project.jenkins in my case) using pattern match (fileglob)?
Using with_fileglob, it'll always run on the local/source/master machine where you are running ansible-playbook/ansible. Ansible docs for Loops doesn't clarifies this info (http://docs.ansible.com/ansible/playbooks_loops.html#id4) but I found this clarification here: https://github.com/lorin/ansible-quickref
Thus, while looking for the pattern, it's picking the ~ for user c123456.
Console output is showing [server01.project.jenkins] as it's a different processing/step to read what's there in the inventory/hosts file.
I tried to use with_lines as well as per this post: ansible: Is there something like with_fileglobs for files on remote machine?
But, when I tried the following, it still didn't work i.e. read the pattern on local machine instead of target machine (Ansible docs tells with_items doesn't run on local machine but on the controlling machine):
file: path="{{ item }}" ....
with_items: ls -1 {{ tools_dir }}/vmwaretools-lib-*/lib/insecure_private_key
become_user: {{ build_user }}
Finally to solve the issue, I just went on the plain OS command round using shell (again, this might not be a very good solution if the target env is not a Linux type OS) but for now I'm good.
- name: Set private key file access
shell: "chmod 0400 {{ tools_dir }}/vmtools-lib-*/lib/insecure_private_key"
become_user: "{{ build_user }}"
tags:
- koba