3 from getpass import getuser
4 from httplib import HTTPConnection
5 from optparse import OptionParser
6 from os.path import basename
7 from sys import argv, exit, stdin
13 DEFAULT_HOST = 'pithos.dev.grnet.gr'
17 class Fault(Exception):
18 def __init__(self, data=''):
19 Exception.__init__(self, data)
24 def __init__(self, host, account, api='v1', verbose=False, debug=False):
25 """`host` can also include a port, e.g '127.0.0.1:8000'."""
28 self.account = account
30 self.verbose = verbose or debug
33 def req(self, method, path, body=None, headers=None, format='text'):
34 full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path, format)
35 conn = HTTPConnection(self.host)
38 kwargs['headers'] = headers or {}
39 kwargs['headers']['Content-Length'] = len(body) if body else 0
42 kwargs['headers']['Content-Type'] = 'application/octet-stream'
44 conn.request(method, full_path, **kwargs)
45 resp = conn.getresponse()
46 headers = dict(resp.getheaders())
49 print '%d %s' % (resp.status, resp.reason)
50 for key, val in headers.items():
51 print '%s: %s' % (key.capitalize(), val)
61 data = json.loads(data)
63 return resp.status, headers, data
65 def delete(self, path, format='text'):
66 return self.req('DELETE', path, format=format)
68 def get(self, path, format='text'):
69 return self.req('GET', path, format=format)
71 def head(self, path, format='text'):
72 return self.req('HEAD', path, format=format)
74 def post(self, path, body=None, format='text', headers=None):
75 return self.req('POST', path, body, headers=headers, format=format)
77 def put(self, path, body=None, format='text', headers=None):
78 return self.req('PUT', path, body, headers=headers, format=format)
80 def _list(self, path, detail=False):
81 format = 'json' if detail else 'text'
82 status, headers, data = self.get(path, format=format)
85 def _get_metadata(self, path, prefix):
86 status, headers, data = self.head(path)
87 prefixlen = len(prefix)
89 for key, val in headers.items():
90 if key.startswith(prefix):
96 # Storage Account Services
98 def list_containers(self, detail=False):
99 return self._list('', detail)
101 def account_metadata(self):
102 return self._get_metadata('', 'x-account-')
105 # Storage Container Services
107 def list_objects(self, container, detail=False):
108 return self._list('/' + container, detail)
110 def create_container(self, container):
111 status, header, data = self.put('/' + container)
118 def delete_container(self, container):
119 self.delete('/' + container)
121 def retrieve_container_metadata(self, container):
122 return self._get_metadata('/%s' % container, 'x-container-')
125 # Storage Object Services
127 def retrieve_object(self, container, object):
128 path = '/%s/%s' % (container, object)
129 status, headers, data = self.get(path)
132 def create_object(self, container, object, data):
133 path = '/%s/%s' % (container, object)
136 def copy_object(self, src_container, src_object, dst_container, dst_object):
137 path = '/%s/%s' % (dst_container, dst_object)
139 headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
140 headers['Content-Length'] = 0
141 self.put(path, headers=headers)
143 def delete_object(self, container, object):
144 self.delete('/%s/%s' % (container, object))
146 def retrieve_object_metadata(self, container, object):
147 path = '/%s/%s' % (container, object)
148 return self._get_metadata(path, 'x-object-meta-')
150 def update_object_metadata(self, container, object, **meta):
151 path = '/%s/%s' % (container, object)
153 for key, val in meta.items():
154 http_key = 'X-Object-Meta-' + key
155 headers[http_key] = val
156 self.post(path, headers=headers)
161 def cli_command(*args):
165 _cli_commands[name] = cls
169 def class_for_cli_command(name):
170 return _cli_commands[name]
172 def print_dict(d, header='name'):
175 for key, val in sorted(d.items()):
176 print '%s: %s' % (key.rjust(15), val)
179 class Command(object):
180 def __init__(self, argv):
181 parser = OptionParser()
182 parser.add_option('--host', dest='host', metavar='HOST', default=DEFAULT_HOST,
183 help='use server HOST')
184 parser.add_option('--user', dest='user', metavar='USERNAME', default=getuser(),
185 help='use account USERNAME')
186 parser.add_option('--api', dest='api', metavar='API', default=DEFAULT_API,
188 parser.add_option('-v', action='store_true', dest='verbose', default=False,
189 help='use verbose output')
190 parser.add_option('-d', action='store_true', dest='debug', default=False,
191 help='use debug output')
192 self.add_options(parser)
193 options, args = parser.parse_args(argv)
195 # Add options to self
196 for opt in parser.option_list:
199 val = getattr(options, key)
200 setattr(self, key, val)
202 self.client = Client(self.host, self.user, self.api, self.verbose, self.debug)
207 def add_options(self, parser):
210 def execute(self, *args):
214 @cli_command('list', 'ls')
216 syntax = '[container]'
217 description = 'list containers or objects'
219 def add_options(self, parser):
220 parser.add_option('-l', action='store_true', dest='detail', default=False,
221 help='show detailed output')
223 def execute(self, container=None):
225 self.list_objects(container)
227 self.list_containers()
229 def list_containers(self):
231 for container in self.client.list_containers(detail=True):
232 print_dict(container)
235 print self.client.list_containers().strip()
237 def list_objects(self, container):
239 for obj in self.client.list_objects(container, detail=True):
243 print self.client.list_objects(container).strip()
248 syntax = '[<container>[/<object>]]'
249 description = 'get the metadata of an account, a container or an object'
251 def execute(self, path=''):
252 container, sep, object = path.partition('/')
254 meta = self.client.retrieve_object_metadata(container, object)
256 meta = self.client.retrieve_container_metadata(container)
258 meta = self.client.account_metadata()
259 print_dict(meta, header=None)
262 @cli_command('create')
263 class CreateContainer(Command):
264 syntax = '<container>'
265 description = 'create a container'
267 def execute(self, container):
268 ret = self.client.create_container(container)
270 print 'Container already exists'
273 @cli_command('delete', 'rm')
274 class Delete(Command):
275 syntax = '<container>[/<object>]'
276 description = 'delete a container or an object'
278 def execute(self, path):
279 container, sep, object = path.partition('/')
281 self.client.delete_object(container, object)
283 self.client.delete_container(container)
287 class GetObject(Command):
288 syntax = '<container>/<object>'
289 description = 'get the data of an object'
291 def execute(self, path):
292 container, sep, object = path.partition('/')
293 print self.client.retrieve_object(container, object)
297 class PutObject(Command):
298 syntax = '<container>/<object> <path>'
299 description = 'create or update an object with contents of path'
301 def execute(self, path, srcpath):
302 container, sep, object = path.partition('/')
303 f = open(srcpath) if srcpath != '-' else stdin
305 self.client.create_object(container, object, data)
309 @cli_command('copy', 'cp')
310 class CopyObject(Command):
311 syntax = '<src container>/<src object> [<dst container>/]<dst object>'
312 description = 'copies an object to a different location'
314 def execute(self, src, dst):
315 src_container, sep, src_object = src.partition('/')
316 dst_container, sep, dst_object = dst.partition('/')
318 dst_container = src_container
320 self.client.copy_object(src_container, src_object, dst_container, dst_object)
324 class SetOjectMeta(Command):
325 syntax = '<container>/<object> key=val [key=val] [...]'
326 description = 'set object metadata'
328 def execute(self, path, *args):
329 container, sep, object = path.partition('/')
332 key, sep, val = arg.partition('=')
333 meta[key.strip()] = val.strip()
334 self.client.update_object_metadata(container, object, **meta)
340 parser.usage = '%prog <command> [options]'
344 for cls in set(_cli_commands.values()):
345 name = ', '.join(cls.commands)
346 description = getattr(cls, 'description', '')
347 commands.append(' %s %s' % (name.ljust(12), description))
348 print '\nCommands:\n' + '\n'.join(sorted(commands))
355 cls = class_for_cli_command(name)
356 except (IndexError, KeyError):
363 cmd.execute(*cmd.args)
365 cmd.parser.usage = '%%prog %s [options] %s' % (name, cmd.syntax)
366 cmd.parser.print_help()
372 if __name__ == '__main__':