Codebase list python-cs / 4a124ad
New upstream release. Vincent Bernat 6 years ago
8 changed file(s) with 529 addition(s) and 316 deletion(s). Raw diff Collapse all Expand all
11 dist
22 .tox
33 build
4 *.pyc
77 A simple, yet powerful CloudStack API client for python and the command-line.
88
99 * Python 2.6+ and 3.3+ support.
10 * Async support for Python 3.5+
1011 * All present and future CloudStack API calls and parameters are supported.
1112 * Syntax highlight in the command-line client if Pygments is installed.
1213 * BSD license.
2122 Usage
2223 -----
2324
24 In Python::
25 In Python:
26
27 .. code-block:: python
2528
2629 from cs import CloudStack
2730
8184 * A ``cloudstack.ini`` file in the current working directory,
8285 * A ``.cloudstack.ini`` file in the home directory.
8386
84 To use that configuration scheme from your Python code::
87 To use that configuration scheme from your Python code:
88
89 .. code-block:: python
8590
8691 from cs import CloudStack, read_config
8792
132137
133138 cs.listVirtualMachines(fetch_list=True)
134139
140 Async client
141 ------------
142
143 ``cs`` provides the ``AIOCloudStack`` class for async/await calls in Python
144 3.5+.
145
146 .. code-block:: python
147
148 from cs import AIOCloudStack, read_config
149
150 cs = AIOCloudStack(**read_config())
151 vms = await cs.listVirtualMachines()
152
153 By default, this client polls CloudStack's async jobs to return actual results
154 for commands that result in an async job being created. You can customize this
155 behavior with ``job_timeout`` (default: None -- wait indefinitely) and
156 ``poll_interval`` (default: 2s).
157
158 .. code-block:: python
159
160 cs = AIOCloudStack(**read_config(), job_timeout=300, poll_interval=5)
161
162 Async deployment of multiple vms
163 ________________________________
164
165 .. code-block:: python
166
167 import asyncio
168 from cs import AIOCloudStack, read_config
169
170 cs = AIOCloudStack(**read_config())
171 tasks = [asyncio.ensure_future(cs.deployVirtualMachine(zoneid='',
172 serviceofferingid='',
173 templateid='')) for _ in range(5)]
174 results = []
175 done, pending = await asyncio.wait(tasks)
176 exceptions = 0
177 last_exception = None
178 for t in done:
179 if t.exception():
180 exceptions += 1
181 last_exception = t.exception()
182 elif t.result():
183 results.append(t.result())
184 if exceptions:
185 print(f"{exceptions} deployment(s) failed")
186 raise last_exception
187
188 # Destroy all of them, but skip waiting on the job results
189 tasks = [cs.destroyVirtualMachine(id=vm['id'], fetch_result=False)
190 for vm in results]
191 await asyncio.wait(tasks)
192
135193 Links
136194 -----
137195
0 import argparse
1 import json
2 import os
3 import sys
4 import time
5 from collections import defaultdict
6
7 try:
8 from configparser import NoSectionError
9 except ImportError: # python 2
10 from ConfigParser import NoSectionError
11
12 try:
13 import pygments
14 from pygments.lexers import JsonLexer
15 from pygments.formatters import TerminalFormatter
16 except ImportError:
17 pygments = None
18
19 from .client import read_config, CloudStack, CloudStackException # noqa
20
21
22 __all__ = ['read_config', 'CloudStack', 'CloudStackException']
23
24 if sys.version_info >= (3, 5):
25 try:
26 import aiohttp # noqa
27 except ImportError:
28 pass
29 else:
30 from ._async import AIOCloudStack # noqa
31 __all__.append('AIOCloudStack')
32
33
34 def main():
35 parser = argparse.ArgumentParser(description='Cloustack client.')
36 parser.add_argument('--region', metavar='REGION',
37 help='Cloudstack region in ~/.cloudstack.ini',
38 default=os.environ.get('CLOUDSTACK_REGION',
39 'cloudstack'))
40 parser.add_argument('--post', action='store_true', default=False,
41 help='use POST instead of GET')
42 parser.add_argument('--async', action='store_true', default=False,
43 help='do not wait for async result')
44 parser.add_argument('--quiet', '-q', action='store_true', default=False,
45 help='do not display additional status messages')
46 parser.add_argument('command', metavar="COMMAND",
47 help='Cloudstack API command to execute')
48
49 def parse_option(x):
50 if '=' not in x:
51 raise ValueError("{!r} is not a correctly formatted "
52 "option".format(x))
53 return x.split('=', 1)
54
55 parser.add_argument('arguments', metavar="OPTION=VALUE",
56 nargs='*', type=parse_option,
57 help='Cloudstack API argument')
58
59 options = parser.parse_args()
60 command = options.command
61 kwargs = defaultdict(set)
62 for arg in options.arguments:
63 key, value = arg
64 kwargs[key].add(value.strip(" \"'"))
65
66 try:
67 config = read_config(ini_group=options.region)
68 except NoSectionError:
69 raise SystemExit("Error: region '%s' not in config" % options.region)
70
71 if options.post:
72 config['method'] = 'post'
73 cs = CloudStack(**config)
74 ok = True
75 try:
76 response = getattr(cs, command)(**kwargs)
77 except CloudStackException as e:
78 response = e.args[1]
79 if not options.quiet:
80 sys.stderr.write("Cloudstack error: HTTP response "
81 "{0}\n".format(response.status_code))
82 sys.stderr.write(response.text)
83 sys.exit(1)
84
85 if 'Async' not in command and 'jobid' in response and not options.async:
86 if not options.quiet:
87 sys.stderr.write("Polling result... ^C to abort\n")
88 while True:
89 try:
90 res = cs.queryAsyncJobResult(**response)
91 if res['jobstatus'] != 0:
92 response = res
93 if res['jobresultcode'] != 0:
94 ok = False
95 break
96 time.sleep(3)
97 except KeyboardInterrupt:
98 if not options.quiet:
99 sys.stderr.write("Result not ready yet.\n")
100 break
101
102 data = json.dumps(response, indent=2, sort_keys=True)
103
104 if pygments and sys.stdout.isatty():
105 data = pygments.highlight(data, JsonLexer(), TerminalFormatter())
106 sys.stdout.write(data)
107 sys.stdout.write('\n')
108 sys.exit(int(not ok))
0 from . import main
1
2
3 if __name__ == '__main__':
4 main()
0 import asyncio
1 import ssl
2
3 import aiohttp
4
5 from . import CloudStack, CloudStackException
6 from .client import transform
7
8
9 class AIOCloudStack(CloudStack):
10 def __init__(self, job_timeout=None, poll_interval=2.0,
11 *args, **kwargs):
12 super().__init__(*args, **kwargs)
13 self.job_timeout = job_timeout
14 self.poll_interval = poll_interval
15
16 def __getattr__(self, command):
17 async def handler(**kwargs):
18 return (await self._request(command, **kwargs))
19 return handler
20
21 async def _request(self, command, json=True, opcode_name='command',
22 fetch_list=False, fetch_result=True, **kwargs):
23 kwarg, kwargs = self._prepare_request(command, json, opcode_name,
24 fetch_list, **kwargs)
25
26 ssl_context = None
27 if self.cert:
28 ssl_context = ssl.create_default_context(cafile=self.cert)
29 connector = aiohttp.TCPConnector(verify_ssl=self.verify,
30 ssl_context=ssl_context)
31
32 async with aiohttp.ClientSession(read_timeout=self.timeout,
33 conn_timeout=self.timeout,
34 connector=connector) as session:
35 handler = getattr(session, self.method)
36
37 done = False
38 final_data = []
39 page = 1
40 while not done:
41 if fetch_list:
42 kwargs['page'] = page
43
44 kwargs = transform(kwargs)
45 kwargs.pop('signature', None)
46 kwargs['signature'] = self._sign(kwargs)
47 response = await handler(self.endpoint, **{kwarg: kwargs})
48
49 ctype = response.headers['content-type'].split(';')[0]
50 try:
51 data = await response.json(content_type=ctype)
52 except ValueError as e:
53 msg = "Make sure endpoint URL {!r} is correct.".format(
54 self.endpoint)
55 raise CloudStackException(
56 "HTTP {0} response from CloudStack".format(
57 response.status),
58 response,
59 "{}. {}".format(e, msg))
60
61 [key] = data.keys()
62 data = data[key]
63 if response.status != 200:
64 raise CloudStackException(
65 "HTTP {0} response from CloudStack".format(
66 response.status), response, data)
67 if fetch_list:
68 try:
69 [key] = [k for k in data.keys() if k != 'count']
70 except ValueError:
71 done = True
72 else:
73 final_data.extend(data[key])
74 page += 1
75 if fetch_result and 'jobid' in data:
76 try:
77 final_data = await asyncio.wait_for(
78 self._jobresult(data['jobid']),
79 self.job_timeout)
80 except asyncio.TimeoutError:
81 raise CloudStackException(
82 "Timeout waiting for async job result",
83 data['jobid'])
84 done = True
85 else:
86 final_data = data
87 done = True
88 return final_data
89
90 async def _jobresult(self, jobid):
91 failures = 0
92 while True:
93 try:
94 j = await self.queryAsyncJobResult(jobid=jobid,
95 fetch_result=False)
96 failures = 0
97 if j['jobstatus'] != 0:
98 if j['jobresultcode'] != 0 or j['jobstatus'] != 1:
99 raise CloudStackException("Job failure", j)
100 if 'jobresult' not in j:
101 raise CloudStackException("Unkonwn job result", j)
102 return j['jobresult']
103
104 except CloudStackException:
105 raise
106
107 except Exception:
108 failures += 1
109 if failures > 10:
110 raise
111
112 await asyncio.sleep(self.poll_interval)
0 #! /usr/bin/env python
1
2 import base64
3 import hashlib
4 import hmac
5 import os
6 import sys
7
8 try:
9 from configparser import ConfigParser
10 except ImportError: # python 2
11 from ConfigParser import ConfigParser
12
13 try:
14 from urllib.parse import quote
15 except ImportError: # python 2
16 from urllib import quote
17
18 import requests
19
20 PY2 = sys.version_info < (3, 0)
21
22 if PY2:
23 text_type = unicode # noqa
24 string_type = basestring # noqa
25 integer_types = int, long # noqa
26 else:
27 text_type = str
28 string_type = str
29 integer_types = int
30
31 if sys.version_info >= (3, 5):
32 try:
33 from cs.async import AIOCloudStack # noqa
34 except ImportError:
35 pass
36
37
38 def cs_encode(value):
39 """
40 Try to behave like cloudstack, which uses
41 java.net.URLEncoder.encode(stuff).replace('+', '%20').
42 """
43 if isinstance(value, int):
44 value = str(value)
45 elif PY2 and isinstance(value, text_type):
46 value = value.encode('utf-8')
47 return quote(value, safe=".-*_")
48
49
50 def transform(params):
51 for key, value in list(params.items()):
52 if value is None or value == "":
53 params.pop(key)
54 continue
55 if isinstance(value, string_type):
56 continue
57 elif isinstance(value, integer_types):
58 params[key] = text_type(value)
59 elif isinstance(value, (list, tuple, set)):
60 if not value:
61 params.pop(key)
62 else:
63 if isinstance(value, set):
64 value = list(value)
65 if not isinstance(value[0], dict):
66 params[key] = ",".join(value)
67 else:
68 params.pop(key)
69 for index, val in enumerate(value):
70 for k, v in val.items():
71 params["%s[%d].%s" % (key, index, k)] = v
72 else:
73 raise ValueError(type(value))
74 return params
75
76
77 class CloudStackException(Exception):
78 pass
79
80
81 class Unauthorized(CloudStackException):
82 pass
83
84
85 class CloudStack(object):
86 def __init__(self, endpoint, key, secret, timeout=10, method='get',
87 verify=True, cert=None, name=None):
88 self.endpoint = endpoint
89 self.key = key
90 self.secret = secret
91 self.timeout = int(timeout)
92 self.method = method.lower()
93 self.verify = verify
94 self.cert = cert
95 self.name = name
96
97 def __repr__(self):
98 return '<CloudStack: {0}>'.format(self.name or self.endpoint)
99
100 def __getattr__(self, command):
101 def handler(**kwargs):
102 return self._request(command, **kwargs)
103 return handler
104
105 def _prepare_request(self, command, json, opcode_name, fetch_list,
106 **kwargs):
107 kwargs.update({
108 'apiKey': self.key,
109 opcode_name: command,
110 })
111 if json:
112 kwargs['response'] = 'json'
113 if 'page' in kwargs or fetch_list:
114 kwargs.setdefault('pagesize', 500)
115
116 kwarg = 'params' if self.method == 'get' else 'data'
117 return kwarg, kwargs
118
119 def _request(self, command, json=True, opcode_name='command',
120 fetch_list=False, **kwargs):
121 kwarg, kwargs = self._prepare_request(command, json, opcode_name,
122 fetch_list, **kwargs)
123
124 done = False
125 final_data = []
126 page = 1
127 while not done:
128 if fetch_list:
129 kwargs['page'] = page
130
131 kwargs = transform(kwargs)
132 kwargs.pop('signature', None)
133 kwargs['signature'] = self._sign(kwargs)
134
135 response = getattr(requests, self.method)(self.endpoint,
136 timeout=self.timeout,
137 verify=self.verify,
138 cert=self.cert,
139 **{kwarg: kwargs})
140
141 try:
142 data = response.json()
143 except ValueError as e:
144 msg = "Make sure endpoint URL '%s' is correct." % self.endpoint
145 raise CloudStackException(
146 "HTTP {0} response from CloudStack".format(
147 response.status_code), response, "%s. " % str(e) + msg)
148
149 [key] = data.keys()
150 data = data[key]
151 if response.status_code != 200:
152 raise CloudStackException(
153 "HTTP {0} response from CloudStack".format(
154 response.status_code), response, data)
155 if fetch_list:
156 try:
157 [key] = [k for k in data.keys() if k != 'count']
158 except ValueError:
159 done = True
160 else:
161 final_data.extend(data[key])
162 page += 1
163 else:
164 final_data = data
165 done = True
166 return final_data
167
168 def _sign(self, data):
169 """
170 Computes a signature string according to the CloudStack
171 signature method (hmac/sha1).
172 """
173 params = "&".join(sorted([
174 "=".join((key, cs_encode(value)))
175 for key, value in data.items()
176 ])).lower()
177 digest = hmac.new(
178 self.secret.encode('utf-8'),
179 msg=params.encode('utf-8'),
180 digestmod=hashlib.sha1).digest()
181 return base64.b64encode(digest).decode('utf-8').strip()
182
183
184 def read_config(ini_group=None):
185 if not ini_group:
186 ini_group = os.environ.get('CLOUDSTACK_REGION', 'cloudstack')
187 # Try env vars first
188 os.environ.setdefault('CLOUDSTACK_METHOD', 'get')
189 os.environ.setdefault('CLOUDSTACK_TIMEOUT', '10')
190 keys = ['endpoint', 'key', 'secret', 'method', 'timeout']
191 env_conf = {}
192 for key in keys:
193 if 'CLOUDSTACK_{0}'.format(key.upper()) not in os.environ:
194 break
195 else:
196 env_conf[key] = os.environ['CLOUDSTACK_{0}'.format(key.upper())]
197 else:
198 env_conf['verify'] = os.environ.get('CLOUDSTACK_VERIFY', True)
199 env_conf['cert'] = os.environ.get('CLOUDSTACK_CERT', None)
200 env_conf['name'] = None
201 return env_conf
202
203 # Config file: $PWD/cloudstack.ini or $HOME/.cloudstack.ini
204 # Last read wins in configparser
205 paths = (
206 os.path.join(os.path.expanduser('~'), '.cloudstack.ini'),
207 os.path.join(os.getcwd(), 'cloudstack.ini'),
208 )
209 # Look at CLOUDSTACK_CONFIG first if present
210 if 'CLOUDSTACK_CONFIG' in os.environ:
211 paths += (os.path.expanduser(os.environ['CLOUDSTACK_CONFIG']),)
212 if not any([os.path.exists(c) for c in paths]):
213 raise SystemExit("Config file not found. Tried {0}".format(
214 ", ".join(paths)))
215 conf = ConfigParser()
216 conf.read(paths)
217 try:
218 cs_conf = conf[ini_group]
219 except AttributeError: # python 2
220 cs_conf = dict(conf.items(ini_group))
221 cs_conf['name'] = ini_group
222 return cs_conf
+0
-305
cs.py less more
0 #! /usr/bin/env python
1
2 import argparse
3 import base64
4 import hashlib
5 import hmac
6 import json
7 import os
8 import sys
9 import time
10
11 from collections import defaultdict
12
13 try:
14 from configparser import ConfigParser, NoSectionError
15 except ImportError: # python 2
16 from ConfigParser import ConfigParser, NoSectionError
17
18 try:
19 from urllib.parse import quote
20 except ImportError: # python 2
21 from urllib import quote
22
23 try:
24 import pygments
25 from pygments.lexers import JsonLexer
26 from pygments.formatters import TerminalFormatter
27 except ImportError:
28 pygments = None
29
30 import requests
31
32
33 PY2 = sys.version_info < (3, 0)
34
35 if PY2:
36 text_type = unicode # noqa
37 string_type = basestring # noqa
38 integer_types = int, long # noqa
39 else:
40 text_type = str
41 string_type = str
42 integer_types = int
43
44
45 def cs_encode(value):
46 """
47 Try to behave like cloudstack, which uses
48 java.net.URLEncoder.encode(stuff).replace('+', '%20').
49 """
50 if isinstance(value, int):
51 value = str(value)
52 elif PY2 and isinstance(value, text_type):
53 value = value.encode('utf-8')
54 return quote(value, safe=".-*_")
55
56
57 def transform(params):
58 for key, value in list(params.items()):
59 if value is None or value == "":
60 params.pop(key)
61 continue
62 if isinstance(value, string_type):
63 continue
64 elif isinstance(value, integer_types):
65 params[key] = text_type(value)
66 elif isinstance(value, (list, tuple, set)):
67 if not value:
68 params.pop(key)
69 else:
70 if isinstance(value, set):
71 value = list(value)
72 if not isinstance(value[0], dict):
73 params[key] = ",".join(value)
74 else:
75 params.pop(key)
76 for index, val in enumerate(value):
77 for k, v in val.items():
78 params["%s[%d].%s" % (key, index, k)] = v
79 else:
80 raise ValueError(type(value))
81 return params
82
83
84 class CloudStackException(Exception):
85 pass
86
87
88 class Unauthorized(CloudStackException):
89 pass
90
91
92 class CloudStack(object):
93 def __init__(self, endpoint, key, secret, timeout=10, method='get',
94 verify=True, cert=None, name=None):
95 self.endpoint = endpoint
96 self.key = key
97 self.secret = secret
98 self.timeout = int(timeout)
99 self.method = method.lower()
100 self.verify = verify
101 self.cert = cert
102 self.name = name
103
104 def __repr__(self):
105 return '<CloudStack: {0}>'.format(self.name or self.endpoint)
106
107 def __getattr__(self, command):
108 def handler(**kwargs):
109 return self._request(command, **kwargs)
110 return handler
111
112 def _request(self, command, json=True, opcode_name='command',
113 fetch_list=False, **kwargs):
114 kwargs.update({
115 'apiKey': self.key,
116 opcode_name: command,
117 })
118 if json:
119 kwargs['response'] = 'json'
120 if 'page' in kwargs or fetch_list:
121 kwargs.setdefault('pagesize', 500)
122
123 kwarg = 'params' if self.method == 'get' else 'data'
124
125 done = False
126 final_data = []
127 page = 1
128 while not done:
129 if fetch_list:
130 kwargs['page'] = page
131
132 kwargs = transform(kwargs)
133 kwargs.pop('signature', None)
134 kwargs['signature'] = self._sign(kwargs)
135
136 response = getattr(requests, self.method)(self.endpoint,
137 timeout=self.timeout,
138 verify=self.verify,
139 cert=self.cert,
140 **{kwarg: kwargs})
141
142 try:
143 data = response.json()
144 except ValueError as e:
145 msg = "Make sure endpoint URL '%s' is correct." % self.endpoint
146 raise CloudStackException(
147 "HTTP {0} response from CloudStack".format(
148 response.status_code), response, "%s. " % str(e) + msg)
149
150 [key] = data.keys()
151 data = data[key]
152 if response.status_code != 200:
153 raise CloudStackException(
154 "HTTP {0} response from CloudStack".format(
155 response.status_code), response, data)
156 if fetch_list:
157 try:
158 [key] = [k for k in data.keys() if k != 'count']
159 except ValueError:
160 done = True
161 else:
162 final_data.extend(data[key])
163 page += 1
164 else:
165 final_data = data
166 done = True
167 return final_data
168
169 def _sign(self, data):
170 """
171 Computes a signature string according to the CloudStack
172 signature method (hmac/sha1).
173 """
174 params = "&".join(sorted([
175 "=".join((key, cs_encode(value)))
176 for key, value in data.items()
177 ])).lower()
178 digest = hmac.new(
179 self.secret.encode('utf-8'),
180 msg=params.encode('utf-8'),
181 digestmod=hashlib.sha1).digest()
182 return base64.b64encode(digest).decode('utf-8').strip()
183
184
185 def read_config(ini_group=None):
186 if not ini_group:
187 ini_group = os.environ.get('CLOUDSTACK_REGION', 'cloudstack')
188 # Try env vars first
189 os.environ.setdefault('CLOUDSTACK_METHOD', 'get')
190 os.environ.setdefault('CLOUDSTACK_TIMEOUT', '10')
191 keys = ['endpoint', 'key', 'secret', 'method', 'timeout']
192 env_conf = {}
193 for key in keys:
194 if 'CLOUDSTACK_{0}'.format(key.upper()) not in os.environ:
195 break
196 else:
197 env_conf[key] = os.environ['CLOUDSTACK_{0}'.format(key.upper())]
198 else:
199 env_conf['verify'] = os.environ.get('CLOUDSTACK_VERIFY', True)
200 env_conf['cert'] = os.environ.get('CLOUDSTACK_CERT', None)
201 env_conf['name'] = None
202 return env_conf
203
204 # Config file: $PWD/cloudstack.ini or $HOME/.cloudstack.ini
205 # Last read wins in configparser
206 paths = (
207 os.path.join(os.path.expanduser('~'), '.cloudstack.ini'),
208 os.path.join(os.getcwd(), 'cloudstack.ini'),
209 )
210 # Look at CLOUDSTACK_CONFIG first if present
211 if 'CLOUDSTACK_CONFIG' in os.environ:
212 paths += (os.path.expanduser(os.environ['CLOUDSTACK_CONFIG']),)
213 if not any([os.path.exists(c) for c in paths]):
214 raise SystemExit("Config file not found. Tried {0}".format(
215 ", ".join(paths)))
216 conf = ConfigParser()
217 conf.read(paths)
218 try:
219 cs_conf = conf[ini_group]
220 except AttributeError: # python 2
221 cs_conf = dict(conf.items(ini_group))
222 cs_conf['name'] = ini_group
223 return cs_conf
224
225
226 def main():
227 parser = argparse.ArgumentParser(description='Cloustack client.')
228 parser.add_argument('--region', metavar='REGION',
229 help='Cloudstack region in ~/.cloudstack.ini',
230 default=os.environ.get('CLOUDSTACK_REGION',
231 'cloudstack'))
232 parser.add_argument('--post', action='store_true', default=False,
233 help='use POST instead of GET')
234 parser.add_argument('--async', action='store_true', default=False,
235 help='do not wait for async result')
236 parser.add_argument('--quiet', '-q', action='store_true', default=False,
237 help='do not display additional status messages')
238 parser.add_argument('command', metavar="COMMAND",
239 help='Cloudstack API command to execute')
240
241 def parse_option(x):
242 if '=' not in x:
243 raise ValueError("{!r} is not a correctly formatted "
244 "option".format(x))
245 return x.split('=', 1)
246
247 parser.add_argument('arguments', metavar="OPTION=VALUE",
248 nargs='*', type=parse_option,
249 help='Cloudstack API argument')
250
251 options = parser.parse_args()
252 command = options.command
253 kwargs = defaultdict(set)
254 for arg in options.arguments:
255 key, value = arg
256 kwargs[key].add(value.strip(" \"'"))
257
258 try:
259 config = read_config(ini_group=options.region)
260 except NoSectionError:
261 raise SystemExit("Error: region '%s' not in config" % options.region)
262
263 if options.post:
264 config['method'] = 'post'
265 cs = CloudStack(**config)
266 ok = True
267 try:
268 response = getattr(cs, command)(**kwargs)
269 except CloudStackException as e:
270 response = e.args[1]
271 if not options.quiet:
272 sys.stderr.write("Cloudstack error: HTTP response "
273 "{0}\n".format(response.status_code))
274 sys.stderr.write(response.text)
275 sys.exit(1)
276
277 if 'Async' not in command and 'jobid' in response and not options.async:
278 if not options.quiet:
279 sys.stderr.write("Polling result... ^C to abort\n")
280 while True:
281 try:
282 res = cs.queryAsyncJobResult(**response)
283 if res['jobstatus'] != 0:
284 response = res
285 if res['jobresultcode'] != 0:
286 ok = False
287 break
288 time.sleep(3)
289 except KeyboardInterrupt:
290 if not options.quiet:
291 sys.stderr.write("Result not ready yet.\n")
292 break
293
294 data = json.dumps(response, indent=2, sort_keys=True)
295
296 if pygments and sys.stdout.isatty():
297 data = pygments.highlight(data, JsonLexer(), TerminalFormatter())
298 sys.stdout.write(data)
299 sys.stdout.write('\n')
300 sys.exit(int(not ok))
301
302
303 if __name__ == '__main__':
304 main()
00 # coding: utf-8
1 from setuptools import setup
1 import sys
2 import setuptools
3 from setuptools import find_packages, setup
24
35 with open('README.rst', 'r') as f:
46 long_description = f.read()
57
8 install_requires = ['requests']
9 extras_require = {
10 'highlight': ['pygments'],
11 }
12
13 if int(setuptools.__version__.split(".", 1)[0]) < 18:
14 if sys.version_info[0:2] >= (3, 5):
15 install_requires.append("aiohttp")
16 else:
17 extras_require[":python_version>='3.5'"] = ["aiohttp"]
18
619 setup(
720 name='cs',
8 version='1.1.1',
21 version='2.0.0',
922 url='https://github.com/exoscale/cs',
1023 license='BSD',
1124 author=u'Bruno ReniƩ',
1225 description=('A simple yet powerful CloudStack API client for '
1326 'Python and the command-line.'),
1427 long_description=long_description,
15 py_modules=('cs',),
28 packages=find_packages(exclude=['tests']),
1629 zip_safe=False,
1730 include_package_data=True,
1831 platforms='any',
2538 'Programming Language :: Python :: 2',
2639 'Programming Language :: Python :: 3',
2740 ),
28 install_requires=(
29 'requests',
30 ),
31 extras_require={
32 'highlight': ['pygments'],
33 },
41 install_requires=install_requires,
42 extras_require=extras_require,
3443 test_suite='tests',
3544 entry_points={
3645 'console_scripts': [