Ansible Substring From List Item - ansible

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

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 }}"

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 }}"

Ansible PLaybook: Escape '$' in Linux path

I have a path which looks like this -
base_dir/123/path/to/G\$/subdirectory/html/
When I try to set this path in Ansible playbook, it throws error. If add \$ to escape '\', it throws unexpected failure error.
Playbkook -
- hosts: localhost
vars:
account_id: 123
tasks:
- name: Add \ to path
debug:
var: "base_dir/{{ account_id }}/path/to/G\\$/subdirectory/html/"
Result -
TASK [Gathering Facts] *************************************************************************************************************************************************
task path: /playbooks/example_path.yml:2
ok: [localhost]
META: ran handlers
TASK [Add \ to path] ***************************************************************************************************************************************************
task path: /playbooks/exmaple_path.yml:6
fatal: [localhost]: FAILED! => {
"msg": "Unexpected failure during module execution."
}
PLAY RECAP *************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1
As explained in the debug module documentation, the var option is expecting a variable name, not a scalar for output. You are getting an error because \ is not expected in a variable name. Running the playbook with -vvv will give you a little more explanations.
In this case you need to use the msg option.
- hosts: localhost
gather_facts: false
vars:
account_id: 123
tasks:
- name: Add \ to path
debug:
msg: "base_dir/{{ account_id }}/path/to/G\\$/subdirectory/html/"
Result
PLAY [localhost] ***************************************************************
TASK [Add \ to path] ***********************************************************
ok: [localhost] => {
"msg": "base_dir/123/path/to/G\\$/subdirectory/html/"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The next option is to use the Single-Quoted Style. See the example below
- hosts: localhost
vars:
my_dir1: "/scratch/tmp/G1\\$"
my_dir2: '/scratch/tmp/G2\$'
tasks:
- file:
state: directory
path: "{{ item }}"
loop:
- "{{ my_dir1 }}"
- "{{ my_dir2 }}"
# ls -1 /scratch/tmp/
'G1\$'
'G2\$'

Passing/updating global variables (e.g. group vars) from within the playbook?

I there any way to pass/update group variables from within the playbook task?
I need to define variables based on results of some commands from one host to use them for other roles and tasks. I know about set_fact but it stores variable as local variable so that I need to address specific host to get it but hostname/address of this host can vary.
Googling and reading docs.ansible.com doesn't help still.
UPD: there is 2 different roles that playing tasks one after another and I need to pass variables between plays.
An option would be to use ansible modules lineinfile, blockinfile, template, and ini_file to update group variables.
For example the play below
- hosts: test_jails
gather_facts: false
vars:
my_groupvar_file: "{{ inventory_dir }}/group_vars/test_jails.yml"
tasks:
- debug:
var: my_last_run
- block:
- command: date "+%F %T"
register: result
- lineinfile:
path: "{{ my_groupvar_file }}"
regexp: "^my_last_run: "
line: "my_last_run: {{ result.stdout }}"
backup: yes
delegate_to: localhost
run_once: true
with group variables group_vars/test_jails.yml
my_last_run: 2019-04-19 11:51:00
gives (abridged):
> ansible-playbook test1.yml
PLAY [test_jails]
TASK [debug]
ok: [test_01] => {
"my_last_run": "2019-04-19 11:51:00"
}
ok: [test_03] => {
"my_last_run": "2019-04-19 11:51:00"
}
ok: [test_02] => {
"my_last_run": "2019-04-19 11:51:00"
}
TASK [command]
changed: [test_01]
TASK [lineinfile]
changed: [test_01 -> localhost]
PLAY RECAP
test_01 : ok=3 changed=2 unreachable=0 failed=0
test_02 : ok=1 changed=0 unreachable=0 failed=0
test_03 : ok=1 changed=0 unreachable=0 failed=0
> cat group_vars/test_jails.yml
my_last_run: 2019-04-19 11:56:51

Resources