Ansible compare zip listing to directory - ansible

EDIT: I resolved this a different way by getting a checksum of the 7z contents and checking
a) if the directory existed
b) if it did - did it's contents checksum match
I have an ansible playbook which uses a 7zip shell command, but I want to check if the 7z has been inflated already so I have the following
- name: Get zip listing
shell: '7z l {{ sz_file }} | tail -n +21 | head -n -2 | cut -c 54-'
register: sz_contents
- name: Compare zip listing to file contents
stat:
path: '{{ extract_dir }}/{{ item }}'
register: result
with_items: '{{ sz_contents.stdout_lines }}'
- name: Inflate 7z file if needed
shell: 7z x {{ sz_file }}
when: ???
I want the following to happen:
Stop the Compare task the first time results.stat.exists == False (the 7z has many files and continuing the comparison after that is pointless)
Register if the file needs inflating and do so as needed

It sounds like you want to make the extract task conditional on
whether the compare tasks succeeds or fails, and you want the compare
task to fail as soon as it finds a file that doesn't exist.
We can get most of the way there.
Normally, the stat module doesn't trigger a failure when you point
it at a path that doesn't exist. For example, the following playbook:
- hosts: localhost
gather_facts: false
tasks:
- stat:
path: /does-not-exist
register: result
- debug:
var: result
Yields:
TASK [stat] ***********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"result": {
"changed": false,
"failed": false,
"stat": {
"exists": false
}
}
}
Ansible provides us with the failed_when directive to control when a
task fails. This means we can rewrite your compare task to fail on a
missing file like this:
- name: Compare zip listing to file contents
stat:
path: '{{ extract_dir }}/{{ item }}'
register: result
failed_when: not result.stat.exists
ignore_errors: true
with_items: '{{ sz_contents.stdout_lines }}'
The failed_when directive tells Ansible to consider the task
"failed" if the file passed to stat doesn't exist, and the
ignore_errors directive tells Ansible to continue executing the
playbook rather than aborting when the task fails.
We can make the extract task condition on this one with a simple
when directive:
- name: Inflate 7z file if needed
shell: 7z x {{ sz_file }}
when: result is failed
The only problem with this solution is that Ansible won't exit a loop
when an individual item causes a failure, so it's going to check
through all of sz_contents.stdout_lines regardless.
Update
I was discussing this issue on irc and #bcoca pointed out the when
is evaluated before register, so we can actually get the behavior
you want by writing the compare task like this:
- name: Compare zip listing to file contents
stat:
path: '{{ extract_dir }}/{{ item }}'
register: result
when: result is defined or result is success
failed_when: not result.stat.exists
ignore_errors: true
with_items: '{{ sz_contents.stdout_lines }}'
The when statement will cause all loop iterations after the first
failure to be skipped.

Related

Use skip_reason as a condition in a task

Is it possible to use the skip_reason as condition to another task?
Here is the task:
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
ignore_errors: true.
register: PSP_VAL
when: >
not 'VMware' in HWMODEL.stdout
Which output:
TASK [OS_minor_upgrade : PSP Postwork] ******************************************************************************************************************************************************
task path: /home/ansible/linuxpatching_OS_Upgrade/roles/OS_minor_upgrade/tasks/upgrade.yml:264
skipping: [server123] => {
"changed": false,
"skip_reason": "Conditional result was False"
}
Now I want to use the above as condition to execute another task, I tried with the task below but it seem like it is not working.
- name: OSupgrade done
shell: echo {{ inventory_hostname }} "OS Upgrade Done" > OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
delegate_to: localhost
when: >
fs_check.rc == 0 and val_etrust.rc == 0 and 'PSP Installation is successfully completed' in PSP_VAL.stdout or 'Conditional result was False' in PSP_VAL.skip_reason
How can this be achieved?
Technically, you can use 'skip_reason' as any other variable, but I STRONGLY suggest you not to.
The reason is that person, reading your code (you, 1 week later) would be in a total loss over such decision.
If you have important information about your host, you can use set_fact module to update your host information. Further tasks can use this to make decisions.
- name: Update vmware info
set_fact:
vmware_flag: ('VMware' in HWMODEL.stdout)
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
failed_when: false
register: PSP_VAL
when: not vmware_flag
- name: OSupgrade done
delegate_to: localhost
copy:
dest: OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
content: '{{ inventory_hostname }} "OS Upgrade Done"'
when: (some other conditions) or vmware_flag
There are specific ansible tests to verify the registered result of a task
In your specific case:
when: <...all other conditions...> or PSP_VAL is skipped

detect file difference (change) with `ansible`

In this task I found a roundabout method to compare two files (dconfDump and dconfDumpLocalCurrent) and to set a variable (previously defined as false) to true if the two files differ.
The solution seem to work, but it looks ugly and, as a beginner with ansible, I have the impression a better solution should be existing.
---
# vars file for dconfLoad
local_changed : false
target_changed : false
---
- name: local changed is true when previous target different then local current
shell: diff /home/frank/dconfDump /home/frank/dconfDumpLocalCurrent
register: diff_oldtarget_localCurrent
register: local_changed
ignore_errors: true
- debug:
msg: CHANGED LOCALLY
when: local_changed
Some background to the task, which is an attempt to synchronize files: A file LocalCurrent is compared with LocalOld and CurrentTarget, to determine if the LocalCurrent is changed and if it is different than currentTarget. If LocalCurrent is not changed and CurrentTarget is changed, then apply the change (and set LocalOld to CurrentTarget); if LocalCurrent is changed then upload to controller.
What is the appropriate approach with ansible? Thank you for help!
You can use stat to get the checksum and then compare it. Please see below.
tasks:
- name: Stat of dconfDump
stat:
path : "/tmp/dconfDump"
register: dump
- name: SHA1 of dconfDump
set_fact:
dump_sha1: "{{ dump.stat.checksum }}"
- name: Stat of dconfDumpLocalCurrent
stat:
path : "/tmp/dconfDumpLocalCurrent"
register: dump_local
- name: SHA1 of dconfDumpLocalCurrent
set_fact:
local_sha1: "{{ dump_local.stat.checksum }}"
- name: Same
set_fact:
val: "False"
when: dump_sha1 != local_sha1
- name: Different
set_fact:
val: "True"
when: dump_sha1 == local_sha1
- name: Print
debug:
msg: "{{val}}"
Use stat and create dictionary of checksums. For example
- stat:
path: "{{ item }}"
loop:
- LocalOld
- LocalCurrent
- CurrentTarget
register: result
- set_fact:
my_files: "{{ dict(paths|zip(chkms)) }}"
vars:
paths: "{{ result.results|map(attribute='stat.path')|list }}"
chkms: "{{ result.results|map(attribute='stat.checksum')|list }}"
- debug:
var: my_files
gives (abridged) if all files are the same
my_files:
CurrentTarget: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalCurrent: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalOld: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
Then use the dictionary to compare the checksums and copy files. For example
# If LocalCurrent is not changed and CurrentTarget is changed,
# then apply the change (and set LocalOld to CurrentTarget)
- debug:
msg: Set LocalOld to CurrentTarget
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] != my_files['CurrentTarget']
- debug:
msg: Do not copy anything
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] == my_files['CurrentTarget']
gives
TASK [debug] ****
skipping: [localhost]
TASK [debug] ****
ok: [localhost] =>
msg: Do not copy anything

How do I copy files to a machine based on a vars flag using ansible?

I'm trying to get a playbook to copy a different set of files to a server, depending an a flag set on the group it belongs too in the ansible inventory.
The code is as follows
- name: check if they exist already
stat:
path: /home/me/set1files
register: st
- name: copy pre-prod if they don't
copy:
src: /home/me/set1files
dest: /home/me
owner: me
group: me
mode: "0644"
when: (st.stat.exists==false) and (is_emms_pp==true)
- name: check if prod files exist
stat:
path: /home/me/set2files
register: stprod
- name: copy prod files
copy:
src: /home/me/set2files
dest: /home/me
owner: me
group: me
mode: "0644"
when: (stprod.stat.exists==false) and (is_emms_pp==false)
and in the inventory file
[PPEMMS]
emmspp1
emmspp2
emmspp3
[PPEMMS:vars]
is_emms_pp = true
[PRODEMMS]
emms1
emms2
emms3
[PRODEMMS:vars]
is_emms_pp = false
[EMMS:children]
PPEMMS
PRODEMMS
When I run the script on a new PP machine, it all seems to work fine, but when I run it on a new Prod machine, it still copies the PP files and skips the step for the Prod machine.
Is this a valid test or do I have something wrong?
One thought I had was that because I'm limiting the script to one machine, rather than the group, its not picking up the group_var?
I'm using the script as
ansible-playbook -i inventory/hosts -l emms1 install_emms.yml --ask-vault-pass --extra-vars '#passwd.yml'
For each machine.
The variable is_emms_pp = true is a string. Quoting from Defining variables at runtime
"Values passed in using the key=value syntax are interpreted as strings. Use the JSON format if you need to pass non-string values such as Booleans, integers, floats, lists, and so on."
Given the inventory to simplify the test
test_01 is_emms_pp=true
test_02 is_emms_pp=false
The playbook
- hosts: test_01,test_02
tasks:
- debug:
var: is_emms_pp|type_debug
gives
ok: [test_01] =>
is_emms_pp|type_debug: str
ok: [test_02] =>
is_emms_pp|type_debug: str
Comparison between a string and a boolean will fail
- debug:
msg: Copy file
when: is_emms_pp == true
gives
skipping: [test_01]
skipping: [test_02]
One option is to compare strings. The task below works as expected
- debug:
msg: Copy file
when: is_emms_pp == 'true'
gives
ok: [test_01] =>
msg: Copy file
skipping: [test_02]
The cleaner option is to convert the variable to boolean. The task below gives the same result
- debug:
msg: Copy file
when: is_emms_pp|bool
See CONDITIONAL_BARE_VARS
Example of how to fix the conditions
- name: check if they exist already
stat:
path: /home/me/set1files
register: st
- name: copy pre-prod if they don't
debug:
msg: Copy set1files
when:
- not st.stat.exists
- is_emms_pp|bool
- name: check if prod files exist
stat:
path: /home/me/set2files
register: st
- name: copy prod files
debug:
msg: Copy set2files
when:
- not st.stat.exists
- not is_emms_pp|bool

Need Syntax to add Ansible meta module to existing Playbook

I wish to search for a string ("AC245") in all files with extension *.db under /home/examples directory. Below is what i tried.
---
- name: "Find the details here "
hosts: localhost
any_errors_fatal: true
serial: 1
tasks:
- name: Ansible find files multiple patterns examples
find:
paths: /home/examples
patterns: "*.db"
recurse: yes
register: files_matched
- name: Search for String in the matched files
command: grep -i {{ myString }} {{ item.path }}
register: command_result
failed_when: command_result.rc == 0
with_items:
- "{{ files_matched.files }}"
run the above find.yml using this command:
ansible-playbook find.yml -e "myString=AC245"
My requirement is that if the string is found I wish to abort the play immediately using "meta: end_play" marking the playbook as FAILED.
Can you help suggest how can I update my current code to add the end_play feature as soon as the string is found in any *.db file ?
One possible solution is to move the task into a separate tasks file and loop over it. That will allow discrete control over each iteration of the task. For example:
playbook.yml:
---
- name: "Find the details here "
hosts: localhost
any_errors_fatal: true
serial: 1
tasks:
- name: Ansible find files multiple patterns examples
find:
paths: /home/mparkins/bin/playbooks_sandpit/outputs/dir
patterns: "*.db"
recurse: yes
register: files_matched
- name: Search for String in the matched files
include_tasks:
tasks.yml
with_items:
- "{{ files_matched.files }}"
tasks.yml:
- command: grep -i {{ myString }} {{ item.path }}
register: command_result
failed_when: command_result.rc == 0
You can add additional tasks in the tasks file if you wish to fail in a different way, for example using the fail module or meta: end_play
Q: "The requirement is that if the string is found I wish to abort the play immediately using "meta: end_play" marking the playbook as FAILED."
(ansible 2.7.9)
A: It's not possible.
1) It's not possible to execute meta: end_play in a loop on a condition (string is found)
2) It's not possible to execute both meta: end_play and fail/assert in one play
1) It is possible to write a filter_plugin (Python2)
$ cat filter_plugins/file_filters.py
import mmap
def file_list_search(list, string):
for file in list:
if file_search(file, string):
break
return file_search(file, string)
def file_search(file, string):
f = open(file)
s = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
if s.find(string) != -1:
return True
else:
return False
class FilterModule(object):
''' Ansible filters for operating on files '''
def filters(self):
return {
'file_search' : file_search,
'file_list_search' : file_list_search
}
and use it in a play. For example
- hosts: localhost
vars:
myString: "test string"
tasks:
- find:
paths: /home/examples
patterns: "*.db"
register: files_matched
- name: End of play when myString found
meta: end_play
when: "files_matched.files|
json_query('[*].path')|
file_list_search(myString)"
- debug:
msg: continue
2) It is possible to set_stats. The play below
- name: End of play when myString found
block:
- set_stats:
data:
FAILED: 1
- meta: end_play
when: "files_matched.files|
json_query('[*].path')|
file_list_search(myString)"
- debug:
msg: continue
- set_stats:
data:
FAILED: 0
gives the output below if the string is found
PLAY RECAP **********************************************************************************
127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0
CUSTOM STATS: *******************************************************************************
RUN: { "FAILED": 1}
Allow show_custom_stats
$ grep stats ansible.cfg
show_custom_stats = True

ansible AttributeError: 'list' object has no attribute 'startswith'

I am running ansible and trying to make this task work, it fails with this error:
An exception occurred during task execution. To see the full
traceback, use -vvv. The error was: AttributeError: 'list' object has
no attribute 'startswith' fatal: [test-1]: FAILED! =>
{"failed": true, "msg": "Unexpected failure during module execution.",
"stdout": ""} msg: Unexpected failure during module execution.
The code is:
- name: Register env Type
shell: facter configured_setup
register: setup
- name: foo tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- name: fetch group_vars
fetch:
src:
- { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
"fetch group_vars" is the task that always fails, any idea how this can work?
What I am trying to do is add more source files to fetch from different setvers.
So I want to have more lines under "src:", saying - { filename, when setup.stdout =="something else" }
The full error is:
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 96, in run
item_results = self._run_loop(items)
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 252, in _run_loop
res = self._execute(variables=task_vars)
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 446, in _execute
result = self._handler.run(task_vars=variables)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/fetch.py", line 62, in run
source = self._remote_expand_user(source)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/init.py", line 460, in _remote_expand_user
if not path.startswith('~'): # FIXME: Windows paths may start with "~ instead of just ~
AttributeError: 'list' object has no attribute 'startswith'
fatal: [test-1]: FAILED! => {"failed": true, "msg":
"Unexpected failure during module execution.", "stdout": ""} msg:
Unexpected failure during module execution.
The ansible documentation clearly states that fetch, fetches a file, not a list of files. Although one can program an application to deal with both a scalar and a sequence loaded from a YAML document, that is not automatic and would almost certainly have been reflected in the documentation.
Since you already have a sequence at a higher level, just extend that.
- name: Register env Type
shell: facter configured_setup
register: setup
- name: transparency tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- name: fetch group_vars
fetch:
src: { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
- name: fetch group_vars2
fetch:
src: { filename, when setup.stdout =="something else" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
You might be able to reduce the repetitiveness somewhat by using YAML's anchor and merge:
- name: Register env Type
shell: facter configured_setup
register: setup
- name: transparency tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- &fetchtask
name: fetch group_vars
fetch: &fetchsrc
src: { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
- <<: *fetchtask
name: fetch group_vars2
fetch:
<<: *fetchsrc
src: { filename, when setup.stdout =="something else" }
Ansible probably expands the {{...}} before handing the document to the YAML parser, otherwise the value for shell in the "transparency task" would throw an error. But you should probably still quote that like you do with the value for dest
So I ended up doing this (which is working):
- name: fetch group_vars test
fetch:
src: "{{ item }}"
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
when: setup.stdout == "something" and {{something_else}} == True
I also noticed that there might be a bug in ansible related to registers.
while using the "when" statement, even if the condition is not met, the register statement takes affect:
- name: test tasks something enabled
shell: /tmp/{{populate_script}} -u -a {{hosts}} -r
register: variable
when: setup.stdout == "test" and something == True
- name: test tasks something disabled
shell: /tmp/{{populate_script}} -u -a {{hosts}}
register: variable
when: setup.stdout == "test" and something == False
only one of these conditions will be met, in case the first one is met the second condition will override "variable"

Resources