I have some output from a text file on a Windows system (using winrm) that I am parsing. I am trying to make the playbook fail if it does not see the word 'test' more than once.
This is what I have that works just for continuing the playbook if it sees the word 'test' at least one time:
- name: "Windows Output | Parse file"
win_shell: |
(Get-Content C:\TEST\output.txt)
register: 'testing_parse'
- name: "File output | verification"
assert:
that:
- "'test' in testing_parse.stdout"
fail_msg: "Exiting now."
success_msg: "Proceeding with task."
I thought that the following would work and it did not:
- name: "File output | verification"
assert:
that:
- "'test' in testing_parse.stdout >= '2'"
fail_msg: "Exiting now."
success_msg: "Proceeding with task."
How can I fix my assert task so that it meets my requirement?
TL;DR
- name: "File output | verification"
vars:
watch_regex: '(?:^|\W)(test)(?:$|\W)'
assert:
that:
- testing_parse.stdout | regex_findall(watch_regex) | length > 1
fail_msg: "Exiting now."
success_msg: "Proceeding with task."
Why the in test is not appropriate
in returns a boolean
Let's first see what we get back from the in test in several situations
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e "my_test='a test'"
localhost | SUCCESS => {
"msg": true
}
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e "my_test='a test and an other test'"
localhost | SUCCESS => {
"msg": true
}
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e "my_test='no word we look for'"
localhost | SUCCESS => {
"msg": false
}
As you can see, it will always return a boolean depending on the presence or not of the needle in the haystack.
in does not find words
Note also that in is not very good at finding words (since you mentioned that) as demonstrated below:
# It will match a substring in a middle of an other word
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e my_test="blahtesttoto"
localhost | SUCCESS => {
"msg": true
}
# It will not match a word inside an element of a list...
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e '{"my_test":["this is", "a test"]}'
localhost | SUCCESS => {
"msg": false
}
# ... but only an exact match of an element of a list
$ ansible localhost -m debug -a "msg={{ 'test' in my_test }}" \
-e '{"my_test":["a", "test"]}'
localhost | SUCCESS => {
"msg": true
}
Your expression is wrong anyway
To end with, let's look at your try when you wrote the expression:
'test' in testing_parse.stdout >= '2'
This means:
check if the string testing_parse.stdout is lexically superior or equal to the string '2'.
now check if the string 'test' is found inside the preceding boolean result.
As you can now guess with the explanation, there is absolutely no chance this will ever return true.
regex_findall to the rescue
A way to look for a specific word is to use a regex. The following will look for the test word i.e. the "test" string preceded and followed by any non-word character (end of line, beginning of line, white space, tab, punctuation....).
(?:^|\W)(test)(?:$|\W)
If you are not familiar with regular expressions, see this answer for a specific explanation and https://www.regextutorial.org/ for a general resource (I'm not affiliated, you can find others using your favorite search engine).
The regex_find_all filter can return all matches of a regex against a string into a list
$ ansible localhost -m debug \
-a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
-e "my_test='test'"
localhost | SUCCESS => {
"msg": [
"test"
]
}
$ ansible localhost -m debug \
-a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
-e "my_test='test and an other test but not blahtesttoto yet test'"
localhost | SUCCESS => {
"msg": [
"test",
"test",
"test"
]
}
$ ansible localhost -m debug \
-a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') }}" \
-e "my_test='notest'"
localhost | SUCCESS => {
"msg": []
}
Once we have that, we only need to count the number of elements in the returned list whith the length filter.
$ ansible localhost -m debug \
-a "msg={{ my_test | regex_findall('(?:^|\\W)(test)(?:$|\\W)') | length }}" \
-e "my_test='test and an other test but not blahtesttoto yet test'"
localhost | SUCCESS => {
"msg": "3"
}
And finally we can fix your assert task according to your requirement:
- name: "File output | verification"
vars:
watch_regex: '(?:^|\W)(test)(?:$|\W)'
assert:
that:
- testing_parse.stdout | regex_findall(watch_regex) | length > 1
fail_msg: "Exiting now."
success_msg: "Proceeding with task."
I have had an issue with connecting to local_action when running my playbook, my playbook is used to create users in SQL Server and need to run local action to get generate random password
fatal: [w961412]: UNREACHABLE! => {"changed": false, "msg": "ntlm: HTTPConnectionPool(host='localhost', port=5985): Max retries exceeded with url: /wsman (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f4572858790>: Failed to establish a new connection: [Errno 111] Connection refused',))", "unreachable": true}
task for ssh
- name: get random password
command: tr -dc 'A-HJ-NP-Za-km-z2-9' < /dev/urandom | dd bs=12 count=1 status=none
register: secret
tasks for winrm:
- name: Alter database credentials.
win_shell: |
sqlcmd -S {{ sql_hostname }},{{ SQL_PORT }} -E -q "alter login {{ dbuser }} with password=N'{{ secret.password }}'" -o alter.log
register: alter_result
Foremost, what you said is actually correct: you want a local action; I don't think it needs to connect back to your control host over ssh just to generate a password
So, I would expect you could use:
- name: get a random password
connection: local
shell: tr -dc 'A-HJ-NP-Za-km-z2-9' < /dev/urandom | dd bs=12 count=1 status=none
register: secret
- win_shell: |
echo "and now you are back to the normal playbook connection"
Your code snippet also had a bug in it by trying to use command: with a string containing a pipe -- shell operators are not supported by command:, that's why shell: exists
Then, separately, you don't have to use a bunch of shell commands, along with some magic tr string literal: ansible has a random password lookup such that you can:
- win_shell: |
sqlcmd -q "alter login with password=N'{{ item }}'"
register: alter_result
with_password: /dev/null length=12
trying to find and add hosts dynamically like so
---
- hosts: localhost
gather_facts: no
tasks:
- name: Gather EC2 remote facts.
ec2_remote_facts:
region: 'us-east-1'
register: ec2_remote_facts
- name: Debug.
debug:
msg: "{{ ec2_remote_facts }}"
- name: get instances for tags
add_host:
name: "{{ item }}"
group: dynamically_created_hosts
with_items: |
"{{ ec2_remote_facts.instances |
selectattr('tags.AppName', 'defined') | selectattr('tags.AppName', 'equalto', 'sql') |
selectattr('tags.AppType', 'defined') | selectattr('tags.AppType', 'equalto', 'infra') |
map(attribute='private_ip_address') | list }}"
- hosts:
- dynamically_created_hosts
become: yes
become_user: root
serial: 1
vars_files:
- group_vars/all
tasks:
- name: run command
shell: "uname -a"
I get following when i run in verbose mode
TASK [get instances for tags] **************************************************
task path: /Users/me/gitfork2/fornax/dynhst.yml:39
creating host via 'add_host': hostname="[u'10.112.114.241']"
changed: [localhost] => (item="[u'10.112.114.241']") => {"add_host": {"groups": ["dynamically_created_hosts"], "host_name": "\"[u'10.112.114.241']\"", "host_vars": {"group": "dynamically_created_hosts"}}, "changed": true, "invocation": {"module_args": {"group": "dynamically_created_hosts", "hostname": "\"[u'10.112.114.241']\""}, "module_name": "add_host"}, "item": "\"[u'10.112.114.241']\""}
PLAY [dynamically_created_hosts] ***********************************************
TASK [setup] *******************************************************************
<"[u'10.112.114.241']"> ESTABLISH SSH CONNECTION FOR USER: None
<"[u'10.112.114.241']"> SSH: ansible.cfg set ssh_args: (-F)(/Users/me/.ssh/config)
<"[u'10.112.114.241']"> SSH: ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled: (-o)(StrictHostKeyChecking=no)
<"[u'10.112.114.241']"> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<"[u'10.112.114.241']"> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<"[u'10.112.114.241']"> SSH: PlayContext set ssh_common_args: ()
<"[u'10.112.114.241']"> SSH: PlayContext set ssh_extra_args: ()
<"[u'10.112.114.241']"> SSH: EXEC ssh -C -vvv -F /Users/me/.ssh/config -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 '"[u'"'"'10.112.114.241'"'"']"' '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" && echo ansible-tmp-1509843772.58-176166317659656="` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" ) && sleep 0'"'"''
fatal: ["[u'10.112.114.241']"]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true}
to retry, use: --limit #./dynhst.retry
The odd thing here I see is
SSH: EXEC ssh -C -vvv -F /Users/me/.ssh/config -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 '"[u'"'"'10.112.114.241'"'"']"' '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" && echo ansible-tmp-1509843772.58-176166317659656="` echo $HOME/.ansible/tmp/ansible-tmp-1509843772.58-176166317659656 `" ) && sleep 0'"'"''
Seems like it is trying to ssh into '"[u'"'"'10.112.114.241'"'"']"' ... seems like the dynamically_created_hosts is being used as a string and not as a list
Any ideas why?
You pass a list (of IP addresses) to an argument name which requires a string:
hostname="[u'10.112.114.241']"
[ ] is a JSON representation of a list (single element in the example above).
If you want the first address from the list (and there seems to be no more for any of your hosts), then:
add_host:
name: "{{ item[0] }}"
group: dynamically_created_hosts
with_items: ...
I'm working on automating a task which needs to append the latest version of software to a file. I don't want it to do this multiple times for the same version.
It looks at the following example file:
var software releases = new Array(
"4.3.0",
"4.4.0",
"4.5.0",
"4.7.0",
"4.8.0",
"4.11.0",
"4.12.1",
"4.14.0",
"4.15.0",
"4.16.0",
);
the defaults main.yml would pass in something like
VERSION: 4.16.2
code
- name: register version check
shell: cat /root/versions.js | grep -q {{VERSION}}
register: current_version
- debug: msg="The registered variable output is {{ current_version.rc }}"
- name: append to versions.js
lineinfile:
dest: /root/versions.js
regexp: '^\);'
insertbefore: '^#\);'
line: " \"{{VERSION}}\",\n);"
owner: root
state: present
when: current_version.rc == 1
problem: the debug message is evaluating current_version.rc and showing me boolean values based on the grep commands output, but I can't re-use this in the when conditional to determine if the task should be run.
Edit: the output:
PLAY [localhost] **************************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [test | register version check] *****************************************
failed: [localhost] => {"changed": true, "cmd": "cat /root/versions.js | grep -q 3.19.2", "delta": "0:00:00.003570", "end": "2015-12-17 00:24:49.729078", "rc": 1, "start": "2015-12-17 00:24:49.725508", "warnings": []}
FATAL: all hosts have already failed -- aborting
PLAY RECAP ********************************************************************
to retry, use: --limit #/root/site.retry
localhost : ok=1 changed=0 unreachable=0 failed=1
As nikobelia pointed out in the comments, grep returns an exit code of 1 when it doesn't match any lines. Ansible then interprets this (actually any status code other than 0 from a shell/command task) as an error and so promptly fails.
You can tell Ansible to ignore the response code from the shell/command task by using ignore_errors. Although with grep this will ignore actual errors (given by a return code of 2) so instead you might want to use failed_when like this:
- name: register version check
shell: cat /root/versions.js | grep -q {{VERSION}}
register: current_version
failed_when: current_version.rc == 2
I would like to quickly monitor some hosts using commands like ps,dstat etc using ansible-playbook. The ansible command itself perfectly does what I want, for instance I'd use:
ansible -m shell -a "ps -eo pcpu,user,args | sort -r -k1 | head -n5"
and it nicely prints all std output for every host like this:
localhost | success | rc=0 >>
0.0 root /sbin/init
0.0 root [kthreadd]
0.0 root [ksoftirqd/0]
0.0 root [migration/0]
otherhost | success | rc=0 >>
0.0 root /sbin/init
0.0 root [kthreadd]
0.0 root [ksoftirqd/0]
0.0 root [migration/0]
However this requires me to keep a bunch of shell scripts around for every task which is not very 'ansible' so I put this in a playbook:
---
-
hosts: all
gather_facts: no
tasks:
- shell: ps -eo pcpu,user,args | sort -r -k1 | head -n5
and run it with -vv, but the output baiscally shows the dictionary content and newlines are not printed as such so this results in an unreadable mess like this:
changed: [localhost] => {"changed": true, "cmd": "ps -eo pcpu,user,args | sort -r -k1
head -n5 ", "delta": "0:00:00.015337", "end": "2013-12-13 10:57:25.680708", "rc": 0,
"start": "2013-12-13 10:57:25.665371", "stderr": "", "stdout": "47.3 xxx Xvnc4 :24
-desktop xxx:24 (xxx) -auth /home/xxx/.Xauthority -geometry 1920x1200\n
....
I also tried adding register: var and the a 'debug' task to show {{ var.stdout }} but the result is of course the same.
Is there a way to get nicely formatted output from a command's stdout/stderr when run via a playbook? I can think of a number of possible ways (format output using sed? redirect output to file on the host then get that file back and echo it to the screen?), but with my limited knowledge of the shell/ansible it would take me a day to just try it out.
The debug module could really use some love, but at the moment the best you can do is use this:
- hosts: all
gather_facts: no
tasks:
- shell: ps -eo pcpu,user,args | sort -r -k1 | head -n5
register: ps
- debug: var=ps.stdout_lines
It gives an output like this:
ok: [host1] => {
"ps.stdout_lines": [
"%CPU USER COMMAND",
" 1.0 root /usr/bin/python",
" 0.6 root sshd: root#notty ",
" 0.2 root java",
" 0.0 root sort -r -k1"
]
}
ok: [host2] => {
"ps.stdout_lines": [
"%CPU USER COMMAND",
" 4.0 root /usr/bin/python",
" 0.6 root sshd: root#notty ",
" 0.1 root java",
" 0.0 root sort -r -k1"
]
}
This is a start may be :
- hosts: all
gather_facts: no
tasks:
- shell: ps -eo pcpu,user,args | sort -r -k1 | head -n5
register: ps
- local_action: command echo item
with_items: ps.stdout_lines
NOTE: Docs regarding ps.stdout_lines are covered here: ('Register Variables' chapter).
Expanding on what leucos said in his answer, you can also print information with Ansible's humble debug module:
- hosts: all
gather_facts: no
tasks:
- shell: ps -eo pcpu,user,args | sort -r -k1 | head -n5
register: ps
# Print the shell task's stdout.
- debug: msg={{ ps.stdout }}
# Print all contents of the shell task's output.
- debug: var=ps
I found using the minimal stdout_callback with ansible-playbook gave similar output to using ad-hoc ansible.
In your ansible.cfg (Note that I'm on OS X so modify the callback_plugins path to suit your install)
stdout_callback = minimal
callback_plugins = /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ansible/plugins/callback
So that a ansible-playbook task like yours
---
-
hosts: example
gather_facts: no
tasks:
- shell: ps -eo pcpu,user,args | sort -r -k1 | head -n5
Gives output like this, like an ad-hoc command would
example | SUCCESS | rc=0 >>
%CPU USER COMMAND
0.2 root sshd: root#pts/3
0.1 root /usr/sbin/CROND -n
0.0 root [xfs-reclaim/vda]
0.0 root [xfs_mru_cache]
I'm using ansible-playbook 2.2.1.0
ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook /tmp/foo.yml -vvv
Tasks with STDOUT will then have a section:
STDOUT:
What ever was in STDOUT
If you need a specific exit status, Ansible provides a way to do that via callback plugins.
Example. It's a very good option if you need a 100% accurate exit status.
If not, you can always use the Debug Module, which is the standard for this cases of use.
Cheers
for me, the only one that works (because of register+with_items combination) is this:
- name: "download and distribute certs"
shell: "python3 /tmp/bla.py {{ item.name }}"
register: python3
with_items: "{{ my_list }}"
- debug: msg="{{ item.stdout_lines | join("\n") }}"
with_items: "{{ python3['results'] }}"
I personally have several shell or command calls in a playbook and gather that's output to different variables. At the end I sum up the information and list all at once like so:
- ansible.builtin.shell: "df -h"
register: disk
- ansible.builtin.shell: "free -m"
register: mem
.....
- name: Summarize
local_action: ansible.builtin.debug var={{ item }}
become: no
with_items:
- disk.stdout_lines
- mem.stdout_lines
if you call it with
ANSIBLE_STDOUT_CALLBACK=minimal ansible-playbook getServerInfo.yml
it gives a nice, clean output
Perhaps not relevant if you're looking to do this ONLY using ansible. But it's much easier for me to have a function in my .bash_profile and then run _check_machine host1 host2
function _check_machine() {
echo 'hostname,num_physical_procs,cores_per_procs,memory,Gen,RH Release,bios_hp_power_profile,bios_intel_qpi_link_power_management,bios_hp_power_regulator,bios_idle_power_state,bios_memory_speed,'
hostlist=$1
for h in `echo $hostlist | sed 's/ /\n/g'`;
do
echo $h | grep -qE '[a-zA-Z]'
[ $? -ne 0 ] && h=plabb$h
echo -n $h,
ssh root#$h 'grep "^physical id" /proc/cpuinfo | sort -u | wc -l; grep "^cpu cores" /proc/cpuinfo |sort -u | awk "{print \$4}"; awk "{print \$2/1024/1024; exit 0}" /proc/meminfo; /usr/sbin/dmidecode | grep "Product Name"; cat /etc/redhat-release; /etc/facter/bios_facts.sh;' | sed 's/Red at Enterprise Linux Server release //g; s/.*=//g; s/\tProduct Name: ProLiant BL460c //g; s/-//g' | sed 's/Red Hat Enterprise Linux Server release //g; s/.*=//g; s/\tProduct Name: ProLiant BL460c //g; s/-//g' | tr "\n" ","
echo ''
done
}
E.g.
$ _machine_info '10 20 1036'
hostname,num_physical_procs,cores_per_procs,memory,Gen,RH Release,bios_hp_power_profile,bios_intel_qpi_link_power_management,bios_hp_power_regulator,bios_idle_power_state,bios_memory_speed,
plabb10,2,4,47.1629,G6,5.11 (Tikanga),Maximum_Performance,Disabled,HP_Static_High_Performance_Mode,No_CStates,1333MHz_Maximum,
plabb20,2,4,47.1229,G6,6.6 (Santiago),Maximum_Performance,Disabled,HP_Static_High_Performance_Mode,No_CStates,1333MHz_Maximum,
plabb1036,2,12,189.12,Gen8,6.6 (Santiago),Custom,Disabled,HP_Static_High_Performance_Mode,No_CStates,1333MHz_Maximum,
$
Needless to say function won't work for you as it is. You need to update it appropriately.