Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli.py @ 2bcb595a

History | View | Annotate | Download (30.9 kB)

1 5d1d131b Giorgos Verigakis
#!/usr/bin/env python
2 5d1d131b Giorgos Verigakis
3 5d1d131b Giorgos Verigakis
# Copyright 2011 GRNET S.A. All rights reserved.
4 5d1d131b Giorgos Verigakis
#
5 5d1d131b Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
6 5d1d131b Giorgos Verigakis
# without modification, are permitted provided that the following
7 5d1d131b Giorgos Verigakis
# conditions are met:
8 5d1d131b Giorgos Verigakis
#
9 5d1d131b Giorgos Verigakis
#   1. Redistributions of source code must retain the above
10 5d1d131b Giorgos Verigakis
#      copyright notice, this list of conditions and the following
11 5d1d131b Giorgos Verigakis
#      disclaimer.
12 5d1d131b Giorgos Verigakis
#
13 5d1d131b Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
14 5d1d131b Giorgos Verigakis
#      copyright notice, this list of conditions and the following
15 5d1d131b Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
16 5d1d131b Giorgos Verigakis
#      provided with the distribution.
17 5d1d131b Giorgos Verigakis
#
18 5d1d131b Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 5d1d131b Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 5d1d131b Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 5d1d131b Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 5d1d131b Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 5d1d131b Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 5d1d131b Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 5d1d131b Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 5d1d131b Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 5d1d131b Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 5d1d131b Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 5d1d131b Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
30 5d1d131b Giorgos Verigakis
#
31 5d1d131b Giorgos Verigakis
# The views and conclusions contained in the software and
32 5d1d131b Giorgos Verigakis
# documentation are those of the authors and should not be
33 5d1d131b Giorgos Verigakis
# interpreted as representing official policies, either expressed
34 5d1d131b Giorgos Verigakis
# or implied, of GRNET S.A.
35 5d1d131b Giorgos Verigakis
36 eb3ca8ca Giorgos Verigakis
"""
37 eb3ca8ca Giorgos Verigakis
To add a command create a new class and add a 'command' decorator. The class
38 eb3ca8ca Giorgos Verigakis
must have a 'main' method which will contain the code to be executed.
39 eb3ca8ca Giorgos Verigakis
Optionally a command can implement an 'update_parser' class method in order
40 eb3ca8ca Giorgos Verigakis
to add command line arguments, or modify the OptionParser in any way.
41 eb3ca8ca Giorgos Verigakis

42 eb3ca8ca Giorgos Verigakis
The name of the class is important and it will determine the name and grouping
43 eb3ca8ca Giorgos Verigakis
of the command. This behavior can be overriden with the 'group' and 'name'
44 eb3ca8ca Giorgos Verigakis
decorator arguments:
45 eb3ca8ca Giorgos Verigakis

46 a1c50326 Giorgos Verigakis
    @command(api='compute')
47 eb3ca8ca Giorgos Verigakis
    class server_list(object):
48 eb3ca8ca Giorgos Verigakis
        # This command will be named 'list' under group 'server'
49 eb3ca8ca Giorgos Verigakis
        ...
50 eb3ca8ca Giorgos Verigakis

51 a1c50326 Giorgos Verigakis
    @command(api='compute', name='ls')
52 eb3ca8ca Giorgos Verigakis
    class server_list(object):
53 eb3ca8ca Giorgos Verigakis
        # This command will be named 'ls' under group 'server'
54 eb3ca8ca Giorgos Verigakis
        ...
55 eb3ca8ca Giorgos Verigakis

56 eb3ca8ca Giorgos Verigakis
The docstring of a command class will be used as the command description in
57 eb3ca8ca Giorgos Verigakis
help messages, unless overriden with the 'description' decorator argument.
58 eb3ca8ca Giorgos Verigakis

59 eb3ca8ca Giorgos Verigakis
The syntax of a command will be generated dynamically based on the signature
60 eb3ca8ca Giorgos Verigakis
of the 'main' method, unless overriden with the 'syntax' decorator argument:
61 eb3ca8ca Giorgos Verigakis

62 eb3ca8ca Giorgos Verigakis
    def main(self, server_id, network=None):
63 eb3ca8ca Giorgos Verigakis
        # This syntax of this command will be: '<server id> [network]'
64 eb3ca8ca Giorgos Verigakis
        ...
65 eb3ca8ca Giorgos Verigakis

66 eb3ca8ca Giorgos Verigakis
The order of commands is important, it will be preserved in the help output.
67 eb3ca8ca Giorgos Verigakis
"""
68 eb3ca8ca Giorgos Verigakis
69 5d1d131b Giorgos Verigakis
import inspect
70 5d1d131b Giorgos Verigakis
import logging
71 5d1d131b Giorgos Verigakis
import os
72 5d1d131b Giorgos Verigakis
73 57b8dd5a Giorgos Verigakis
from base64 import b64encode
74 57b8dd5a Giorgos Verigakis
from grp import getgrgid
75 5d1d131b Giorgos Verigakis
from optparse import OptionParser
76 f3ddb705 Giorgos Verigakis
from os.path import abspath, basename, exists, expanduser
77 57b8dd5a Giorgos Verigakis
from pwd import getpwuid
78 176894c1 Giorgos Verigakis
from sys import argv, exit, stdout
79 5d1d131b Giorgos Verigakis
80 6a0b1658 Giorgos Verigakis
from clint import args
81 6a0b1658 Giorgos Verigakis
from clint.textui import puts, puts_err, indent, progress
82 6a0b1658 Giorgos Verigakis
from clint.textui.colored import magenta, red, yellow
83 f3ddb705 Giorgos Verigakis
from clint.textui.cols import columns
84 f3ddb705 Giorgos Verigakis
85 6a0b1658 Giorgos Verigakis
from requests.exceptions import ConnectionError
86 6a0b1658 Giorgos Verigakis
87 a1c50326 Giorgos Verigakis
from kamaki import clients
88 f3ddb705 Giorgos Verigakis
from kamaki.config import Config
89 614b80ce Giorgos Verigakis
from kamaki.utils import OrderedDict, print_addresses, print_dict, print_items
90 5d1d131b Giorgos Verigakis
91 5d1d131b Giorgos Verigakis
92 f3ddb705 Giorgos Verigakis
# Path to the file that stores the configuration
93 f3ddb705 Giorgos Verigakis
CONFIG_PATH = expanduser('~/.kamakirc')
94 f3ddb705 Giorgos Verigakis
95 f3ddb705 Giorgos Verigakis
# Name of a shell variable to bypass the CONFIG_PATH value
96 f3ddb705 Giorgos Verigakis
CONFIG_ENV = 'KAMAKI_CONFIG'
97 f3ddb705 Giorgos Verigakis
98 653b0597 Giorgos Verigakis
99 eb3ca8ca Giorgos Verigakis
_commands = OrderedDict()
100 eb3ca8ca Giorgos Verigakis
101 653b0597 Giorgos Verigakis
102 f3ddb705 Giorgos Verigakis
GROUPS = {
103 f3ddb705 Giorgos Verigakis
    'config': "Configuration commands",
104 f3ddb705 Giorgos Verigakis
    'server': "Compute API server commands",
105 f3ddb705 Giorgos Verigakis
    'flavor': "Compute API flavor commands",
106 f3ddb705 Giorgos Verigakis
    'image': "Compute API image commands",
107 f3ddb705 Giorgos Verigakis
    'network': "Compute API network commands (Cyclades extension)",
108 f3ddb705 Giorgos Verigakis
    'glance': "Image API commands",
109 f3ddb705 Giorgos Verigakis
    'store': "Storage API commands"}
110 f3ddb705 Giorgos Verigakis
111 f3ddb705 Giorgos Verigakis
112 f3ddb705 Giorgos Verigakis
def command(api=None, group=None, name=None, syntax=None):
113 eb3ca8ca Giorgos Verigakis
    """Class decorator that registers a class as a CLI command."""
114 eb3ca8ca Giorgos Verigakis
    
115 eb3ca8ca Giorgos Verigakis
    def decorator(cls):
116 eb3ca8ca Giorgos Verigakis
        grp, sep, cmd = cls.__name__.partition('_')
117 eb3ca8ca Giorgos Verigakis
        if not sep:
118 eb3ca8ca Giorgos Verigakis
            grp, cmd = None, cls.__name__
119 5d1d131b Giorgos Verigakis
        
120 eb3ca8ca Giorgos Verigakis
        cls.api = api
121 eb3ca8ca Giorgos Verigakis
        cls.group = group or grp
122 eb3ca8ca Giorgos Verigakis
        cls.name = name or cmd
123 5d1d131b Giorgos Verigakis
        
124 f3ddb705 Giorgos Verigakis
        short_description, sep, long_description = cls.__doc__.partition('\n')
125 f3ddb705 Giorgos Verigakis
        cls.description = short_description
126 f3ddb705 Giorgos Verigakis
        cls.long_description = long_description or short_description
127 f3ddb705 Giorgos Verigakis
        
128 f3ddb705 Giorgos Verigakis
        cls.syntax = syntax
129 eb3ca8ca Giorgos Verigakis
        if cls.syntax is None:
130 eb3ca8ca Giorgos Verigakis
            # Generate a syntax string based on main's arguments
131 eb3ca8ca Giorgos Verigakis
            spec = inspect.getargspec(cls.main.im_func)
132 eb3ca8ca Giorgos Verigakis
            args = spec.args[1:]
133 eb3ca8ca Giorgos Verigakis
            n = len(args) - len(spec.defaults or ())
134 eb3ca8ca Giorgos Verigakis
            required = ' '.join('<%s>' % x.replace('_', ' ') for x in args[:n])
135 eb3ca8ca Giorgos Verigakis
            optional = ' '.join('[%s]' % x.replace('_', ' ') for x in args[n:])
136 eb3ca8ca Giorgos Verigakis
            cls.syntax = ' '.join(x for x in [required, optional] if x)
137 8ab2c986 Giorgos Verigakis
            if spec.varargs:
138 8ab2c986 Giorgos Verigakis
                cls.syntax += ' <%s ...>' % spec.varargs
139 5d1d131b Giorgos Verigakis
        
140 eb3ca8ca Giorgos Verigakis
        if cls.group not in _commands:
141 eb3ca8ca Giorgos Verigakis
            _commands[cls.group] = OrderedDict()
142 eb3ca8ca Giorgos Verigakis
        _commands[cls.group][cls.name] = cls
143 eb3ca8ca Giorgos Verigakis
        return cls
144 eb3ca8ca Giorgos Verigakis
    return decorator
145 5d1d131b Giorgos Verigakis
146 5d1d131b Giorgos Verigakis
147 f3ddb705 Giorgos Verigakis
@command(api='config')
148 eb3ca8ca Giorgos Verigakis
class config_list(object):
149 f3ddb705 Giorgos Verigakis
    """List configuration options"""
150 eb3ca8ca Giorgos Verigakis
    
151 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
152 d2cea1e2 Giorgos Verigakis
        parser.add_option('-a', dest='all', action='store_true',
153 f3ddb705 Giorgos Verigakis
                          default=False, help='include default values')
154 d2cea1e2 Giorgos Verigakis
    
155 eb3ca8ca Giorgos Verigakis
    def main(self):
156 f3ddb705 Giorgos Verigakis
        include_defaults = self.options.all
157 f3ddb705 Giorgos Verigakis
        for section in sorted(self.config.sections()):
158 f3ddb705 Giorgos Verigakis
            items = self.config.items(section, include_defaults)
159 f3ddb705 Giorgos Verigakis
            for key, val in sorted(items):
160 f3ddb705 Giorgos Verigakis
                puts('%s.%s = %s' % (section, key, val))
161 5d1d131b Giorgos Verigakis
162 5d1d131b Giorgos Verigakis
163 f3ddb705 Giorgos Verigakis
@command(api='config')
164 eb3ca8ca Giorgos Verigakis
class config_get(object):
165 f3ddb705 Giorgos Verigakis
    """Show a configuration option"""
166 5d1d131b Giorgos Verigakis
    
167 f3ddb705 Giorgos Verigakis
    def main(self, option):
168 f3ddb705 Giorgos Verigakis
        section, sep, key = option.rpartition('.')
169 f3ddb705 Giorgos Verigakis
        section = section or 'global'
170 f3ddb705 Giorgos Verigakis
        value = self.config.get(section, key)
171 f3ddb705 Giorgos Verigakis
        if value is not None:
172 f3ddb705 Giorgos Verigakis
            print value
173 eb3ca8ca Giorgos Verigakis
174 eb3ca8ca Giorgos Verigakis
175 f3ddb705 Giorgos Verigakis
@command(api='config')
176 eb3ca8ca Giorgos Verigakis
class config_set(object):
177 f3ddb705 Giorgos Verigakis
    """Set a configuration option"""
178 eb3ca8ca Giorgos Verigakis
    
179 f3ddb705 Giorgos Verigakis
    def main(self, option, value):
180 f3ddb705 Giorgos Verigakis
        section, sep, key = option.rpartition('.')
181 f3ddb705 Giorgos Verigakis
        section = section or 'global'
182 f3ddb705 Giorgos Verigakis
        self.config.set(section, key, value)
183 f3ddb705 Giorgos Verigakis
        self.config.write()
184 eb3ca8ca Giorgos Verigakis
185 eb3ca8ca Giorgos Verigakis
186 f3ddb705 Giorgos Verigakis
@command(api='config')
187 f3ddb705 Giorgos Verigakis
class config_delete(object):
188 f3ddb705 Giorgos Verigakis
    """Delete a configuration option (and use the default value)"""
189 eb3ca8ca Giorgos Verigakis
    
190 f3ddb705 Giorgos Verigakis
    def main(self, option):
191 f3ddb705 Giorgos Verigakis
        section, sep, key = option.rpartition('.')
192 f3ddb705 Giorgos Verigakis
        section = section or 'global'
193 f3ddb705 Giorgos Verigakis
        self.config.remove_option(section, key)
194 f3ddb705 Giorgos Verigakis
        self.config.write()
195 eb3ca8ca Giorgos Verigakis
196 eb3ca8ca Giorgos Verigakis
197 a1c50326 Giorgos Verigakis
@command(api='compute')
198 eb3ca8ca Giorgos Verigakis
class server_list(object):
199 6a0b1658 Giorgos Verigakis
    """List servers"""
200 eb3ca8ca Giorgos Verigakis
    
201 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
202 eb3ca8ca Giorgos Verigakis
        parser.add_option('-l', dest='detail', action='store_true',
203 eb3ca8ca Giorgos Verigakis
                default=False, help='show detailed output')
204 5d1d131b Giorgos Verigakis
    
205 5d1d131b Giorgos Verigakis
    def main(self):
206 eb3ca8ca Giorgos Verigakis
        servers = self.client.list_servers(self.options.detail)
207 a6757cbc Giorgos Verigakis
        print_items(servers)
208 5d1d131b Giorgos Verigakis
209 5d1d131b Giorgos Verigakis
210 a1c50326 Giorgos Verigakis
@command(api='compute')
211 eb3ca8ca Giorgos Verigakis
class server_info(object):
212 6a0b1658 Giorgos Verigakis
    """Get server details"""
213 5d1d131b Giorgos Verigakis
    
214 5d1d131b Giorgos Verigakis
    def main(self, server_id):
215 5d1d131b Giorgos Verigakis
        server = self.client.get_server_details(int(server_id))
216 5d1d131b Giorgos Verigakis
        print_dict(server)
217 5d1d131b Giorgos Verigakis
218 5d1d131b Giorgos Verigakis
219 a1c50326 Giorgos Verigakis
@command(api='compute')
220 eb3ca8ca Giorgos Verigakis
class server_create(object):
221 6a0b1658 Giorgos Verigakis
    """Create a server"""
222 76f01c50 Giorgos Verigakis
    
223 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
224 eb3ca8ca Giorgos Verigakis
        parser.add_option('--personality', dest='personalities',
225 6a0b1658 Giorgos Verigakis
                          action='append', default=[],
226 6a0b1658 Giorgos Verigakis
                          metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
227 6a0b1658 Giorgos Verigakis
                          help='add a personality file')
228 eb3ca8ca Giorgos Verigakis
        parser.epilog = "If missing, optional personality values will be " \
229 6a0b1658 Giorgos Verigakis
                        "filled based on the file at PATH."
230 eb3ca8ca Giorgos Verigakis
    
231 eb3ca8ca Giorgos Verigakis
    def main(self, name, flavor_id, image_id):
232 57b8dd5a Giorgos Verigakis
        personalities = []
233 eb3ca8ca Giorgos Verigakis
        for personality in self.options.personalities:
234 57b8dd5a Giorgos Verigakis
            p = personality.split(',')
235 57b8dd5a Giorgos Verigakis
            p.extend([None] * (5 - len(p)))     # Fill missing fields with None
236 57b8dd5a Giorgos Verigakis
            
237 57b8dd5a Giorgos Verigakis
            path = p[0]
238 57b8dd5a Giorgos Verigakis
            
239 57b8dd5a Giorgos Verigakis
            if not path:
240 57b8dd5a Giorgos Verigakis
                log.error("Invalid personality argument '%s'", p)
241 eb3ca8ca Giorgos Verigakis
                return 1
242 a1c50326 Giorgos Verigakis
            if not exists(path):
243 57b8dd5a Giorgos Verigakis
                log.error("File %s does not exist", path)
244 eb3ca8ca Giorgos Verigakis
                return 1
245 57b8dd5a Giorgos Verigakis
            
246 57b8dd5a Giorgos Verigakis
            with open(path) as f:
247 57b8dd5a Giorgos Verigakis
                contents = b64encode(f.read())
248 57b8dd5a Giorgos Verigakis
            
249 57b8dd5a Giorgos Verigakis
            st = os.stat(path)
250 57b8dd5a Giorgos Verigakis
            personalities.append({
251 a1c50326 Giorgos Verigakis
                'path': p[1] or abspath(path),
252 57b8dd5a Giorgos Verigakis
                'owner': p[2] or getpwuid(st.st_uid).pw_name,
253 57b8dd5a Giorgos Verigakis
                'group': p[3] or getgrgid(st.st_gid).gr_name,
254 57b8dd5a Giorgos Verigakis
                'mode': int(p[4]) if p[4] else 0x7777 & st.st_mode,
255 57b8dd5a Giorgos Verigakis
                'contents': contents})
256 76f01c50 Giorgos Verigakis
        
257 eb3ca8ca Giorgos Verigakis
        reply = self.client.create_server(name, int(flavor_id), image_id,
258 eb3ca8ca Giorgos Verigakis
                personalities)
259 5d1d131b Giorgos Verigakis
        print_dict(reply)
260 5d1d131b Giorgos Verigakis
261 5d1d131b Giorgos Verigakis
262 a1c50326 Giorgos Verigakis
@command(api='compute')
263 eb3ca8ca Giorgos Verigakis
class server_rename(object):
264 6a0b1658 Giorgos Verigakis
    """Update a server's name"""
265 5d1d131b Giorgos Verigakis
    
266 5d1d131b Giorgos Verigakis
    def main(self, server_id, new_name):
267 5d1d131b Giorgos Verigakis
        self.client.update_server_name(int(server_id), new_name)
268 5d1d131b Giorgos Verigakis
269 5d1d131b Giorgos Verigakis
270 a1c50326 Giorgos Verigakis
@command(api='compute')
271 eb3ca8ca Giorgos Verigakis
class server_delete(object):
272 6a0b1658 Giorgos Verigakis
    """Delete a server"""
273 5d1d131b Giorgos Verigakis
    
274 5d1d131b Giorgos Verigakis
    def main(self, server_id):
275 5d1d131b Giorgos Verigakis
        self.client.delete_server(int(server_id))
276 5d1d131b Giorgos Verigakis
277 5d1d131b Giorgos Verigakis
278 a1c50326 Giorgos Verigakis
@command(api='compute')
279 eb3ca8ca Giorgos Verigakis
class server_reboot(object):
280 6a0b1658 Giorgos Verigakis
    """Reboot a server"""
281 5d1d131b Giorgos Verigakis
    
282 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
283 eb3ca8ca Giorgos Verigakis
        parser.add_option('-f', dest='hard', action='store_true',
284 eb3ca8ca Giorgos Verigakis
                default=False, help='perform a hard reboot')
285 5d1d131b Giorgos Verigakis
    
286 5d1d131b Giorgos Verigakis
    def main(self, server_id):
287 eb3ca8ca Giorgos Verigakis
        self.client.reboot_server(int(server_id), self.options.hard)
288 5d1d131b Giorgos Verigakis
289 5d1d131b Giorgos Verigakis
290 b3b32add Giorgos Verigakis
@command(api='cyclades')
291 eb3ca8ca Giorgos Verigakis
class server_start(object):
292 6a0b1658 Giorgos Verigakis
    """Start a server"""
293 5d1d131b Giorgos Verigakis
    
294 5d1d131b Giorgos Verigakis
    def main(self, server_id):
295 5d1d131b Giorgos Verigakis
        self.client.start_server(int(server_id))
296 5d1d131b Giorgos Verigakis
297 5d1d131b Giorgos Verigakis
298 b3b32add Giorgos Verigakis
@command(api='cyclades')
299 eb3ca8ca Giorgos Verigakis
class server_shutdown(object):
300 6a0b1658 Giorgos Verigakis
    """Shutdown a server"""
301 5d1d131b Giorgos Verigakis
    
302 5d1d131b Giorgos Verigakis
    def main(self, server_id):
303 5d1d131b Giorgos Verigakis
        self.client.shutdown_server(int(server_id))
304 5d1d131b Giorgos Verigakis
305 5d1d131b Giorgos Verigakis
306 b3b32add Giorgos Verigakis
@command(api='cyclades')
307 eb3ca8ca Giorgos Verigakis
class server_console(object):
308 6a0b1658 Giorgos Verigakis
    """Get a VNC console"""
309 eb3ca8ca Giorgos Verigakis
    
310 5d1d131b Giorgos Verigakis
    def main(self, server_id):
311 5d1d131b Giorgos Verigakis
        reply = self.client.get_server_console(int(server_id))
312 5d1d131b Giorgos Verigakis
        print_dict(reply)
313 5d1d131b Giorgos Verigakis
314 5d1d131b Giorgos Verigakis
315 b3b32add Giorgos Verigakis
@command(api='cyclades')
316 eb3ca8ca Giorgos Verigakis
class server_firewall(object):
317 6a0b1658 Giorgos Verigakis
    """Set the server's firewall profile"""
318 5d1d131b Giorgos Verigakis
    
319 5d1d131b Giorgos Verigakis
    def main(self, server_id, profile):
320 5d1d131b Giorgos Verigakis
        self.client.set_firewall_profile(int(server_id), profile)
321 5d1d131b Giorgos Verigakis
322 5d1d131b Giorgos Verigakis
323 b3b32add Giorgos Verigakis
@command(api='cyclades')
324 eb3ca8ca Giorgos Verigakis
class server_addr(object):
325 6a0b1658 Giorgos Verigakis
    """List a server's addresses"""
326 5d1d131b Giorgos Verigakis
    
327 5d1d131b Giorgos Verigakis
    def main(self, server_id, network=None):
328 5d1d131b Giorgos Verigakis
        reply = self.client.list_server_addresses(int(server_id), network)
329 5d1d131b Giorgos Verigakis
        margin = max(len(x['name']) for x in reply)
330 5d1d131b Giorgos Verigakis
        print_addresses(reply, margin)
331 5d1d131b Giorgos Verigakis
332 5d1d131b Giorgos Verigakis
333 a1c50326 Giorgos Verigakis
@command(api='compute')
334 eb3ca8ca Giorgos Verigakis
class server_meta(object):
335 6a0b1658 Giorgos Verigakis
    """Get a server's metadata"""
336 5d1d131b Giorgos Verigakis
    
337 5d1d131b Giorgos Verigakis
    def main(self, server_id, key=None):
338 5d1d131b Giorgos Verigakis
        reply = self.client.get_server_metadata(int(server_id), key)
339 5d1d131b Giorgos Verigakis
        print_dict(reply)
340 5d1d131b Giorgos Verigakis
341 5d1d131b Giorgos Verigakis
342 a1c50326 Giorgos Verigakis
@command(api='compute')
343 eb3ca8ca Giorgos Verigakis
class server_addmeta(object):
344 6a0b1658 Giorgos Verigakis
    """Add server metadata"""
345 5d1d131b Giorgos Verigakis
    
346 5d1d131b Giorgos Verigakis
    def main(self, server_id, key, val):
347 5d1d131b Giorgos Verigakis
        reply = self.client.create_server_metadata(int(server_id), key, val)
348 5d1d131b Giorgos Verigakis
        print_dict(reply)
349 5d1d131b Giorgos Verigakis
350 5d1d131b Giorgos Verigakis
351 a1c50326 Giorgos Verigakis
@command(api='compute')
352 eb3ca8ca Giorgos Verigakis
class server_setmeta(object):
353 6a0b1658 Giorgos Verigakis
    """Update server's metadata"""
354 5d1d131b Giorgos Verigakis
    
355 5d1d131b Giorgos Verigakis
    def main(self, server_id, key, val):
356 ec52784d Giorgos Verigakis
        metadata = {key: val}
357 ec52784d Giorgos Verigakis
        reply = self.client.update_server_metadata(int(server_id), **metadata)
358 5d1d131b Giorgos Verigakis
        print_dict(reply)
359 5d1d131b Giorgos Verigakis
360 5d1d131b Giorgos Verigakis
361 a1c50326 Giorgos Verigakis
@command(api='compute')
362 eb3ca8ca Giorgos Verigakis
class server_delmeta(object):
363 6a0b1658 Giorgos Verigakis
    """Delete server metadata"""
364 5d1d131b Giorgos Verigakis
    
365 5d1d131b Giorgos Verigakis
    def main(self, server_id, key):
366 5d1d131b Giorgos Verigakis
        self.client.delete_server_metadata(int(server_id), key)
367 5d1d131b Giorgos Verigakis
368 5d1d131b Giorgos Verigakis
369 b3b32add Giorgos Verigakis
@command(api='cyclades')
370 eb3ca8ca Giorgos Verigakis
class server_stats(object):
371 6a0b1658 Giorgos Verigakis
    """Get server statistics"""
372 5d1d131b Giorgos Verigakis
    
373 5d1d131b Giorgos Verigakis
    def main(self, server_id):
374 5d1d131b Giorgos Verigakis
        reply = self.client.get_server_stats(int(server_id))
375 5d1d131b Giorgos Verigakis
        print_dict(reply, exclude=('serverRef',))
376 5d1d131b Giorgos Verigakis
377 5d1d131b Giorgos Verigakis
378 a1c50326 Giorgos Verigakis
@command(api='compute')
379 eb3ca8ca Giorgos Verigakis
class flavor_list(object):
380 6a0b1658 Giorgos Verigakis
    """List flavors"""
381 eb3ca8ca Giorgos Verigakis
    
382 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
383 eb3ca8ca Giorgos Verigakis
        parser.add_option('-l', dest='detail', action='store_true',
384 eb3ca8ca Giorgos Verigakis
                default=False, help='show detailed output')
385 5d1d131b Giorgos Verigakis
    
386 5d1d131b Giorgos Verigakis
    def main(self):
387 eb3ca8ca Giorgos Verigakis
        flavors = self.client.list_flavors(self.options.detail)
388 a6757cbc Giorgos Verigakis
        print_items(flavors)
389 5d1d131b Giorgos Verigakis
390 5d1d131b Giorgos Verigakis
391 a1c50326 Giorgos Verigakis
@command(api='compute')
392 eb3ca8ca Giorgos Verigakis
class flavor_info(object):
393 6a0b1658 Giorgos Verigakis
    """Get flavor details"""
394 5d1d131b Giorgos Verigakis
    
395 5d1d131b Giorgos Verigakis
    def main(self, flavor_id):
396 5d1d131b Giorgos Verigakis
        flavor = self.client.get_flavor_details(int(flavor_id))
397 5d1d131b Giorgos Verigakis
        print_dict(flavor)
398 5d1d131b Giorgos Verigakis
399 5d1d131b Giorgos Verigakis
400 a1c50326 Giorgos Verigakis
@command(api='compute')
401 eb3ca8ca Giorgos Verigakis
class image_list(object):
402 6a0b1658 Giorgos Verigakis
    """List images"""
403 eb3ca8ca Giorgos Verigakis
    
404 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
405 eb3ca8ca Giorgos Verigakis
        parser.add_option('-l', dest='detail', action='store_true',
406 eb3ca8ca Giorgos Verigakis
                default=False, help='show detailed output')
407 eb3ca8ca Giorgos Verigakis
    
408 5d1d131b Giorgos Verigakis
    def main(self):
409 eb3ca8ca Giorgos Verigakis
        images = self.client.list_images(self.options.detail)
410 a6757cbc Giorgos Verigakis
        print_items(images)
411 5d1d131b Giorgos Verigakis
412 5d1d131b Giorgos Verigakis
413 a1c50326 Giorgos Verigakis
@command(api='compute')
414 eb3ca8ca Giorgos Verigakis
class image_info(object):
415 6a0b1658 Giorgos Verigakis
    """Get image details"""
416 5d1d131b Giorgos Verigakis
    
417 5d1d131b Giorgos Verigakis
    def main(self, image_id):
418 eb3ca8ca Giorgos Verigakis
        image = self.client.get_image_details(image_id)
419 5d1d131b Giorgos Verigakis
        print_dict(image)
420 5d1d131b Giorgos Verigakis
421 5d1d131b Giorgos Verigakis
422 a1c50326 Giorgos Verigakis
@command(api='compute')
423 eb3ca8ca Giorgos Verigakis
class image_delete(object):
424 6a0b1658 Giorgos Verigakis
    """Delete image"""
425 5d1d131b Giorgos Verigakis
    
426 5d1d131b Giorgos Verigakis
    def main(self, image_id):
427 eb3ca8ca Giorgos Verigakis
        self.client.delete_image(image_id)
428 5d1d131b Giorgos Verigakis
429 5d1d131b Giorgos Verigakis
430 a1c50326 Giorgos Verigakis
@command(api='compute')
431 eb3ca8ca Giorgos Verigakis
class image_meta(object):
432 6a0b1658 Giorgos Verigakis
    """Get image metadata"""
433 5d1d131b Giorgos Verigakis
    
434 5d1d131b Giorgos Verigakis
    def main(self, image_id, key=None):
435 eb3ca8ca Giorgos Verigakis
        reply = self.client.get_image_metadata(image_id, key)
436 5d1d131b Giorgos Verigakis
        print_dict(reply)
437 5d1d131b Giorgos Verigakis
438 5d1d131b Giorgos Verigakis
439 a1c50326 Giorgos Verigakis
@command(api='compute')
440 eb3ca8ca Giorgos Verigakis
class image_addmeta(object):
441 6a0b1658 Giorgos Verigakis
    """Add image metadata"""
442 5d1d131b Giorgos Verigakis
    
443 5d1d131b Giorgos Verigakis
    def main(self, image_id, key, val):
444 eb3ca8ca Giorgos Verigakis
        reply = self.client.create_image_metadata(image_id, key, val)
445 5d1d131b Giorgos Verigakis
        print_dict(reply)
446 5d1d131b Giorgos Verigakis
447 5d1d131b Giorgos Verigakis
448 a1c50326 Giorgos Verigakis
@command(api='compute')
449 eb3ca8ca Giorgos Verigakis
class image_setmeta(object):
450 6a0b1658 Giorgos Verigakis
    """Update image metadata"""
451 5d1d131b Giorgos Verigakis
    
452 5d1d131b Giorgos Verigakis
    def main(self, image_id, key, val):
453 ec52784d Giorgos Verigakis
        metadata = {key: val}
454 eb3ca8ca Giorgos Verigakis
        reply = self.client.update_image_metadata(image_id, **metadata)
455 5d1d131b Giorgos Verigakis
        print_dict(reply)
456 5d1d131b Giorgos Verigakis
457 5d1d131b Giorgos Verigakis
458 a1c50326 Giorgos Verigakis
@command(api='compute')
459 eb3ca8ca Giorgos Verigakis
class image_delmeta(object):
460 6a0b1658 Giorgos Verigakis
    """Delete image metadata"""
461 5d1d131b Giorgos Verigakis
    
462 5d1d131b Giorgos Verigakis
    def main(self, image_id, key):
463 eb3ca8ca Giorgos Verigakis
        self.client.delete_image_metadata(image_id, key)
464 5d1d131b Giorgos Verigakis
465 5d1d131b Giorgos Verigakis
466 b3b32add Giorgos Verigakis
@command(api='cyclades')
467 eb3ca8ca Giorgos Verigakis
class network_list(object):
468 6a0b1658 Giorgos Verigakis
    """List networks"""
469 5d1d131b Giorgos Verigakis
    
470 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
471 eb3ca8ca Giorgos Verigakis
        parser.add_option('-l', dest='detail', action='store_true',
472 eb3ca8ca Giorgos Verigakis
                default=False, help='show detailed output')
473 5d1d131b Giorgos Verigakis
    
474 5d1d131b Giorgos Verigakis
    def main(self):
475 eb3ca8ca Giorgos Verigakis
        networks = self.client.list_networks(self.options.detail)
476 a6757cbc Giorgos Verigakis
        print_items(networks)
477 5d1d131b Giorgos Verigakis
478 5d1d131b Giorgos Verigakis
479 b3b32add Giorgos Verigakis
@command(api='cyclades')
480 eb3ca8ca Giorgos Verigakis
class network_create(object):
481 6a0b1658 Giorgos Verigakis
    """Create a network"""
482 5d1d131b Giorgos Verigakis
    
483 5d1d131b Giorgos Verigakis
    def main(self, name):
484 5d1d131b Giorgos Verigakis
        reply = self.client.create_network(name)
485 5d1d131b Giorgos Verigakis
        print_dict(reply)
486 5d1d131b Giorgos Verigakis
487 5d1d131b Giorgos Verigakis
488 b3b32add Giorgos Verigakis
@command(api='cyclades')
489 eb3ca8ca Giorgos Verigakis
class network_info(object):
490 6a0b1658 Giorgos Verigakis
    """Get network details"""
491 eb3ca8ca Giorgos Verigakis
    
492 5d1d131b Giorgos Verigakis
    def main(self, network_id):
493 5d1d131b Giorgos Verigakis
        network = self.client.get_network_details(network_id)
494 5d1d131b Giorgos Verigakis
        print_dict(network)
495 5d1d131b Giorgos Verigakis
496 5d1d131b Giorgos Verigakis
497 b3b32add Giorgos Verigakis
@command(api='cyclades')
498 eb3ca8ca Giorgos Verigakis
class network_rename(object):
499 6a0b1658 Giorgos Verigakis
    """Update network name"""
500 5d1d131b Giorgos Verigakis
    
501 eb3ca8ca Giorgos Verigakis
    def main(self, network_id, new_name):
502 eb3ca8ca Giorgos Verigakis
        self.client.update_network_name(network_id, new_name)
503 5d1d131b Giorgos Verigakis
504 5d1d131b Giorgos Verigakis
505 b3b32add Giorgos Verigakis
@command(api='cyclades')
506 eb3ca8ca Giorgos Verigakis
class network_delete(object):
507 6a0b1658 Giorgos Verigakis
    """Delete a network"""
508 5d1d131b Giorgos Verigakis
    
509 5d1d131b Giorgos Verigakis
    def main(self, network_id):
510 5d1d131b Giorgos Verigakis
        self.client.delete_network(network_id)
511 5d1d131b Giorgos Verigakis
512 eb3ca8ca Giorgos Verigakis
513 b3b32add Giorgos Verigakis
@command(api='cyclades')
514 eb3ca8ca Giorgos Verigakis
class network_connect(object):
515 6a0b1658 Giorgos Verigakis
    """Connect a server to a network"""
516 5d1d131b Giorgos Verigakis
    
517 5d1d131b Giorgos Verigakis
    def main(self, server_id, network_id):
518 5d1d131b Giorgos Verigakis
        self.client.connect_server(server_id, network_id)
519 5d1d131b Giorgos Verigakis
520 5d1d131b Giorgos Verigakis
521 b3b32add Giorgos Verigakis
@command(api='cyclades')
522 eb3ca8ca Giorgos Verigakis
class network_disconnect(object):
523 6a0b1658 Giorgos Verigakis
    """Disconnect a server from a network"""
524 eb3ca8ca Giorgos Verigakis
    
525 5d1d131b Giorgos Verigakis
    def main(self, server_id, network_id):
526 5d1d131b Giorgos Verigakis
        self.client.disconnect_server(server_id, network_id)
527 5d1d131b Giorgos Verigakis
528 5d1d131b Giorgos Verigakis
529 a1c50326 Giorgos Verigakis
@command(api='image')
530 eb3ca8ca Giorgos Verigakis
class glance_list(object):
531 6a0b1658 Giorgos Verigakis
    """List images"""
532 5d1d131b Giorgos Verigakis
    
533 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
534 a6757cbc Giorgos Verigakis
        parser.add_option('-l', dest='detail', action='store_true',
535 a6757cbc Giorgos Verigakis
                default=False, help='show detailed output')
536 a6757cbc Giorgos Verigakis
        parser.add_option('--container-format', dest='container_format',
537 a6757cbc Giorgos Verigakis
                metavar='FORMAT', help='filter by container format')
538 a6757cbc Giorgos Verigakis
        parser.add_option('--disk-format', dest='disk_format',
539 a6757cbc Giorgos Verigakis
                metavar='FORMAT', help='filter by disk format')
540 a6757cbc Giorgos Verigakis
        parser.add_option('--name', dest='name', metavar='NAME',
541 a6757cbc Giorgos Verigakis
                help='filter by name')
542 a6757cbc Giorgos Verigakis
        parser.add_option('--size-min', dest='size_min', metavar='BYTES',
543 a6757cbc Giorgos Verigakis
                help='filter by minimum size')
544 a6757cbc Giorgos Verigakis
        parser.add_option('--size-max', dest='size_max', metavar='BYTES',
545 a6757cbc Giorgos Verigakis
                help='filter by maximum size')
546 a6757cbc Giorgos Verigakis
        parser.add_option('--status', dest='status', metavar='STATUS',
547 a6757cbc Giorgos Verigakis
                help='filter by status')
548 a6757cbc Giorgos Verigakis
        parser.add_option('--order', dest='order', metavar='FIELD',
549 a6757cbc Giorgos Verigakis
                help='order by FIELD (use a - prefix to reverse order)')
550 a6757cbc Giorgos Verigakis
    
551 eb3ca8ca Giorgos Verigakis
    def main(self):
552 a6757cbc Giorgos Verigakis
        filters = {}
553 a6757cbc Giorgos Verigakis
        for filter in ('container_format', 'disk_format', 'name', 'size_min',
554 a6757cbc Giorgos Verigakis
                       'size_max', 'status'):
555 a6757cbc Giorgos Verigakis
            val = getattr(self.options, filter, None)
556 a6757cbc Giorgos Verigakis
            if val is not None:
557 a6757cbc Giorgos Verigakis
                filters[filter] = val
558 a6757cbc Giorgos Verigakis
        
559 a6757cbc Giorgos Verigakis
        order = self.options.order or ''
560 a6757cbc Giorgos Verigakis
        images = self.client.list_public(self.options.detail, filters=filters,
561 a6757cbc Giorgos Verigakis
                                         order=order)
562 a6757cbc Giorgos Verigakis
        print_items(images, title=('name',))
563 a6757cbc Giorgos Verigakis
564 a6757cbc Giorgos Verigakis
565 a1c50326 Giorgos Verigakis
@command(api='image')
566 8ab2c986 Giorgos Verigakis
class glance_meta(object):
567 6a0b1658 Giorgos Verigakis
    """Get image metadata"""
568 8ab2c986 Giorgos Verigakis
    
569 8ab2c986 Giorgos Verigakis
    def main(self, image_id):
570 8ab2c986 Giorgos Verigakis
        image = self.client.get_meta(image_id)
571 8ab2c986 Giorgos Verigakis
        print_dict(image)
572 8ab2c986 Giorgos Verigakis
573 8ab2c986 Giorgos Verigakis
574 a1c50326 Giorgos Verigakis
@command(api='image')
575 a6757cbc Giorgos Verigakis
class glance_register(object):
576 6a0b1658 Giorgos Verigakis
    """Register an image"""
577 a6757cbc Giorgos Verigakis
    
578 77eaa0be Giorgos Verigakis
    def update_parser(self, parser):
579 a6757cbc Giorgos Verigakis
        parser.add_option('--checksum', dest='checksum', metavar='CHECKSUM',
580 a6757cbc Giorgos Verigakis
                help='set image checksum')
581 a6757cbc Giorgos Verigakis
        parser.add_option('--container-format', dest='container_format',
582 a6757cbc Giorgos Verigakis
                metavar='FORMAT', help='set container format')
583 a6757cbc Giorgos Verigakis
        parser.add_option('--disk-format', dest='disk_format',
584 a6757cbc Giorgos Verigakis
                metavar='FORMAT', help='set disk format')
585 a6757cbc Giorgos Verigakis
        parser.add_option('--id', dest='id',
586 a6757cbc Giorgos Verigakis
                metavar='ID', help='set image ID')
587 a6757cbc Giorgos Verigakis
        parser.add_option('--owner', dest='owner',
588 a6757cbc Giorgos Verigakis
                metavar='USER', help='set image owner (admin only)')
589 a6757cbc Giorgos Verigakis
        parser.add_option('--property', dest='properties', action='append',
590 a6757cbc Giorgos Verigakis
                metavar='KEY=VAL',
591 a6757cbc Giorgos Verigakis
                help='add a property (can be used multiple times)')
592 a6757cbc Giorgos Verigakis
        parser.add_option('--public', dest='is_public', action='store_true',
593 a6757cbc Giorgos Verigakis
                help='mark image as public')
594 a6757cbc Giorgos Verigakis
        parser.add_option('--size', dest='size', metavar='SIZE',
595 a6757cbc Giorgos Verigakis
                help='set image size')
596 a6757cbc Giorgos Verigakis
    
597 a6757cbc Giorgos Verigakis
    def main(self, name, location):
598 a6757cbc Giorgos Verigakis
        params = {}
599 a6757cbc Giorgos Verigakis
        for key in ('checksum', 'container_format', 'disk_format', 'id',
600 6a0b1658 Giorgos Verigakis
                    'owner', 'size'):
601 a6757cbc Giorgos Verigakis
            val = getattr(self.options, key)
602 a6757cbc Giorgos Verigakis
            if val is not None:
603 a6757cbc Giorgos Verigakis
                params[key] = val
604 a6757cbc Giorgos Verigakis
        
605 6a0b1658 Giorgos Verigakis
        if self.options.is_public:
606 6a0b1658 Giorgos Verigakis
            params['is_public'] = 'true'
607 6a0b1658 Giorgos Verigakis
        
608 a6757cbc Giorgos Verigakis
        properties = {}
609 a6757cbc Giorgos Verigakis
        for property in self.options.properties or []:
610 a6757cbc Giorgos Verigakis
            key, sep, val = property.partition('=')
611 a6757cbc Giorgos Verigakis
            if not sep:
612 a6757cbc Giorgos Verigakis
                log.error("Invalid property '%s'", property)
613 a6757cbc Giorgos Verigakis
                return 1
614 a6757cbc Giorgos Verigakis
            properties[key.strip()] = val.strip()
615 a6757cbc Giorgos Verigakis
        
616 a6757cbc Giorgos Verigakis
        self.client.register(name, location, params, properties)
617 eb3ca8ca Giorgos Verigakis
618 eb3ca8ca Giorgos Verigakis
619 a1c50326 Giorgos Verigakis
@command(api='image')
620 8ab2c986 Giorgos Verigakis
class glance_members(object):
621 6a0b1658 Giorgos Verigakis
    """Get image members"""
622 8ab2c986 Giorgos Verigakis
    
623 8ab2c986 Giorgos Verigakis
    def main(self, image_id):
624 8ab2c986 Giorgos Verigakis
        members = self.client.list_members(image_id)
625 8ab2c986 Giorgos Verigakis
        for member in members:
626 8ab2c986 Giorgos Verigakis
            print member['member_id']
627 8ab2c986 Giorgos Verigakis
628 8ab2c986 Giorgos Verigakis
629 a1c50326 Giorgos Verigakis
@command(api='image')
630 8ab2c986 Giorgos Verigakis
class glance_shared(object):
631 6a0b1658 Giorgos Verigakis
    """List shared images"""
632 8ab2c986 Giorgos Verigakis
    
633 8ab2c986 Giorgos Verigakis
    def main(self, member):
634 8ab2c986 Giorgos Verigakis
        images = self.client.list_shared(member)
635 8ab2c986 Giorgos Verigakis
        for image in images:
636 8ab2c986 Giorgos Verigakis
            print image['image_id']
637 8ab2c986 Giorgos Verigakis
638 8ab2c986 Giorgos Verigakis
639 a1c50326 Giorgos Verigakis
@command(api='image')
640 8ab2c986 Giorgos Verigakis
class glance_addmember(object):
641 6a0b1658 Giorgos Verigakis
    """Add a member to an image"""
642 8ab2c986 Giorgos Verigakis
    
643 8ab2c986 Giorgos Verigakis
    def main(self, image_id, member):
644 8ab2c986 Giorgos Verigakis
        self.client.add_member(image_id, member)
645 8ab2c986 Giorgos Verigakis
646 8ab2c986 Giorgos Verigakis
647 a1c50326 Giorgos Verigakis
@command(api='image')
648 8ab2c986 Giorgos Verigakis
class glance_delmember(object):
649 6a0b1658 Giorgos Verigakis
    """Remove a member from an image"""
650 8ab2c986 Giorgos Verigakis
    
651 8ab2c986 Giorgos Verigakis
    def main(self, image_id, member):
652 8ab2c986 Giorgos Verigakis
        self.client.remove_member(image_id, member)
653 8ab2c986 Giorgos Verigakis
654 8ab2c986 Giorgos Verigakis
655 a1c50326 Giorgos Verigakis
@command(api='image')
656 8ab2c986 Giorgos Verigakis
class glance_setmembers(object):
657 6a0b1658 Giorgos Verigakis
    """Set the members of an image"""
658 8ab2c986 Giorgos Verigakis
    
659 8ab2c986 Giorgos Verigakis
    def main(self, image_id, *member):
660 8ab2c986 Giorgos Verigakis
        self.client.set_members(image_id, member)
661 8ab2c986 Giorgos Verigakis
662 8ab2c986 Giorgos Verigakis
663 2bcb595a Giorgos Verigakis
class _store_account_command(object):
664 2bcb595a Giorgos Verigakis
    """Base class for account level storage commands"""
665 d2cea1e2 Giorgos Verigakis
    
666 2bcb595a Giorgos Verigakis
    def update_parser(self, parser):
667 60560d7c Giorgos Verigakis
        parser.add_option('--account', dest='account', metavar='NAME',
668 6a0b1658 Giorgos Verigakis
                          help="Specify an account to use")
669 d2cea1e2 Giorgos Verigakis
    
670 6a0b1658 Giorgos Verigakis
    def progress(self, message):
671 6a0b1658 Giorgos Verigakis
        """Return a generator function to be used for progress tracking"""
672 6a0b1658 Giorgos Verigakis
        
673 6a0b1658 Giorgos Verigakis
        MESSAGE_LENGTH = 25
674 6a0b1658 Giorgos Verigakis
        MAX_PROGRESS_LENGTH = 32
675 6a0b1658 Giorgos Verigakis
        
676 6a0b1658 Giorgos Verigakis
        def progress_gen(n):
677 6a0b1658 Giorgos Verigakis
            msg = message.ljust(MESSAGE_LENGTH)
678 6a0b1658 Giorgos Verigakis
            width = min(n, MAX_PROGRESS_LENGTH)
679 6a0b1658 Giorgos Verigakis
            hide = self.config.get('global', 'silent') or (n < 2)
680 6a0b1658 Giorgos Verigakis
            for i in progress.bar(range(n), msg, width, hide):
681 6a0b1658 Giorgos Verigakis
                yield
682 6a0b1658 Giorgos Verigakis
            yield
683 176894c1 Giorgos Verigakis
        
684 6a0b1658 Giorgos Verigakis
        return progress_gen
685 6a0b1658 Giorgos Verigakis
    
686 6a0b1658 Giorgos Verigakis
    def main(self):
687 6a0b1658 Giorgos Verigakis
        if self.options.account is not None:
688 6a0b1658 Giorgos Verigakis
            self.client.account = self.options.account
689 2bcb595a Giorgos Verigakis
690 2bcb595a Giorgos Verigakis
691 2bcb595a Giorgos Verigakis
class _store_container_command(_store_account_command):
692 2bcb595a Giorgos Verigakis
    """Base class for container level storage commands"""
693 2bcb595a Giorgos Verigakis
    
694 2bcb595a Giorgos Verigakis
    def update_parser(self, parser):
695 2bcb595a Giorgos Verigakis
        super(_store_container_command, self).update_parser(parser)
696 2bcb595a Giorgos Verigakis
        parser.add_option('--container', dest='container', metavar='NAME',
697 2bcb595a Giorgos Verigakis
                          help="Specify a container to use")
698 2bcb595a Giorgos Verigakis
    
699 2bcb595a Giorgos Verigakis
    def main(self):
700 2bcb595a Giorgos Verigakis
        super(_store_container_command, self).main()
701 6a0b1658 Giorgos Verigakis
        if self.options.container is not None:
702 6a0b1658 Giorgos Verigakis
            self.client.container = self.options.container
703 176894c1 Giorgos Verigakis
704 176894c1 Giorgos Verigakis
705 176894c1 Giorgos Verigakis
@command(api='storage')
706 2bcb595a Giorgos Verigakis
class store_create(_store_account_command):
707 6a0b1658 Giorgos Verigakis
    """Create a container"""
708 60560d7c Giorgos Verigakis
    
709 60560d7c Giorgos Verigakis
    def main(self, container):
710 6a0b1658 Giorgos Verigakis
        if self.options.account:
711 6a0b1658 Giorgos Verigakis
            self.client.account = self.options.account
712 60560d7c Giorgos Verigakis
        self.client.create_container(container)
713 60560d7c Giorgos Verigakis
714 60560d7c Giorgos Verigakis
715 60560d7c Giorgos Verigakis
@command(api='storage')
716 2bcb595a Giorgos Verigakis
class store_container(_store_account_command):
717 6a0b1658 Giorgos Verigakis
    """Get container info"""
718 176894c1 Giorgos Verigakis
    
719 6a0b1658 Giorgos Verigakis
    def main(self, container):
720 6a0b1658 Giorgos Verigakis
        if self.options.account:
721 6a0b1658 Giorgos Verigakis
            self.client.account = self.options.account
722 6a0b1658 Giorgos Verigakis
        reply = self.client.get_container_meta(container)
723 d2cea1e2 Giorgos Verigakis
        print_dict(reply)
724 d2cea1e2 Giorgos Verigakis
725 d2cea1e2 Giorgos Verigakis
726 d2cea1e2 Giorgos Verigakis
@command(api='storage')
727 2bcb595a Giorgos Verigakis
class store_list(_store_container_command):
728 2bcb595a Giorgos Verigakis
    """List objects"""
729 2bcb595a Giorgos Verigakis
    
730 2bcb595a Giorgos Verigakis
    def format_size(self, size):
731 2bcb595a Giorgos Verigakis
        units = ('B', 'K', 'M', 'G', 'T')
732 2bcb595a Giorgos Verigakis
        size = float(size)
733 2bcb595a Giorgos Verigakis
        for unit in units:
734 2bcb595a Giorgos Verigakis
            if size <= 1024:
735 2bcb595a Giorgos Verigakis
                break
736 2bcb595a Giorgos Verigakis
            size /= 1024
737 2bcb595a Giorgos Verigakis
        s = ('%.1f' % size).rstrip('.0')
738 2bcb595a Giorgos Verigakis
        return s + unit
739 2bcb595a Giorgos Verigakis
    
740 2bcb595a Giorgos Verigakis
    
741 2bcb595a Giorgos Verigakis
    def main(self, path=''):
742 2bcb595a Giorgos Verigakis
        super(store_list, self).main()
743 2bcb595a Giorgos Verigakis
        for object in self.client.list_objects():
744 2bcb595a Giorgos Verigakis
            size = self.format_size(object['bytes'])
745 2bcb595a Giorgos Verigakis
            print '%6s %s' % (size, object['name'])
746 2bcb595a Giorgos Verigakis
        
747 2bcb595a Giorgos Verigakis
748 2bcb595a Giorgos Verigakis
@command(api='storage')
749 2bcb595a Giorgos Verigakis
class store_upload(_store_container_command):
750 6a0b1658 Giorgos Verigakis
    """Upload a file"""
751 a1c50326 Giorgos Verigakis
    
752 a1c50326 Giorgos Verigakis
    def main(self, path, remote_path=None):
753 6a0b1658 Giorgos Verigakis
        super(store_upload, self).main()
754 6a0b1658 Giorgos Verigakis
        
755 a1c50326 Giorgos Verigakis
        if remote_path is None:
756 a1c50326 Giorgos Verigakis
            remote_path = basename(path)
757 a1c50326 Giorgos Verigakis
        with open(path) as f:
758 6a0b1658 Giorgos Verigakis
            hash_cb = self.progress('Calculating block hashes')
759 6a0b1658 Giorgos Verigakis
            upload_cb = self.progress('Uploading blocks')
760 6a0b1658 Giorgos Verigakis
            self.client.create_object(remote_path, f, hash_cb=hash_cb,
761 6a0b1658 Giorgos Verigakis
                                      upload_cb=upload_cb)
762 d2cea1e2 Giorgos Verigakis
763 d2cea1e2 Giorgos Verigakis
764 d2cea1e2 Giorgos Verigakis
@command(api='storage')
765 2bcb595a Giorgos Verigakis
class store_download(_store_container_command):
766 6a0b1658 Giorgos Verigakis
    """Download a file"""
767 6a0b1658 Giorgos Verigakis
        
768 6a0b1658 Giorgos Verigakis
    def main(self, remote_path, local_path='-'):
769 6a0b1658 Giorgos Verigakis
        super(store_download, self).main()
770 6a0b1658 Giorgos Verigakis
        
771 6a0b1658 Giorgos Verigakis
        f, size = self.client.get_object(remote_path)
772 176894c1 Giorgos Verigakis
        out = open(local_path, 'w') if local_path != '-' else stdout
773 6a0b1658 Giorgos Verigakis
        
774 6a0b1658 Giorgos Verigakis
        blocksize = 4 * 1024**2
775 6a0b1658 Giorgos Verigakis
        nblocks = 1 + (size - 1) // blocksize
776 6a0b1658 Giorgos Verigakis
        
777 6a0b1658 Giorgos Verigakis
        cb = self.progress('Downloading blocks') if local_path != '-' else None
778 6a0b1658 Giorgos Verigakis
        if cb:
779 6a0b1658 Giorgos Verigakis
            gen = cb(nblocks)
780 6a0b1658 Giorgos Verigakis
            gen.next()
781 6a0b1658 Giorgos Verigakis
        
782 6a0b1658 Giorgos Verigakis
        data = f.read(blocksize)
783 176894c1 Giorgos Verigakis
        while data:
784 176894c1 Giorgos Verigakis
            out.write(data)
785 6a0b1658 Giorgos Verigakis
            data = f.read(blocksize)
786 6a0b1658 Giorgos Verigakis
            if cb:
787 6a0b1658 Giorgos Verigakis
                gen.next()
788 176894c1 Giorgos Verigakis
789 176894c1 Giorgos Verigakis
790 176894c1 Giorgos Verigakis
@command(api='storage')
791 2bcb595a Giorgos Verigakis
class store_delete(_store_container_command):
792 6a0b1658 Giorgos Verigakis
    """Delete a file"""
793 d2cea1e2 Giorgos Verigakis
    
794 d2cea1e2 Giorgos Verigakis
    def main(self, path):
795 176894c1 Giorgos Verigakis
        store_command.main(self)
796 d2cea1e2 Giorgos Verigakis
        self.client.delete_object(path)
797 a1c50326 Giorgos Verigakis
798 a1c50326 Giorgos Verigakis
799 f3ddb705 Giorgos Verigakis
def print_groups():
800 f3ddb705 Giorgos Verigakis
    puts('\nGroups:')
801 f3ddb705 Giorgos Verigakis
    with indent(2):
802 f3ddb705 Giorgos Verigakis
        for group in _commands:
803 f3ddb705 Giorgos Verigakis
            description = GROUPS.get(group, '')
804 f3ddb705 Giorgos Verigakis
            puts(columns([group, 12], [description, 60]))
805 eb3ca8ca Giorgos Verigakis
806 eb3ca8ca Giorgos Verigakis
807 f3ddb705 Giorgos Verigakis
def print_commands(group):
808 f3ddb705 Giorgos Verigakis
    description = GROUPS.get(group, '')
809 f3ddb705 Giorgos Verigakis
    if description:
810 f3ddb705 Giorgos Verigakis
        puts('\n' + description)
811 f3ddb705 Giorgos Verigakis
    
812 f3ddb705 Giorgos Verigakis
    puts('\nCommands:')
813 f3ddb705 Giorgos Verigakis
    with indent(2):
814 f3ddb705 Giorgos Verigakis
        for name, cls in _commands[group].items():
815 f3ddb705 Giorgos Verigakis
            puts(columns([name, 12], [cls.description, 60]))
816 5d1d131b Giorgos Verigakis
817 5d1d131b Giorgos Verigakis
818 6a0b1658 Giorgos Verigakis
def add_handler(name, level, prefix=''):
819 6a0b1658 Giorgos Verigakis
    h = logging.StreamHandler()
820 6a0b1658 Giorgos Verigakis
    fmt = logging.Formatter(prefix + '%(message)s')
821 6a0b1658 Giorgos Verigakis
    h.setFormatter(fmt)
822 6a0b1658 Giorgos Verigakis
    logger = logging.getLogger(name)
823 6a0b1658 Giorgos Verigakis
    logger.addHandler(h)
824 6a0b1658 Giorgos Verigakis
    logger.setLevel(level)
825 6a0b1658 Giorgos Verigakis
826 6a0b1658 Giorgos Verigakis
827 5d1d131b Giorgos Verigakis
def main():
828 eb3ca8ca Giorgos Verigakis
    parser = OptionParser(add_help_option=False)
829 eb3ca8ca Giorgos Verigakis
    parser.usage = '%prog <group> <command> [options]'
830 f3ddb705 Giorgos Verigakis
    parser.add_option('-h', '--help', dest='help', action='store_true',
831 f3ddb705 Giorgos Verigakis
                      default=False,
832 f3ddb705 Giorgos Verigakis
                      help="Show this help message and exit")
833 f3ddb705 Giorgos Verigakis
    parser.add_option('--config', dest='config', metavar='PATH',
834 f3ddb705 Giorgos Verigakis
                      help="Specify the path to the configuration file")
835 6a0b1658 Giorgos Verigakis
    parser.add_option('-d', '--debug', dest='debug', action='store_true',
836 6a0b1658 Giorgos Verigakis
                      default=False,
837 6a0b1658 Giorgos Verigakis
                      help="Include debug output")
838 f3ddb705 Giorgos Verigakis
    parser.add_option('-i', '--include', dest='include', action='store_true',
839 f3ddb705 Giorgos Verigakis
                      default=False,
840 f3ddb705 Giorgos Verigakis
                      help="Include protocol headers in the output")
841 f3ddb705 Giorgos Verigakis
    parser.add_option('-s', '--silent', dest='silent', action='store_true',
842 f3ddb705 Giorgos Verigakis
                      default=False,
843 f3ddb705 Giorgos Verigakis
                      help="Silent mode, don't output anything")
844 f3ddb705 Giorgos Verigakis
    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
845 f3ddb705 Giorgos Verigakis
                      default=False,
846 f3ddb705 Giorgos Verigakis
                      help="Make the operation more talkative")
847 f3ddb705 Giorgos Verigakis
    parser.add_option('-V', '--version', dest='version', action='store_true',
848 f3ddb705 Giorgos Verigakis
                      default=False,
849 f3ddb705 Giorgos Verigakis
                      help="Show version number and quit")
850 21cf6792 Giorgos Verigakis
    parser.add_option('-o', dest='options', action='append',
851 f3ddb705 Giorgos Verigakis
                      default=[], metavar="KEY=VAL",
852 f3ddb705 Giorgos Verigakis
                      help="Override a config values")
853 f3ddb705 Giorgos Verigakis
    
854 f3ddb705 Giorgos Verigakis
    if args.contains(['-V', '--version']):
855 f3ddb705 Giorgos Verigakis
        import kamaki
856 f3ddb705 Giorgos Verigakis
        print "kamaki %s" % kamaki.__version__
857 f3ddb705 Giorgos Verigakis
        exit(0)
858 f3ddb705 Giorgos Verigakis
    
859 f3ddb705 Giorgos Verigakis
    if '--config' in args:
860 f3ddb705 Giorgos Verigakis
        config_path = args.grouped['--config'].get(0)
861 f3ddb705 Giorgos Verigakis
    else:
862 f3ddb705 Giorgos Verigakis
        config_path = os.environ.get(CONFIG_ENV, CONFIG_PATH)
863 21cf6792 Giorgos Verigakis
    
864 f3ddb705 Giorgos Verigakis
    config = Config(config_path)
865 eb3ca8ca Giorgos Verigakis
    
866 f3ddb705 Giorgos Verigakis
    for option in args.grouped.get('-o', []):
867 f3ddb705 Giorgos Verigakis
        keypath, sep, val = option.partition('=')
868 f3ddb705 Giorgos Verigakis
        if not sep:
869 f3ddb705 Giorgos Verigakis
            log.error("Invalid option '%s'", option)
870 f3ddb705 Giorgos Verigakis
            exit(1)
871 f3ddb705 Giorgos Verigakis
        section, sep, key = keypath.partition('.')
872 f3ddb705 Giorgos Verigakis
        if not sep:
873 f3ddb705 Giorgos Verigakis
            log.error("Invalid option '%s'", option)
874 f3ddb705 Giorgos Verigakis
            exit(1)
875 f3ddb705 Giorgos Verigakis
        config.override(section.strip(), key.strip(), val.strip())
876 f3ddb705 Giorgos Verigakis
    
877 f3ddb705 Giorgos Verigakis
    apis = set(['config'])
878 f3ddb705 Giorgos Verigakis
    for api in ('compute', 'image', 'storage'):
879 f3ddb705 Giorgos Verigakis
        if config.getboolean(api, 'enable'):
880 f3ddb705 Giorgos Verigakis
            apis.add(api)
881 f3ddb705 Giorgos Verigakis
    if config.getboolean('compute', 'cyclades_extensions'):
882 f3ddb705 Giorgos Verigakis
        apis.add('cyclades')
883 f3ddb705 Giorgos Verigakis
    if config.getboolean('storage', 'pithos_extensions'):
884 f3ddb705 Giorgos Verigakis
        apis.add('pithos')
885 f3ddb705 Giorgos Verigakis
    
886 f3ddb705 Giorgos Verigakis
    # Remove commands that belong to APIs that are not included
887 eb3ca8ca Giorgos Verigakis
    for group, group_commands in _commands.items():
888 eb3ca8ca Giorgos Verigakis
        for name, cls in group_commands.items():
889 f3ddb705 Giorgos Verigakis
            if cls.api not in apis:
890 f3ddb705 Giorgos Verigakis
                del group_commands[name]
891 f3ddb705 Giorgos Verigakis
        if not group_commands:
892 f3ddb705 Giorgos Verigakis
            del _commands[group]
893 eb3ca8ca Giorgos Verigakis
    
894 f3ddb705 Giorgos Verigakis
    if not args.grouped['_']:
895 eb3ca8ca Giorgos Verigakis
        parser.print_help()
896 f3ddb705 Giorgos Verigakis
        print_groups()
897 a1c50326 Giorgos Verigakis
        exit(0)
898 eb3ca8ca Giorgos Verigakis
    
899 f3ddb705 Giorgos Verigakis
    group = args.grouped['_'][0]
900 eb3ca8ca Giorgos Verigakis
    
901 f3ddb705 Giorgos Verigakis
    if group not in _commands:
902 eb3ca8ca Giorgos Verigakis
        parser.print_help()
903 f3ddb705 Giorgos Verigakis
        print_groups()
904 a1c50326 Giorgos Verigakis
        exit(1)
905 eb3ca8ca Giorgos Verigakis
    
906 eb3ca8ca Giorgos Verigakis
    parser.usage = '%%prog %s <command> [options]' % group
907 eb3ca8ca Giorgos Verigakis
    
908 f3ddb705 Giorgos Verigakis
    if len(args.grouped['_']) == 1:
909 eb3ca8ca Giorgos Verigakis
        parser.print_help()
910 f3ddb705 Giorgos Verigakis
        print_commands(group)
911 a1c50326 Giorgos Verigakis
        exit(0)
912 eb3ca8ca Giorgos Verigakis
    
913 f3ddb705 Giorgos Verigakis
    name = args.grouped['_'][1]
914 5d1d131b Giorgos Verigakis
    
915 f3ddb705 Giorgos Verigakis
    if name not in _commands[group]:
916 eb3ca8ca Giorgos Verigakis
        parser.print_help()
917 f3ddb705 Giorgos Verigakis
        print_commands(group)
918 a1c50326 Giorgos Verigakis
        exit(1)
919 5d1d131b Giorgos Verigakis
    
920 f3ddb705 Giorgos Verigakis
    cmd = _commands[group][name]()
921 5d1d131b Giorgos Verigakis
    
922 f3ddb705 Giorgos Verigakis
    syntax = '%s [options]' % cmd.syntax if cmd.syntax else '[options]'
923 eb3ca8ca Giorgos Verigakis
    parser.usage = '%%prog %s %s %s' % (group, name, syntax)
924 f3ddb705 Giorgos Verigakis
    parser.description = cmd.description
925 eb3ca8ca Giorgos Verigakis
    parser.epilog = ''
926 f3ddb705 Giorgos Verigakis
    if hasattr(cmd, 'update_parser'):
927 f3ddb705 Giorgos Verigakis
        cmd.update_parser(parser)
928 eb3ca8ca Giorgos Verigakis
    
929 6a0b1658 Giorgos Verigakis
    options, arguments = parser.parse_args(argv)
930 6a0b1658 Giorgos Verigakis
    
931 6a0b1658 Giorgos Verigakis
    if options.help:
932 eb3ca8ca Giorgos Verigakis
        parser.print_help()
933 a1c50326 Giorgos Verigakis
        exit(0)
934 eb3ca8ca Giorgos Verigakis
    
935 6a0b1658 Giorgos Verigakis
    if options.silent:
936 6a0b1658 Giorgos Verigakis
        add_handler('', logging.CRITICAL)
937 6a0b1658 Giorgos Verigakis
    elif options.debug:
938 6a0b1658 Giorgos Verigakis
        add_handler('requests', logging.INFO, prefix='* ')
939 6a0b1658 Giorgos Verigakis
        add_handler('clients.send', logging.DEBUG, prefix='> ')
940 6a0b1658 Giorgos Verigakis
        add_handler('clients.recv', logging.DEBUG, prefix='< ')
941 6a0b1658 Giorgos Verigakis
    elif options.verbose:
942 6a0b1658 Giorgos Verigakis
        add_handler('requests', logging.INFO, prefix='* ')
943 6a0b1658 Giorgos Verigakis
        add_handler('clients.send', logging.INFO, prefix='> ')
944 6a0b1658 Giorgos Verigakis
        add_handler('clients.recv', logging.INFO, prefix='< ')
945 6a0b1658 Giorgos Verigakis
    elif options.include:
946 6a0b1658 Giorgos Verigakis
        add_handler('clients.recv', logging.INFO)
947 6a0b1658 Giorgos Verigakis
    else:
948 6a0b1658 Giorgos Verigakis
        add_handler('', logging.WARNING)
949 5d1d131b Giorgos Verigakis
    
950 f3ddb705 Giorgos Verigakis
    api = cmd.api
951 6a0b1658 Giorgos Verigakis
    if api in ('compute', 'cyclades'):
952 6a0b1658 Giorgos Verigakis
        url = config.get('compute', 'url')
953 6a0b1658 Giorgos Verigakis
        token = config.get('compute', 'token') or config.get('global', 'token')
954 6a0b1658 Giorgos Verigakis
        if config.getboolean('compute', 'cyclades_extensions'):
955 6a0b1658 Giorgos Verigakis
            cmd.client = clients.cyclades(url, token)
956 6a0b1658 Giorgos Verigakis
        else:
957 6a0b1658 Giorgos Verigakis
            cmd.client = clients.compute(url, token)
958 6a0b1658 Giorgos Verigakis
    elif api in ('storage', 'pithos'):
959 6a0b1658 Giorgos Verigakis
        url = config.get('storage', 'url')
960 6a0b1658 Giorgos Verigakis
        token = config.get('storage', 'token') or config.get('global', 'token')
961 6a0b1658 Giorgos Verigakis
        account = config.get('storage', 'account')
962 6a0b1658 Giorgos Verigakis
        container = config.get('storage', 'container')
963 6a0b1658 Giorgos Verigakis
        if config.getboolean('storage', 'pithos_extensions'):
964 6a0b1658 Giorgos Verigakis
            cmd.client = clients.pithos(url, token, account, container)
965 6a0b1658 Giorgos Verigakis
        else:
966 6a0b1658 Giorgos Verigakis
            cmd.client = clients.storage(url, token, account, container)
967 6a0b1658 Giorgos Verigakis
    elif api == 'image':
968 6a0b1658 Giorgos Verigakis
        url = config.get('image', 'url')
969 6a0b1658 Giorgos Verigakis
        token = config.get('image', 'token') or config.get('global', 'token')
970 6a0b1658 Giorgos Verigakis
        cmd.client = clients.image(url, token)
971 6a0b1658 Giorgos Verigakis
    
972 6a0b1658 Giorgos Verigakis
    cmd.options = options
973 6a0b1658 Giorgos Verigakis
    cmd.config = config
974 6a0b1658 Giorgos Verigakis
    
975 5d1d131b Giorgos Verigakis
    try:
976 6a0b1658 Giorgos Verigakis
        ret = cmd.main(*arguments[3:])
977 a1c50326 Giorgos Verigakis
        exit(ret)
978 a6757cbc Giorgos Verigakis
    except TypeError as e:
979 a6757cbc Giorgos Verigakis
        if e.args and e.args[0].startswith('main()'):
980 a6757cbc Giorgos Verigakis
            parser.print_help()
981 a1c50326 Giorgos Verigakis
            exit(1)
982 a6757cbc Giorgos Verigakis
        else:
983 a6757cbc Giorgos Verigakis
            raise
984 6a0b1658 Giorgos Verigakis
    except clients.ClientError as err:
985 6a0b1658 Giorgos Verigakis
        if err.status == 404:
986 6a0b1658 Giorgos Verigakis
            color = yellow
987 6a0b1658 Giorgos Verigakis
        elif 500 <= err.status < 600:
988 6a0b1658 Giorgos Verigakis
            color = magenta
989 6a0b1658 Giorgos Verigakis
        else:
990 6a0b1658 Giorgos Verigakis
            color = red
991 6a0b1658 Giorgos Verigakis
        
992 6a0b1658 Giorgos Verigakis
        puts_err(color(err.message))
993 6a0b1658 Giorgos Verigakis
        if err.details and (options.verbose or options.debug):
994 6a0b1658 Giorgos Verigakis
            puts_err(err.details)
995 a1c50326 Giorgos Verigakis
        exit(2)
996 6a0b1658 Giorgos Verigakis
    except ConnectionError as err:
997 6a0b1658 Giorgos Verigakis
        puts_err(red("Connection error"))
998 6a0b1658 Giorgos Verigakis
        exit(1)
999 5d1d131b Giorgos Verigakis
1000 5d1d131b Giorgos Verigakis
1001 5d1d131b Giorgos Verigakis
if __name__ == '__main__':
1002 a1c50326 Giorgos Verigakis
    main()