Ansible - Start at specific role - ansible

I have a playbook that only calls roles. This is what it looks like: (there are about 20 roles in it)
---
- hosts: prod1234
roles:
- role1
- role2
- role3
Sometimes, a role fails, and I don't want to start over as each role is huge and I would just like to start at that point or the next one.
With tasks, I know there's a flag for --start-at-task="task-name". Is there something similar I can do with roles?
My current solution is to comment out all the lines I don't need and run it again..
Thanks ahead!~~

Quick n dirty solution. The following roleimport.yml playbook
# Note you will have to implement error management (e.g. you give a role that does not exist).
- name: quickNdirty roles start demo
hosts: localhost
gather_facts: false
vars:
full_role_list:
- myfirstrole
- mysecondrole
- thirdone
- next
- last
# We calculate a start index for the above list. By default it will be 0.
# Unless we pass `start_role` var in extra_vars: the index will be set
# to that element
start_index: "{{ full_role_list.index(start_role|default('myfirstrole')) }}"
# We slice the list from start_index to end
current_role_list: "{{ full_role_list[start_index|int:] }}"
tasks:
# Real task commented out for this example
# - name: Import selected roles in order
# import_role:
# name: "{{ item }}"
# loop: "{{ current_role_list }}"
- name: Debug roles that would be used in above commented task
debug:
msg: "I would import role {{ item }}"
loop: "{{ current_role_list }}"
gives:
$ ansible-playbook roleimport.yml
PLAY [quickNdirty roles start demo] *******************************************************************************************************************************************************************************************
TASK [Debug roles that would be used in above commented task] *****************************************************************************************************************************************************************
ok: [localhost] => (item=myfirstrole) => {
"msg": "I would import role myfirstrole"
}
ok: [localhost] => (item=mysecondrole) => {
"msg": "I would import role mysecondrole"
}
ok: [localhost] => (item=thirdone) => {
"msg": "I would import role thirdone"
}
ok: [localhost] => (item=next) => {
"msg": "I would import role next"
}
ok: [localhost] => (item=last) => {
"msg": "I would import role last"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible-playbook roleimport.yml -e start_role=thirdone
PLAY [quickNdirty roles start demo] *******************************************************************************************************************************************************************************************
TASK [Debug roles that would be used in above commented task] *****************************************************************************************************************************************************************
ok: [localhost] => (item=thirdone) => {
"msg": "I would import role thirdone"
}
ok: [localhost] => (item=next) => {
"msg": "I would import role next"
}
ok: [localhost] => (item=last) => {
"msg": "I would import role last"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible-playbook roleimport.yml -e start_role=last
PLAY [quickNdirty roles start demo] *******************************************************************************************************************************************************************************************
TASK [Debug roles that would be used in above commented task] *****************************************************************************************************************************************************************
ok: [localhost] => (item=last) => {
"msg": "I would import role last"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# Implement error management yourself if you need it.
$ ansible-playbook roleimport.yml -e start_role=thisIsAnError
PLAY [quickNdirty roles start demo] *******************************************************************************************************************************************************************************************
TASK [Debug roles that would be used in above commented task] *****************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while templating '{{ full_role_list[start_index|int:] }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: An unhandled exception occurred while templating '{{ full_role_list.index(start_role|default('myfirstrole')) }}'. Error was a <class 'ValueError'>, original message: 'thisIsAnError' is not in list"}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

Related

Ansible - Compare dictionaries works but will not accept an addition

I am trying to compare two dictionaries and create a 3rd dictionary if an item has changed or is not present. I have this playbook
---
- hosts: localhost
connection: local
gather_facts: no
vars:
host_list1:
host1: 1.1.1.1
host2: 2.2.2.2
host3: 3.3.3.3
host_list2:
host1: 1.1.1.1
host2: 2.2.2.2
host3: 3.3.3.3
tasks:
- name: Define Dict
set_fact:
diff_list: {}
- name: Compare lists
set_fact:
diff_list: "{{ diff_list|combine({item: host_list1[item]}) }}"
loop: "{{ host_list1.keys()|list }}"
when: host_list1[item] != host_list2[item]
- name: Display Results
debug:
msg: "{{ diff_list }}"
If I change the IP in host_list1 it has the desired result and will update the diff_list.
PLAY [localhost] **************************************************************************************************************************************************************
TASK [Define Dict] ************************************************************************************************************************************************************
ok: [localhost]
TASK [Compare lists] **********************************************************************************************************************************************************
ok: [localhost] => (item=host3)
ok: [localhost] => (item=host2)
skipping: [localhost] => (item=host1)
TASK [Display Results] ********************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"host2": "2.2.2.5",
"host3": "3.3.3.4"
}
}
PLAY RECAP ********************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
However if I try and add another host into the host_list1 dictionary I get the following error
PLAY [localhost] **************************************************************************************************************************************************************
TASK [Define Dict] ************************************************************************************************************************************************************
ok: [localhost]
TASK [Compare lists] **********************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'host_list1[item] != host_list2[item]' failed. The error was: error while evaluating conditional (host_list1[item] != host_list2[item]): 'dict object' has no attribute u'host4'\n\nThe error appears to be in '/home/playbooks/test.yml': line 20, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Compare lists\n ^ here\n"}
PLAY RECAP ********************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
I have tried updating the when statement
when: host_list1[item] != host_list2[item] or not defined
However this does not work and I am unable to get the new host appended to the diff_list dictionary
The problem is your conditional statement:
when: host_list1[item] != host_list2[item]
If item is key that is present in only host_list1, then host_list2[item] is an error. There are (at least!) two ways of fixing this.
Use a default value
You can use the default filter to provide a default value when the result of an item lookup is undefined:
when: host_list1[item] != host_list2[item]|default(none)
Explicitly check for membership
You can check that the key exists in host_list2 before attempting to get its value:
when: item not in host_list2 or host_list1[item] != host_list2[item]
You can get your result using a single jinja2 expression without using any tasks.
The following playbook:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
hosts_current:
host1: 1.1.1.1
host2: 2.2.2.3
host3: 3.3.3.4
host4: 5.5.5.5
hosts_origin:
host1: 1.1.1.1
host2: 2.2.2.2
host3: 3.3.3.3
diff_dict: >-
{{
hosts_origin | dict2items
| symmetric_difference(hosts_current | dict2items)
| items2dict
}}
tasks:
- name: Show the calculated var
debug:
var: diff_dict
gives:
PLAY [localhost] **************************************************************************************************************************************************************************************************************
TASK [Show the calculated var] ************************************************************************************************************************************************************************************************
ok: [localhost] => {
"diff_dict": {
"host2": "2.2.2.3",
"host3": "3.3.3.4",
"host4": "5.5.5.5"
}
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Fetch hosts without domain from inventory file

I have following inventory file
$ cat hosts
[web]
server1.example.com
server2.example.com
I would like to fetch the hostname, without the part of domain (.example.com).
I tried with the following playbook, however, it is still fetching with the entire hostname..
$ playbook.yaml
- hosts: localhost
tasks:
- debug:
msg: "{{ groups['web'] }}"
Output
PLAY [localhost] *************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"server1.example.com"
"server2.example.com"
]
}
PLAY RECAP *******************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Expected output
PLAY [localhost] *************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"server1"
"server2"
]
}
PLAY RECAP *******************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can get what you want from a magic variable called inventory_hostname_short which basically returns anything before the first . found in the inventory_hostname.
To get this in a normal play host loop, it's as easy as:
- hosts: all
tasks:
- name: show target short name
debug:
var: inventory_hostname_short
If you need to get that for hosts not in the host loop, you will have to go through hostvars. Here is an example to get all those names in a list for a given group running from localhost:
- hosts: localhost
gather_facts: false
tasks:
- name: show list of shortnames for group 'toto'
debug:
msg: "{{ groups['toto'] | map('extract', hostvars, 'inventory_hostname_short') }}"
An other example to get that name only for the first server in group 'toto'
- hosts: localhost
gather_facts: false
tasks:
- name: show shortnames for first server in group 'toto'
vars:
server_name: "{{ groups['toto'][0] }}"
debug:
msg: "{{ hostvars[server_name].inventory_hostname_short }}"

Select host to execute task based on multiple conditions

Using Ansible v2.9.12
Imagine 4 hosts in a play, with the following vars:
# host1
my_var:
one: true
two: master
# host2
my_var:
one: false
two: master
# host3
my_var:
one: false
two: master
# host4
my_var:
one: false
two: arbiter
I want to execute a task on a single host, which is has my_var.two == 'master' and has my_var.one set to false. In this case it would relate to either host2 or host3.
Now, this won't work:
- shell: echo
when:
- my_var.two == 'master'
- not my_var.one
run_once: true
Because Ansible executes the task only on the first host in the group, which most likely relates to host1, which is undesired. I'm also fiddling around in the answer described in my previous question, which is somewhat related, but without avail.
Nothing stops you, in a playbook, to have multiple plays:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ my_var }}"
- hosts: host2
gather_facts: no
tasks:
- debug:
msg: "{{ my_var }}"
# ^--- Off course, now the when become superfluous,
# since we target a host that we know have those conditions
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "Keep on going with all hosts"
Would give the recap:
PLAY [all] **********************************************************************************************************
TASK [debug] ********************************************************************************************************
ok: [host1] => {
"msg": {
"one": true,
"two": "master"
}
}
ok: [host2] => {
"msg": {
"one": false,
"two": "master"
}
}
ok: [host3] => {
"msg": {
"one": false,
"two": "master"
}
}
ok: [host4] => {
"msg": {
"one": false,
"two": "arbiter"
}
}
PLAY [host2] ********************************************************************************************************
TASK [debug] ********************************************************************************************************
ok: [host2] => {
"msg": {
"one": false,
"two": "master"
}
}
PLAY [all] **********************************************************************************************************
TASK [debug] ********************************************************************************************************
ok: [host1] => {
"msg": "Keep on going with all hosts"
}
ok: [host2] => {
"msg": "Keep on going with all hosts"
}
ok: [host3] => {
"msg": "Keep on going with all hosts"
}
ok: [host4] => {
"msg": "Keep on going with all hosts"
}
PLAY RECAP **********************************************************************************************************
host1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host4 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Alternatively, for a more dynamic solution, you could select the correct host, looping through your hosts variables via the magic variable hostvars and, then, delegate the task to this host with delegate_to.
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- set_fact:
host_master_not_one: "{{ item }}"
when:
- host_master_not_one is not defined
- not hostvars[item].my_var.one
- "'master' == hostvars[item].my_var.two"
run_once: true
loop: "{{ ansible_play_hosts }}"
- debug:
msg: "Running only once, on host {{ host_master_not_one }}"
delegate_to: "{{ host_master_not_one }}"
run_once: true
It gives the recap:
PLAY [all] **********************************************************************************************************
TASK [set_fact] *****************************************************************************************************
skipping: [host1] => (item=host1)
ok: [host1] => (item=host2)
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
TASK [debug] ********************************************************************************************************
ok: [host1 -> host2] => {
"msg": "Running only once, on host host2"
}
PLAY RECAP **********************************************************************************************************
host1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The important part of this recap would be the [host1 -> host2] that indicates that the task was delegated and so run on host2.
Or even, on the same train of thoughts, skip hosts that does not match the one having those said conditions:
- hosts: all
gather_facts: no
tasks:
- set_fact:
host_master_not_one: "{{ item }}"
when:
- host_master_not_one is not defined
- not hostvars[item].my_var.one
- "'master' == hostvars[item].my_var.two"
run_once: true
loop: "{{ ansible_play_hosts }}"
- debug:
msg: "Running only once, on host {{ host_master_not_one }}"
when: inventory_hostname == host_master_not_one
Gives the recap:
PLAY [all] **********************************************************************************************************
TASK [set_fact] *****************************************************************************************************
skipping: [host1] => (item=host1)
ok: [host1] => (item=host2)
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
TASK [debug] ********************************************************************************************************
skipping: [host1]
ok: [host2] => {
"msg": "Running only once, on host host2"
}
skipping: [host3]
skipping: [host4]
PLAY RECAP **********************************************************************************************************
host1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host4 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

Ansible Substring From List Item

To keep this simple, I have one whitelist directory, one directory being passed in. I need to make sure the full directory of "/tmp/dir1/dir2" FAILS, where a directory of "/local/web/dir1" SUCCEEDS.
This code, always says it's good. No matter what I pass. What am I missing?
EDIT
First of all {{ dir }} is passed in, it's all running under AaaS. Data passed in looks like:
should succeed:
/local/web/test1/dir1
should fail:
/home/test
ansible-playbook-yml
---
- name: Generate Directory Structure and by list.
hosts: target_hosts
vars:
dir: {{ dir }}
whitelist_dir:
- "/local/web"
tasks:
- name: Validate Search {{ dir }}
debug:
msg: "directory is good!"
when: item is search(dir)
with_items:
- "{{ whitelist_dir }}"
Following your edit and my last comment, the only real problem I see is that you reversed your parameters in your where clause (although it does not explain IMO why it would always succeed...).
If I check with parameters in the right order, I get the result you expect. I even added a second whitelist path in the following MCVE to make sure your loop was working correctly. The following test.yml playbook
---
- name: Check if directory is in whitelist path
hosts: localhost
gather_facts: false
vars_prompt:
- name: dir
prompt: Type in full path you want to check
private: no
vars:
whitelist_dir:
- "/local/web"
- "/toto/pipo"
tasks:
- name: Validate Search {{ dir }}
debug:
msg: "directory is good!"
when: dir is search(item)
with_items:
- "{{ whitelist_dir }}"
Gives (3 differents tests)
$ ansible-playbook test.yml
Type in full path you want to check: /local/web/test1/dir1
PLAY [Check if directory is in whitelist path] *****************************************************************************************************************************************************************************************
TASK [Validate Search /local/web/test1/dir1] *******************************************************************************************************************************************************************************************
ok: [localhost] => (item=/local/web) => {
"msg": "directory is good!"
}
skipping: [localhost] => (item=/toto/pipo)
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible-playbook test.yml
Type in full path you want to check: /home/test
PLAY [Check if directory is in whitelist path] *****************************************************************************************************************************************************************************************
TASK [Validate Search /home/test] ******************************************************************************************************************************************************************************************************
skipping: [localhost] => (item=/local/web)
skipping: [localhost] => (item=/toto/pipo)
skipping: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
$ ansible-playbook test.yml
Type in full path you want to check: /toto/pipo/test.txt
PLAY [Check if directory is in whitelist path] *****************************************************************************************************************************************************************************************
TASK [Validate Search /toto/pipo/test.txt] *********************************************************************************************************************************************************************************************
skipping: [localhost] => (item=/local/web)
ok: [localhost] => (item=/toto/pipo) => {
"msg": "directory is good!"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Issue adding duplicate name with different ansible_user to add_host dynamic inventory

Here is my playbook that builds a dynamic inventory using add_host:
---
- name: "Play 1"
hosts: localhost
gather_facts: no
tasks:
- name: "Search database"
command: > mysql --user=root --password=p#ssword deployment
--host=localhost -Ns -e "SELECT dest_ip,username FROM deploy_dets"
register: command_result
- name: Add hosts
add_host:
name: "{{ item.split('\t')[0] }}"
ansible_user: "{{ item.split('\t')[1] }}"
groups: dest_nodes
with_items: "{{ command_result.stdout_lines }}"
- hosts: dest_nodes
gather_facts: false
tasks:
- debug:
msg: Run the shell script with the arguments `{{ ansible_user }}` here"
The Output is good and as expected when the 'name:' attribute of add_host are of different values IPs viz '10.9.0.100' & '10.8.2.144'
$ ansible-playbook duplicate_hosts.yml
PLAY [Play 1] ***********************************************************************************************************************************************
TASK [Search database] **************************************************************************************************************************************
changed: [localhost]
TASK [Add hosts] ********************************************************************************************************************************************
changed: [localhost] => (item=10.9.0.100 user1)
changed: [localhost] => (item=10.8.2.144 user2)
PLAY [dest_nodes] *******************************************************************************************************************************************
TASK [debug] ************************************************************************************************************************************************
ok: [10.9.0.100] => {
"msg": "Run the shell script with the arguments `user1` here\""
}
ok: [10.8.2.144] => {
"msg": "Run the shell script with the arguments `user2` here\""
}
PLAY RECAP **************************************************************************************************************************************************
10.8.2.144 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.9.0.100 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The problem is when the 'name:' attribute for add_host gets duplicate entry say 10.8.2.144 despite having unique 'ansible_user' value the play ignores the first name, ansible_user entry and runs only once with the latest final entry.
$ ansible-playbook duplicate_hosts.yml
PLAY [Play 1] ***********************************************************************************************************************************************
TASK [Search database] **************************************************************************************************************************************
changed: [localhost]
TASK [Add hosts] ********************************************************************************************************************************************
changed: [localhost] => (item=10.8.2.144 user1)
changed: [localhost] => (item=10.8.2.144 user2)
PLAY [dest_nodes] *******************************************************************************************************************************************
TASK [debug] ************************************************************************************************************************************************
ok: [10.8.2.144] => {
"msg": "Run the shell script with the arguments `user2` here\""
}
PLAY RECAP **************************************************************************************************************************************************
10.8.2.144 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Interestingly the debug shows two entries for add_host name: 10.8.2.144 with the different ansible_users i.e 'user1' and 'user2' but when we run the group it runs just the single and latest name entry and seen in the output above.
I'm on the latest version of ansible.
Can you please provide some solution where i can run the play for every unique 'ansible_user' on the same host ?
In summary: I wish to run multiple tasks on the same host first with 'user1' and then with 'user2'
You can add a alias as inventory hostname. Here I have given the username as hostname(alias).
Please try this, I have not tested it.
- name: Add hosts
add_host:
hostname: "{{ item.split('\t')[1] }}"
ansible_host: "{{ item.split('\t')[0] }}"
ansible_user: "{{ item.split('\t')[1] }}"
groups: dest_nodes
with_items: "{{ command_result.stdout_lines }}"

Resources