ansible : how to use the variable ${item} from with_items in notify? - ansible

I am new to Ansible and I am trying to create several virtual environments (one for each project, the list of projects being defined in a variable).
The task works well, I got all the folders, however the handler does not work, it does not init each folder with the virtual environment. The ${item} varialbe in the handler does not work.
How can I use an handler when I use with_items ?
tasks:
- name: create virtual env for all projects ${projects}
file: state=directory path=${virtualenvs_dir}/${item}
with_items: ${projects}
notify: deploy virtual env
handlers:
- name: deploy virtual env
command: virtualenv ${virtualenvs_dir}/${item}

Handlers are just 'flagged' for execution once whatever (itemized sub-)task requests it (had the changed: yes in its result).
At that time handlers are just like a next regular tasks, and don't know about the itemized loop.
A possible solution is not with a handler but with an extratask + conditional
Something like
- hosts: all
gather_facts: false
tasks:
- action: shell echo {{item}}
with_items:
- 1
- 2
- 3
- 4
- 5
register: task
- debug: msg="{{item.item}}"
with_items: task.results
when: item.changed == True

To sum up the previous discussion and adjusting for the modern Ansible...
- hosts: localhost,
gather_facts: false
tasks:
- action: shell echo {{item}} && exit {{item}}
with_items:
- 1
- 2
- 3
- 4
- 5
register: task
changed_when: task.rc == 3
failed_when: no
notify: update service
handlers:
- name: update service
debug: msg="updated {{item}}"
with_items: >
{{
task.results
| selectattr('changed')
| map(attribute='item')
| list
}}

Related

Sending shutdown command on specific switch port interfaces through ansible using cli_command module

here is my _main.yml _
---
- name: run show version on switch
cli_command:
command: show version
register: show
- name: print command output
debug: var=show.stdout_lines[0]
- name: Shutdown TOR-A port
cli_command:
command: "{{ item }}"
register: command_output
with_items:
- "config"
- "interface Tengigabitethernet 0/35"
- "shutdown"
- name: Debug command output
debug:
msg: "{{ command_output }}"
How do I access port 36,7,8, and so on on the same switch and issue shutdown on mentioned switch ports in a sequence?
I have to repeat the same activity for another switch TOR-B , the task should be identical.However , I am not sure how to dynamically achieve the objective in one yaml file.Any help will be appreciated!
The switch credentials are saved in hosts file
The following should basically do the job (Note: I do not have any cli compatible switch to test against).
Create a shutdown_ports.yml file with the following content. This file can be at the same level of you playbook, or inside your tasks folder in role if you include it from there.
---
- name: "shutdown port"
cli_command:
command: "{{ item }}"
with_items:
- "interface Tengigabitethernet 0/{{ port }}"
- "shutdown"
Use that file as an include in a loop like in the following playbook example
---
- name: shutdown same ports on all switches
hosts: group_containing_my_switches
vars:
shutdown_ports:
- 35
- 36
- 7
- 8
tasks:
- name: switch to config mode
cli_command:
command: config
- name: shutdown all referenced ports
include_tasks: shutdown_ports.yml
loop: "{{ shutdown_ports }}"
loop_control:
loop_var: port
An other possible solution is to dynamically create an array containing all command to send and play all those in a loop. Example playbook again
---
- name: shutdown same ports on all switches
hosts: group_containing_my_switches
vars:
shutdown_ports:
- 35
- 36
- 7
- 8
tasks:
- name: Add config mode as first command
set_fact:
cli_switch_commands:
- config
- name: Add commands for each port shutdown
vars:
current_port_command: "interface Tengigabitethernet 0/{{ item }}"
set_fact:
cli_switch_commands: "{{ cli_switch_commands + [current_port_command, 'shutdown'] }}"
loop: "{{ shutdown_ports }}"
- name: Send all commands to switch
cli_command:
command: "{{ item }}"
loop: "{{ cli_switch_commands }}"

Pause time between hosts in the Ansible Inventory

I am trying the below task in my playbook. but the pause is not executed. i want the play should be paused for 30 sec once after the each host is deleted.
name: delete host from the NagiosXI
shell: curl -k -XDELETE "https://10.000.00.00/nagiosxi/api/v1/config/host?apikey=qdjcwc&pretty=1&host_name={{ item }}&applyconfig=1"
- pause:
seconds: 120
ignore_error: yes
with_items:
- "{{ groups['grp1'] }}"
can someone suggest if this is the right way if doing or propose me the right way. i also used serial=1 module but its still not working.
You can use pause under your loop:
- name: Pause
hosts: all
gather_facts: False
tasks:
- name: delete host from the NagiosXI
shell: curl -k -XDELETE "https://10.000.00.00/nagiosxi/api/v1/config/host?apikey=qdjcwc&pretty=1&host_name={{ item }}&applyconfig=1"
ignore_errors: True
with_items:
- "{{ groups['grp1'] }}"
loop_control:
pause: 120
Unfortunately, applying multiple tasks to with_items is not possible in Ansible at the moment, but is still doable with the include directive. As example,
The main play file would be
---
- hosts: localhost
connection: local
gather_facts: no
remote_user: me
tasks:
- include: sub_play.yml nagios_host={{ item }}
with_items:
- host1
- host2
- host3
The sub_play yml which is included in the main play would be,
---
- shell: echo "{{ nagios_host }}"
- pause:
prompt: "Waiting for {{ nagios_host }}"
seconds: 5
In this case, the include statement is executed over a loop which executes all the tasks in the sub_task yml.

lineinfile ansible module skips a line

I have a need to know the index of host names in the inventory. I am using the below code to create a variable file that I can use in a subsequent play book
- name: Debug me
hosts: hosts
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
I have the following inventory file
[hosts]
my.host1.com
my.host2.com
Now when I run this, the test.conf that gets generated under /tmp sometimes has both hostnames like this
host1= my.host2.com
host0= my.host1.com
when I run the same playbook a few times each time emptying the test.conf before running. quite a few times the file only has one entry
host1= my.host2.com
or
host0= my.host1.com
how come the same ansible playbook behaving differently?
I believe the issue is your running two threads against different hosts, and using local_action is not thread safe.
Try using the serial keyword:
- name: Debug me
hosts: hosts
serial: 1
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
Edit: A better way to do this if just trying to operate on the list of host in inventory on the localhost, would be to avoid doing the action on the host and using local_action in the first place.
- name: Debug me
hosts: localhost
tasks:
- lineinfile:
create: yes
dest: /tmp/test.conf
line: "host{{ groups['hosts'].index(item)}}={{ item }}"
with_items: " {{ groups['hosts'] }}"
This will get you the results you desire. Then you can add another play to do operations against the hosts themselves.
The solution I am trying to avoid problems with race conditions with non-thread safe Local_action: lineinfile to write gathered data to local file. Split it across 2 different plays in the same file.
eg:
- name: gather_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: get_Aptus_device_count_list
shell: gather_data.sh
become: true
register: Aptus_device_count_list
changed_when: false
- name: Log_gathered_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: log_gathered_info
local_action:
module: lineinfile
dest: /home/rms-mit/MyAnsible/record_Device_count_collection.out
line: "\n--- {{ inventory_hostname }} --- \n
{{ Aptus_device_count_list.stdout }} \n.\n---\n"
changed_when: false

Ansible - Check if multiple directories exist - if so run a script on each directory - How?

Im creating a deployment playbook for our web services. Each web service is in its own directory such as:
/webapps/service-one/
/webapps/service-two/
/webapps/service-three/
I want to check to see if the service directory exists, and if so, I want to run a shell script that stops the service gracefully. Currently, I am able to complete this step by using ignore_errors: yes.
- name: Stop services
with_items: services_to_stop
shell: "/webapps/scripts/stopService.sh {{item}}"
ignore_errors: yes
While this works, the output is very messy if one of the directories doesnt exist or a service is being deployed for the first time. I effectively want to something like one of these:
This:
- name: Stop services
with_items: services_to_stop
shell: "/webapps/scripts/stopService.sh {{item}}"
when: shell: [ -d /webapps/{{item}} ]
or this:
- name: Stop services
with_items: services_to_stop
shell: "/webapps/scripts/stopService.sh {{item}}"
stat:
path: /webapps/{{item}}
register: path
when: path.stat.exists == True
I'd collect facts first and then do only necessary things.
- name: Check existing services
stat:
path: "/tmp/{{ item }}"
with_items: "{{ services_to_stop }}"
register: services_stat
- name: Stop existing services
with_items: "{{ services_stat.results | selectattr('stat.exists') | map(attribute='item') | list }}"
shell: "/webapps/scripts/stopService.sh {{ item }}"
Also note, that bare variables in with_items don't work since Ansible 2.2, so you should template them.
This will let you get a list of existing directory names into the list variable dir_names (use recurse: no to read only the first level under webapps):
---
- hosts: localhost
connection: local
vars:
dir_names: []
tasks:
- find:
paths: "/webapps"
file_type: directory
recurse: no
register: tmp_dirs
- set_fact: dir_names="{{ dir_names+ [item['path']] }}"
no_log: True
with_items:
- "{{ tmp_dirs['files'] }}"
- debug: var=dir_names
You can then use dir_names in your "Stop services" task via a with_items. It looks like you're intending to use only the name of the directory under "webapps" so you probably want to use the | basename jinja2 filter to get that, so something like this:
- name: Stop services
with_items: "{{ dir_names }}"
shell: "/webapps/scripts/stopService.sh {{item | basename }}"

ansible: using with_items with notify handler

I want to pass a variable to a notification handler, but can't find anywhere be it here on SO, the docs or the issues in the github repo, how to do it. What I'm doing is deploying multiple webapps, and when the code for one of those webapps is changed, it should restart the service for that webapp.
From this SO question, I got this to work, somewhat:
- hosts: localhost
tasks:
- name: "task 1"
shell: "echo {{ item }}"
register: "task_1_output"
with_items: [a,b]
- name: "task 2"
debug:
msg: "{{ item.item }}"
when: item.changed
with_items: task_1_output.results
(Put it in test.yml and run it with ansible-playbook test.yml -c local.)
But this registers the result of the first task and conditionally loops over that in the second task. My problem is that it gets messy when you have two or more tasks that need to notify the second task! For example, restart the web service if either the code was updated or the configuration was changed.
AFAICT, there's no way to pass a variable to a handler. That would cleanly fix it for me. I found some issues on github where other people run into the same problem, and some syntaxes are proposed, but none of them actually work.
Including a sub-playbook won't work either, because using with_items together with include was deprecated.
In my playbooks, I have a site.yml that lists the roles of a group, then in the group_vars for that group I define the list of webapps (including the versions) that should be installed. This seems correct to me, because this way I can use the same playbook for staging and production. But maybe the only solution is to define the role multiple times, and duplicate the list of roles for staging and production.
So what is the wisdom here?
Variables in Ansible are global so there is no reason to pass a variable to handler. If you are trying to make a handler parameterized in a way that you are trying to use a variable in the name of a handler you won't be able to do that in Ansible.
What you can do is create a handler that loops over a list of services easily enough, here is a working example that can be tested locally:
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
with_items:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item }} has changed!"
when: item.changed
with_items: files_created.results
I finally solved it by splitting the apps out over multiple instances of the same role. This way, the handler in the role can refer to variables that are defined as role variable.
In site.yml:
- hosts: localhost
roles:
- role: something
name: a
- role: something
name: b
In roles/something/tasks/main.yml:
- name: do something
shell: "echo {{ name }}"
notify: something happened
- name: do something else
shell: "echo {{ name }}"
notify: something happened
In roles/something/handlers/main.yml:
- name: something happened
debug:
msg: "{{ name }}"
Seems a lot less hackish than the first solution!
To update jarv's answer above, Ansible 2.5 replaces with_items with loop. When getting results, item by itself will not work. You will need to explicitly get the name, e.g., item.name.
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
loop:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item.name }} has changed!"
when: item.changed
loop: files_created.results
I got mine to work like this - I had to add some curly brackets
tasks:
- name: Aktivieren von Security-, Backport- und Non-Security-Upgrades
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^[^"//"]*"\${distro_id}:\${distro_codename}-{{ item }}";'
line: ' "${distro_id}:${distro_codename}-{{ item }}";'
insertafter: "Unattended-Upgrade::Allowed-Origins {"
state: present
register: aenderung
loop:
- updates
- security
- backports
notify: Auskommentierte Zeilen entfernen
handlers:
- name: Auskommentierte Zeilen entfernen
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^\/\/.*{{ item.item }}";.*'
state: absent
when: item.changed
loop: "{{ aenderung.results }}"

Resources