|
26 | 26 | import os |
27 | 27 | import xml.dom.minidom |
28 | 28 | import re |
| 29 | +import base64 |
| 30 | +import hmac |
| 31 | +import hashlib |
| 32 | +import httplib |
29 | 33 |
|
30 | 34 | class CloudAPI: |
31 | 35 |
|
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()) |
41 | 49 |
|
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 |
74 | 118 |
|
75 | 119 |
|
76 | 120 | 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: |
95 | 139 | 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 | + |
112 | 156 | #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) |
145 | 189 |
|
146 | 190 |
|
147 | 191 | for name,meth in load_dynamic_methods(): |
|
0 commit comments