Skip to content

Commit 62f2457

Browse files
added curl reproduceable
1 parent 14eaebc commit 62f2457

File tree

2 files changed

+76
-24
lines changed

2 files changed

+76
-24
lines changed

SoftLayer/CLI/core.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ def output_diagnostics(env, verbose=0, **kwargs):
149149

150150
env.err(env.fmt(diagnostic_table))
151151

152+
if verbose > 1:
153+
for call in env.client.transport.get_last_calls():
154+
call_table = formatting.Table(['','{}::{}'.format(call.service, call.method)])
155+
nice_mask = ''
156+
if call.mask is not None:
157+
nice_mask = call.mask
158+
159+
call_table.add_row(['id', call.identifier])
160+
call_table.add_row(['mask', call.mask])
161+
call_table.add_row(['filter', call.filter])
162+
call_table.add_row(['limit', call.limit])
163+
call_table.add_row(['offset', call.offset])
164+
env.err(env.fmt(call_table))
165+
166+
if verbose > 2:
167+
for call in env.client.transport.get_last_calls():
168+
env.err(env.client.transport.print_reproduceable(call))
169+
152170

153171
def main(reraise_exceptions=False, **kwargs):
154172
"""Main program. Catches several common errors and displays them nicely."""

SoftLayer/transports.py

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ def __init__(self):
120120
#: Exception any exceptions that got caught
121121
self.exception = None
122122

123+
#: String Url parameters used in the Rest transport
124+
self.params = None
125+
123126

124127
class SoftLayerListResult(list):
125128
"""A SoftLayer API list result."""
@@ -165,8 +168,9 @@ def __call__(self, request):
165168
headers[header_name] = {'id': request.identifier}
166169

167170
if request.mask is not None:
168-
headers.update(_format_object_mask_xmlrpc(request.mask,
169-
request.service))
171+
request.mask = _format_object_mask(request.mask)
172+
headers.update(_format_object_mask_xmlrpc(request.mask, request.service))
173+
170174

171175
if request.filter is not None:
172176
headers['%sObjectFilter' % request.service] = request.filter
@@ -261,7 +265,9 @@ def print_reproduceable(self, request):
261265
ElementTree.dump(xml)
262266
==========================''')
263267

268+
264269
safe_payload = re.sub(r'<string>[a-z0-9]{64}</string>', r'<string>API_KEY_GOES_HERE</string>', request.payload)
270+
safe_payload = re.sub(r'(\s+)', r' ', safe_payload)
265271
substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers,
266272
timeout=self.timeout, verify=request.verify, cert=request.cert, proxy=_proxies_dict(self.proxy))
267273
return output.substitute(substitutions)
@@ -301,7 +307,8 @@ def __call__(self, request):
301307
"""
302308
params = request.headers.copy()
303309
if request.mask:
304-
params['objectMask'] = _format_object_mask(request.mask)
310+
request.mask = _format_object_mask(request.mask)
311+
params['objectMask'] = request.mask
305312

306313
if request.limit:
307314
params['limit'] = request.limit
@@ -312,6 +319,8 @@ def __call__(self, request):
312319
if request.filter:
313320
params['objectFilter'] = json.dumps(request.filter)
314321

322+
request.params = params
323+
315324
auth = None
316325
if request.transport_user:
317326
auth = requests.auth.HTTPBasicAuth(
@@ -331,9 +340,9 @@ def __call__(self, request):
331340
method = 'POST'
332341
body['parameters'] = request.args
333342

334-
raw_body = None
343+
335344
if body:
336-
raw_body = json.dumps(body)
345+
request.payload = json.dumps(body)
337346

338347
url_parts = [self.endpoint_url, request.service]
339348
if request.identifier is not None:
@@ -342,32 +351,29 @@ def __call__(self, request):
342351
if request.method is not None:
343352
url_parts.append(request.method)
344353

345-
url = '%s.%s' % ('/'.join(url_parts), 'json')
354+
request.url = '%s.%s' % ('/'.join(url_parts), 'json')
346355

347356
# Prefer the request setting, if it's not None
348-
verify = request.verify
349-
if verify is None:
350-
verify = self.verify
351357

352-
LOGGER.debug("=== REQUEST ===")
353-
LOGGER.debug(url)
354-
LOGGER.debug(request.transport_headers)
355-
LOGGER.debug(raw_body)
358+
if request.verify is None:
359+
request.verify = self.verify
360+
356361
try:
357-
resp = self.client.request(method, url,
362+
resp = self.client.request(method, request.url,
358363
auth=auth,
359364
headers=request.transport_headers,
360-
params=params,
361-
data=raw_body,
365+
params=request.params,
366+
data=request.payload,
362367
timeout=self.timeout,
363-
verify=verify,
368+
verify=request.verify,
364369
cert=request.cert,
365370
proxies=_proxies_dict(self.proxy))
366-
LOGGER.debug("=== RESPONSE ===")
367-
LOGGER.debug(resp.headers)
368-
LOGGER.debug(resp.text)
371+
372+
request.url = resp.url
373+
369374
resp.raise_for_status()
370375
result = json.loads(resp.text)
376+
request.result = result
371377

372378
if isinstance(result, list):
373379
return SoftLayerListResult(
@@ -376,11 +382,36 @@ def __call__(self, request):
376382
return result
377383
except requests.HTTPError as ex:
378384
message = json.loads(ex.response.text)['error']
379-
raise exceptions.SoftLayerAPIError(ex.response.status_code,
380-
message)
385+
request.url = ex.response.url
386+
raise exceptions.SoftLayerAPIError(ex.response.status_code, message)
381387
except requests.RequestException as ex:
388+
request.url = ex.response.url
382389
raise exceptions.TransportError(0, str(ex))
383390

391+
def print_reproduceable(self, request):
392+
"""Prints out the minimal python code to reproduce a specific request
393+
394+
The will also automatically replace the API key so its not accidently exposed.
395+
396+
:param request request: Request object
397+
"""
398+
command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'"
399+
400+
method = REST_SPECIAL_METHODS.get(request.method)
401+
402+
if method is None:
403+
method = 'GET'
404+
if request.args:
405+
method = 'POST'
406+
407+
data = ''
408+
if request.payload is not None:
409+
data = "-d '{}'".format(request.payload)
410+
411+
headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()]
412+
headers = " -H ".join(headers)
413+
return command.format(method=method, headers=headers, data=data, uri=request.url)
414+
384415

385416
class DebugTransport(object):
386417
"""Transport that records API call timings."""
@@ -414,11 +445,14 @@ def pre_transport_log(self, call):
414445
LOGGER.warning("Calling: {}::{}(id={})".format(call.service, call.method, call.identifier))
415446

416447
def post_transport_log(self, call):
417-
LOGGER.debug(self.transport.print_reproduceable(call))
448+
LOGGER.debug("Returned Data: \n{}".format(call.result))
418449

419450
def get_last_calls(self):
420451
return self.requests
421452

453+
def print_reproduceable(self, call):
454+
return self.transport.print_reproduceable(call)
455+
422456
class TimingTransport(object):
423457
"""Transport that records API call timings."""
424458

@@ -480,7 +514,6 @@ def _format_object_mask_xmlrpc(objectmask, service):
480514
mheader = '%sObjectMask' % service
481515
else:
482516
mheader = 'SoftLayer_ObjectMask'
483-
objectmask = _format_object_mask(objectmask)
484517

485518
return {mheader: {'mask': objectmask}}
486519

@@ -495,6 +528,7 @@ def _format_object_mask(objectmask):
495528
496529
"""
497530
objectmask = objectmask.strip()
531+
objectmask = re.sub(r'(\s+)', r' ', objectmask)
498532
if (not objectmask.startswith('mask') and
499533
not objectmask.startswith('[')):
500534
objectmask = "mask[%s]" % objectmask

0 commit comments

Comments
 (0)