mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-02 14:40:19 -07:00
HTTP(S) API connection plugin (#39224)
* HTTPAPI connection * Punt run_commands to cliconf or httpapi * Fake enable_mode on eapi * Pull changes to nxos * Move load_config to edit_config for future-preparedness * Don't fail on lldp disabled * Re-enable check_rc on nxos' run_commands * Reorganize nxos httpapi plugin for compatibility * draft docs for connection: httpapi * restores docs for connection:local for eapi * Add _remote_is_local to httpapi
This commit is contained in:
parent
cc61c86049
commit
e9d7fa0418
277 changed files with 1325 additions and 1676 deletions
0
lib/ansible/plugins/httpapi/__init__.py
Normal file
0
lib/ansible/plugins/httpapi/__init__.py
Normal file
158
lib/ansible/plugins/httpapi/eos.py
Normal file
158
lib/ansible/plugins/httpapi/eos.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
# (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
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
class HttpApi:
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
def send_request(self, data, **message_kwargs):
|
||||
if 'become' in message_kwargs:
|
||||
display.vvvv('firing event: on_become')
|
||||
# TODO ??? self._terminal.on_become(passwd=auth_pass)
|
||||
|
||||
output = message_kwargs.get('output', 'text')
|
||||
request = request_builder(data, output)
|
||||
headers = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
response = self.connection.send('/command-api', request, headers=headers, method='POST')
|
||||
response = json.loads(to_text(response.read()))
|
||||
return handle_response(response)
|
||||
|
||||
def get_prompt(self):
|
||||
# Hack to keep @enable_mode working
|
||||
return '#'
|
||||
|
||||
# Imported from module_utils
|
||||
def edit_config(self, config, commit=False, replace=False):
|
||||
"""Loads the configuration onto the remote devices
|
||||
|
||||
If the device doesn't support configuration sessions, this will
|
||||
fallback to using configure() to load the commands. If that happens,
|
||||
there will be no returned diff or session values
|
||||
"""
|
||||
session = 'ansible_%s' % int(time.time())
|
||||
result = {'session': session}
|
||||
banner_cmd = None
|
||||
banner_input = []
|
||||
|
||||
commands = ['configure session %s' % session]
|
||||
if replace:
|
||||
commands.append('rollback clean-config')
|
||||
|
||||
for command in config:
|
||||
if command.startswith('banner'):
|
||||
banner_cmd = command
|
||||
banner_input = []
|
||||
elif banner_cmd:
|
||||
if command == 'EOF':
|
||||
command = {'cmd': banner_cmd, 'input': '\n'.join(banner_input)}
|
||||
banner_cmd = None
|
||||
commands.append(command)
|
||||
else:
|
||||
banner_input.append(command)
|
||||
continue
|
||||
else:
|
||||
commands.append(command)
|
||||
|
||||
response = self.send_request(commands)
|
||||
|
||||
commands = ['configure session %s' % session, 'show session-config diffs']
|
||||
if commit:
|
||||
commands.append('commit')
|
||||
else:
|
||||
commands.append('abort')
|
||||
|
||||
response = self.send_request(commands, output='text')
|
||||
diff = response[1].strip()
|
||||
if diff:
|
||||
result['diff'] = diff
|
||||
|
||||
return result
|
||||
|
||||
def run_commands(self, commands, check_rc=True):
|
||||
"""Runs list of commands on remote device and returns results
|
||||
"""
|
||||
output = None
|
||||
queue = list()
|
||||
responses = list()
|
||||
|
||||
def run_queue(queue, output):
|
||||
response = to_list(self.send_request(queue, output=output))
|
||||
if output == 'json':
|
||||
response = [json.loads(item) for item in response]
|
||||
return response
|
||||
|
||||
for item in to_list(commands):
|
||||
cmd_output = None
|
||||
if isinstance(item, dict):
|
||||
command = item['command']
|
||||
if command.endswith('| json'):
|
||||
command = command.replace('| json', '')
|
||||
cmd_output = 'json'
|
||||
elif 'output' in item:
|
||||
cmd_output = item['output']
|
||||
else:
|
||||
command = item
|
||||
cmd_output = 'json'
|
||||
|
||||
if output and output != cmd_output:
|
||||
responses.extend(run_queue(queue, output))
|
||||
queue = list()
|
||||
|
||||
output = cmd_output or 'json'
|
||||
queue.append(command)
|
||||
|
||||
if queue:
|
||||
responses.extend(run_queue(queue, output))
|
||||
|
||||
return responses
|
||||
|
||||
def load_config(self, config, commit=False, replace=False):
|
||||
"""Loads the configuration onto the remote devices
|
||||
|
||||
If the device doesn't support configuration sessions, this will
|
||||
fallback to using configure() to load the commands. If that happens,
|
||||
there will be no returned diff or session values
|
||||
"""
|
||||
return self.edit_config(config, commit, replace)
|
||||
|
||||
|
||||
def handle_response(response):
|
||||
if 'error' in response:
|
||||
error = response['error']
|
||||
raise ConnectionError(error['message'], code=error['code'])
|
||||
|
||||
results = []
|
||||
for result in response['result']:
|
||||
if 'messages' in result:
|
||||
results.append(result['messages'][0])
|
||||
elif 'output' in result:
|
||||
results.append(result['output'].strip())
|
||||
else:
|
||||
results.append(json.dumps(result))
|
||||
|
||||
if len(results) == 1:
|
||||
return results[0]
|
||||
return results
|
||||
|
||||
|
||||
def request_builder(commands, output, reqid=None):
|
||||
params = dict(version=1, cmds=to_list(commands), format=output)
|
||||
return json.dumps(dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params))
|
135
lib/ansible/plugins/httpapi/nxos.py
Normal file
135
lib/ansible/plugins/httpapi/nxos.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
# (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
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
class HttpApi:
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
def _run_queue(self, queue, output):
|
||||
request = request_builder(queue, output)
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = self.connection.send('/ins', request, headers=headers, method='POST')
|
||||
response = json.loads(to_text(response.read()))
|
||||
return handle_response(response)
|
||||
|
||||
def send_request(self, data, **message_kwargs):
|
||||
output = None
|
||||
queue = list()
|
||||
responses = list()
|
||||
|
||||
for item in to_list(data):
|
||||
cmd_output = message_kwargs.get('output', 'json')
|
||||
if isinstance(item, dict):
|
||||
command = item['command']
|
||||
if command.endswith('| json'):
|
||||
command = command.rsplit('|', 1)[0]
|
||||
cmd_output = 'json'
|
||||
elif 'output' in item:
|
||||
cmd_output = item['output']
|
||||
else:
|
||||
command = item
|
||||
|
||||
if output and output != cmd_output:
|
||||
responses.extend(self._run_queue(queue, output))
|
||||
queue = list()
|
||||
|
||||
output = cmd_output or 'json'
|
||||
queue.append(command)
|
||||
|
||||
if queue:
|
||||
responses.extend(self._run_queue(queue, output))
|
||||
|
||||
if len(responses) == 1:
|
||||
return responses[0]
|
||||
return responses
|
||||
|
||||
# Migrated from module_utils
|
||||
def edit_config(self, command):
|
||||
responses = self.send_request(command, output='config')
|
||||
return json.dumps(responses)
|
||||
|
||||
def run_commands(self, commands, check_rc=True):
|
||||
"""Runs list of commands on remote device and returns results
|
||||
"""
|
||||
try:
|
||||
out = self.send_request(commands)
|
||||
except ConnectionError as exc:
|
||||
if check_rc:
|
||||
raise
|
||||
out = to_text(exc)
|
||||
|
||||
out = to_list(out)
|
||||
for index, response in enumerate(out):
|
||||
if response[0] == '{':
|
||||
out[index] = json.loads(response)
|
||||
return out
|
||||
|
||||
|
||||
def handle_response(response):
|
||||
results = []
|
||||
|
||||
if response['ins_api'].get('outputs'):
|
||||
for output in to_list(response['ins_api']['outputs']['output']):
|
||||
if output['code'] != '200':
|
||||
raise ConnectionError('%s: %s' % (output['input'], output['msg']))
|
||||
elif 'body' in output:
|
||||
result = output['body']
|
||||
if isinstance(result, dict):
|
||||
result = json.dumps(result)
|
||||
|
||||
results.append(result.strip())
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def request_builder(commands, output, version='1.0', chunk='0', sid=None):
|
||||
"""Encodes a NXAPI JSON request message
|
||||
"""
|
||||
output_to_command_type = {
|
||||
'text': 'cli_show_ascii',
|
||||
'json': 'cli_show',
|
||||
'bash': 'bash',
|
||||
'config': 'cli_conf'
|
||||
}
|
||||
|
||||
maybe_output = commands[0].split('|')[-1].strip()
|
||||
if maybe_output in output_to_command_type:
|
||||
command_type = output_to_command_type[maybe_output]
|
||||
commands = [command.split('|')[0].strip() for command in commands]
|
||||
else:
|
||||
try:
|
||||
command_type = output_to_command_type[output]
|
||||
except KeyError:
|
||||
msg = 'invalid format, received %s, expected one of %s' % \
|
||||
(output, ','.join(output_to_command_type.keys()))
|
||||
raise ConnectionError(msg)
|
||||
|
||||
if isinstance(commands, (list, set, tuple)):
|
||||
commands = ' ;'.join(commands)
|
||||
|
||||
msg = {
|
||||
'version': version,
|
||||
'type': command_type,
|
||||
'chunk': chunk,
|
||||
'sid': sid,
|
||||
'input': commands,
|
||||
'output_format': 'json'
|
||||
}
|
||||
return json.dumps(dict(ins_api=msg))
|
Loading…
Add table
Add a link
Reference in a new issue