Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions SoftLayer/API.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def create_client_from_env(username=None,
config_file=None,
proxy=None,
user_agent=None,
transport=None):
transport=None,
verify=True):
"""Creates a SoftLayer API client using your environment.

Settings are loaded via keyword arguments, environemtal variables and
Expand All @@ -68,6 +69,8 @@ def create_client_from_env(username=None,
calls if you wish to bypass the packages built in User Agent string
:param transport: An object that's callable with this signature:
transport(SoftLayer.transports.Request)
:param bool verify: decide to verify the server's SSL/TLS cert. DO NOT SET
TO FALSE WITHOUT UNDERSTANDING THE IMPLICATIONS.

Usage:

Expand All @@ -83,6 +86,7 @@ def create_client_from_env(username=None,
endpoint_url=endpoint_url,
timeout=timeout,
proxy=proxy,
verify=verify,
config_file=config_file)

if transport is None:
Expand All @@ -94,6 +98,7 @@ def create_client_from_env(username=None,
proxy=settings.get('proxy'),
timeout=settings.get('timeout'),
user_agent=user_agent,
verify=verify,
)
else:
# Default the transport to use XMLRPC
Expand All @@ -102,6 +107,7 @@ def create_client_from_env(username=None,
proxy=settings.get('proxy'),
timeout=settings.get('timeout'),
user_agent=user_agent,
verify=verify,
)

# If we have enough information to make an auth driver, let's do it
Expand Down Expand Up @@ -240,7 +246,8 @@ def call(self, service, method, *args, **kwargs):
request.filter = kwargs.get('filter')
request.limit = kwargs.get('limit')
request.offset = kwargs.get('offset')
request.verify = kwargs.get('verify')
if kwargs.get('verify') is not None:
request.verify = kwargs.get('verify')

if self.auth:
extra_headers = self.auth.get_headers()
Expand Down
33 changes: 24 additions & 9 deletions SoftLayer/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __init__(self):
self.transport_headers = {}

#: Boolean specifying if the server certificate should be verified.
self.verify = True
self.verify = None

#: Client certificate file path.
self.cert = None
Expand Down Expand Up @@ -103,13 +103,15 @@ def __init__(self,
endpoint_url=None,
timeout=None,
proxy=None,
user_agent=None):
user_agent=None,
verify=True):

self.endpoint_url = (endpoint_url or
consts.API_PUBLIC_ENDPOINT).rstrip('/')
self.timeout = timeout or None
self.proxy = proxy
self.user_agent = user_agent or consts.USER_AGENT
self.verify = verify

def __call__(self, request):
"""Makes a SoftLayer API call against the XML-RPC endpoint.
Expand Down Expand Up @@ -145,6 +147,12 @@ def __call__(self, request):
payload = utils.xmlrpc_client.dumps(tuple(largs),
methodname=request.method,
allow_none=True)

# Prefer the request setting, if it's not None
verify = request.verify
if verify is None:
verify = self.verify

LOGGER.debug("=== REQUEST ===")
LOGGER.info('POST %s', url)
LOGGER.debug(request.transport_headers)
Expand All @@ -155,7 +163,7 @@ def __call__(self, request):
data=payload,
headers=request.transport_headers,
timeout=self.timeout,
verify=request.verify,
verify=verify,
cert=request.cert,
proxies=_proxies_dict(self.proxy))
LOGGER.debug("=== RESPONSE ===")
Expand Down Expand Up @@ -202,13 +210,15 @@ def __init__(self,
endpoint_url=None,
timeout=None,
proxy=None,
user_agent=None):
user_agent=None,
verify=True):

self.endpoint_url = (endpoint_url or
consts.API_PUBLIC_ENDPOINT_REST).rstrip('/')
self.timeout = timeout or None
self.proxy = proxy
self.user_agent = user_agent or consts.USER_AGENT
self.verify = verify

def __call__(self, request):
"""Makes a SoftLayer API call against the REST endpoint.
Expand Down Expand Up @@ -269,6 +279,11 @@ def __call__(self, request):

url = '%s.%s' % ('/'.join(url_parts), 'json')

# Prefer the request setting, if it's not None
verify = request.verify
if verify is None:
verify = self.verify

LOGGER.debug("=== REQUEST ===")
LOGGER.info(url)
LOGGER.debug(request.transport_headers)
Expand All @@ -280,24 +295,24 @@ def __call__(self, request):
params=params,
data=raw_body,
timeout=self.timeout,
verify=request.verify,
verify=verify,
cert=request.cert,
proxies=_proxies_dict(self.proxy))
LOGGER.debug("=== RESPONSE ===")
LOGGER.debug(resp.headers)
LOGGER.debug(resp.content)
LOGGER.debug(resp.text)
resp.raise_for_status()
result = json.loads(resp.content)
result = json.loads(resp.text)

if isinstance(result, list):
return SoftLayerListResult(
result, int(resp.headers.get('softlayer-total-items', 0)))
else:
return result
except requests.HTTPError as ex:
content = json.loads(ex.response.content)
message = json.loads(ex.response.text)['error']
raise exceptions.SoftLayerAPIError(ex.response.status_code,
content['error'])
message)
except requests.RequestException as ex:
raise exceptions.TransportError(0, str(ex))

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[pytest]
[tool:pytest]
python_files = *_tests.py

[wheel]
Expand Down
25 changes: 23 additions & 2 deletions tests/api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class Inititialization(testing.TestCase):
def test_init(self):
client = SoftLayer.Client(username='doesnotexist',
api_key='issurelywrong',
timeout=10)
timeout=10,
endpoint_url='http://example.com/v3/xmlrpc/')

self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication)
self.assertEqual(client.auth.username, 'doesnotexist')
Expand Down Expand Up @@ -95,7 +96,7 @@ def test_simple_call(self):
offset=None,
)

def test_verify(self):
def test_verify_request_false(self):
client = SoftLayer.BaseClient(transport=self.mocks)
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
mock.return_value = {"test": "result"}
Expand All @@ -105,6 +106,26 @@ def test_verify(self):
self.assertEqual(resp, {"test": "result"})
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=False)

def test_verify_request_true(self):
client = SoftLayer.BaseClient(transport=self.mocks)
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
mock.return_value = {"test": "result"}

resp = client.call('SERVICE', 'METHOD', verify=True)

self.assertEqual(resp, {"test": "result"})
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=True)

def test_verify_request_not_specified(self):
client = SoftLayer.BaseClient(transport=self.mocks)
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
mock.return_value = {"test": "result"}

resp = client.call('SERVICE', 'METHOD')

self.assertEqual(resp, {"test": "result"})
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=None)

@mock.patch('SoftLayer.API.BaseClient.iter_call')
def test_iterate(self, _iter_call):
self.client['SERVICE'].METHOD(iter=True)
Expand Down
97 changes: 77 additions & 20 deletions tests/transport_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import warnings

import mock
import pytest
import requests
import six

Expand All @@ -17,26 +18,31 @@
from SoftLayer import transports


def get_xmlrpc_response():
response = requests.Response()
list_body = six.b('''<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<array>
<data/>
</array>
</value>
</param>
</params>''')
response.raw = io.BytesIO(list_body)
response.headers['SoftLayer-Total-Items'] = 10
response.status_code = 200
return response


class TestXmlRpcAPICall(testing.TestCase):

def set_up(self):
self.transport = transports.XmlRpcTransport(
endpoint_url='http://something.com',
)
self.response = requests.Response()
list_body = six.b('''<?xml version="1.0" encoding="utf-8"?>
<params>
<param>
<value>
<array>
<data/>
</array>
</value>
</param>
</params>''')
self.response.raw = io.BytesIO(list_body)
self.response.headers['SoftLayer-Total-Items'] = 10
self.response.status_code = 200
self.response = get_xmlrpc_response()

@mock.patch('requests.request')
def test_call(self, request):
Expand Down Expand Up @@ -251,6 +257,55 @@ def test_request_exception(self, request):
self.assertRaises(SoftLayer.TransportError, self.transport, req)


@mock.patch('requests.request')
@pytest.mark.parametrize(
"transport_verify,request_verify,expected",
[
(True, True, True),
(True, False, False),
(True, None, True),

(False, True, True),
(False, False, False),
(False, None, False),

(None, True, True),
(None, False, False),
(None, None, True),
]
)
def test_verify(request,
transport_verify,
request_verify,
expected):
request.return_value = get_xmlrpc_response()

transport = transports.XmlRpcTransport(
endpoint_url='http://something.com',
)

req = transports.Request()
req.service = 'SoftLayer_Service'
req.method = 'getObject'

if request_verify is not None:
req.verify = request_verify

if transport_verify is not None:
transport.verify = transport_verify

transport(req)

request.assert_called_with('POST',
'http://something.com/SoftLayer_Service',
data=mock.ANY,
headers=mock.ANY,
cert=mock.ANY,
proxies=mock.ANY,
timeout=mock.ANY,
verify=expected)


class TestRestAPICall(testing.TestCase):

def set_up(self):
Expand All @@ -261,6 +316,7 @@ def set_up(self):
@mock.patch('requests.request')
def test_basic(self, request):
request().content = '[]'
request().text = '[]'
request().headers = requests.structures.CaseInsensitiveDict({
'SoftLayer-Total-Items': '10',
})
Expand Down Expand Up @@ -290,7 +346,7 @@ def test_error(self, request):
e = requests.HTTPError('error')
e.response = mock.MagicMock()
e.response.status_code = 404
e.response.content = '''{
e.response.text = '''{
"error": "description",
"code": "Error Code"
}'''
Expand All @@ -315,14 +371,15 @@ def test_proxy_without_protocol(self):

@mock.patch('requests.request')
def test_valid_proxy(self, request):
request().content = '{}'
request().text = '{}'
self.transport.proxy = 'http://localhost:3128'

req = transports.Request()
req.service = 'SoftLayer_Service'
req.method = 'Resource'

self.transport(req)

request.assert_called_with(
'GET', 'http://something.com/SoftLayer_Service/Resource.json',
proxies={'https': 'http://localhost:3128',
Expand All @@ -337,7 +394,7 @@ def test_valid_proxy(self, request):

@mock.patch('requests.request')
def test_with_id(self, request):
request().content = '{}'
request().text = '{}'

req = transports.Request()
req.service = 'SoftLayer_Service'
Expand All @@ -361,7 +418,7 @@ def test_with_id(self, request):

@mock.patch('requests.request')
def test_with_args(self, request):
request().content = '{}'
request().text = '{}'

req = transports.Request()
req.service = 'SoftLayer_Service'
Expand All @@ -385,7 +442,7 @@ def test_with_args(self, request):

@mock.patch('requests.request')
def test_with_filter(self, request):
request().content = '{}'
request().text = '{}'

req = transports.Request()
req.service = 'SoftLayer_Service'
Expand All @@ -410,7 +467,7 @@ def test_with_filter(self, request):

@mock.patch('requests.request')
def test_with_mask(self, request):
request().content = '{}'
request().text = '{}'

req = transports.Request()
req.service = 'SoftLayer_Service'
Expand Down