Conditionally registered variables are considered defined, even when the condition is false - variables

Apologies for lack of clarify, rewriting my ask:
I am struggling to get the appropriate start_index value passed on to the third task below "Echo parameters". If user_defined_index is "" I want the echo unique UIDs task to execute and output passed in the start_index variable. Likewise if user_define_index is not "" I want the second task below to execute and populate the start_index variable. I essentially need to pass either A or B to the echo parameters task.
The Echo parameters task expects to get some UIDs. The first task autogenerates UIDs based on what you see in the shell command. the second task allows for user to specify UIDs. So whichever WHEN command is valid that set of UIDs need to get used by the third task. Using debug statements I have confirmed that both ECHO UNIQUE UIDS and CAPTURE USER DEFINED UIDs tasks work fine and the corresponding register variables have the right data.
My issue is 3rd task only picks up values from the 1st task regardless, whether it be auto generated values or blank with skipped equals true.
I need the correct corresponding value in the start_index to be fed into the 3rd task.
- name: echo unique UIDs
shell: echo $(((0x$(hostid) + $(date '+%s'))*100000 + {{ item[0] }}*100000 + {{ start_stress_index }}))
with_indexed_items:
- "{{ load_cfg }}"
register: start_index
when: user_defined_index == ""
changed_when: False
- name: Capture user defined UIDs
shell: echo '{{ user_defined_index }}' | tr , '\n'
with_indexed_items:
- "{{ load_cfg }}"
register: start_index
when: user_defined_index != ""
changed_when: False
- name: Echo parameters
command: echo --cfg='{{ start_index }}' --si={{ item[1].stdout }}
with_together:
- "{{ load_cfg }}"
- "{{ start_index.results }}"
For the above regardless of user_define_index output from the echo unique UIDs always gets passed through to the 3rd task. After googling I finally found a potential solution to use the ternary filter:
https://github.com/ansible/ansible/issues/33827
I have modified my code to be:
- name: echo unique UIDs
shell: echo $(((0x$(hostid) + $(date '+%s'))*100000 + {{ item[0] }}*100000 + {{ start_stress_index }}))
with_indexed_items:
- "{{ load_cfg }}"
register: start_auto_index
when: user_defined_index == ""
changed_when: False
- name: Capture user defined UIDs
shell: echo '{{ user_defined_index }}' | tr , '\n'
with_indexed_items:
- "{{ load_cfg }}"
register: start_user_index
when: user_defined_index != ""
changed_when: False
- name: Echo parameters
command: echo --cfg='{{ start_index }}' --si={{ item[1].stdout }}
with_together:
- "{{ load_cfg }}"
- "{{ ((start_auto_index is not skipped)|ternary(start_auto_index,start_user_index))['results'] }}"
However, I still have same issue as with my first example when i run the above I again only get output from start_auto_index sent to 3rd task echo parameters no matter what i do with user_defined_index.
I hope this clarifies my question.

Explanation
The problem is that your tasks contain a loop and in such case Ansible returns separate statuses for the task and each loop iteration.
With start_auto_index is not skipped you check the status of the whole task, but it is iterations that get status "skipped".
Solution
Since your conditional user_defined_index for the tasks containing the loop is constant for each loop iteration, all iterations will have the same skipped-status, you can modify the condition in the Echo parameters task to checking just one of them:
"{{ ((start_auto_index[0] is not skipped)|ternary(start_auto_index,start_user_index))['results'] }}"
Besides, in the Echo parameters task with_together does not seem to serve any function, as you don't refer to the item[0].

Related

Ansible script with | differences of empty map fails [duplicate]

I'm customizing linux users creation inside my role. I need to let users of my role customize home_directory, group_name, name, password.
I was wondering if there's a more flexible way to cope with default values.
I know that the code below is possible:
- name: Create default
user:
name: "default_name"
when: my_variable is not defined
- name: Create custom
user:
name: "{{my_variable}}"
when: my_variable is defined
But as I mentioned, there's a lot of optional variables and this creates a lot of possibilities.
Is there something like the code above?
user:
name: "default_name", "{{my_variable}}"
The code should set name="default_name" when my_variable isn't defined.
I could set all variables on defaults/main.yml and create the user like that:
- name: Create user
user:
name: "{{my_variable}}"
But those variables are inside a really big hash and there are some hashes inside that hash that can't be a default.
You can use Jinja's default:
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
Not totally related, but you can also check for both undefined AND empty (for e.g my_variable:) variable. (NOTE: only works with ansible version > 1.9, see: link)
- name: Create user
user:
name: "{{ ((my_variable == None) | ternary('default_value', my_variable)) \
if my_variable is defined else 'default_value' }}"
If anybody is looking for an option which handles nested variables, there are several such options in this github issue.
In short, you need to use "default" filter for every level of nested vars. For a variable "a.nested.var" it would look like:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ((a | default({})).nested | default({}) ).var | default('bar') }}"
or you could set default values of empty dicts for each level of vars, maybe using "combine" filter. Or use "json_query" filter. But the option I chose seems simpler to me if you have only one level of nesting.
In case you using lookup to set default read from environment you have also set the second parameter of default to true:
- set_facts:
ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
You can also concatenate multiple default definitions:
- set_facts:
ansible_ssh_user: "{{ some_var.split('-')[1] | default(lookup('env','USER'), true) | default('foo') }}"
If you are assigning default value for boolean fact then ensure that no quotes is used inside default().
- name: create bool default
set_fact:
name: "{{ my_bool | default(true) }}"
For other variables used the same method given in verified answer.
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
If you have a single play that you want to loop over the items, define that list in group_vars/all or somewhere else that makes sense:
all_items:
- first
- second
- third
- fourth
Then your task can look like this:
- name: List items or default list
debug:
var: item
with_items: "{{ varlist | default(all_items) }}"
Pass in varlist as a JSON array:
ansible-playbook <playbook_name> --extra-vars='{"varlist": [first,third]}'
Prior to that, you might also want a task that checks that each item in varlist is also in all_items:
- name: Ensure passed variables are in all_items
fail:
msg: "{{ item }} not in all_items list"
when: item not in all_items
with_items: "{{ varlist | default(all_items) }}"
The question is quite old, but what about:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ( a | default({})).get('nested', {}).get('var','bar') }}"
It looks less cumbersome to me...
#Roman Kruglov mentioned json_query. It's perfect for nested queries.
An example of json_query sample playbook for existing and non-existing value:
- hosts: localhost
gather_facts: False
vars:
level1:
level2:
level3:
level4: "LEVEL4"
tasks:
- name: Print on existing level4
debug:
var: level1 | json_query('level2.level3.level4') # prints 'LEVEL4'
when: level1 | json_query('level2.level3.level4')
- name: Skip on inexistent level5
debug:
var: level1 | json_query('level2.level3.level4.level5') # skipped
when: level1 | json_query('level2.level3.level4.level5')

Can variables be use as ansible filter parameters

I am creating a list of URLs (just strings really).
I want to select the one that contains the string in a ansible variable 'blog'.
Can't seem to code the select parameter correctly.
Is this possible?
- name: get blog_urls
shell:
cmd: wp site list --field=url
chdir: "{{ blog_docroot }}"
register: blog_urls
- name: show blog value
debug:
msg: "{{ blog }}"
- name: select the correct url
set_fact:
url: "{{ blog_urls.stdout_lines | select('contains', blog ) }}"
blog_url conatins:
"http://jackson2.sjfc.edu/",
"https://jackson2.sjfc.edu/oit/",
"https://jackson2.sjfc.edu/aaforms/",
"https://jackson2.sjfc.edu/admissions/",
"https://jackson2.sjfc.edu/arts-sciences/",
"https://jackson2.sjfc.edu/pharmacy/",
"https://jackson2.sjfc.edu/grants/",
"https://jackson2.sjfc.edu/alumni/",
"https://jackson2.sjfc.edu/wson/",
"https://jackson2.sjfc.edu/education/",
"https://jackson2.sjfc.edu/images/",
"https://jackson2.sjfc.edu/oit-test/",
"https://jackson2.sjfc.edu/provost/",
"https://jackson2.sjfc.edu/registrar/",
"https://jackson2.sjfc.edu/faculty-committees/",
"https://jackson2.sjfc.edu/faculty-files/",
"https://jackson2.sjfc.edu/avdownload/",
"https://jackson2.sjfc.edu/mac/",
"https://jackson2.sjfc.edu/giddnfellowship/",
"https://jackson2.sjfc.edu/dnp-mentors/"
blog would contain 'aaforms' or 'oit-test' or 'provost'...
If I hard code the desired value of blog (see line below) it works:
- name: select the correct url
set_fact:
url: "{{ blog_urls.stdout_lines | select('contains', 'aaforms' ) }}"
But of course I want to pass the second parameter of the 'select' filter as a ansible variable.
Your example in question is an almost working one. The select filter will return an a list based on the match criteria.
For example, let's say blog variable is aaforms:
vars:
blog: aaforms
tasks:
- set_fact:
my_blogs: "{{ blog_urls.stdout_lines | select('contains', blog) | list }}"
- debug:
var: my_blogs
This gives the URLs matched by the blog variable (in this case one).
"my_blogs": [
"https://jackson2.sjfc.edu/aaforms/"
]

Ansible registered variable has attribute not found error due to multiple when conditions

How do we check for a registered variable if only one of the two conditions turns out to be true having the same registered variable?
Below is my playbook that executes only one of the two shell modules.
- name: Check file
shell: cat /tmp/front.txt
register: myresult
when: Layer == 'front'
- fail:
msg: data was read from front.txt and print whatever
when: myresult.rc != 0
- name: Check file
shell: cat /tmp/back.txt
register: myresult
when: Layer == 'back'
- fail:
msg: data was read from back.txt and print whatever
when: myresult.rc != 0
Run the above playbook as
ansible-playbook test.yml -e Layer="front"
I do get error that says myresult does not have an attribute rc. What is the best way to print debug one statements based on the condition met?
Note: I wish the fail to terminate the execution of the play as soon as the condition is met hence I beleive ignore_errors with fail will not help.
Note: The shell modules can be any Unix command.
I tried myresult is changed but that too does not help. Can you please suggest.
You may want to look at this logical grouping of tasks: blocks
- name: Check file
block:
- name: check file
shell: cat /tmp/front.txt
register: myresult
ignore_errors: true
- fail:
msg: data was read from front.txt and print whatever
when: myresult.rc != 0
when: Layer == 'front'
- name: Check file
block:
- name: check file
shell: cat /tmp/back.txt
register: myresult
ignore_erros: true
- fail:
msg: data was read from back.txt and print whatever
when: myresult.rc != 0
when: Layer == 'back'
when the variable Layer is set to the front it will execute the shell command for front. but in case when the file doesn't exists it will give the error no such file exists and stop the play. so i have put the ignore_errors in the shell task.it will ignore it and jump to the fail module.

Unable to set default value for set_fact in Ansible

Lets consider the user passes a Number parameter to my ansible-playbook as
ansible-playbook /app/test.yml
-e "Number=22235_ReDep_292001105550"
I want set_fact "Number_New" to be the string before the first underscore "_" i.e 22235 and "Status" to be everything after the first underscore "_" i.e
Expectation:
Number_New should be "22235"
Status should be "ReDep_292001105550"
Second scenario; the user may pass -e "Number=22235". This this case i want the set_fact "Number_New" should be same as the "Number=22235" passed while the Status should be "%" modulus symbol.
Expectation:
Number_New should be "22235"
Status should be "%"
Below is my playbook attempt which works fine when parameter passed is "Number=22235_ReDep_292001105550" but fails when -e "Number=22235"
tasks:
- name: Populate number and status from user input
set_fact:
Number_New: "{{ Number.split('_')[0] | default(Number) }}"
Status: "{{ Number.split('_')[1] }}_{{ Number.split('_')[2] | default('%') }}"
Error when it fails:
fatal: [localhost]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: list object has no element 1\n\nThe error appears to be in '/app/test.yml': line 32, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Populate number and status from user input\n ^ here\n"
}
The task below does the job
- set_fact:
Number_New: "{{ nsplit.0 }}"
Status: "{{ nsplit.1|default('%') }}"
vars:
nsplit: "{{ Number.split('_', 1) }}"

How do we assure that an ansible registered variable has a particular attribute

I have a playbook that has the below SQL query:
- name: "Search for Front"
command: >
mysql --user="{{ DBUSER }}" --password="{{ DBPASS }}" deployment
--host=localhost --batch --skip-column-names
--execute="SELECT * FROM mytable WHERE num LIKE '{{ Number }}'"
register: result_front
when: Layer == 'Front'
- name: "Search for Back"
command: >
mysql --user="{{ DBUSER }}" --password="{{ DBPASS }}" deployment
--host=localhost --batch --skip-column-names
--execute="SELECT * FROM mytable WHERE num LIKE '{{ Number }}'"
register: result_back
when: Layer == 'Back'
I trigger this playbook as below:
ansible-playbook test.yml -e "Layer=Front" -e "Number=1234" -e "DBUSER=root" -e "DBPASS=password"
Note that at a time only one of the two sql query will execute depending on the parameter "Layer" passed.
I wish to set_fact "reqnum" to string "Deploy" if any of the above two SQL queries returns records found.
Below is what i did:
- set_fact:
reqnum: "Deploy"
when: result_back.stdout_lines | default([]) | length != "" and result_back.skipped == False
- set_fact:
reqnum: "Deploy"
when: result_front.stdout_lines | default([]) | length != "" and result_front.skipped == False
However, I get the following error:
TASK [set_fact] ****************************************************************
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'result_back.stdout_lines == \"\" and result_back.skipped == False' failed. The error was: error while evaluating conditional (result_back.stdout_lines == \"\" and result_back.skipped == False): 'dict object' has no attribute 'stdout_lines'\n\nThe error appears to be in '/app/deploy.yml': line 149, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - set_fact:\n ^ here\n"}
What is the correct way to to check such conditions where one variable gets skipped while the other gets a failure or success as a result ?
What is the correct way to to check such conditions where one variable gets skipped while the other gets a failure or success as a result ?
It is to change the order of the when: so it evaluates the is skipped check first, and then won't try to blow up on the variable reference; for example:
- command: date
register: alpha
when: doit == 'alpha'
- command: uptime
register: beta
when: doit == 'beta'
- set_fact:
reqnum: Deploy Alpha
when: alpha is not skipped and (alpha.stdout_lines|length) > 0
- set_fact:
reqnum: Deploy Beta
when: beta is not skipped and (beta.stdout_lines|length) > 0
- debug: var=reqnum
All of that is exactly what #Zeitounator was trying to tell you, but just packaged into one working example