Skip to content

Instantly share code, notes, and snippets.

@ppmathis
Last active August 29, 2015 14:21
Show Gist options
  • Save ppmathis/269618ba001b51abb0d2 to your computer and use it in GitHub Desktop.
Save ppmathis/269618ba001b51abb0d2 to your computer and use it in GitHub Desktop.
Ansible Connection Bootstrapper
---
# First, we are going to check if the netcat binary is available.
# This is required to quickly check if the preferred SSH port is reachable.
- name: Connection Bootstrapper >> Check if 'nc' binary is available
delegate_to: 127.0.0.1
local_action: stat path=/usr/bin/nc
register: nc_binary_check
- name: Connection Bootstrapper >> Abort execution if 'nc' binary does not exist
fail:
msg: "The configuration host does not provide the 'nc' binary (netcat), please install and retry."
when: not nc_binary_check.stat.exists
# Here we are checking if the preferred SSH port is reachable and if the
# test succeeds, we try connecting and authenticating against the host.
- name: Connection Bootstrapper >> Check if preferred SSH port is reachable
local_action: shell /usr/bin/nc -w5 {{ inventory_hostname }} {{ deployment.ssh_port }}
register: preferred_port_check
failed_when: preferred_port_check.stderr
changed_when: false
ignore_errors: true
- name: Connection Bootstrapper >> [Preferred Credentials] Set host facts
set_fact:
ansible_ssh_user: '{{ deployment.ssh_user }}'
ansible_ssh_port: '{{ deployment.ssh_port }}'
when: preferred_port_check|success
- name: Connection Bootstrapper >> [Preferred Credentials] Try connecting and authenticating against host
action: ping
environment:
soft_connection_errors: true
register: preferred_credentials_check
when: preferred_port_check|success
ignore_errors: true
# If the preferred credentials failed, because either the port or connection check
# failed, we try if the fallback credentials work. This is especially useful for
# bootstrapping servers which do not have the required SSH configurations.
- name: Connection Bootstrapper >> [Fallback Credentials] Set host facts
set_fact:
ansible_ssh_user: '{{ deployment.ssh_fallback_user }}'
ansible_ssh_port: '{{ deployment.ssh_fallback_port }}'
when: preferred_port_check|failed or preferred_credentials_check|failed
- name: Connection Bootstrapper >> [Fallback Credentials] Try connecting and authenticating against host
action: ping
environment:
soft_connection_errors: true
register: fallback_credentials_check
when: preferred_port_check|failed or preferred_credentials_check|failed
ignore_errors: true
# If both credentials failed, we abort the execution for the host before any other
# play gets executed. Hereby we are able to ensure that the user gets a nice error message.
- name: Connection Bootstrapper >> Abort execution if both connection credentials have failed
fail:
msg: "Connection to host seems to be impossible, all tries have failed."
when: (preferred_port_check|failed or preferred_credentials_check|failed) and fallback_credentials_check|failed
import ansible.runner
class object(object): pass
class SoftConnectionErrorRunner(ansible.runner.Runner):
def __init__(self, *args, **kwargs):
super(SoftConnectionErrorRunner, self).__init__(*args, **kwargs)
# Array which contains the hostnames where connection errors were suppressed
self.__ignored_hosts = []
# Wrap our SoftConnectionErrrorRunnerCallbacks() class around the supplied XYZRunnerCallbacks() class
if not isinstance(self.callbacks, SoftConnectionErrorRunnerCallbacks):
SoftConnectionErrorRunnerCallbacks.__bases__ = (self.callbacks.__class__,)
self.callbacks.__class__ = SoftConnectionErrorRunnerCallbacks
def _partition_results(self, results):
# All connection errors which were suppressed should be now changed to failed results.
# This can be done by adjusting the ReturnData object manually and calling the appropriate callback.
for result in results:
if result.host in self.__ignored_hosts:
result.comm_ok = True
self.callbacks.on_failed(result.host, result.result, True)
return super(SoftConnectionErrorRunner, self)._partition_results(results)
class SoftConnectionErrorRunnerCallbacks(object):
def on_unreachable(self, host, res):
# Check if the task turned soft connection errors on and if not, just proceed with the usual callbacks
if not self.task.environment.get('soft_connection_errors', False):
return super(SoftConnectionErrorRunnerCallbacks, self).on_unreachable(host, res)
# Do not execute any 3rd-party callbacks and remember the hostname so that it can be whitelisted later on
if host not in self.runner._SoftConnectionErrorRunner__ignored_hosts:
self.runner._SoftConnectionErrorRunner__ignored_hosts.append(host)
class CallbackModule(object):
def __init__(self):
# Overwrite the Ansible Runner() class with our own, which hooks a few methods of the original class.
# This might break in future releases, but seems to be the best method to achieve what we are looking for.
ansible.runner.Runner = SoftConnectionErrorRunner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment