I'm new to Ansible, trying to use Assert module to validate the length
of a string. The result register contains string: "'MWCC' | length == 3".
Could you please help to remove the single quotes ' inside the string so the
result would be like this: "MWCC | length == 3”?
Thanks.
- set_fact:
test_code: 'MWCC'
- name: validate three characters code
assert:
that:
"'{{test_code}}' | length == 3 "
ignore_errors: True
register: code_result
- debug: var=code_result.assertion
- name: extract the string from assertion test output
set_fact:
extract_result: "{{code_result.assertion |regex_replace('\'')}}"
TASK [set_fact] *********************************************************************************
ok: [localhost] => {
"ansible_facts": {
"test_code": "MWCC"
},
"changed": false
}
TASK [validate three characters code] ****************************************************************************
fatal: [localhost]: FAILED! => {
"assertion": "'MWCC' | length == 3 ",
"changed": false,
"evaluated_to": false
}
...ignoring
TASK [debug] ***************************************************************************
ok: [localhost] => {
"code_result.assertion": "'MWCC' | length == 3 "
}
TASK [extract the string from assertion test output] **********************************************************************
fatal: [localhost]: FAILED! => {
"msg": "template error while templating string: unexpected char u\"'\" at 41. String: {{code_result.assertion |regex_replace(''')}}"
}
"{{ test_string |replace(\"'\",'') }}"
This has the replace Jinja2 filter with two arguments:
the single quote, that you want to replace, surrounded by escaped double-quotes.
the empty string denoted by two single quotes.
I think your assert should not have another pair of quotes, like this:
"{{test_code}} | length == 3 "
And surely, 4 character string length should be 4, shouldn't it?
Related
I currently have a playbook which includes a task file. In that task file, I would like to check for a condition. If the exit code of that condition is not equal to 0, all steps in the task file should be repeated. I have tried a few variations with block and loops but I have not figured out a way to make it do what I described above.
Currently I have something like this:
tasks:
- name: call task file
include: task_file.yml
In task_file.yml,
- name: perform an operations
shell: do A
- name: check
shell: do B
register: result
Next, I would like to tell the main playbook that if result.rc != 0, please repeat task_file.yml until result.rc == 0.
Any pointers would be greatly appreciated
The playbook seems to end no matter what the exit code.
There's no direct way to reach your goal as include_tasks does not support the retry/until loop keywords.
There is an attempt to circumvent that limitation by teaching ansible a new loop_control.until keyword for loops which could be used for includes. Unfortunately, the pull request has been opened since Sep. 2019 and has still not reached a realease.
The good news is you can implement that with some work by using include recursion with a block. The below example is largely inspired by a blog article on https://dev.to. I adapted to the current context, fixed some good practice and added features like flexible retries number and delay between retries. Here we go:
The tasks to be retried go in task_file.yml
---
- name: group of tasks to repeat until success
block:
- name: increment attempts counter
ansible.builtin.set_fact:
attempt_number: "{{ attempt_number | d(0) | int + 1 }}"
- name: dummy task
ansible.builtin.debug:
msg: "I'm a dummy task"
- name: task to check for success.
# Just for the example. Will return success on attempt number 3
ansible.builtin.command: "[ {{ attempt_number | int }} -eq 3 ]"
changed_when: false
rescue:
- name: "Fail if we reached the max of {{ max_attempts | d(3) }} attempts"
# Default will be 3 attempts if max_attempts is not passed as a parameter
ansible.builtin.fail:
msg: Maximum number of attempts reached
when: attempt_number | int == max_attempts | int | d(3)
- ansible.builtin.debug:
msg: "group of tasks failed on attempt {{ attempt_number }}. Retrying"
- name: add delay if needed
# no delay if retry_delay is not passed as parameter
ansible.builtin.wait_for:
timeout: "{{ retry_delay | int | d(omit) }}"
when: retry_delay is defined
# include ourselves to retry.
- ansible.builtin.include_tasks: task_file.yml
As you can see, the file includes itself again in case of failure until success of max attempts is reached. Also, note that a retry will happen if any task fails inside the block, not only the last one. If you have a more complex scenario, you can implement more checks for fail/not fail in the rescue section an even add an always section if needed. See anbile blocks
Then you can call this file from your playbook:
---
- hosts: localhost
gather_facts: false
tasks:
- name: Include tasks to retry. 7 attempts max with 1 second delay
ansible.builtin.include_tasks: task_file.yml
vars:
max_attempts: 7
retry_delay: 1
Playing this example succeeds on third attempt as hardcoded and expected. (You can play around with the parameters to test a fail scenario)
$ ansible-playbook playbook.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Include tasks to retry] **********************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "1", "-eq", "3", "]"], "delta": "0:00:00.002104", "end": "2022-12-08 14:16:27.850578", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:27.848474", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 1. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "2", "-eq", "3", "]"], "delta": "0:00:00.004009", "end": "2022-12-08 14:16:29.496509", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:29.492500", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 2. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=14 changed=0 unreachable=0 failed=0 skipped=2 rescued=2 ignored=0
I have a task with an API call that returns dictionary as output. From this output I need only an ID. This API call is triggered only once (when item == "1"), but I need it's output available also in the following cycles. Here is the code example I used:
register: output
when: item == "1"
ignore_errors: yes
- debug:
var: output.json.id
- name: show id
debug:
msg: output.json.id is "{{ output.json.id }}"
This is filtered output result I get in 1st cycle:
ok: [localhost] => {
"msg": "output.json.id is \"kjfld4343009394\""
}
In the 2nd cycle API call is skipped (item is not 1) but output from previous cycle is not available any more :
ok: [localhost] => {
"output.json.id": "VARIABLE IS NOT DEFINED!: 'dict object' has no attribute 'json'"
}
BTW In case "debug: var: output.json.id" should be executed just in first cycle, I tried with putting it with conditional item=1 and ignore_errors=yes but that didn't help.
- debug:
var: output.json.id
when: item == "1"
ignore_errors: yes
What can I do to have this output available in other cycles?
Thanks!
I just found solution with set_fact.
- name: Set var id (set_fact)
set_fact:
var_id: "{{ output.json.id }}"
when: item == "1"
- debug:
msg: id is "{{ var_id }}"
When saved like this var_id can be used in the following loop cycles.
Hello Developer Community!
I have been working on developing some Ansible playbooks to manage Citrix NetScaler configuration and would like to get some help about the following. I have the following data structure defined in a variable named nsapp_lb_server:
nsapp_lb_server:
- name: "SRV-1"
ipaddress: "10.102.102.1"
comment: "Chewbacca"
- name: "SRV-2"
ipaddress: "10.102.102.2"
comment: "C-3PO"
- name: "SRV-3"
ipaddress: "10.102.102.3"
comment: "Obi-Wan Kenobi"
...
[+ another 1200 item...]
and I have the follow task:
- name: "Check variables (loop)"
ansible.builtin.assert:
that:
- ( (item.name is defined) and (item.name | length > 0) )
- ( (item.ipaddress is defined) and (item.ipaddress | ipaddr() == item.ipaddress) )
- ( (item.comment | length > 0) if (item.comment is defined) else omit )
loop: "{{ nsapp_lb_server }}"
My problem is that, when I have thousands of records in the nsapp_lb_server variable, the loop is incredibly slow. The task finishes in 30 minutes, which is a very long time... :-(
After some digging on the Internet, it seems, the issue is caused by Ansible "loop" function, so I would like to check if there are any other methods what I can use instead of loop.
Are there any alternatives of Ansible "loop" which can provide the same result (looping over the entries of the variable)? I was thinking about using json_query, but still do not know how to implement it in this specific case.
My environment:
$ ansible --version
ansible [core 2.12.6]
config file = /home/ansible/.ansible.cfg
configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/ansible/ansible/lib/python3.9/site-packages/ansible
ansible collection location = /home/ansible/.ansible/collections:/usr/share/ansible/collections
executable location = /home/ansible/ansible/bin/ansible
python version = 3.9.7 (default, Sep 21 2021, 00:13:39) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]
jinja version = 3.0.3
libyaml = True
Could anyone please point me to the right direction? I have been working on my code set for a very long time and after testing the code with large data, the code seems to be useless, because of the running time. I have also checked the hardware resource allocated to the VM where Ansible controller is running on, nothing problematic.
Many thanks in advance!
Running this validation as thousands of individual tasks is very slow because it adds a lot of execution and callback overhead. You can instead do it in a single task, with the caveat that it will be harder to track down the invalid list item(s):
- hosts: localhost
gather_facts: false
vars:
nsapp_lb_server: "{{ nsapp_lb_samples * 10000 }}"
nsapp_lb_samples:
- name: "SRV-1"
ipaddress: "10.102.102.1"
comment: "Chewbacca"
- name: "SRV-2"
ipaddress: "10.102.102.2"
comment: "C-3PO"
- name: "SRV-3"
ipaddress: "10.102.102.3"
comment: "Obi-Wan Kenobi"
tasks:
- assert:
that:
- nsapp_lb_server | rejectattr('name') | length == 0
- (nsapp_lb_server | map(attribute='ipaddress') | map('ipaddr')) == (nsapp_lb_server | map(attribute='ipaddress'))
- nsapp_lb_server | selectattr('comment', 'defined') | rejectattr('comment') | length == 0
This runs in ~5 seconds for the 30,000 test entries I fed it.
To make it easier to find the bad values without making the task extremely ugly, you can split it up into a series of tasks:
- hosts: localhost
gather_facts: false
vars:
nsapp_lb_server: "{{ nsapp_lb_samples * 10000 }}"
nsapp_lb_samples:
- name: "SRV-1"
ipaddress: "10.102.102.1"
comment: "Chewbacca"
- name: "SRV-2"
ipaddress: "10.102.102.2"
comment: "C-3PO"
- name: "SRV-3"
ipaddress: "10.102.102.3"
comment: "Obi-Wan Kenobi"
tasks:
- name: Check for missing names
assert:
that: nsapp_lb_server | rejectattr('name', 'defined') | length == 0
fail_msg: "Bad entries: {{ nsapp_lb_server | rejectattr('name', 'defined') }}"
- name: Check for bad names
assert:
that: nsapp_lb_server | rejectattr('name') | length == 0
fail_msg: "Bad entries: {{ nsapp_lb_server | rejectattr('name') }}"
- name: Check for missing IP addresses
assert:
that: nsapp_lb_server | rejectattr('ipaddress', 'defined') | length == 0
fail_msg: "Bad entries: {{ nsapp_lb_server | rejectattr('ipaddress', 'defined') }}"
- name: Check for bad IP addresses
assert:
that: (nsapp_lb_server | map(attribute='ipaddress') | map('ipaddr')) == (nsapp_lb_server | map(attribute='ipaddress'))
fail_msg: "Suspicious values: {{ nsapp_lb_server | map(attribute='ipaddress') | map('ipaddr') | symmetric_difference(nsapp_lb_server | map(attribute='ipaddress')) }}"
- name: Check for bad comments
assert:
that: nsapp_lb_server | selectattr('comment', 'defined') | rejectattr('comment') | length == 0
fail_msg: "Bad entries: {{ nsapp_lb_server | selectattr('comment', 'defined') | rejectattr('comment') }}"
This runs in ~12 seconds for the same list of 30,000 test entries.
I need to skip the value null from a list in ansible loop. I am using the when condition, still the null value gets printed.
Below is my playbook:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item != "null"
- name: Print
debug:
msg: "{{val}}"
Output:
TASK [Print] ***
ok: [localhost] => {
"msg": [
"read",
"write",
null,
"test"
]
}
Please advise.
Quoting from YAML 10.2.1.1. Null
"Represents the lack of a value. This is typically bound to a native null-like value (e.g., undef in Perl, None in Python). ..."
There are more options on how to test null.
Compare to Python None
when: item != None
Use Jinja test none
when: item is not none
If you for whatever reason have to compare to a string the Jinja filter string converts null to string 'None'
when: item|string != 'None'
The most efficient way is to remove null values from the list before the iteration
loop: "{{ show|reject('none')|list }}"
I don't know if you want to check if the variable is "null" as a text or if the variable doesn't have a value and it's nullified so I will write both examples :), make null value a string and compare as a string:
- hosts: localhost
vars:
show:
- read
- write
- "null"
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: "'{{item}}' != 'null'"
- name: Print
debug:
msg: "{{val}}"
Test if the value is nullified and it really doesn't have any value we will use, is none for that example:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item is not none
- name: Print
debug:
msg: "{{val}}"
Variable file
I have a variable file
fruits:
- apple
- banana
- orange
- strawberry
- grapes
- papaya
users:
- user_name: 'john'
user_age: 45
- user_name: 'yash'
user_age: 95
- user_name: 'srk'
user_age: 52
- user_name: 'alia'
user_age: 26
Playbook Tasks
and my ansible tasks, just trying to make a text file and adding variables in file in vertical order.
- hosts: localhost
gather_facts: true
vars_files:
- variables.var # this is my variable file in the same dir that playbook have.
tasks:
- name: add fruits to the list
lineinfile:
create: yes
line: "{{ item }}"
path: /home/ansible/ansible-demo2/fruits.txt
loop:
- "{{ fruits|flatten }}"
- name: add uses to the list
lineinfile:
create: yes
line: "{{ item.user_name }} ==> {{ item.user_age }}"
path: /home/ansible/ansible-demo2/users.txt
loop:
- "{{ users|flatten(levels=1) }}"
Errors
But I am getting weird behavior. Below is the output of fruits task and error of the users task.
TASK [add fruits to the list] ***************************************************************************************************************************
ok: [localhost] => (item=[u'apple', u'banana', u'orange', u'strawberry', u'grapes', u'papaya'])
[WARNING]: The value ['apple', 'banana', 'orange', 'strawberry', 'grapes', 'papaya'] (type list) in a string field was converted to u"['apple',
'banana', 'orange', 'strawberry', 'grapes', 'papaya']" (type string). If this does not look like what you expect, quote the entire value to ensure it
does not change.
TASK [add uses to the list] *****************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'user_name'
\n\nThe error appears to be in '/home/ansible/ansible-demo2/myansible.yml': line 18, column 7, but may\nbe elsewhere in the file depending on the exact s
yntax problem.\n\nThe offending line appears to be:\n\n\n - name: add uses to the list\n ^ here\n"}
Expected Output
List in vertical order in text file.
Comment on question:
I am amazed every thing works fine with with_items and not with loop, even user_name variable is defined but still it is saying undefined. Now I unable to find out what's wrong going on.
Reference:
Here is the doc that I am referencing : Migrating from with_X to loop
Edit: Variables debug output
I debug the variables. output is below
TASK [debug] ********************************************************************************************************************************************
ok: [localhost] => {
"fruits": [
"apple",
"banana",
"orange",
"strawberry", "grapes", "papaya"
]
}
TASK [debug] ********************************************************************************************************************************************
ok: [localhost] => {
"users": [
{
"user_age": 45,
"user_name": "john"
},
{
"user_age": 95,
"user_name": "yash"
},
{
"user_age": 52,
"user_name": "srk"
},
{
"user_age": 26,
"user_name": "alia"
}
]
}
Q: *"[WARNING]: The value ['apple', ... ] (type list) in a string field was converted to u"['apple', ... ]" (type string).
A: From the code, it's not clear what is the reason for this conversion. The data and the playbook below work as expected.
shell> cat data.yml
fruits:
- apple
- banana
- orange
- strawberry
- grapes
- papaya
users:
- user_name: 'john'
user_age: 45
- user_name: 'yash'
user_age: 95
- user_name: 'srk'
user_age: 52
- user_name: 'alia'
user_age: 26
shell> cat playbook.yml
- hosts: localhost
vars_files:
- data.yml
tasks:
- name: add fruits to the list
lineinfile:
create: yes
line: "{{ item }}"
path: fruits.txt
loop: "{{ fruits }}"
- name: add uses to the list
lineinfile:
create: yes
line: "{{ item.user_name }} ==> {{ item.user_age }}"
path: users.txt
loop: "{{ users }}"
Give
shell> cat fruits.txt
apple
banana
orange
strawberry
grapes
papaya
shell> cat users.txt
john ==> 45
yash ==> 95
srk ==> 52
alia ==> 26
Q: "2 variables fruits1 and fruits2 ... append their data into single file ... in single task with 2 vars"
A: With the modified data
shell> cat data.yml
fruits1:
- apple
- banana
- orange
fruits2:
- strawberry
- grapes
- papaya
this task gives the same result
- name: add fruits to the list
lineinfile:
create: yes
line: "{{ item }}"
path: fruits.txt
loop: "{{ fruits1 + fruits2 }}"
See Append list variable to another list in Ansible.
Comment on the question: "I am amazed everything works fine with with_items and not with loop"
A: See Comparing loop and with_*.