Skip to content

Commit 4ec5501

Browse files
Merge pull request #1152 from allmightyspiff/issues1104
IBMID auth support
2 parents b4e6d4c + edeff99 commit 4ec5501

File tree

10 files changed

+109
-13
lines changed

10 files changed

+109
-13
lines changed

SoftLayer/CLI/account/invoice_detail.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
help="Shows a very detailed list of charges")
1616
@environment.pass_env
1717
def cli(env, identifier, details):
18-
"""Invoices and all that mess"""
18+
"""Invoice details"""
1919

2020
manager = AccountManager(env.client)
2121
top_items = manager.get_billing_items(identifier)

SoftLayer/CLI/account/invoices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
help="Return ALL invoices. There may be a lot of these.")
1919
@environment.pass_env
2020
def cli(env, limit, closed=False, get_all=False):
21-
"""Invoices and all that mess"""
21+
"""List invoices"""
2222

2323
manager = AccountManager(env.client)
2424
invoices = manager.get_invoices(limit, closed, get_all)

SoftLayer/CLI/config/setup.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def get_api_key(client, username, secret):
1919
"""
2020

2121
# Try to use a client with username/api key
22-
if len(secret) == 64:
22+
if len(secret) == 64 or username == 'apikey':
2323
try:
2424
client['Account'].getCurrentUser()
2525
return secret
@@ -40,7 +40,10 @@ def get_api_key(client, username, secret):
4040
@click.command()
4141
@environment.pass_env
4242
def cli(env):
43-
"""Edit configuration."""
43+
"""Setup the ~/.softlayer file with username and apikey.
44+
45+
Set the username to 'apikey' for cloud.ibm.com accounts.
46+
"""
4447

4548
username, secret, endpoint_url, timeout = get_user_input(env)
4649
new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout)

SoftLayer/CLI/hardware/bandwidth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Get details for a hardware device."""
1+
"""GBandwidth data over date range. Bandwidth is listed in GB"""
22
# :license: MIT, see LICENSE for more details.
33

44
import click

SoftLayer/auth.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,17 @@ def __init__(self, username, api_key):
7373

7474
def get_request(self, request):
7575
"""Sets token-based auth headers."""
76-
request.headers['authenticate'] = {
77-
'username': self.username,
78-
'apiKey': self.api_key,
79-
}
76+
77+
# See https://cloud.ibm.com/docs/iam?topic=iam-iamapikeysforservices for why this is the way it is
78+
if self.username == 'apikey':
79+
request.transport_user = self.username
80+
request.transport_password = self.api_key
81+
else:
82+
request.headers['authenticate'] = {
83+
'username': self.username,
84+
'apiKey': self.api_key,
85+
}
86+
8087
return request
8188

8289
def __repr__(self):

SoftLayer/transports.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ def __call__(self, request):
170170
largs = list(request.args)
171171
headers = request.headers
172172

173+
auth = None
174+
if request.transport_user:
175+
auth = requests.auth.HTTPBasicAuth(request.transport_user, request.transport_password)
176+
173177
if request.identifier is not None:
174178
header_name = request.service + 'InitParameters'
175179
headers[header_name] = {'id': request.identifier}
@@ -208,6 +212,7 @@ def __call__(self, request):
208212
try:
209213
resp = self.client.request('POST', request.url,
210214
data=request.payload,
215+
auth=auth,
211216
headers=request.transport_headers,
212217
timeout=self.timeout,
213218
verify=request.verify,
@@ -253,6 +258,7 @@ def print_reproduceable(self, request):
253258
from string import Template
254259
output = Template('''============= testing.py =============
255260
import requests
261+
from requests.auth import HTTPBasicAuth
256262
from requests.adapters import HTTPAdapter
257263
from urllib3.util.retry import Retry
258264
from xml.etree import ElementTree
@@ -261,6 +267,9 @@ def print_reproduceable(self, request):
261267
retry = Retry(connect=3, backoff_factor=3)
262268
adapter = HTTPAdapter(max_retries=retry)
263269
client.mount('https://', adapter)
270+
# This is only needed if you are using an cloud.ibm.com api key
271+
#auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY)
272+
auth=None
264273
url = '$url'
265274
payload = """$payload"""
266275
transport_headers = $transport_headers
@@ -269,7 +278,7 @@ def print_reproduceable(self, request):
269278
cert = $cert
270279
proxy = $proxy
271280
response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout,
272-
verify=verify, cert=cert, proxies=proxy)
281+
verify=verify, cert=cert, proxies=proxy, auth=auth)
273282
xml = ElementTree.fromstring(response.content)
274283
ElementTree.dump(xml)
275284
==========================''')

docs/cli.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ To check the configuration, you can use `slcli config show`.
4848
:..............:..................................................................:
4949

5050

51+
If you are using an account created from the https://cloud.ibm.com portal, your username will be literally `apikey`, and use the key provided. `How to create an IBM apikey <https://cloud.ibm.com/docs/iam?topic=iam-userapikey#create_user_key>`_
52+
5153
To see more about the config file format, see :ref:`config_file`.
5254

5355
.. _usage-examples:

docs/cli/config.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.. _cli_config:
2+
3+
Config
4+
========
5+
6+
`Creating an IBMID apikey <https://cloud.ibm.com/docs/iam?topic=iam-userapikey#create_user_key>`_
7+
`IBMid for services <https://cloud.ibm.com/docs/iam?topic=iam-iamapikeysforservices>`_
8+
9+
`Creating a SoftLayer apikey <https://cloud.ibm.com/docs/customer-portal?topic=customer-portal-customerportal_api>`_
10+
11+
.. click:: SoftLayer.CLI.config.setup:cli
12+
:prog: config setup
13+
:show-nested:
14+
15+
16+
.. click:: SoftLayer.CLI.config.show:cli
17+
:prog: config show
18+
:show-nested:

docs/config_file.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ automatically by the `slcli setup` command detailed here:
2525
api_key = oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha
2626
endpoint_url = https://api.softlayer.com/xmlrpc/v3/
2727
timeout = 40
28+
29+
30+
*Cloud.ibm.com Config Example*
31+
::
32+
33+
[softlayer]
34+
username = apikey
35+
api_key = 123cNyhzg45Ab6789ADyzwR_2LAagNVbySgY73tAQOz1
36+
endpoint_url = https://api.softlayer.com/rest/v3.1/
37+
timeout = 40

tests/transport_tests.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ def test_call(self, request):
7878
data=data,
7979
timeout=None,
8080
cert=None,
81-
verify=True)
81+
verify=True,
82+
auth=None)
8283
self.assertEqual(resp, [])
8384
self.assertIsInstance(resp, transports.SoftLayerListResult)
8485
self.assertEqual(resp.total_count, 10)
@@ -114,7 +115,8 @@ def test_valid_proxy(self, request):
114115
headers=mock.ANY,
115116
timeout=None,
116117
cert=None,
117-
verify=True)
118+
verify=True,
119+
auth=None)
118120

119121
@mock.patch('SoftLayer.transports.requests.Session.request')
120122
def test_identifier(self, request):
@@ -264,6 +266,50 @@ def test_print_reproduceable(self):
264266
output_text = self.transport.print_reproduceable(req)
265267
self.assertIn("https://test.com", output_text)
266268

269+
@mock.patch('SoftLayer.transports.requests.Session.request')
270+
@mock.patch('requests.auth.HTTPBasicAuth')
271+
def test_ibm_id_call(self, auth, request):
272+
request.return_value = self.response
273+
274+
data = '''<?xml version='1.0'?>
275+
<methodCall>
276+
<methodName>getObject</methodName>
277+
<params>
278+
<param>
279+
<value><struct>
280+
<member>
281+
<name>headers</name>
282+
<value><struct>
283+
</struct></value>
284+
</member>
285+
</struct></value>
286+
</param>
287+
</params>
288+
</methodCall>
289+
'''
290+
291+
req = transports.Request()
292+
req.service = 'SoftLayer_Service'
293+
req.method = 'getObject'
294+
req.transport_user = 'apikey'
295+
req.transport_password = '1234567890qweasdzxc'
296+
resp = self.transport(req)
297+
298+
auth.assert_called_with('apikey', '1234567890qweasdzxc')
299+
request.assert_called_with('POST',
300+
'http://something.com/SoftLayer_Service',
301+
headers={'Content-Type': 'application/xml',
302+
'User-Agent': consts.USER_AGENT},
303+
proxies=None,
304+
data=data,
305+
timeout=None,
306+
cert=None,
307+
verify=True,
308+
auth=mock.ANY)
309+
self.assertEqual(resp, [])
310+
self.assertIsInstance(resp, transports.SoftLayerListResult)
311+
self.assertEqual(resp.total_count, 10)
312+
267313

268314
@mock.patch('SoftLayer.transports.requests.Session.request')
269315
@pytest.mark.parametrize(
@@ -311,7 +357,8 @@ def test_verify(request,
311357
cert=mock.ANY,
312358
proxies=mock.ANY,
313359
timeout=mock.ANY,
314-
verify=expected)
360+
verify=expected,
361+
auth=None)
315362

316363

317364
class TestRestAPICall(testing.TestCase):

0 commit comments

Comments
 (0)