Fixes and additions for f5 modules (#39986)
Small fixes in the f5 module utils. I believe the action plugins now work consistently across types of connections
This commit is contained in:
parent
8654508cbd
commit
00a6b19e58
6 changed files with 172 additions and 162 deletions
|
@ -80,15 +80,15 @@ class F5RestClient(F5BaseClient):
|
||||||
payload = {
|
payload = {
|
||||||
'username': self.provider['user'],
|
'username': self.provider['user'],
|
||||||
'password': self.provider['password'],
|
'password': self.provider['password'],
|
||||||
'loginProviderName': self.provider['auth_provider']
|
'loginProviderName': self.provider['auth_provider'] or 'tmos'
|
||||||
}
|
}
|
||||||
session = iControlRestSession()
|
session = iControlRestSession()
|
||||||
session.verify = self.provider['validate_certs']
|
session.verify = self.provider['validate_certs']
|
||||||
response = session.post(url, json=payload)
|
response = session.post(url, json=payload)
|
||||||
|
|
||||||
if response.status_code not in [200]:
|
if response.status not in [200]:
|
||||||
raise F5ModuleError('{0} Unexpected Error: {1} for uri: {2}\nText: {3}'.format(
|
raise F5ModuleError('Status code: {0}. Unexpected Error: {1} for uri: {2}\nText: {3}'.format(
|
||||||
response.status_code, response.reason, response.url, response._content
|
response.status, response.reason, response.url, response._content
|
||||||
))
|
))
|
||||||
|
|
||||||
session.headers['X-F5-Auth-Token'] = response.json()['token']['token']
|
session.headers['X-F5-Auth-Token'] = response.json()['token']['token']
|
||||||
|
@ -98,7 +98,7 @@ class F5RestClient(F5BaseClient):
|
||||||
exc = ex
|
exc = ex
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
error = 'Unable to connect to {0} on port {1}.'.format(
|
error = 'Unable to connect to {0} on port {1}.'.format(
|
||||||
self.params['server'], self.params['server_port']
|
self.provider['server'], self.provider['server_port']
|
||||||
)
|
)
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
error += ' The reported error was "{0}".'.format(str(exc))
|
error += ' The reported error was "{0}".'.format(str(exc))
|
||||||
|
|
|
@ -61,35 +61,43 @@ class F5Client(F5BaseClient):
|
||||||
|
|
||||||
|
|
||||||
class F5RestClient(F5BaseClient):
|
class F5RestClient(F5BaseClient):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(F5RestClient, self).__init__(*args, **kwargs)
|
||||||
|
self.provider = self.merge_provider_params()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self):
|
||||||
ex = None
|
exc = None
|
||||||
if self._client:
|
if self._client:
|
||||||
return self._client
|
return self._client
|
||||||
for x in range(0, 10):
|
for x in range(0, 10):
|
||||||
try:
|
try:
|
||||||
server = self.params['provider']['server'] or self.params['server']
|
url = "https://{0}:{1}/mgmt/shared/authn/login".format(
|
||||||
user = self.params['provider']['user'] or self.params['user']
|
self.provider['server'], self.provider['server_port']
|
||||||
password = self.params['provider']['password'] or self.params['password']
|
|
||||||
server_port = self.params['provider']['server_port'] or self.params['server_port'] or 443
|
|
||||||
validate_certs = self.params['provider']['validate_certs'] or self.params['validate_certs']
|
|
||||||
|
|
||||||
# Should we import from module??
|
|
||||||
# self.module.params['server'],
|
|
||||||
result = iControlRestSession(
|
|
||||||
server,
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
port=server_port,
|
|
||||||
verify=validate_certs,
|
|
||||||
auth_provider='local',
|
|
||||||
debug=is_ansible_debug(self.module)
|
|
||||||
)
|
)
|
||||||
self._client = result
|
payload = {
|
||||||
|
'username': self.provider['user'],
|
||||||
|
'password': self.provider['password'],
|
||||||
|
'loginProviderName': self.provider['auth_provider'] or 'local'
|
||||||
|
}
|
||||||
|
session = iControlRestSession()
|
||||||
|
session.verify = self.provider['validate_certs']
|
||||||
|
response = session.post(url, json=payload)
|
||||||
|
|
||||||
|
if response.status not in [200]:
|
||||||
|
raise F5ModuleError('Status code: {0}. Unexpected Error: {1} for uri: {2}\nText: {3}'.format(
|
||||||
|
response.status, response.reason, response.url, response._content
|
||||||
|
))
|
||||||
|
|
||||||
|
session.headers['X-F5-Auth-Token'] = response.json()['token']['token']
|
||||||
|
self._client = session
|
||||||
return self._client
|
return self._client
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
exc = ex
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
error = 'Unable to connect to {0} on port {1}.'.format(self.params['server'], self.params['server_port'])
|
error = 'Unable to connect to {0} on port {1}.'.format(
|
||||||
if ex is not None:
|
self.provider['server'], self.provider['server_port']
|
||||||
error += ' The reported error was "{0}".'.format(str(ex))
|
)
|
||||||
|
if exc is not None:
|
||||||
|
error += ' The reported error was "{0}".'.format(str(exc))
|
||||||
raise F5ModuleError(error)
|
raise F5ModuleError(error)
|
||||||
|
|
|
@ -295,8 +295,6 @@ def compare_dictionary(want, have):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool:
|
bool:
|
||||||
:param have:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
if want == [] and have is None:
|
if want == [] and have is None:
|
||||||
return None
|
return None
|
||||||
|
@ -328,6 +326,26 @@ def exit_json(module, results, client=None):
|
||||||
module.exit_json(**results)
|
module.exit_json(**results)
|
||||||
|
|
||||||
|
|
||||||
|
def is_uuid(uuid=None):
|
||||||
|
"""Check to see if value is an F5 UUID
|
||||||
|
|
||||||
|
UUIDs are used in BIG-IQ and in select areas of BIG-IP (notably ASM). This method
|
||||||
|
will check to see if the provided value matches a UUID as known by these products.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uuid (string): The value to check for UUID-ness
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool:
|
||||||
|
"""
|
||||||
|
if uuid is None:
|
||||||
|
return False
|
||||||
|
pattern = r'[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}'
|
||||||
|
if re.match(pattern, uuid):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Noop(object):
|
class Noop(object):
|
||||||
"""Represent no-operation required
|
"""Represent no-operation required
|
||||||
|
|
||||||
|
@ -405,7 +423,7 @@ class F5BaseClient(object):
|
||||||
elif self.params.get('auth_provider', None):
|
elif self.params.get('auth_provider', None):
|
||||||
result['auth_provider'] = self.params.get('auth_provider', None)
|
result['auth_provider'] = self.params.get('auth_provider', None)
|
||||||
else:
|
else:
|
||||||
result['auth_provider'] = 'tmos'
|
result['auth_provider'] = None
|
||||||
|
|
||||||
if provider.get('user', None):
|
if provider.get('user', None):
|
||||||
result['user'] = provider.get('user', None)
|
result['user'] = provider.get('user', None)
|
||||||
|
|
|
@ -35,8 +35,8 @@ Use this module to make calls to an F5 REST server. It is influenced by the same
|
||||||
API that the Python ``requests`` tool uses, but the two are not the same, as the
|
API that the Python ``requests`` tool uses, but the two are not the same, as the
|
||||||
library here is **much** more simple and targeted specifically to F5's needs.
|
library here is **much** more simple and targeted specifically to F5's needs.
|
||||||
|
|
||||||
The ``requests`` design was chosen due to familiarity with the tool. Internals though
|
The ``requests`` design was chosen due to familiarity with the tool. Internally,
|
||||||
use Ansible native libraries.
|
the classes contained herein use Ansible native libraries.
|
||||||
|
|
||||||
The means by which you should use it are similar to ``requests`` basic usage.
|
The means by which you should use it are similar to ``requests`` basic usage.
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ class PreparedRequest(object):
|
||||||
class Response(object):
|
class Response(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._content = None
|
self._content = None
|
||||||
self.status_code = None
|
self.status = None
|
||||||
self.headers = dict()
|
self.headers = dict()
|
||||||
self.url = None
|
self.url = None
|
||||||
self.reason = None
|
self.reason = None
|
||||||
|
@ -187,9 +187,14 @@ class iControlRestSession(object):
|
||||||
self.headers = self.default_headers()
|
self.headers = self.default_headers()
|
||||||
self.verify = True
|
self.verify = True
|
||||||
self.params = {}
|
self.params = {}
|
||||||
self.auth = None
|
|
||||||
self.timeout = 30
|
self.timeout = 30
|
||||||
|
|
||||||
|
self.server = None
|
||||||
|
self.user = None
|
||||||
|
self.password = None
|
||||||
|
self.server_port = None
|
||||||
|
self.auth_provider = None
|
||||||
|
|
||||||
def _normalize_headers(self, headers):
|
def _normalize_headers(self, headers):
|
||||||
result = {}
|
result = {}
|
||||||
result.update(dict((k.lower(), v) for k, v in headers))
|
result.update(dict((k.lower(), v) for k, v in headers))
|
||||||
|
@ -259,12 +264,13 @@ class iControlRestSession(object):
|
||||||
method=request.method,
|
method=request.method,
|
||||||
data=request.body,
|
data=request.body,
|
||||||
timeout=kwargs.get('timeout', None) or self.timeout,
|
timeout=kwargs.get('timeout', None) or self.timeout,
|
||||||
|
validate_certs=kwargs.get('verify', None) or self.verify,
|
||||||
headers=request.headers
|
headers=request.headers
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = open_url(request.url, **params)
|
result = open_url(request.url, **params)
|
||||||
response._content = result.read()
|
response._content = result.read().decode('utf-8')
|
||||||
response.status = result.getcode()
|
response.status = result.getcode()
|
||||||
response.url = result.geturl()
|
response.url = result.geturl()
|
||||||
response.msg = "OK (%s bytes)" % result.headers.get('Content-Length', 'unknown')
|
response.msg = "OK (%s bytes)" % result.headers.get('Content-Length', 'unknown')
|
||||||
|
@ -280,79 +286,19 @@ class iControlRestSession(object):
|
||||||
response.status_code = e.code
|
response.status_code = e.code
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, url, **kwargs):
|
def delete(self, url, json=None, **kwargs):
|
||||||
"""Sends a HTTP DELETE command to an F5 REST Server.
|
return self.request('DELETE', url, json=json, **kwargs)
|
||||||
|
|
||||||
Use this method to send a DELETE command to an F5 product.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (string): URL to call.
|
|
||||||
data (bytes): An object specifying additional data to send to the server,
|
|
||||||
or ``None`` if no such data is needed. Currently HTTP requests are the
|
|
||||||
only ones that use data. The supported object types include bytes,
|
|
||||||
file-like objects, and iterables.
|
|
||||||
See https://docs.python.org/3/library/urllib.request.html#urllib.request.Request
|
|
||||||
\\*\\*kwargs (dict): Optional arguments to send to the request.
|
|
||||||
"""
|
|
||||||
return self.request('DELETE', url, **kwargs)
|
|
||||||
|
|
||||||
def get(self, url, **kwargs):
|
def get(self, url, **kwargs):
|
||||||
"""Sends a HTTP GET command to an F5 REST Server.
|
|
||||||
|
|
||||||
Use this method to send a GET command to an F5 product.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (string): URL to call.
|
|
||||||
\\*\\*kwargs (dict): Optional arguments to send to the request.
|
|
||||||
"""
|
|
||||||
return self.request('GET', url, **kwargs)
|
return self.request('GET', url, **kwargs)
|
||||||
|
|
||||||
def patch(self, url, data=None, **kwargs):
|
def patch(self, url, data=None, **kwargs):
|
||||||
"""Sends a HTTP PATCH command to an F5 REST Server.
|
|
||||||
|
|
||||||
Use this method to send a PATCH command to an F5 product.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (string): URL to call.
|
|
||||||
data (bytes): An object specifying additional data to send to the server,
|
|
||||||
or ``None`` if no such data is needed. Currently HTTP requests are the
|
|
||||||
only ones that use data. The supported object types include bytes,
|
|
||||||
file-like objects, and iterables.
|
|
||||||
See https://docs.python.org/3/library/urllib.request.html#urllib.request.Request
|
|
||||||
\\*\\*kwargs (dict): Optional arguments to send to the request.
|
|
||||||
"""
|
|
||||||
return self.request('PATCH', url, data=data, **kwargs)
|
return self.request('PATCH', url, data=data, **kwargs)
|
||||||
|
|
||||||
def post(self, url, data=None, json=None, **kwargs):
|
def post(self, url, data=None, json=None, **kwargs):
|
||||||
"""Sends a HTTP POST command to an F5 REST Server.
|
|
||||||
|
|
||||||
Use this method to send a POST command to an F5 product.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (string): URL to call.
|
|
||||||
data (dict): An object specifying additional data to send to the server,
|
|
||||||
or ``None`` if no such data is needed. Currently HTTP requests are the
|
|
||||||
only ones that use data. The supported object types include bytes,
|
|
||||||
file-like objects, and iterables.
|
|
||||||
See https://docs.python.org/3/library/urllib.request.html#urllib.request.Request
|
|
||||||
\\*\\*kwargs (dict): Optional arguments to the request.
|
|
||||||
"""
|
|
||||||
return self.request('POST', url, data=data, json=json, **kwargs)
|
return self.request('POST', url, data=data, json=json, **kwargs)
|
||||||
|
|
||||||
def put(self, url, data=None, **kwargs):
|
def put(self, url, data=None, **kwargs):
|
||||||
"""Sends a HTTP PUT command to an F5 REST Server.
|
|
||||||
|
|
||||||
Use this method to send a PUT command to an F5 product.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (string): URL to call.
|
|
||||||
data (bytes): An object specifying additional data to send to the server,
|
|
||||||
or ``None`` if no such data is needed. Currently HTTP requests are the
|
|
||||||
only ones that use data. The supported object types include bytes,
|
|
||||||
file-like objects, and iterables.
|
|
||||||
See https://docs.python.org/3/library/urllib.request.html#urllib.request.Request
|
|
||||||
\\*\\*kwargs (dict): Optional arguments to the request.
|
|
||||||
"""
|
|
||||||
return self.request('PUT', url, data=data, **kwargs)
|
return self.request('PUT', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
@ -43,17 +44,16 @@ except ImportError:
|
||||||
class ActionModule(_ActionModule):
|
class ActionModule(_ActionModule):
|
||||||
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
def run(self, tmp=None, task_vars=None):
|
||||||
del tmp # tmp no longer has any effect
|
socket_path = None
|
||||||
|
transport = 'rest'
|
||||||
|
|
||||||
if self._play_context.connection == 'network_cli':
|
if self._play_context.connection == 'network_cli':
|
||||||
provider = self._task.args.get('provider', {})
|
provider = self._task.args.get('provider', {})
|
||||||
if any(provider.values()):
|
if any(provider.values()):
|
||||||
display.warning('provider is unnecessary when using network_cli and will be ignored')
|
display.warning("'provider' is unnecessary when using 'network_cli' and will be ignored")
|
||||||
del self._task.args['provider']
|
|
||||||
elif self._play_context.connection == 'local':
|
elif self._play_context.connection == 'local':
|
||||||
provider = load_provider(f5_provider_spec, self._task.args)
|
provider = load_provider(f5_provider_spec, self._task.args)
|
||||||
|
transport = provider['transport'] or transport
|
||||||
transport = provider['transport'] or 'rest'
|
|
||||||
|
|
||||||
display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
|
display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
|
||||||
|
|
||||||
|
@ -70,17 +70,14 @@ class ActionModule(_ActionModule):
|
||||||
|
|
||||||
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
|
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
|
||||||
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
|
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
|
||||||
|
|
||||||
socket_path = connection.run()
|
socket_path = connection.run()
|
||||||
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
||||||
if not socket_path:
|
if not socket_path:
|
||||||
return {'failed': True,
|
return {'failed': True,
|
||||||
'msg': 'unable to open shell. Please see: ' +
|
'msg': 'Unable to open shell. Please see: ' +
|
||||||
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
||||||
|
|
||||||
task_vars['ansible_socket'] = socket_path
|
task_vars['ansible_socket'] = socket_path
|
||||||
else:
|
|
||||||
self._task.args['provider'] = ActionModule.rest_implementation(provider, self._play_context)
|
|
||||||
else:
|
else:
|
||||||
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
|
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
|
||||||
|
|
||||||
|
@ -96,61 +93,5 @@ class ActionModule(_ActionModule):
|
||||||
conn.send_command('exit')
|
conn.send_command('exit')
|
||||||
out = conn.get_prompt()
|
out = conn.get_prompt()
|
||||||
|
|
||||||
result = super(ActionModule, self).run(task_vars=task_vars)
|
result = super(ActionModule, self).run(tmp, task_vars)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def rest_implementation(provider, play_context):
|
|
||||||
"""Provides a generic argument spec using Play context vars
|
|
||||||
|
|
||||||
This method will return a set of default values to use for connecting
|
|
||||||
to a remote BIG-IP in the event that you do not use either
|
|
||||||
|
|
||||||
* The environment fallback variables F5_USER, F5_PASSWORD, etc
|
|
||||||
* The "provider" spec
|
|
||||||
|
|
||||||
With this "spec" (for lack of a better name) Ansible will attempt
|
|
||||||
to fill in the provider arguments itself using the play context variables.
|
|
||||||
These variables are contained in the list of MAGIC_VARIABLE_MAPPING
|
|
||||||
found in the constants file
|
|
||||||
|
|
||||||
* https://github.com/ansible/ansible/blob/devel/lib/ansible/constants.py
|
|
||||||
|
|
||||||
Therefore, if you do not use the provider nor that environment args, this
|
|
||||||
method here will be populate the "provider" dict with with the necessary
|
|
||||||
F5 connection params, from the following host vars,
|
|
||||||
|
|
||||||
* remote_addr=('ansible_ssh_host', 'ansible_host'),
|
|
||||||
* remote_user=('ansible_ssh_user', 'ansible_user'),
|
|
||||||
* password=('ansible_ssh_pass', 'ansible_password'),
|
|
||||||
* port=('ansible_ssh_port', 'ansible_port'),
|
|
||||||
* timeout=('ansible_ssh_timeout', 'ansible_timeout'),
|
|
||||||
* private_key_file=('ansible_ssh_private_key_file', 'ansible_private_key_file'),
|
|
||||||
|
|
||||||
For example, this may leave your inventory looking like this
|
|
||||||
|
|
||||||
bigip2 ansible_host=1.2.3.4 ansible_port=10443 ansible_user=admin ansible_password=admin
|
|
||||||
|
|
||||||
:param provider:
|
|
||||||
:param play_context:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
provider['transport'] = 'rest'
|
|
||||||
|
|
||||||
if provider.get('server') is None:
|
|
||||||
provider['server'] = play_context.remote_addr
|
|
||||||
|
|
||||||
if provider.get('server_port') is None:
|
|
||||||
default_port = provider['server_port'] if provider['server_port'] else 443
|
|
||||||
provider['server_port'] = int(play_context.port or default_port)
|
|
||||||
|
|
||||||
if provider.get('timeout') is None:
|
|
||||||
provider['timeout'] = C.PERSISTENT_COMMAND_TIMEOUT
|
|
||||||
|
|
||||||
if provider.get('user') is None:
|
|
||||||
provider['user'] = play_context.connection_user
|
|
||||||
|
|
||||||
if provider.get('password') is None:
|
|
||||||
provider['password'] = play_context.password
|
|
||||||
|
|
||||||
return provider
|
|
||||||
|
|
97
lib/ansible/plugins/action/bigiq.py
Normal file
97
lib/ansible/plugins/action/bigiq.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ansible import constants as C
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.connection import Connection
|
||||||
|
from ansible.module_utils.network.common.utils import load_provider
|
||||||
|
from ansible.plugins.action.normal import ActionModule as _ActionModule
|
||||||
|
|
||||||
|
try:
|
||||||
|
from library.module_utils.network.f5.common import f5_provider_spec
|
||||||
|
except:
|
||||||
|
from ansible.module_utils.network.f5.common import f5_provider_spec
|
||||||
|
|
||||||
|
try:
|
||||||
|
from __main__ import display
|
||||||
|
except ImportError:
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(_ActionModule):
|
||||||
|
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
socket_path = None
|
||||||
|
transport = 'rest'
|
||||||
|
|
||||||
|
if self._play_context.connection == 'network_cli':
|
||||||
|
provider = self._task.args.get('provider', {})
|
||||||
|
if any(provider.values()):
|
||||||
|
display.warning("'provider' is unnecessary when using 'network_cli' and will be ignored")
|
||||||
|
elif self._play_context.connection == 'local':
|
||||||
|
provider = load_provider(f5_provider_spec, self._task.args)
|
||||||
|
transport = provider['transport'] or transport
|
||||||
|
|
||||||
|
display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
|
||||||
|
|
||||||
|
if transport == 'cli':
|
||||||
|
pc = copy.deepcopy(self._play_context)
|
||||||
|
pc.connection = 'network_cli'
|
||||||
|
pc.network_os = 'bigiq'
|
||||||
|
pc.remote_addr = provider.get('server', self._play_context.remote_addr)
|
||||||
|
pc.port = int(provider['server_port'] or self._play_context.port or 22)
|
||||||
|
pc.remote_user = provider.get('user', self._play_context.connection_user)
|
||||||
|
pc.password = provider.get('password', self._play_context.password)
|
||||||
|
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
|
||||||
|
pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
|
||||||
|
|
||||||
|
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
|
||||||
|
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
|
||||||
|
socket_path = connection.run()
|
||||||
|
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
||||||
|
if not socket_path:
|
||||||
|
return {'failed': True,
|
||||||
|
'msg': 'unable to open shell. Please see: ' +
|
||||||
|
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
||||||
|
|
||||||
|
task_vars['ansible_socket'] = socket_path
|
||||||
|
else:
|
||||||
|
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
|
||||||
|
|
||||||
|
if (self._play_context.connection == 'local' and transport == 'cli') or self._play_context.connection == 'network_cli':
|
||||||
|
# make sure we are in the right cli context which should be
|
||||||
|
# enable mode and not config module
|
||||||
|
if socket_path is None:
|
||||||
|
socket_path = self._connection.socket_path
|
||||||
|
conn = Connection(socket_path)
|
||||||
|
out = conn.get_prompt()
|
||||||
|
while '(config' in to_text(out, errors='surrogate_then_replace').strip():
|
||||||
|
display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
|
||||||
|
conn.send_command('exit')
|
||||||
|
out = conn.get_prompt()
|
||||||
|
|
||||||
|
result = super(ActionModule, self).run(tmp, task_vars)
|
||||||
|
return result
|
Loading…
Reference in a new issue