Ansible, saving list of hosts into a variable - sql

I have a playbook where I am first running a SQL statement to get a list of hosts from a database. I then save that list into a variable and want to run the next set of tasks over this list of hosts. But I am not sure how to do this or if it is even possible to dynamically define the hosts into an Ansible variable?
Below is a snippet of my code and what I am trying to do.
---
- hosts: all
gather_facts: no
tasks:
- name: Get list of hosts
command: sqlcmd -d testDB -q "SET NOCOUNT ON; SELECT DISTINCT HostName FROM Servers" -S "Central_Server" -h -1
register: sql_servers
- hosts: '{{ sql_servers.stdout_lines }}'
serial: 1
gather_facts: no
tasks:
........
other tasks
........
In the above code, I am trying to save the list of hosts into the sql_servers variables and want to run the 2nd set of my playbook over those hosts.

You're running the play with hosts: all. This implicates that there might be more hosts and, as a result, more lists of sql_servers too. Let's concatenate the lists. For example, whatever the source of the lists might be, given the inventory
shell> cat hosts
srv1 sql_servers='["a", "b"]'
srv2 sql_servers='["c", "a"]'
srv3 sql_servers='["e", "b"]'
the play
- hosts: all
tasks:
- set_fact:
srvs: "{{ ansible_play_hosts|
map('extract', hostvars, 'sql_servers')|
flatten|unique }}"
run_once: true
gives
srvs:
- a
- b
- c
- e
Now, use add_host and create group sql_servers
- add_host:
hostname: "{{ item }}"
groups: sql_servers
loop: "{{ srvs }}"
run_once: true
Use this group in the next play. The complete simplified playbook
- hosts: all
tasks:
- add_host:
hostname: "{{ item }}"
groups: sql_servers
loop: "{{ ansible_play_hosts|
map('extract', hostvars, 'sql_servers')|
flatten|unique }}"
run_once: true
- hosts: sql_servers
tasks:
- debug:
var: ansible_play_hosts_all
run_once: true
gives
ansible_play_hosts_all:
- a
- b
- c
- e
Fit the control flow to your needs.

Related

Ansible playbook passing variable to next play

I have built a playbook in ansible that creates 2 groups of ec2 instances.
In a second playbook, I want that the first play lists the existing group to the user so the user can choose one. Then in a second play, use this group in hosts
---
- name: playbook
hosts: localhost
vars_prompt:
- name: groupvar
prompt: "Select a group"
private: no
tasks:
- name: task 1
debug:
msg: "{{ groupvar}}"
- name: Another play
hosts: "{{ groupvar }}"
# ...
How can I pass on the value of groupvar to the second play in this playbook?
Note: make sure you are not simply re-inventing the existing --limit option of the ansible-playbook command line
As you found out, vars_prompt do not survive the play they're declared in. In that case you have to use set_fact. Here is an example using your above code as a starting point:
- name: playbook
hosts: localhost
vars_prompt:
- name: groupvar
prompt: "Select a group"
private: no
tasks:
- name: task 1
debug:
msg: "{{ groupvar }}"
- name: Save value in a fact for current host
set_fact:
groupvar: "{{ groupvar }}"
- name: Another play running on above chosen group
# Remember we have set the fact on current host above which was localhost
hosts: "{{ hostvars['localhost'].groupvar }}"
# ... rest of your play.

Ansible: Issues with vars_prompt, error: variable is undefined

I think I may be using vars_prompt incorrectly because when I define a variable (used as a host) from command line, the host is used for the following task correctly:
ansible-playbook newfile -v -e 'target_host=uat:prd'
- hosts '{{ target_host }}'
tasks:
...
But when I define the same variable using vars_prompt:
- name: run task
hosts: localhost
gather_facts: no
vars_prompt:
- name: target_host
prompt: please choose a host site
private: no
- hosts: '{{ target_host }}'
tasks:
...
I get error: 'target_host' is undefined pointing at the - hosts: '{{ target_host }}'
Note: it does ask the prompt before getting the error
Thank you for the suggestion to add to host group #JBone. Sadly I have already tried this approach and I get:
Failed to connect to the host via ssh: ssh: Could not resolve hostname uat:prd: Name or service not known
Even though if I fill the host in the playbook as uat:prd it runs on each host
this approach does work for uat or prd by themselves but not uat:prd
you should add this variable value to a new host group using add_host module.
- name: run task
hosts: localhost
gather_facts: no
vars_prompt:
- name: target_host
prompt: please choose a host site
private: no
tasks:
- name: add host
add_host:
name: "{{ target_host }}"
groups: new_hosts_grp
- hosts: new_hosts_grp
tasks:
...
try that one.

Set ansible connection differently according to given condition

I have a playbook that, for one of the hosts, how I need to connect differs according to whether certain tasks have previously succeeded.
In this specific case there's a tunnel between two of them, and one routes all its traffic over that tunnel, so once configured I need to use the other as a jump box in order to connect - but I can imagine many other circumstances where you might want to change connection method mid-playbook, from as simple as modifying users/passwords.
How can I have a conditional connection method?
I can't simply update with set_fact, since by the time I reach that task ansible will already have tried and possibly failed to 'gather facts' at the start, and won't proceed.
The devil is in the details for such a question, for sure, but in general I think use of add_host will be the most legible way to do what you want. You can also change the connection on a per-task basis, or conditionally change the connection for the whole playbook against that host:
- hosts: all
connection: ssh # <-- or whatever bootstrap connection plugin
gather_facts: no
tasks:
- command: echo "do something here"
register: the_thing
# now, you can either switch to the alternate connection per task:
- command: echo "do the other thing"
connection: lxd # <-- or whatever
when: the_thing is success
# OR, you can make the alternate connection the default
# for the rest of the current playbook
- name: switch the rest of the playbook
set_fact:
ansible_connection: chroot
when: the_thing is success
# OR, perhaps run another playbook using the alternate connection
# by adding the newly configured host to a special group
- add_host:
name: '{{ ansible_host }}'
groups:
- configured_hosts
when: the_thing is success
# and then running the other playbook against configured hosts
- hosts: configured_hosts
connection: docker # <-- or whatever connection you want
tasks:
- setup:
I use the following snippet as a role and invoke this role depending on the situation whether I need jumphost(bastion or proxy) or not. An example is also given in the comments. This role can add multiple hosts at the same time. Put the following contents in roles/inventory/tasks/main.yml
# Description: |
# Adds given hosts to inventory.
# Inputs:
# hosts_info: |
# (mandatory)
# List of hosts with the structure which looks like this:
#
# - name: <host name>
# address: <url or ip address of host>
# groups: [] list of groups to which this host will be added.
# user: <SSH user>
# ssh_priv_key_path: <private key path for ssh access to host>
# proxy: <define following structure if host should be accessed using proxy>
# ssh_priv_key_path: <priv key path for ssh access to proxy node>
# user: <login user on proxy node>
# host: <proxy host address>
#
# Example Usage:
# - include_role:
# name: inventory
# vars:
# hosts_info:
# - name: controller-0
# address: 10.100.10.13
# groups:
# - controller
# user: user1
# ssh_priv_key_path: /home/user/.ssh/id_rsa
# - name: node-0
# address: 10.10.1.14
# groups:
# - worker
# - nodes
# user: user1
# ssh_priv_key_path: /home/user/.ssh/id_rsa
# proxy:
# ssh_priv_key_path: /home/user/jumphost_key.rsa.priv
# user: jumphost-user
# host: 10.100.10.13
- name: validate inventory input
assert:
that:
- "single_host_info.name is defined"
- "single_host_info.groups is defined"
- "single_host_info.address is defined"
- "single_host_info.user is defined"
- "single_host_info.ssh_priv_key_path is defined"
loop: "{{ hosts_info }}"
loop_control:
loop_var: single_host_info
- name: validate inventory proxy input
assert:
that:
- "single_host_info.proxy.host is defined"
- "single_host_info.proxy.user is defined"
- "single_host_info.proxy.ssh_priv_key_path is defined"
when: "single_host_info.proxy is defined"
loop: "{{ hosts_info }}"
loop_control:
loop_var: single_host_info
- name: Add hosts to inventory without proxy
add_host:
groups: "{{ single_host_info.groups | join(',') }}"
name: "{{ single_host_info.name }}"
host: "{{ single_host_info.name }}"
hostname: "{{ single_host_info.name }}"
ansible_host: "{{ single_host_info.address }}"
ansible_connection: ssh
ansible_ssh_user: "{{ single_host_info.user }}"
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ansible_ssh_private_key_file: "{{ single_host_info.ssh_priv_key_path }}"
loop: "{{ hosts_info | json_query(\"[?contains(keys(#), 'proxy') == `false`]\") | list }}"
loop_control:
loop_var: single_host_info
- name: Add hosts to inventory with proxy
add_host:
groups: "{{ single_host_info.groups | join(',') }}"
name: "{{ single_host_info.name }}"
host: "{{ single_host_info.name }}"
hostname: "{{ single_host_info.name }}"
ansible_host: "{{ single_host_info.address }}"
ansible_connection: ssh
ansible_ssh_user: "{{ single_host_info.user }}"
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ansible_ssh_private_key_file: "{{ single_host_info.ssh_priv_key_path }}"
ansible_ssh_common_args: >-
-o ProxyCommand='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
-W %h:%p -q -i {{ single_host_info.proxy.ssh_priv_key_path }}
{{ single_host_info.proxy.user }}#{{ single_host_info.proxy.host }}'
loop: "{{ hosts_info | json_query(\"[?contains(keys(#), 'proxy') == `true`]\") }}"
loop_control:
loop_var: single_host_info

Ansible how to ignore unreachable hosts before ansible 2.7.x

I'm using ansible to run a command against multiple servers at once. I want to ignore any hosts that fail because of the '"SSH Error: data could not be sent to remote host \"1.2.3.4\". Make sure this host can be reached over ssh"' error because some of the hosts in the list will be offline. How can I do this? Is there a default option in ansible to ignore offline hosts without failing the playbook? Is there an option to do this in a single ansible cli argument outside of a playbook?
Update: I am aware that the ignore_unreachable: true works for ansible 2.7 or greater, but I am working in an ansible 2.6.1 environment.
I found a good solution here. You ping each host locally to see if you can connect and then run commands against the hosts that passed:
---
- hosts: all
connection: local
gather_facts: no
tasks:
- block:
- name: determine hosts that are up
wait_for_connection:
timeout: 5
vars:
ansible_connection: ssh
- name: add devices with connectivity to the "running_hosts" group
group_by:
key: "running_hosts"
rescue:
- debug: msg="cannot connect to {{inventory_hostname}}"
- hosts: running_hosts
gather_facts: no
tasks:
- command: date
With current version on Ansible (2.8) something like this is possible:
- name: identify reachable hosts
hosts: all
gather_facts: false
ignore_errors: true
ignore_unreachable: true
tasks:
- block:
- name: this does nothing
shell: exit 1
register: result
always:
- add_host:
name: "{{ inventory_hostname }}"
group: reachable
- name: Converge
hosts: reachable
gather_facts: false
tasks:
- debug: msg="{{ inventory_hostname }} is reachable"

Ansible modify global applicative variable

on Ansible 2.2, I want to implement this logic:
---
- name "PLB1"
- hosts: localhost
- tasks:
... init variable/fact X ...
- name "PLB2"
- hosts: huge_group
- tasks:
... each run add something to variable X ...
- name "PLB3"
- hosts: localhost
- tasks
- debug:
msg="{{X}}"
I don't understand how to define and then modify the global variable (or fact) X
Could you help me?
Riccardo
There are no global variables in Ansible.
Please look at your task again – do you really need them?
There is hostvars workaround exist:
---
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
counter: 0
- hosts: mygroup
gather_facts: no
serial: 1
tasks:
- set_fact:
counter: "{{ hostvars['localhost'].counter | int + 1 }}"
delegate_to: localhost
delegate_facts: yes
- hosts: localhost
gather_facts: no
tasks:
- debug:
var: counter
Note serial: 1 for second Play – it is used to reread hostvars between task runs. If you don't use serial run, variable's value will be the same for all hosts.