Skip to content

Commit 18255e9

Browse files
committed
add apikey/securitykey in cloud-cli
1 parent 8d4c720 commit 18255e9

1 file changed

Lines changed: 151 additions & 107 deletions

File tree

cloud-cli/cloudapis/cloud.py

Lines changed: 151 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -26,122 +26,166 @@
2626
import os
2727
import xml.dom.minidom
2828
import re
29+
import base64
30+
import hmac
31+
import hashlib
32+
import httplib
2933

3034
class CloudAPI:
3135

32-
@describe("server", "Management Server host name or address")
33-
@describe("responseformat", "Response format: xml or json")
34-
@describe("stripxml", "True if xml tags have to be stripped in the output, false otherwise")
35-
def __init__(self,
36-
server="127.0.0.1:8096",
37-
responseformat="xml",
38-
stripxml="true"
39-
):
40-
self.__dict__.update(locals())
36+
@describe("server", "Management Server host name or address")
37+
@describe("apikey", "Management Server apiKey")
38+
@describe("securitykey", "Management Server securityKey")
39+
@describe("responseformat", "Response format: xml or json")
40+
@describe("stripxml", "True if xml tags have to be stripped in the output, false otherwise")
41+
def __init__(self,
42+
server="127.0.0.1:8096",
43+
responseformat="xml",
44+
stripxml="true",
45+
apiKey=None,
46+
securityKey=None
47+
):
48+
self.__dict__.update(locals())
4149

42-
def _make_request(self,command,parameters=None):
43-
'''Command is a string, parameters is a dictionary'''
44-
if ":" in self.server:
45-
host,port = self.server.split(":")
46-
port = int(port)
47-
else:
48-
host = self.server
49-
port = 8096
50-
51-
url = "http://" + self.server + "/?"
52-
53-
if not parameters: parameters = {}
54-
parameters["command"] = command
55-
parameters["response"] = self.responseformat
56-
querystring = urllib.urlencode(parameters)
57-
url += querystring
58-
59-
f = urllib2.urlopen(url)
60-
data = f.read()
61-
if self.stripxml == "true":
62-
data=re.sub("<\?.*\?>", "\n", data);
63-
data=re.sub("</[a-z]*>", "\n", data);
64-
data=data.replace(">", "=");
65-
data=data.replace("=<", "\n");
66-
data=data.replace("\n<", "\n");
67-
data=re.sub("\n.*cloud-stack-version=.*", "", data);
68-
data=data.replace("\n\n\n", "\n");
69-
else:
70-
data="\n"+data+"\n"
71-
return data
72-
73-
return data
50+
def _make_request_with_keys(self,command,requests={}):
51+
requests["command"] = command
52+
requests["apiKey"] = self.apiKey
53+
requests["response"] = "xml"
54+
requests = zip(requests.keys(), requests.values())
55+
requests.sort(key=lambda x: str.lower(x[0]))
56+
57+
requestUrl = "&".join(["=".join([request[0], urllib.quote_plus(str(request[1]))]) for request in requests])
58+
hashStr = "&".join(["=".join([str.lower(request[0]), urllib.quote_plus(str.lower(str(request[1])))]) for request in requests])
59+
60+
sig = urllib.quote_plus(base64.encodestring(hmac.new(self.securityKey, hashStr, hashlib.sha1).digest()).strip())
61+
62+
requestUrl += "&signature=%s"%sig
63+
return requestUrl
64+
65+
66+
def _make_request_with_auth(self, command, requests):
67+
self.connection = httplib.HTTPConnection("%s"%(self.server))
68+
requests["command"] = command
69+
requests["apiKey"] = self.apiKey
70+
requests["response"] = self.responseformat
71+
requests = zip(requests.keys(), requests.values())
72+
requests.sort(key=lambda x: str.lower(x[0]))
73+
74+
requestUrl = "&".join(["=".join([request[0], urllib.quote_plus(str(request[1]))]) for request in requests])
75+
hashStr = "&".join(["=".join([str.lower(request[0]), urllib.quote_plus(str.lower(str(request[1])))]) for request in requests])
76+
77+
sig = urllib.quote_plus(base64.encodestring(hmac.new(self.securityKey, hashStr, hashlib.sha1).digest()).strip())
78+
79+
requestUrl += "&signature=%s"%sig
80+
81+
self.connection.request("GET", "/client/api?%s"%requestUrl)
82+
return self.connection.getresponse().read()
83+
84+
def _make_request(self,command,parameters=None):
85+
86+
'''Command is a string, parameters is a dictionary'''
87+
if ":" in self.server:
88+
host,port = self.server.split(":")
89+
port = int(port)
90+
else:
91+
host = self.server
92+
port = 8096
93+
94+
url = "http://" + self.server + "/?"
95+
96+
if not parameters: parameters = {}
97+
if self.apiKey is not None and self.securityKey is not None:
98+
return self._make_request_with_auth(command, parameters)
99+
else:
100+
parameters["command"] = command
101+
parameters["response"] = self.responseformat
102+
querystring = urllib.urlencode(parameters)
103+
104+
url += querystring
105+
106+
f = urllib2.urlopen(url)
107+
data = f.read()
108+
if self.stripxml == "true":
109+
data=re.sub("<\?.*\?>", "\n", data);
110+
data=re.sub("</[a-z]*>", "\n", data);
111+
data=data.replace(">", "=");
112+
data=data.replace("=<", "\n");
113+
data=data.replace("\n<", "\n");
114+
data=re.sub("\n.*cloud-stack-version=.*", "", data);
115+
data=data.replace("\n\n\n", "\n");
116+
117+
return data
74118

75119

76120
def load_dynamic_methods():
77-
'''creates smart function objects for every method in the commands.xml file'''
78-
79-
def getText(nodelist):
80-
rc = []
81-
for node in nodelist:
82-
if node.nodeType == node.TEXT_NODE: rc.append(node.data)
83-
return ''.join(rc)
84-
85-
# FIXME figure out installation and packaging
86-
xmlfile = os.path.join("/etc/cloud/cli/","commands.xml")
87-
dom = xml.dom.minidom.parse(xmlfile)
88-
89-
for cmd in dom.getElementsByTagName("command"):
90-
name = getText(cmd.getElementsByTagName('name')[0].childNodes).strip()
91-
assert name
92-
93-
description = getText(cmd.getElementsByTagName('description')[0].childNodes).strip()
94-
if description:
121+
'''creates smart function objects for every method in the commands.xml file'''
122+
123+
def getText(nodelist):
124+
rc = []
125+
for node in nodelist:
126+
if node.nodeType == node.TEXT_NODE: rc.append(node.data)
127+
return ''.join(rc)
128+
129+
# FIXME figure out installation and packaging
130+
xmlfile = os.path.join("/etc/cloud/cli/","commands.xml")
131+
dom = xml.dom.minidom.parse(xmlfile)
132+
133+
for cmd in dom.getElementsByTagName("command"):
134+
name = getText(cmd.getElementsByTagName('name')[0].childNodes).strip()
135+
assert name
136+
137+
description = getText(cmd.getElementsByTagName('description')[0].childNodes).strip()
138+
if description:
95139
description = '"""%s"""' % description
96-
else: description = ''
97-
arguments = []
98-
options = []
99-
descriptions = []
100-
101-
for param in cmd.getElementsByTagName("request")[0].getElementsByTagName("arg"):
102-
argname = getText(param.getElementsByTagName('name')[0].childNodes).strip()
103-
assert argname
104-
105-
required = getText(param.getElementsByTagName('required')[0].childNodes).strip()
106-
if required == 'true': required = True
107-
elif required == 'false': required = False
108-
else: raise AssertionError, "Not reached"
109-
if required: arguments.append(argname)
110-
options.append(argname)
111-
140+
else: description = ''
141+
arguments = []
142+
options = []
143+
descriptions = []
144+
145+
for param in cmd.getElementsByTagName("request")[0].getElementsByTagName("arg"):
146+
argname = getText(param.getElementsByTagName('name')[0].childNodes).strip()
147+
assert argname
148+
149+
required = getText(param.getElementsByTagName('required')[0].childNodes).strip()
150+
if required == 'true': required = True
151+
elif required == 'false': required = False
152+
else: raise AssertionError, "Not reached"
153+
if required: arguments.append(argname)
154+
options.append(argname)
155+
112156
#import ipdb; ipdb.set_trace()
113-
requestDescription = param.getElementsByTagName('description')
114-
if requestDescription:
115-
descriptionParam = getText(requestDescription[0].childNodes)
116-
else:
117-
descriptionParam = ''
118-
if descriptionParam: descriptions.append( (argname,descriptionParam) )
119-
120-
funcparams = ["self"] + [ "%s=None"%o for o in options ]
121-
funcparams = ", ".join(funcparams)
122-
123-
code = """
124-
def %s(%s):
125-
%s
126-
parms = locals()
127-
del parms["self"]
128-
for arg in %r:
129-
if locals()[arg] is None:
130-
raise TypeError, "%%s is a required option"%%arg
131-
for k,v in parms.items():
132-
if v is None: del parms[k]
133-
output = self._make_request("%s",parms)
134-
return output
135-
"""%(name,funcparams,description,arguments,name)
136-
137-
namespace = {}
138-
exec code.strip() in namespace
139-
140-
func = namespace[name]
141-
for argname,description in descriptions:
142-
func = describe(argname,description)(func)
143-
144-
yield (name,func)
157+
requestDescription = param.getElementsByTagName('description')
158+
if requestDescription:
159+
descriptionParam = getText(requestDescription[0].childNodes)
160+
else:
161+
descriptionParam = ''
162+
if descriptionParam: descriptions.append( (argname,descriptionParam) )
163+
164+
funcparams = ["self"] + [ "%s=None"%o for o in options ]
165+
funcparams = ", ".join(funcparams)
166+
167+
code = """
168+
def %s(%s):
169+
%s
170+
parms = locals()
171+
del parms["self"]
172+
for arg in %r:
173+
if locals()[arg] is None:
174+
raise TypeError, "%%s is a required option"%%arg
175+
for k,v in parms.items():
176+
if v is None: del parms[k]
177+
output = self._make_request("%s",parms)
178+
return output
179+
"""%(name,funcparams,description,arguments,name)
180+
181+
namespace = {}
182+
exec code.strip() in namespace
183+
184+
func = namespace[name]
185+
for argname,description in descriptions:
186+
func = describe(argname,description)(func)
187+
188+
yield (name,func)
145189

146190

147191
for name,meth in load_dynamic_methods():

0 commit comments

Comments
 (0)