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
119 changes: 78 additions & 41 deletions SoftLayer/API.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@
from SoftLayer import consts
from SoftLayer import transports

# pylint: disable=invalid-name


API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT
API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT
__all__ = ['Client', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT']

VALID_CALL_ARGS = set([
__all__ = [
'create_client_from_env',
'Client',
'BaseClient',
'API_PUBLIC_ENDPOINT',
'API_PRIVATE_ENDPOINT',
]

VALID_CALL_ARGS = set((
'id',
'mask',
'filter',
Expand All @@ -25,11 +34,22 @@
'raw_headers',
'limit',
'offset',
])
))


class Client(object):
"""A SoftLayer API client.
def create_client_from_env(username=None,
api_key=None,
endpoint_url=None,
timeout=None,
auth=None,
config_file=None,
proxy=None,
user_agent=None,
transport=None):
"""Creates a SoftLayer API client using your environment.

Settings are loaded via keyword arguments, environemtal variables and
config file.

:param username: an optional API username if you wish to bypass the
package's built-in username
Expand All @@ -51,38 +71,62 @@ class Client(object):
Usage:

>>> import SoftLayer
>>> client = SoftLayer.Client(username="username", api_key="api_key")
>>> client = SoftLayer.create_client_from_env()
>>> resp = client['Account'].getObject()
>>> resp['companyName']
'Your Company'

"""
settings = config.get_client_settings(username=username,
api_key=api_key,
endpoint_url=endpoint_url,
timeout=timeout,
proxy=proxy,
config_file=config_file)

# Default the transport to use XMLRPC
if transport is None:
transport = transports.XmlRpcTransport(
endpoint_url=settings.get('endpoint_url'),
proxy=settings.get('proxy'),
timeout=settings.get('timeout'),
user_agent=user_agent,
)

# If we have enough information to make an auth driver, let's do it
if auth is None and settings.get('username') and settings.get('api_key'):

auth = slauth.BasicAuthentication(
settings.get('username'),
settings.get('api_key'),
)

return BaseClient(auth=auth, transport=transport)


def Client(**kwargs):
"""Get a SoftLayer API Client using environmental settings.

Deprecated in favor of create_client_from_env()
"""
warnings.warn("use SoftLayer.create_client_from_env() instead",
DeprecationWarning)
return create_client_from_env(**kwargs)


class BaseClient(object):
"""Base SoftLayer API client.

:param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase
:param transport: An object that's callable with this signature:
transport(SoftLayer.transports.Request)
"""

_prefix = "SoftLayer_"

def __init__(self, username=None, api_key=None, endpoint_url=None,
timeout=None, auth=None, config_file=None, proxy=None,
user_agent=None, transport=None):

settings = config.get_client_settings(username=username,
api_key=api_key,
endpoint_url=endpoint_url,
timeout=timeout,
auth=auth,
proxy=proxy,
config_file=config_file)
self.auth = settings.get('auth')

self.endpoint_url = (settings.get('endpoint_url') or
API_PUBLIC_ENDPOINT).rstrip('/')
self.transport = transport or transports.XmlRpcTransport()

self.timeout = None
if settings.get('timeout'):
self.timeout = float(settings.get('timeout'))
self.proxy = None
if settings.get('proxy'):
self.proxy = settings.get('proxy')
self.user_agent = user_agent
def __init__(self, auth=None, transport=None):
self.auth = auth
self.transport = transport

def authenticate_with_password(self, username, password,
security_question_id=None,
Expand Down Expand Up @@ -145,13 +189,10 @@ def call(self, service, method, *args, **kwargs):
raise TypeError(
'Invalid keyword arguments: %s' % ','.join(invalid_kwargs))

if not service.startswith(self._prefix):
if self._prefix and not service.startswith(self._prefix):
service = self._prefix + service

http_headers = {
'User-Agent': self.user_agent or consts.USER_AGENT,
'Content-Type': 'application/xml',
}
http_headers = {}

if kwargs.get('compress', True):
http_headers['Accept'] = '*/*'
Expand All @@ -161,13 +202,10 @@ def call(self, service, method, *args, **kwargs):
http_headers.update(kwargs.get('raw_headers'))

request = transports.Request()
request.endpoint = self.endpoint_url
request.service = service
request.method = method
request.args = args
request.transport_headers = http_headers
request.timeout = self.timeout
request.proxy = self.proxy
request.identifier = kwargs.get('id')
request.mask = kwargs.get('mask')
request.filter = kwargs.get('filter')
Expand Down Expand Up @@ -244,8 +282,7 @@ def iter_call(self, service, method, *args, **kwargs):
break

def __repr__(self):
return "<Client: endpoint=%s, user=%r>" % (self.endpoint_url,
self.auth)
return "Client(transport=%r, auth=%r)" % (self.transport, self.auth)

__str__ = __repr__

Expand Down
10 changes: 8 additions & 2 deletions SoftLayer/CLI/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ def get_settings_from_client(client):
settings = {
'username': '',
'api_key': '',
'timeout': client.timeout or None,
'endpoint_url': client.endpoint_url,
'timeout': '',
'endpoint_url': '',
}
try:
settings['username'] = client.auth.username
settings['api_key'] = client.auth.api_key
except AttributeError:
pass

try:
settings['timeout'] = client.transport.transport.timeout
settings['endpoint_url'] = client.transport.transport.endpoint_url
except AttributeError:
pass

return settings


Expand Down
31 changes: 28 additions & 3 deletions SoftLayer/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"""
# pylint: disable=no-self-use

__all__ = ['BasicAuthentication', 'TokenAuthentication', 'AuthenticationBase']
__all__ = [
'BasicAuthentication',
'TokenAuthentication',
'BasicHTTPAuthentication',
'AuthenticationBase',
]


class AuthenticationBase(object):
Expand Down Expand Up @@ -51,7 +56,7 @@ def get_request(self, request):
return request

def __repr__(self):
return "<TokenAuthentication: %s %s>" % (self.user_id, self.auth_token)
return "TokenAuthentication(%r)" % self.user_id


class BasicAuthentication(AuthenticationBase):
Expand All @@ -73,4 +78,24 @@ def get_request(self, request):
return request

def __repr__(self):
return "<BasicAuthentication: %s>" % (self.username)
return "BasicAuthentication(username=%r)" % self.username


class BasicHTTPAuthentication(AuthenticationBase):
"""Token-based authentication class.

:param username str: a user's username
:param api_key str: a user's API key
"""
def __init__(self, username, api_key):
self.username = username
self.api_key = api_key

def get_request(self, request):
"""Sets token-based auth headers."""
request.transport_user = self.username
request.transport_password = self.api_key
return request

def __repr__(self):
return "BasicHTTPAuthentication(username=%r)" % self.username
45 changes: 17 additions & 28 deletions SoftLayer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import os
import os.path

from SoftLayer import auth
from SoftLayer import utils


Expand All @@ -17,32 +16,26 @@ def get_client_settings_args(**kwargs):

:param \\*\\*kwargs: Arguments that are passed into the client instance
"""
settings = {
return {
'endpoint_url': kwargs.get('endpoint_url'),
'timeout': kwargs.get('timeout'),
'auth': kwargs.get('auth'),
'timeout': float(kwargs.get('timeout') or 0),
'proxy': kwargs.get('proxy'),
'username': kwargs.get('username'),
'api_key': kwargs.get('api_key'),
}
username = kwargs.get('username')
api_key = kwargs.get('api_key')
if username and api_key and not settings['auth']:
settings['auth'] = auth.BasicAuthentication(username, api_key)
return settings


def get_client_settings_env(**_):
"""Retrieve client settings from environment settings.

:param \\*\\*kwargs: Arguments that are passed into the client instance
"""
username = os.environ.get('SL_USERNAME')
api_key = os.environ.get('SL_API_KEY')
proxy = os.environ.get('https_proxy')

config = {'proxy': proxy}
if username and api_key:
config['auth'] = auth.BasicAuthentication(username, api_key)
return config
return {
'proxy': os.environ.get('https_proxy'),
'username': os.environ.get('SL_USERNAME'),
'api_key': os.environ.get('SL_API_KEY'),
}


def get_client_settings_config_file(**kwargs):
Expand All @@ -58,24 +51,22 @@ def get_client_settings_config_file(**kwargs):
'username': '',
'api_key': '',
'endpoint_url': '',
'timeout': '',
'timeout': '0',
'proxy': '',
})
config.read(config_files)

if not config.has_section('softlayer'):
return

settings = {
return {
'endpoint_url': config.get('softlayer', 'endpoint_url'),
'timeout': config.get('softlayer', 'timeout'),
'timeout': config.getfloat('softlayer', 'timeout'),
'proxy': config.get('softlayer', 'proxy'),
'username': config.get('softlayer', 'username'),
'api_key': config.get('softlayer', 'api_key'),
}
username = config.get('softlayer', 'username')
api_key = config.get('softlayer', 'api_key')
if username and api_key:
settings['auth'] = auth.BasicAuthentication(username, api_key)
return settings


SETTING_RESOLVERS = [get_client_settings_args,
get_client_settings_env,
Expand All @@ -86,8 +77,7 @@ def get_client_settings(**kwargs):
"""Parse client settings.

Parses settings from various input methods, preferring earlier values
to later ones. Once an 'auth' value is found, it returns the gathered
settings. The settings currently come from explicit user arguments,
to later ones. The settings currently come from explicit user arguments,
environmental variables and config files.

:param \\*\\*kwargs: Arguments that are passed into the client instance
Expand All @@ -98,6 +88,5 @@ def get_client_settings(**kwargs):
if settings:
settings.update((k, v) for k, v in all_settings.items() if v)
all_settings = settings
if all_settings.get('auth'):
break

return all_settings
12 changes: 7 additions & 5 deletions SoftLayer/managers/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ class MetadataManager(object):
attribs = METADATA_MAPPING

def __init__(self, client=None, timeout=5):
url = consts.API_PRIVATE_ENDPOINT_REST.rstrip('/')
if client is None:
client = SoftLayer.Client(endpoint_url=url,
timeout=timeout,
transport=transports.RestTransport())
transport = transports.RestTransport(
timeout=timeout,
endpoint_url=consts.API_PRIVATE_ENDPOINT_REST,
)
client = SoftLayer.BaseClient(transport=transport)

self.client = client

def get(self, name, param=None):
Expand All @@ -83,7 +85,7 @@ def get(self, name, param=None):
try:
return self.client.call('Resource_Metadata',
self.attribs[name]['call'],
id=param)
param)
except exceptions.SoftLayerAPIError as ex:
if ex.faultCode == 404:
return None
Expand Down
5 changes: 2 additions & 3 deletions SoftLayer/tests/CLI/modules/config_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def test_show(self):
self.assertEqual(json.loads(result.output),
{'Username': 'default-user',
'API Key': 'default-key',
'Endpoint URL': 'default-endpoint-url',
'Timeout': 10.0})
'Endpoint URL': 'not set',
'Timeout': 'not set'})


class TestHelpSetup(testing.TestCase):
Expand Down Expand Up @@ -80,7 +80,6 @@ def test_get_user_input_private(self, input, getpass):
self.assertEqual(username, 'user')
self.assertEqual(secret, 'A' * 64)
self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT)
self.assertEqual(timeout, 10)

@mock.patch('SoftLayer.CLI.environment.Environment.getpass')
@mock.patch('SoftLayer.CLI.environment.Environment.input')
Expand Down
Loading