1+ """
2+ SoftLayer.transports.rest
3+ ~~~~~~~~~~~~~~~~~~~~
4+ REST Style transport library
5+
6+ :license: MIT, see LICENSE for more details.
7+ """
8+
9+ import json
10+ import logging
11+ import requests
12+
13+ from SoftLayer import consts
14+ from SoftLayer import exceptions
15+
16+ from .transport import _format_object_mask
17+ from .transport import _proxies_dict
18+ from .transport import ComplexEncoder
19+ from .transport import get_session
20+ from .transport import SoftLayerListResult
21+
22+ REST_SPECIAL_METHODS = {
23+ # 'deleteObject': 'DELETE',
24+ 'createObject' : 'POST' ,
25+ 'createObjects' : 'POST' ,
26+ 'editObject' : 'PUT' ,
27+ 'editObjects' : 'PUT' ,
28+ }
29+
30+
31+ class RestTransport (object ):
32+ """REST transport.
33+
34+ REST calls should mostly work, but is not fully tested.
35+ XML-RPC should be used when in doubt
36+ """
37+
38+ def __init__ (self , endpoint_url = None , timeout = None , proxy = None , user_agent = None , verify = True ):
39+
40+ self .endpoint_url = (endpoint_url or consts .API_PUBLIC_ENDPOINT_REST ).rstrip ('/' )
41+ self .timeout = timeout or None
42+ self .proxy = proxy
43+ self .user_agent = user_agent or consts .USER_AGENT
44+ self .verify = verify
45+ self ._client = None
46+ self .logger = logging .getLogger (__name__ )
47+
48+ @property
49+ def client (self ):
50+ """Returns client session object"""
51+
52+ if self ._client is None :
53+ self ._client = get_session (self .user_agent )
54+ return self ._client
55+
56+ def __call__ (self , request ):
57+ """Makes a SoftLayer API call against the REST endpoint.
58+
59+ REST calls should mostly work, but is not fully tested.
60+ XML-RPC should be used when in doubt
61+
62+ :param request request: Request object
63+ """
64+ params = request .headers .copy ()
65+ if request .mask :
66+ request .mask = _format_object_mask (request .mask )
67+ params ['objectMask' ] = request .mask
68+
69+ if request .limit or request .offset :
70+ limit = request .limit or 0
71+ offset = request .offset or 0
72+ params ['resultLimit' ] = "%d,%d" % (offset , limit )
73+
74+ if request .filter :
75+ params ['objectFilter' ] = json .dumps (request .filter )
76+
77+ request .params = params
78+
79+ auth = None
80+ if request .transport_user :
81+ auth = requests .auth .HTTPBasicAuth (
82+ request .transport_user ,
83+ request .transport_password ,
84+ )
85+
86+ method = REST_SPECIAL_METHODS .get (request .method )
87+
88+ if method is None :
89+ method = 'GET'
90+
91+ body = {}
92+ if request .args :
93+ # NOTE(kmcdonald): force POST when there are arguments because
94+ # the request body is ignored otherwise.
95+ method = 'POST'
96+ body ['parameters' ] = request .args
97+
98+ if body :
99+ request .payload = json .dumps (body , cls = ComplexEncoder )
100+
101+ url_parts = [self .endpoint_url , request .service ]
102+ if request .identifier is not None :
103+ url_parts .append (str (request .identifier ))
104+
105+ if request .method is not None :
106+ url_parts .append (request .method )
107+
108+ request .url = '%s.%s' % ('/' .join (url_parts ), 'json' )
109+
110+ # Prefer the request setting, if it's not None
111+
112+ if request .verify is None :
113+ request .verify = self .verify
114+
115+ try :
116+ resp = self .client .request (method , request .url ,
117+ auth = auth ,
118+ headers = request .transport_headers ,
119+ params = request .params ,
120+ data = request .payload ,
121+ timeout = self .timeout ,
122+ verify = request .verify ,
123+ cert = request .cert ,
124+ proxies = _proxies_dict (self .proxy ))
125+
126+ request .url = resp .url
127+
128+ resp .raise_for_status ()
129+
130+ if resp .text != "" :
131+ try :
132+ result = json .loads (resp .text )
133+ except ValueError as json_ex :
134+ self .logger .warning (json_ex )
135+ raise exceptions .SoftLayerAPIError (resp .status_code , str (resp .text ))
136+ else :
137+ raise exceptions .SoftLayerAPIError (resp .status_code , "Empty response." )
138+
139+ request .result = result
140+
141+ if isinstance (result , list ):
142+ return SoftLayerListResult (
143+ result , int (resp .headers .get ('softlayer-total-items' , 0 )))
144+ else :
145+ return result
146+ except requests .HTTPError as ex :
147+ try :
148+ message = json .loads (ex .response .text )['error' ]
149+ request .url = ex .response .url
150+ except ValueError as json_ex :
151+ if ex .response .text == "" :
152+ raise exceptions .SoftLayerAPIError (resp .status_code , "Empty response." )
153+ self .logger .warning (json_ex )
154+ raise exceptions .SoftLayerAPIError (resp .status_code , ex .response .text )
155+
156+ raise exceptions .SoftLayerAPIError (ex .response .status_code , message )
157+ except requests .RequestException as ex :
158+ raise exceptions .TransportError (0 , str (ex ))
159+
160+ def print_reproduceable (self , request ):
161+ """Prints out the minimal python code to reproduce a specific request
162+
163+ The will also automatically replace the API key so its not accidently exposed.
164+
165+ :param request request: Request object
166+ """
167+ command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'"
168+
169+ method = REST_SPECIAL_METHODS .get (request .method )
170+
171+ if method is None :
172+ method = 'GET'
173+ if request .args :
174+ method = 'POST'
175+
176+ data = ''
177+ if request .payload is not None :
178+ data = "-d '{}'" .format (request .payload )
179+
180+ headers = ['"{0}: {1}"' .format (k , v ) for k , v in request .transport_headers .items ()]
181+ headers = " -H " .join (headers )
182+ return command .format (method = method , headers = headers , data = data , uri = request .url )
0 commit comments