New base class for HttpApi plugins (#41915)

pull/41839/head
Nathaniel Case 6 years ago committed by GitHub
parent a8d4bf8642
commit 97ffb4c4d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -181,25 +181,17 @@ class Connection(ConnectionBase):
self._local = connection_loader.get('local', play_context, '/dev/null')
self._local.set_options()
self._cliconf = None
self._implementation_plugins = []
self._ansible_playbook_pid = kwargs.get('ansible_playbook_pid')
network_os = self._play_context.network_os
if not network_os:
self._network_os = self._play_context.network_os
if not self._network_os:
raise AnsibleConnectionFailure(
'Unable to automatically determine host network os. Please '
'manually configure ansible_network_os value for this host'
)
self._httpapi = httpapi_loader.get(network_os, self)
if self._httpapi:
if hasattr(self._httpapi, 'set_become'):
self._httpapi.set_become(play_context)
display.vvvv('loaded API plugin for network_os %s' % network_os, host=self._play_context.remote_addr)
else:
raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % network_os)
self._url = None
self._auth = None
@ -211,7 +203,7 @@ class Connection(ConnectionBase):
return self.__dict__[name]
except KeyError:
if not name.startswith('_'):
for plugin in (self._httpapi, self._cliconf):
for plugin in self._implementation_plugins:
method = getattr(plugin, name, None)
if method:
return method
@ -244,22 +236,29 @@ class Connection(ConnectionBase):
return messages
def _connect(self):
if self.connected:
return
network_os = self._play_context.network_os
protocol = 'https' if self.get_option('use_ssl') else 'http'
host = self.get_option('host')
port = self.get_option('port') or (443 if protocol == 'https' else 80)
self._url = '%s://%s:%s' % (protocol, host, port)
self._cliconf = cliconf_loader.get(network_os, self)
if self._cliconf:
display.vvvv('loaded cliconf plugin for network_os %s' % network_os, host=host)
else:
display.vvvv('unable to load cliconf for network_os %s' % network_os)
if not self.connected:
protocol = 'https' if self.get_option('use_ssl') else 'http'
host = self.get_option('host')
port = self.get_option('port') or (443 if protocol == 'https' else 80)
self._url = '%s://%s:%s' % (protocol, host, port)
httpapi = httpapi_loader.get(self._network_os, self)
if httpapi:
httpapi.set_become(self._play_context)
httpapi.login(self.get_option('remote_user'), self.get_option('password'))
display.vvvv('loaded API plugin for network_os %s' % self._network_os, host=self._play_context.remote_addr)
else:
raise AnsibleConnectionFailure('unable to load API plugin for network_os %s' % self._network_os)
self._implementation_plugins.append(httpapi)
cliconf = cliconf_loader.get(self._network_os, self)
if cliconf:
display.vvvv('loaded cliconf plugin for network_os %s' % self._network_os, host=host)
self._implementation_plugins.append(cliconf)
else:
display.vvvv('unable to load cliconf for network_os %s' % self._network_os)
self._connected = True
self._connected = True
def _update_connection_state(self):
'''
@ -294,6 +293,7 @@ class Connection(ConnectionBase):
display.vvvv('reset call on connection instance', host=self._play_context.remote_addr)
def close(self):
self._implementation_plugins = []
if self._connected:
self._connected = False
@ -302,14 +302,23 @@ class Connection(ConnectionBase):
Sends the command to the device over api
'''
url_kwargs = dict(
url_username=self.get_option('remote_user'), url_password=self.get_option('password'),
timeout=self.get_option('timeout'), validate_certs=self.get_option('validate_certs'),
)
url_kwargs.update(kwargs)
if self._auth:
url_kwargs['headers']['Cookie'] = self._auth
else:
url_kwargs['url_username'] = self.get_option('remote_user')
url_kwargs['url_password'] = self.get_option('password')
try:
response = open_url(self._url + path, data=data, **url_kwargs)
except URLError as exc:
if exc.reason == 'Unauthorized' and self._auth:
# Stored auth appears to be invalid, clear and retry
self._auth = None
self.login(self.get_option('remote_user'), self.get_option('password'))
return self.send(path, data, **kwargs)
raise AnsibleConnectionFailure('Could not connect to {0}: {1}'.format(self._url, exc.reason))
self._auth = response.info().get('Set-Cookie')

@ -0,0 +1,34 @@
# (c) 2018 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from abc import abstractmethod
from ansible.plugins import AnsiblePlugin
class HttpApiBase(AnsiblePlugin):
def __init__(self, connection):
self.connection = connection
self._become = False
self._become_pass = ''
def set_become(self, become_context):
self._become = become_context.become
self._become_pass = getattr(become_context, 'become_pass') or ''
def login(self, username, password):
"""Call a defined login endpoint to receive an authentication token.
This should only be implemented if the API has a single endpoint which
can turn HTTP basic auth into a token which can be reused for the rest
of the calls for the session.
"""
pass
@abstractmethod
def send_request(self, data, **message_kwargs):
"""Prepares and sends request(s) to device."""
pass

@ -8,8 +8,9 @@ import json
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.httpapi import HttpApiBase
try:
from __main__ import display
@ -18,11 +19,7 @@ except ImportError:
display = Display()
class HttpApi:
def __init__(self, connection):
self.connection = connection
self._become = False
class HttpApi(HttpApiBase):
def send_request(self, data, **message_kwargs):
data = to_list(data)
if self._become:
@ -39,6 +36,7 @@ class HttpApi:
response = json.loads(response_text)
except ValueError:
raise ConnectionError('Response was not valid JSON, got {0}'.format(response_text))
results = handle_response(response)
if self._become:
@ -55,10 +53,6 @@ class HttpApi:
else:
return '>'
def set_become(self, play_context):
self._become = play_context.become
self._become_pass = getattr(play_context, 'become_pass') or ''
# Imported from module_utils
def edit_config(self, config, commit=False, replace=False):
"""Loads the configuration onto the remote devices
@ -120,23 +114,24 @@ class HttpApi:
return response
for item in to_list(commands):
cmd_output = None
cmd_output = 'text'
if isinstance(item, dict):
command = item['command']
if command.endswith('| json'):
command = command.replace('| json', '')
cmd_output = 'json'
elif 'output' in item:
if 'output' in item:
cmd_output = item['output']
else:
command = item
# Emulate '| json' from CLI
if command.endswith('| json'):
command = command.rsplit('|', 1)[0]
cmd_output = 'json'
if output and output != cmd_output:
responses.extend(run_queue(queue, output))
queue = list()
output = cmd_output or 'json'
output = cmd_output
queue.append(command)
if queue:

@ -9,6 +9,7 @@ import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.httpapi import HttpApiBase
try:
from __main__ import display
@ -17,14 +18,12 @@ except ImportError:
display = Display()
class HttpApi:
def __init__(self, connection):
self.connection = connection
class HttpApi(HttpApiBase):
def _run_queue(self, queue, output):
if self._become:
display.vvvv('firing event: on_become')
queue.insert(0, 'enable')
request = request_builder(queue, output)
headers = {'Content-Type': 'application/json'}
@ -74,10 +73,6 @@ class HttpApi:
return responses[0]
return responses
def set_become(self, play_context):
self._become = play_context.become
self._become_pass = getattr(play_context, 'become_pass') or ''
# Migrated from module_utils
def edit_config(self, command):
resp = list()

Loading…
Cancel
Save