Skip to content

Commit

Permalink
Merge pull request #28 from IBM/order
Browse files Browse the repository at this point in the history
Change order of credential path and read_external_sources and more
  • Loading branch information
ehdsouza authored Sep 19, 2019
2 parents 7ecc329 + 164d02e commit a304b1a
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 82 deletions.
2 changes: 1 addition & 1 deletion ibm_cloud_sdk_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
from .jwt_token_manager import JWTTokenManager
from .cp4d_token_manager import CP4DTokenManager
from .api_exception import ApiException
from .utils import datetime_to_string, string_to_datetime, get_authenticator_from_environment
from .utils import datetime_to_string, string_to_datetime, get_authenticator_from_environment, read_external_sources
19 changes: 4 additions & 15 deletions ibm_cloud_sdk_core/base_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import requests
from requests.structures import CaseInsensitiveDict
from .version import __version__
from .utils import has_bad_first_or_last_char, remove_null_values, cleanup_values, read_from_env_variables
from .utils import has_bad_first_or_last_char, remove_null_values, cleanup_values, read_external_sources
from .detailed_response import DetailedResponse
from .api_exception import ApiException
from .authenticators import Authenticator
Expand All @@ -37,15 +37,13 @@ class BaseService(object):
SDK_NAME = 'ibm-python-sdk-core'

def __init__(self,
service_url,
service_url=None,
authenticator=None,
disable_ssl_verification=False,
display_name=None):
disable_ssl_verification=False):
"""
:attr str url: The url for service api calls
:attr str service_url: The url for service api calls
:attr Authenticator authenticator: The authenticator for authentication
:attr bool disable_ssl_verification: enables/ disables ssl verification
:attr str display_name the name used for mapping services in environment file
"""
self.service_url = service_url
self.http_config = {}
Expand All @@ -63,15 +61,6 @@ def __init__(self,
raise ValueError(
'authenticator should be of type Authenticator')

if display_name:
service_name = display_name.replace(' ', '_').lower()
config = read_from_env_variables(service_name)
if config.get('url'):
self.service_url = config.get('url')
if config.get('disable_ssl'):
self.disable_ssl_verification = config.get('disable_ssl')


def _get_system_info(self):
return '{0} {1} {2}'.format(
platform.system(), # OS
Expand Down
109 changes: 59 additions & 50 deletions ibm_cloud_sdk_core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,62 +54,71 @@ def string_to_datetime(string):
"""
return date_parser.parse(string)

def get_authenticator_from_environment(service_name):
def read_external_sources(service_name):
"""
Checks the credentials file and VCAP_SERVICES environment variable
Try to get config from external sources, with the following priority:
1. Credentials file(ibm-credentials.env)
2. Environment variables
3. VCAP Services(Cloud Foundry)
:param service_name: The service name
:return: the authenticator
:return: dict
"""
authenticator = None
# 1. Credentials from credential file
config = {}

config = read_from_credential_file(service_name)
if config:
authenticator = contruct_authenticator(config)

# 2. From env variables
if not authenticator:
if not config:
config = read_from_env_variables(service_name)
if config:
authenticator = contruct_authenticator(config)

# 3. Credentials from VCAP
if not authenticator:
if not config:
config = read_from_vcap_services(service_name)
if config:
authenticator = contruct_authenticator(config)

return config

def get_authenticator_from_environment(service_name):
"""
Try to get authenticator from external sources, with the following priority:
1. Credentials file(ibm-credentials.env)
2. Environment variables
3. VCAP Services(Cloud Foundry)
:param service_name: The service name
:return: the authenticator
"""
authenticator = None
config = read_external_sources(service_name)
if config:
authenticator = _construct_authenticator(config)
return authenticator

def read_from_env_variables(service_name):
"""
:return dict config: parsed env variables
"""
service_name = service_name.replace(' ', '_').lower()
config = {}
for key, value in environ.items():
_parse_key_and_update_config(config, service_name.lower(), key.lower(), value)
_parse_key_and_update_config(config, service_name, key, value)
return config

def read_from_credential_file(service_name, separator='='):
"""
:param str service_name: The service name
:return dict config: parsed key values pairs
"""
service_name = service_name.replace(' ', '_').lower()
DEFAULT_CREDENTIALS_FILE_NAME = 'ibm-credentials.env'

# File path specified by an env variable
credential_file_path = getenv('IBM_CREDENTIALS_FILE')

# Home directory
# Current working directory
if credential_file_path is None:
file_path = join(expanduser('~'), DEFAULT_CREDENTIALS_FILE_NAME)
file_path = join(
dirname(dirname(abspath(__file__))), DEFAULT_CREDENTIALS_FILE_NAME)
if isfile(file_path):
credential_file_path = file_path

# Top-level of the project directory
# Home directory
if credential_file_path is None:
file_path = join(
dirname(dirname(abspath(__file__))), DEFAULT_CREDENTIALS_FILE_NAME)
file_path = join(expanduser('~'), DEFAULT_CREDENTIALS_FILE_NAME)
if isfile(file_path):
credential_file_path = file_path

Expand All @@ -119,62 +128,62 @@ def read_from_credential_file(service_name, separator='='):
for line in fp:
key_val = line.strip().split(separator)
if len(key_val) == 2:
key = key_val[0].lower()
key = key_val[0]
value = key_val[1]
_parse_key_and_update_config(config, service_name, key, value)
return config

def _parse_key_and_update_config(config, service_name, key, value):
if service_name in key:
index = key.find('_')
if index != -1:
config[key[index + 1:]] = value
service_name = service_name.replace(' ', '_').replace('-', '_').upper()
if key.startswith(service_name):
config[key[len(service_name) + 1:]] = value

def read_from_vcap_services(service_name):
service_name = service_name.replace(' ', '_').lower()
vcap_services = getenv('VCAP_SERVICES')
vcap_service_credentials = None
vcap_service_credentials = {}
if vcap_services:
services = json_import.loads(vcap_services)

for key in services.keys():
name = key.replace('-', '_')
if name == service_name:
if key == service_name:
vcap_service_credentials = services[key][0]['credentials']
if vcap_service_credentials is not None and isinstance(vcap_service_credentials, dict):
if vcap_service_credentials.get('username') and vcap_service_credentials.get('password'): # cf
vcap_service_credentials['auth_type'] = 'basic'
vcap_service_credentials['AUTH_TYPE'] = 'basic'
vcap_service_credentials['USERNAME'] = vcap_service_credentials.get('username')
vcap_service_credentials['PASSWORD'] = vcap_service_credentials.get('password')
elif vcap_service_credentials.get('apikey'): # rc
vcap_service_credentials['auth_type'] = 'iam'
vcap_service_credentials['AUTH_TYPE'] = 'iam'
vcap_service_credentials['APIKEY'] = vcap_service_credentials.get('apikey')
else: # no other auth mechanism is supported
vcap_service_credentials = None
vcap_service_credentials = {}
return vcap_service_credentials

def contruct_authenticator(config):
auth_type = config.get('auth_type').lower() if config.get('auth_type') else 'iam'
def _construct_authenticator(config):
auth_type = config.get('AUTH_TYPE').lower() if config.get('AUTH_TYPE') else 'iam'
authenticator = None
from .authenticators import BasicAuthenticator, BearerTokenAuthenticator, CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator

if auth_type == 'basic':
authenticator = BasicAuthenticator(
username=config.get('username'),
password=config.get('password'))
username=config.get('USERNAME'),
password=config.get('PASSWORD'))
elif auth_type == 'bearertoken':
authenticator = BearerTokenAuthenticator(
bearer_token=config.get('bearer_token'))
bearer_token=config.get('BEARER_TOKEN'))
elif auth_type == 'cp4d':
authenticator = CloudPakForDataAuthenticator(
username=config.get('username'),
password=config.get('password'),
url=config.get('auth_url'),
disable_ssl_verification=config.get('auth_disable_ssl'))
elif auth_type == 'iam' and config.get('apikey'):
username=config.get('USERNAME'),
password=config.get('PASSWORD'),
url=config.get('AUTH_URL'),
disable_ssl_verification=config.get('AUTH_DISABLE_SSL'))
elif auth_type == 'iam' and config.get('APIKEY'):
authenticator = IAMAuthenticator(
apikey=config.get('apikey'),
url=config.get('auth_url'),
client_id=config.get('client_id'),
client_secret=config.get('client_secret'),
disable_ssl_verification=config.get('auth_disable_ssl'))
apikey=config.get('APIKEY'),
url=config.get('AUTH_URL'),
client_id=config.get('CLIENT_ID'),
client_secret=config.get('CLIENT_SECRET'),
disable_ssl_verification=config.get('AUTH_DISABLE_SSL'))
elif auth_type == 'noauth':
authenticator = NoAuthAuthenticator()

Expand Down
6 changes: 4 additions & 2 deletions resources/ibm-credentials-iam.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
WATSON_APIKEY=5678efgh
WATSON_AUTH_TYPE=iam
IBM_WATSON_APIKEY=5678efgh
IBM_WATSON_AUTH_TYPE=iam
IBM_WATSON_URL=https://gateway-s.watsonplatform.net/watson/api
IBM_WATSON_DISABLE_SSL=False
18 changes: 7 additions & 11 deletions test/test_base_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ibm_cloud_sdk_core import ApiException
from ibm_cloud_sdk_core import CP4DTokenManager
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator, NoAuthAuthenticator, Authenticator, BasicAuthenticator, CloudPakForDataAuthenticator
from ibm_cloud_sdk_core import get_authenticator_from_environment


class AnyServiceV1(BaseService):
Expand All @@ -23,8 +24,7 @@ def __init__(self,
self,
service_url=service_url,
authenticator=authenticator,
disable_ssl_verification=disable_ssl_verification,
display_name='Watson')
disable_ssl_verification=disable_ssl_verification)
self.version = version

def op_with_path_params(self, path0, path1):
Expand Down Expand Up @@ -135,17 +135,13 @@ def test_fail_http_config():

@responses.activate
def test_iam():
iam_authenticator = IAMAuthenticator('my_apikey', 'https://iam-test.cloud.ibm.com/identity/token')
file_path = os.path.join(
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env')
os.environ['IBM_CREDENTIALS_FILE'] = file_path
os.environ['WATSON_URL'] = 'https://gateway-s.watsonplatform.net/watson/api'
os.environ['WATSON_DISABLE_SSL'] = 'False'
iam_authenticator = get_authenticator_from_environment('ibm-watson')
service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
assert service.service_url == 'https://gateway-s.watsonplatform.net/watson/api'
assert service.service_url == 'https://gateway.watsonplatform.net/test/api'
del os.environ['IBM_CREDENTIALS_FILE']
del os.environ['WATSON_URL']
del os.environ['WATSON_DISABLE_SSL']
assert service.authenticator is not None

response = {
Expand All @@ -157,12 +153,12 @@ def test_iam():
}
responses.add(
responses.POST,
url='https://iam-test.cloud.ibm.com/identity/token',
url='https://iam.cloud.ibm.com/identity/token',
body=json.dumps(response),
status=200)
responses.add(
responses.GET,
url='https://gateway-s.watsonplatform.net/watson/api',
url='https://gateway.watsonplatform.net/test/api',
body=json.dumps({
"foobar": "baz"
}),
Expand Down Expand Up @@ -422,7 +418,7 @@ def test_json():
assert req.get('data') == "{\"hello\": \"world\"}"

def test_service_url_not_set():
service = BaseService(service_url='', authenticator=NoAuthAuthenticator(), display_name='Watson')
service = BaseService(service_url='', authenticator=NoAuthAuthenticator())
with pytest.raises(ValueError) as err:
service.prepare_request('POST', url='')
assert str(err.value) == 'The service_url is required'
13 changes: 10 additions & 3 deletions test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ def test_datetime_conversion():

def test_get_authenticator_from_credential_file():
file_path = os.path.join(
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env')
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env')
os.environ['IBM_CREDENTIALS_FILE'] = file_path
authenticator = get_authenticator_from_environment('watson')
authenticator = get_authenticator_from_environment('ibm watson')
assert authenticator is not None
assert authenticator.token_manager.apikey == '5678efgh'
del os.environ['IBM_CREDENTIALS_FILE']
Expand Down Expand Up @@ -48,7 +48,7 @@ def test_get_authenticator_from_credential_file():
assert authenticator.bearer_token is not None
del os.environ['IBM_CREDENTIALS_FILE']

def test_get_authenticator_from_env_variabled():
def test_get_authenticator_from_env_variables():
os.environ['TEST_APIKEY'] = '5678efgh'
authenticator = get_authenticator_from_environment('test')
assert authenticator is not None
Expand Down Expand Up @@ -86,3 +86,10 @@ def test_vcap_credentials():
authenticator = get_authenticator_from_environment('test')
assert authenticator is None
del os.environ['VCAP_SERVICES']

def test_multi_word_service_name():
os.environ['PERSONALITY_INSIGHTS_APIKEY'] = '5678efgh'
authenticator = get_authenticator_from_environment('personality-insights')
assert authenticator is not None
assert authenticator.token_manager.apikey == '5678efgh'
del os.environ['PERSONALITY_INSIGHTS_APIKEY']

0 comments on commit a304b1a

Please sign in to comment.