How to run JUnit tests on multiple Ansible assertions - junit

I've got a GitLab pipeline that runs some Ansible playbooks in a container. At the end, I want to run some tests to ensure that everything is properly configured, e.g.
- name: Get the Frobzy value
raw: "frobzy -x foobar"
register: frobzy_val
changed_when: false
- name: Check that Frobzy is valid
assert:
that:
- frobzy_val.stdout is match "oogah"
fail_msg: "bad frobzy"
ignore_errors: true # see comments
- name: Get the Blobzy value
raw: "blobzy -y barfoo"
register: blobzy_val
changed_when: false
- name: Check that Blobzy is valid
assert:
that:
- blobzy_val.stdout is match "warra"
fail_msg: "bad blobzy"
ignore_errors: true # see comments
This is all nicely configured so that I get a JUnit report that gets displayed on my GitLab pipeline results.
The problem is, let's say both of these tests fail. If I look at the test results from my pipeline, it correctly displays that the tests failed. Yet the pipeline itself is marked as having passed! If I hadn't taken a detailed look at the test results, I would have assumed that everything is tickety-boo, and merged some potentially broken code.
The reason why the pipeline is marked as successful is because of those ignore_errors: true settings. It registers an error, but follows the directive that this should not fail the pipeline. But if I set ignore_errors: false (which is the default behavior), then as soon as the Frobzy test fails, the pipeline will abort and show a failure on the Frobzy test, but since it never ran the Blobzy test, I don't know that Blobzy would have failed, too.
Basically, I want a way to run all my Ansible assertions, even if some of them fail; I want to see all the test results in my pipeline, and I want the pipeline to fail if any tests failed.
How can I do this?

Here's a quick solution that might help.
Have a task to check if any of the tasks failed. Set a failed variable to true in that case.
Fail the playbook at the end if this variable is set to true.
- hosts: localhost
vars:
failed: false
gather_facts: no
tasks:
- name: Get the Frobzy value
raw: "echo frobzy"
register: frobzy_val
changed_when: false
- name: Check that Frobzy is valid
assert:
that:
- frobzy_val.stdout is match "frobzy"
fail_msg: "bad frobzy"
ignore_errors: true # see comments
- name: Get the Blobzy value
raw: "echo blobzy"
register: blobzy_val
changed_when: false
- name: Check that Blobzy is valid
assert:
that:
- blobzy_val.stdout is match "blobzy"
fail_msg: "bad blobzy"
ignore_errors: true # see comments
- name: Set a variable to true if the task fails
set_fact:
failed: true
ignore_errors: true
when: not ("'blobzy' in blobzy_val.stdout" ) or
not ("'frobzy' in frobzy_val.msg")
- name: Fail if failed is true
fail:
msg: "Failed"
when: failed
Run
─ ansible-playbook test.yaml
PLAY [localhost] ********************************************************************************************************************************************************************************
TASK [Get the Frobzy value] *********************************************************************************************************************************************************************
ok: [localhost]
TASK [Check that Frobzy is valid] ***************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed"
}
TASK [Get the Blobzy value] *********************************************************************************************************************************************************************
ok: [localhost]
TASK [Check that Blobzy is valid] ***************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed"
}
TASK [Set a variable to true if the task fails] *************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail if failed is true] *******************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP **************************************************************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0

Make a single assertion for both conditions and delay it until the end so that it fails after the two tests are done.
Add a debug task to show results for your pipelines.
- name: Get the Frobzy value
raw: "frobzy -x foobar"
register: frobzy_val
changed_when: false
- name: Get the Blobzy value
raw: "blobzy -y barfoo"
register: blobzy_val
changed_when: false
- name: Show results for Frobzy and Blobzy
debug:
msg:
- "Frobzy is {{ 'ok' if frobzy_val.stdout is match 'oogah' else 'bad' }}"
- "Blobzy is {{ 'ok' if lobzy_val.stdout is match 'warra' else 'bad' }}"
- name: Fail if any test is bad
assert:
that:
- frobzy_val.stdout is match "oogah"
- blobzy_val.stdout is match "warra"
fail_msg: "At least one of Frobzy or Blobzy has bad results. See debug above"
``

Related

How can I loop through json object REST API body in Ansible?

When using Ansible I am able to execute when passed one-by-one like this:
---
- name: Using a REST API
become: false
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: “Let’s get list of Interfaces”
- name: Adding a Bridge-Interface
uri:
url: https://router/rest/interface/bridge
method: PUT
validate_certs: false
url_username: ansible
url_password: ansible
force_basic_auth: yes
body_format: json
status_code: 201
body: '{"name":"bridge_ansible"}'
register: results
- debug:
var: results
I want to iterate through a set of commands so I thought of looping, but that does not work for me, I am using this code:
---
- name: Using a REST API
become: false
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: “Let’s get list of Interfaces”
- name: Adding a Bridge-Interface
uri:
url: "{{item.url}}"
method: PUT
validate_certs: false
url_username: ansible
url_password: ansible
force_basic_auth: yes
body_format: json
status_code: 201
body: "{{item.body}}"
register: results
loop:
- {body:'{"name":"bridge_ansible"}', url:'https://router/rest/interface/bridge'}
- {body:'{"address":"6.6.6.6", "interface":"bridge_ansible"}', url:'https://router/rest/ip/address'}
- debug:
var: results
I get an error for this {body:'{"name":"bridge_ansible"}', url:'https://router/rest/interface/bridge'} in the json object { I think my syntax is not correct but cannot understand the correct thing. Can someone please help
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)
Syntax Error while loading YAML.
did not find expected ',' or '}'
The error appears to be in '/ansible-playbook/1-demo.yaml': line 23, column 19, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
loop:
- {body:'\{"name":"bridge_ansible"\}', url:'https://router/rest/interface/bridge'}
^ here
This one looks easy to fix. It seems that there is a value started
with a quote, and the YAML parser is expecting to see the line ended
with the same kind of quote. For instance:
when: "ok" in result.stdout
Could be written as:
when: '"ok" in result.stdout'
Or equivalently:
when: "'ok' in result.stdout"
We could be wrong, but this one looks like it might be an issue with
unbalanced quotes. If starting a value with a quote, make sure the
line ends with the same set of quotes. For instance this arbitrary
example:
foo: "bad" "wolf"
Could be written as:
foo: '"bad" "wolf"'
Thanks

Ansible: create a function

I found this page from answer number 4 by #cobbzilla useful to my use case.
Just want to ask if Ansible is capable to have a function that will handle this command:
2>&1 >> /tmp/debug.log
I have already applied this solution to my yml files and I was looking if the command can be wrapped on a function so that it will show much cleaner.
My Sample Ansible yml:
- name: Deploy
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: perform 1st script
shell: bash -c "1st_script.sh 2>&1 >> /var/tmp/debug.log"
- name: perform 2nd script
shell: bash -c "2nd_script.sh 2>&1 >> /var/tmp/debug.log"
- name: perform 3rd script
shell: bash -c "3rd_script.sh 2>&1 >> /var/tmp/debug.log"
Preliminary note: I am answering your direct question below because this can be useful in other circumstances.
Meanwhile, in the ansible context, playing bash scripts and returning their output and error to a log file on the target is generally not a good idea. You will be left blind if something goes wrong, or if you want to analyze the output, as the module will return an empty stderr and stdout. You will have to rely on analyzing the log file later (and finding the correct output since you mix script output in there).
On an even wider level, you should use shell/command only when there is no other possibility to get the same job done using existing modules
If you don't mind writing some lines of python, a pretty easy and straight forward way to acheive your goal is to use a custom filter. The below example is quickNdirty. You will probably have to harden its code (escape command characters, check for specific errors...) but this should put you on track.
For the example, I am creating the filter in the filter_plugins folder adjacent to the demo playbook. If you need to distribute that filter across projects, search the ansible documentation to learn how to wrap that in a role or a collection.
In filter_plugins/shell_filters.py
def bash_n_log(command, log_file='/var/tmp/debug.log'):
"""Return a formatted string to play the script in bash and log its output"""
return f'bash -c "{command} 2>&1 >> {log_file}"'
class FilterModule(object):
"""collection of shell utility filters."""
def filters(self):
"""Return the filter list."""
return {
'bash_n_log': bash_n_log
}
Then the demo playbook.yml
- name: Custom shell filter demo
hosts: localhost
gather_facts: false
vars:
my_commands:
- echo Hello World
- ls -l /dev/null
tasks:
- name: Play my commands with my filter
shell: "{{ item | bash_n_log }}"
loop: "{{ my_commands }}"
- name: Same example with non default log file
shell: "{{ item | bash_n_log('/tmp/other.log') }}"
loop: "{{ my_commands }}"
- name: Get content of the log files
slurp:
path: "{{ item }}"
register: slurped_logs
loop:
- /var/tmp/debug.log
- /tmp/other.log
- name: Show log file content
debug:
msg: "{{ (item.content | b64decode).split('\n') }}"
loop: "{{ slurped_logs.results }}"
loop_control:
label: "{{ item.item }}"
gives:
$ ansible-playbook playbook.yml
PLAY [Custom shell filter demo] ********************************************************************************************************************************************************************************************************
TASK [Play my commands with my filter] *************************************************************************************************************************************************************************************************
changed: [localhost] => (item=echo Hello World)
changed: [localhost] => (item=ls -l /dev/null)
TASK [Same example with non default log file] ******************************************************************************************************************************************************************************************
changed: [localhost] => (item=echo Hello World)
changed: [localhost] => (item=ls -l /dev/null)
TASK [Get content of the log files] ****************************************************************************************************************************************************************************************************
ok: [localhost] => (item=/var/tmp/debug.log)
ok: [localhost] => (item=/tmp/other.log)
TASK [Show log file content] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => (item=/var/tmp/debug.log) => {
"msg": [
"Hello World",
"crw-rw-rw- 1 root root 1, 3 Mar 27 12:06 /dev/null",
""
]
}
ok: [localhost] => (item=/tmp/other.log) => {
"msg": [
"Hello World",
"crw-rw-rw- 1 root root 1, 3 Mar 27 12:06 /dev/null",
""
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I just tried and tested this simple solution worked, I just added vars section witch points the variable to a command
- name: Deploy
hosts: localhost
connection: local
gather_facts: false
vars:
logger: "2>&1 >> /var/tmp/debug.log"
tasks:
- name: perform 1st script
shell: bash -c "1st_script.sh {{ logger }}"
- name: perform 2nd script
shell: bash -c "2nd_script.sh {{ logger }}"
- name: perform 3rd script
shell: bash -c "3rd_script.sh {{ logger }}"

How to call the ansible play recursively based on until condition

I'm trying to execute a playbook recursively until the condition satisfies. But, I couldn't achieve it some-how. Can anyone suggest me the solution.
Ansible-version: 2.2.1.0
Here is my test-plays.
main_play.yml:
---
- hosts: localhost
tasks:
- name: Wait till you get the needed thing in the get call
include: loop.yml
Here is the loop.yml
- name: Wait until migration jobs reach DbcAllJobxxxxx
uri:
url: "http://<url->/jobs"
method: GET
headers:
Content-Type: "application/json"
Accept: "application/json"
Postman-Token: "31d6"
cache-control: "no-cache"
return_content: yes
register: migration_status
ignore_errors: yes
- debug: msg="{{ migration_status }}"
#write mig-status to file
- copy: content="{{ migration_status.content }}" dest=/path/to/dest/migration_status.json
- name: Get the DbcAllJobxxxxx status from py script
shell: python jsonrc.py /path/to/dest/migration_status.json
register: pyout
- debug: msg="{{ pyout.stdout }}"
- include: loop.yml
when: pyout.stdout != '1'
ignore_errors: yes
- debug: msg="{{ pyout.stdout }}"
Requirement : GET json call will return json. The json may vary time-to-time as it returns dynamic status. So, want to pool the json data continuously to know the value of a key - Which is a sign to call other event. So, I need to wait for the key-value pair in that json. [It may loss within time frame.. Need to catch at that point]. To achieve same parsing the json through python script and catching the return of pyscript and checking the value and calling the same play if it doesn't satisfy the condition.
Executing ansible-playbook main_play.yml
Even the pyout.stdout == '1' it's still throwing ERROR! Unexpected Exception: maximum recursion depth exceeded error. Did I miss any ?? Help me in this regard.
BTW, I tried to achieve this with until using json_query. but, parsing become difficult in this part. So, avoided this solution.
From ansible 2.4 onwards there is the include_tasks builtin that does work recursively.
main_play.yml:
---
- hosts: localhost
tasks:
- set_fact:
counter: 1
- include_tasks: loop.yml
loop.yml:
- set_fact:
counter: "{{counter | int + 1 }}"
- debug: msg="{{ counter }}"
- include_tasks: loop.yml
when: counter | int < 5
Result:
PLAY [localhost] ****************************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************
ok: [localhost]
TASK [include_tasks] ***********************************************************************************************************************************
included: loop.yml for localhost
TASK [set_fact] *****************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "2"
}
TASK [include_tasks] ************************************************************************************************************************************************************************
included: loop.yml for localhost
TASK [set_fact] *****************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "3"
}
TASK [include_tasks] ************************************************************************************************************************************************************************
included: loop.yml for localhost
TASK [set_fact] *****************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "4"
}
TASK [include_tasks] ************************************************************************************************************************************************************************
included: loop.yml for localhost
TASK [set_fact] *****************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "5"
}
TASK [include_tasks] ************************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP **********************************************************************************************************************************************************************************
localhost : ok=14 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
This code is to loop recursivley until I get the right message or it reaches MAX retries (In case just set very high)
- name: Group of tasks that are tightly coupled
block:
- name: Increment the retry count
set_fact:
retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
- name: get state file {{ retry_count }}
command:
cmd: cat /tmp/test.txt
register: out
- debug:
msg: "Found END message"
when: "'END' in out.stdout_lines"
- fail:
msg: "END not reached"
when: "not 'END' in out.stdout_lines"
rescue:
- fail:
msg: Maximum retries of grouped tasks reached
when: retry_count | int == 15
- name: pause a moment
pause:
seconds: 1
when: "not 'END' in out.stdout_lines"
- debug:
msg: "hasen't finished yet, retry"
when: "not 'END' in out.stdout_lines"
- include_tasks: loop_control.yml
when: "not 'END' in out.stdout_lines"
It's obvious, I think.
"Here is the loop.yml"
...
- include: loop.yml
ERROR! Unexpected Exception: maximum recursion depth exceeded error.

Ansible "set_fact" repository url from json file using filters like "from_json"

Using Ansible "set_fact" module, I need to get repository url from json file using filters like "from_json". I tried in couple ways, and still doesn't get it how is should work.
- name: initial validation
tags: bundle
hosts: localhost
connection: local
tasks:
- name: register bundle version_file
include_vars:
file: '/ansible/playbook/workbench-bundle/bundle.json'
register: bundle
- name: debug registered bundle file
debug:
msg: '{{ bundle }}'
I get json that I wanted:
TASK [debug registered bundle file] ************************************************
ok: [127.0.0.1] => {
"msg": {
"ansible_facts": {
"engine-config": "git#bitbucket.org/engine-config.git",
"engine-monitor": "git#bitbucket.org/engine-monitor.git",
"engine-server": "git#bitbucket.org/engine-server.git",
"engine-worker": "git#bitbucket.org/engine-worker.git"
},
"changed": false
}
}
And then I'm trying to select each value by key name to use this value as URL to "npm install" each package in separate instances.
- name: set_fact some paramater
set_fact:
engine_url: "{{ bundle.('engine-server') | from_json }}"
And then I get error:
fatal: [127.0.0.1]: FAILED! => {"failed": true, "msg": "template error
while templating string: expected name or number. String: {{
bundle.('engine-server') }}"}
I many others ways like this loopkup, and it still fails with others errors. Can someone help to understand, how I can find each parameter and store him as "set_fact"? Thanks
Here is a sample working code to set a variable like in the question (although I don't see much sense in it):
- name: initial validation
tags: bundle
hosts: localhost
connection: local
tasks:
- name: register bundle version_file
include_vars:
file: '/ansible/playbook/workbench-bundle/bundle.json'
name: bundle
- debug:
var: bundle
- debug:
var: bundle['engine-server']
- name: set_fact some paramater
set_fact:
engine_url: "{{ bundle['engine-server'] }}"
The above assumes your input data (which you did not include) is:
{
"engine-config": "git#bitbucket.org/engine-config.git",
"engine-monitor": "git#bitbucket.org/engine-monitor.git",
"engine-server": "git#bitbucket.org/engine-server.git",
"engine-worker": "git#bitbucket.org/engine-worker.git"
}

extracting a variable from json output then debug and register the outout with ansible

Hi I have a problem of getting one of the variables extracted from a json output after doing a curl to be parsed and registered back to ansible
Playbook:
- name: debug stdout
debug:
msg: "{{ result.stdout | from_json }}"
register: dataresult
- name: debug fact
debug:
msg: "{{ dataresult.data.start_time_string }}"
output :
TASK [backup_api : debug stdout]
***********************************************
task path: /home/ansible/cm-dha/roles/backup_api/tasks/main.yml:36
ok: [127.0.0.1] => {
"msg": {
"data": [
{
"backup_id": 40362,
"certified": null,
"instance_id": 148,
"start_time": 1506985211,
"start_time_string": "10/03/2017 03:00:11 am"
}
],
"timestamp": 1507022232
}
}
error:
fatal: [127.0.0.1]: FAILED! => {
"failed": true,
"msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'dict object' has no attribute 'data'\n\nThe error appears to have been in '/home/ansible/cm-dha/roles/backup_api/tasks/main.yml': line 48, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: debug fact\n ^ here\n"
The error is happening when trying to extract the value start_time_string
so how to do it probably as I tried too many things like using with_items, with_dict , simulating the data[] output to debug and even doing a json query but without success
so any help here?
Don't use debug to assign facts, use set_fact instead:
- name: debug stdout
set_fact:
dataresult: "{{ result.stdout | from_json }}"
- name: debug fact
debug:
msg: "{{ dataresult.data[0].start_time_string }}"
Thanks to accepted answer, I've made it as below for my case. Leaving it here, maybe it helps someone.
- name: Connectors
shell: curl -X GET http://myserver:8083/connectors
register: out6
- set_fact:
dataresult: "{{ out6.stdout | from_json }}"
- debug: var=dataresult