Revision 14af08c0

/dev/null
1
#!/usr/bin/env python
2

  
3
# Copyright 2011-2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

  
36
from __future__ import print_function
37

  
38
import gevent.monkey
39
#Monkey-patch everything for gevent early on
40
gevent.monkey.patch_all()
41

  
42
import inspect
43
import logging
44
import sys
45

  
46
from argparse import ArgumentParser
47
from base64 import b64encode
48
from os.path import abspath, basename, exists
49
from sys import exit, stdout, stderr
50

  
51
try:
52
    from collections import OrderedDict
53
except ImportError:
54
    from ordereddict import OrderedDict
55

  
56
from colors import magenta, red, yellow, bold
57
#from progress.bar import IncrementalBar
58
#from requests.exceptions import ConnectionError
59

  
60
from . import clients
61
from .config import Config
62
#from .utils import print_list, print_dict, print_items, format_size
63

  
64
_commands = OrderedDict()
65

  
66
GROUPS = {}
67
CLI_LOCATIONS = ['', 'kamaki', 'kamaki.commands']
68

  
69
class CLIError(Exception):
70
    def __init__(self, message, status=0, details='', importance=0):
71
        """importance is set by the raiser
72
        0 is the lowest possible importance
73
        Suggested values: 0, 1, 2, 3
74
        """
75
        super(CLIError, self).__init__(message, status, details)
76
        self.message = message
77
        self.status = status
78
        self.details = details
79
        self.importance = importance
80

  
81
    def __unicode__(self):
82
        return unicode(self.message)
83

  
84
def command(group=None, name=None, syntax=None):
85
    """Class decorator that registers a class as a CLI command."""
86

  
87
    def decorator(cls):
88
        grp, sep, cmd = cls.__name__.partition('_')
89
        if not sep:
90
            grp, cmd = None, cls.__name__
91

  
92
        #cls.api = api
93
        cls.group = group or grp
94
        cls.name = name or cmd
95

  
96
        short_description, sep, long_description = cls.__doc__.partition('\n')
97
        cls.description = short_description
98
        cls.long_description = long_description or short_description
99

  
100
        cls.syntax = syntax
101
        if cls.syntax is None:
102
            # Generate a syntax string based on main's arguments
103
            spec = inspect.getargspec(cls.main.im_func)
104
            args = spec.args[1:]
105
            n = len(args) - len(spec.defaults or ())
106
            required = ' '.join('<%s>' % x.replace('____', '[:').replace('___', ':').replace('__',']').replace('_', ' ') for x in args[:n])
107
            optional = ' '.join('[%s]' % x.replace('____', '[:').replace('___', ':').replace('__', ']').replace('_', ' ') for x in args[n:])
108
            cls.syntax = ' '.join(x for x in [required, optional] if x)
109
            if spec.varargs:
110
                cls.syntax += ' <%s ...>' % spec.varargs
111

  
112
        if cls.group not in _commands:
113
            _commands[cls.group] = OrderedDict()
114
        _commands[cls.group][cls.name] = cls
115
        return cls
116
    return decorator
117

  
118
def set_api_description(api, description):
119
    """Method to be called by api CLIs
120
    Each CLI can set more than one api descriptions"""
121
    GROUPS[api] = description
122

  
123
def main():
124

  
125
    def print_groups():
126
        print('\nGroups:')
127
        for group in _commands:
128
            description = GROUPS.get(group, '')
129
            print(' ', group.ljust(12), description)
130

  
131
    def print_commands(group):
132
        description = GROUPS.get(group, '')
133
        if description:
134
            print('\n' + description)
135

  
136
        print('\nCommands:')
137
        for name, cls in _commands[group].items():
138
            print(' ', name.ljust(14), cls.description)
139

  
140
    def manage_logging_handlers(args):
141
        """This is mostly to handle logging for clients package"""
142

  
143
        def add_handler(name, level, prefix=''):
144
            h = logging.StreamHandler()
145
            fmt = logging.Formatter(prefix + '%(message)s')
146
            h.setFormatter(fmt)
147
            logger = logging.getLogger(name)
148
            logger.addHandler(h)
149
            logger.setLevel(level)
150

  
151
        if args.silent:
152
            add_handler('', logging.CRITICAL)
153
        elif args.debug:
154
            add_handler('requests', logging.INFO, prefix='* ')
155
            add_handler('clients.send', logging.DEBUG, prefix='> ')
156
            add_handler('clients.recv', logging.DEBUG, prefix='< ')
157
        elif args.verbose:
158
            add_handler('requests', logging.INFO, prefix='* ')
159
            add_handler('clients.send', logging.INFO, prefix='> ')
160
            add_handler('clients.recv', logging.INFO, prefix='< ')
161
        elif args.include:
162
            add_handler('clients.recv', logging.INFO)
163
        else:
164
            add_handler('', logging.WARNING)
165

  
166
    def load_groups(config):
167
        """load groups and import CLIs and Modules"""
168
        loaded_modules = {}
169
        for api in config.apis():
170
            api_cli = config.get(api, 'cli')
171
            if None == api_cli or len(api_cli)==0:
172
                print('Warnig: No Command Line Interface "%s" given for API "%s"'%(api_cli, api))
173
                print('\t(cli option in config file)')
174
                continue
175
            if not loaded_modules.has_key(api_cli):
176
                loaded_modules[api_cli] = False
177
                for location in CLI_LOCATIONS:
178
                    location += api_cli if location == '' else '.%s'%api_cli
179
                    try:
180
                        __import__(location)
181
                        loaded_modules[api_cli] = True
182
                        break
183
                    except ImportError:
184
                        pass
185
                if not loaded_modules[api_cli]:
186
                    print('Warning: failed to load Command Line Interface "%s" for API "%s"'%(api_cli, api))
187
                    print('\t(No suitable cli in known paths)')
188
                    continue
189
            if not GROUPS.has_key(api):
190
                GROUPS[api] = 'No description (interface: %s)'%api_cli
191

  
192
    def init_parser(exe):
193
        parser = ArgumentParser(add_help=False)
194
        parser.prog = '%s <group> <command>' % exe
195
        parser.add_argument('-h', '--help', dest='help', action='store_true',
196
                          default=False,
197
                          help="Show this help message and exit")
198
        parser.add_argument('--config', dest='config', metavar='PATH',
199
                          help="Specify the path to the configuration file")
200
        parser.add_argument('-d', '--debug', dest='debug', action='store_true',
201
                          default=False,
202
                          help="Include debug output")
203
        parser.add_argument('-i', '--include', dest='include', action='store_true',
204
                          default=False,
205
                          help="Include protocol headers in the output")
206
        parser.add_argument('-s', '--silent', dest='silent', action='store_true',
207
                          default=False,
208
                          help="Silent mode, don't output anything")
209
        parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
210
                          default=False,
211
                          help="Make the operation more talkative")
212
        parser.add_argument('-V', '--version', dest='version', action='store_true',
213
                          default=False,
214
                          help="Show version number and quit")
215
        parser.add_argument('-o', dest='options', action='append',
216
                          default=[], metavar="KEY=VAL",
217
                          help="Override a config value")
218
        return parser
219

  
220
    def find_term_in_args(arg_list, term_list):
221
        """find an arg_list term in term_list. All other terms up to found
222
        term are rearanged at the end of arg_list, preserving relative order
223
        """
224
        arg_tail = []
225
        while len(arg_list) > 0:
226
            group = arg_list.pop(0)
227
            if group not in term_list:
228
                arg_tail.append(group)
229
            else:
230
                arg_list += arg_tail
231
                return group
232
        return None
233

  
234
    """Main Code"""
235
    exe = basename(sys.argv[0])
236
    parser = init_parser(exe)
237
    args, argv = parser.parse_known_args()
238

  
239
    #print version
240
    if args.version:
241
        import kamaki
242
        print("kamaki %s" % kamaki.__version__)
243
        exit(0)
244

  
245
    config = Config(args.config) if args.config else Config()
246

  
247
    #load config options from command line
248
    for option in args.options:
249
        keypath, sep, val = option.partition('=')
250
        if not sep:
251
            print("Invalid option '%s'" % option)
252
            exit(1)
253
        section, sep, key = keypath.partition('.')
254
        if not sep:
255
            print("Invalid option '%s'" % option)
256
            exit(1)
257
        config.override(section.strip(), key.strip(), val.strip())
258

  
259
    load_groups(config)
260
    group = find_term_in_args(argv, _commands)
261
    if not group:
262
        parser.print_help()
263
        print_groups()
264
        exit(0)
265

  
266
    parser.prog = '%s %s <command>' % (exe, group)
267
    command = find_term_in_args(argv, _commands[group])
268

  
269
    if not command:
270
        parser.print_help()
271
        print_commands(group)
272
        exit(0)
273

  
274
    cmd = _commands[group][command]()
275

  
276
    parser.prog = '%s %s %s' % (exe, group, command)
277
    if cmd.syntax:
278
        parser.prog += '  %s' % cmd.syntax
279
    parser.description = cmd.description
280
    parser.epilog = ''
281
    if hasattr(cmd, 'update_parser'):
282
        cmd.update_parser(parser)
283

  
284
    #check other args
285
    args, argv = parser.parse_known_args()
286
    if group != argv[0]:
287
        errmsg = red('Invalid command group '+argv[0])
288
        print(errmsg, file=stderr)
289
        exit(1)
290
    if command != argv[1]:
291
        errmsg = red('Invalid command "%s" in group "%s"'%(argv[1], argv[0]))
292
        print(errmsg, file=stderr)
293
        exit(1)
294

  
295
    if args.help:
296
        parser.print_help()
297
        exit(0)
298

  
299
    manage_logging_handlers(args)
300
    cmd.args = args
301
    cmd.config = config
302
    try:
303
        ret = cmd.main(*argv[2:])
304
        exit(ret)
305
    except TypeError as e:
306
        if e.args and e.args[0].startswith('main()'):
307
            parser.print_help()
308
            exit(1)
309
        else:
310
            raise
311
    except CLIError as err:
312
        errmsg = 'CLI Error '
313
        errmsg += '(%s): '%err.status if err.status else ': '
314
        errmsg += err.message if err.message else ''
315
        if err.importance == 1:
316
            errmsg = yellow(errmsg)
317
        elif err.importance == 2:
318
            errmsg = magenta(errmsg)
319
        elif err.importance > 2:
320
            errmsg = red(errmsg)
321
        print(errmsg, file=stderr)
322
        exit(1)
323

  
324
if __name__ == '__main__':
325
    main()
b/kamaki/clients/compute.py
182 182
        """
183 183
        path = path4url('flavors', flavor_id, command)
184 184
        success=kwargs.pop('success', 200)
185
        return self.get(path, success=success**kwargs)
185
        return self.get(path, success=success, **kwargs)
186 186

  
187 187
    def list_flavors(self, detail=False):
188 188
        detail = 'detail' if detail else ''
......
201 201
        """
202 202
        path = path4url('images', image_id, command)
203 203
        success=kwargs.pop('success', 200)
204
        return self.get(path, success=success**kwargs)
204
        return self.get(path, success=success, **kwargs)
205 205

  
206 206
    def images_delete(self, image_id='', command='', **kwargs):
207 207
        """DEL ETE base_url[/image_id][/command]
......
210 210
        """
211 211
        path = path4url('images', image_id, command)
212 212
        success=kwargs.pop('success', 204)
213
        return self.delete(path, success=success**kwargs)
213
        return self.delete(path, success=success, **kwargs)
214 214

  
215 215
    def images_post(self, image_id='', command='', json_data=None, **kwargs):
216 216
        """POST base_url/images[/image_id]/[command] request
......
245 245
        return self.put(path, data=data, success=success, **kwargs)
246 246

  
247 247
    def list_images(self, detail=False):
248
        detail = 'detail' if details else ''
248
        detail = 'detail' if detail else ''
249 249
        r = self.images_get(command=detail)
250 250
        return r.json['images']['values']
251 251
    
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

  
34

  
35
from kamaki.cli import command, set_api_description
36
set_api_description('astakos', 'Astakos API commands')
37
from kamaki.clients.astakos import AstakosClient, ClientError
38
from .cli_utils import raiseCLIError
39
from kamaki.utils import print_dict
40

  
41
class _astakos_init(object):
42
	def main(self):
43
		token = self.config.get('astakos', 'token') or self.config.get('global', 'token')
44
		base_url = self.config.get('astakos', 'url') or self.config.get('global', 'url')
45
		if base_url is None:
46
			raise ClientError('no URL for astakos')
47
		self.client = AstakosClient(base_url=base_url, token=token)
48

  
49
@command()
50
class astakos_authenticate(_astakos_init):
51
    """Authenticate a user"""
52

  
53
    def main(self):
54
    	super(astakos_authenticate, self).main()
55
    	try:
56
        	reply = self.client.authenticate()
57
        except ClientError as err:
58
        	raiseCLIError(err)
59
        print_dict(reply)
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

  
34
from kamaki.clients import ClientError
35
from kamaki.cli import CLIError
36
#from inspect import getargspec
37

  
38
def raiseCLIError(err, importance = -1):
39
	if importance < 0:
40
		if err.status <= 0:
41
			importance = 0
42
		elif err.status <= 400:
43
			importance = 1
44
		elif err.status <= 500:
45
			importance = 2
46
		else:
47
			importance = 3
48
	raise CLIError(err.message, err.status, err.details, importance)
49

  
50
def dict2file(d, f, depth = 0):
51
    for k, v in d.items():
52
        f.write('%s%s: '%('\t'*depth, k))
53
        if type(v) is dict:
54
            f.write('\n')
55
            dict2file(v, f, depth+1)
56
        elif type(v) is list:
57
            f.write('\n')
58
            list2file(v, f, depth+1)
59
        else:
60
            f.write(' %s\n'%unicode(v))
61

  
62
def list2file(l, f, depth = 1):
63
    for item in l:
64
        if type(item) is dict:
65
            dict2file(item, f, depth+1)
66
        elif type(item) is list:
67
            list2file(item, f, depth+1)
68
        else:
69
            f.write('%s%s\n'%('\t'*depth, unicode(item)))
/dev/null
1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from kamaki.cli import command, set_api_description, CLIError
35
from kamaki.utils import print_dict, print_items, print_list, format_size
36
from colors import bold
37
set_api_description('server', "Compute/Cyclades API server commands")
38
set_api_description('flavor', "Compute/Cyclades API flavor commands")
39
set_api_description('image', "Compute/Cyclades or Glance API image commands")
40
set_api_description('network', "Compute/Cyclades API network commands")
41
from kamaki.clients.cyclades import CycladesClient, ClientError
42
from .cli_utils import raiseCLIError
43

  
44
from colors import yellow
45

  
46
class _init_cyclades(object):
47
    def main(self):
48
        token = self.config.get('compute', 'token') or self.config.get('global', 'token')
49
        base_url = self.config.get('compute', 'url') or self.config.get('global', 'url')
50
        self.client = CycladesClient(base_url=base_url, token=token)
51

  
52
@command()
53
class server_list(_init_cyclades):
54
    """List servers"""
55

  
56
    def print_servers(self, servers):
57
        for server in servers:
58
            sname = server.pop('name')
59
            sid = server.pop('id')
60
            print('%s (%s)'%(bold(sname), bold(unicode(sid))))
61
            if getattr(self.args, 'detail'):
62
                server_info.print_server(server)
63
            print('- - -')
64

  
65
    def update_parser(self, parser):
66
        parser.add_argument('-l', dest='detail', action='store_true',
67
                default=False, help='show detailed output')
68

  
69
    def main(self):
70
        super(self.__class__, self).main()
71
        try:
72
            servers = self.client.list_servers(self.args.detail)
73
            self.print_servers(servers)
74
            #print_items(servers)
75
        except ClientError as err:
76
            raiseCLIError(err)
77

  
78
@command()
79
class server_info(_init_cyclades):
80
    """Get server details"""
81

  
82
    @classmethod
83
    def print_server(self,server):
84
        addr_dict = {}
85
        if server.has_key('addresses'):
86
            for addr in server['addresses']['values']:
87
                ips = addr.pop('values', [])
88
                for ip in ips:
89
                    addr['IPv%s'%ip['version']] = ip['addr']
90
                if addr.has_key('firewallProfile'):
91
                    addr['firewall'] = addr.pop('firewallProfile')
92
                addr_dict[addr['name']] = addr
93
            server['addresses'] = addr_dict
94
        print_dict(server, ident=14)
95

  
96

  
97
    def main(self, server_id):
98
        super(self.__class__, self).main()
99
        try:
100
            server = self.client.get_server_details(int(server_id))
101
        except ClientError as err:
102
            raiseCLIError(err)
103
        except ValueError as err:
104
            raise CLIError(message='Server id must be positive integer',
105
                importance=1)
106
        self.print_server(server)
107

  
108
@command()
109
class server_create(_init_cyclades):
110
    """Create a server"""
111

  
112
    def update_parser(self, parser):
113
        parser.add_argument('--personality', dest='personalities',
114
                          action='append', default=[],
115
                          metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
116
                          help='add a personality file')
117

  
118
    def main(self, name, flavor_id, image_id):
119
        super(self.__class__, self).main()
120
        personalities = []
121
        for personality in self.args.personalities:
122
            p = personality.split(',')
123
            p.extend([None] * (5 - len(p)))     # Fill missing fields with None
124

  
125
            path = p[0]
126

  
127
            if not path:
128
                raise CLIError(message='Invalid personality argument %s'%p, importance=1)
129
            if not exists(path):
130
                raise CLIError(message="File %s does not exist" % path, importance=1)
131

  
132
            with open(path) as f:
133
                contents = b64encode(f.read())
134

  
135
            d = {'path': p[1] or abspath(path), 'contents': contents}
136
            if p[2]:
137
                d['owner'] = p[2]
138
            if p[3]:
139
                d['group'] = p[3]
140
            if p[4]:
141
                d['mode'] = int(p[4])
142
            personalities.append(d)
143

  
144
        try:
145
            reply = self.client.create_server(name, int(flavor_id), image_id,
146
                personalities)
147
        except ClientError as err:
148
            raiseCLIError(err)
149
        print_dict(reply)
150

  
151
@command()
152
class server_rename(_init_cyclades):
153
    """Update a server's name"""
154

  
155
    def main(self, server_id, new_name):
156
        super(self.__class__, self).main()
157
        try:
158
            self.client.update_server_name(int(server_id), new_name)
159
        except ClientError as err:
160
            raiseCLIError(err)
161
        except ValueError:
162
            raise CLIError(message='Server id must be positive integer', importance=1)
163

  
164
@command()
165
class server_delete(_init_cyclades):
166
    """Delete a server"""
167

  
168
    def main(self, server_id):
169
        super(self.__class__, self).main()
170
        try:
171
            self.client.delete_server(int(server_id))
172
        except ClientError as err:
173
            raiseCLIError(err)
174
        except ValueError:
175
            raise CLIError(message='Server id must be positive integer', importance=1)
176

  
177
@command()
178
class server_reboot(_init_cyclades):
179
    """Reboot a server"""
180

  
181
    def update_parser(self, parser):
182
        parser.add_argument('-f', dest='hard', action='store_true',
183
                default=False, help='perform a hard reboot')
184

  
185
    def main(self, server_id):
186
        super(self.__class__, self).main()
187
        try:
188
            self.client.reboot_server(int(server_id), self.args.hard)
189
        except ClientError as err:
190
            raiseCLIError(err)
191
        except ValueError:
192
            raise CLIError(message='Server id must be positive integer', importance=1)
193

  
194
@command()
195
class server_start(_init_cyclades):
196
    """Start a server"""
197

  
198
    def main(self, server_id):
199
        super(self.__class__, self).main()
200
        try:
201
            self.client.start_server(int(server_id))
202
        except ClientError as err:
203
            raiseCLIError(err)
204
        except ValueError:
205
            raise CLIError(message='Server id must be positive integer', importance=1)
206

  
207
@command()
208
class server_shutdown(_init_cyclades):
209
    """Shutdown a server"""
210

  
211
    def main(self, server_id):
212
        super(self.__class__, self).main()
213
        try:
214
            self.client.shutdown_server(int(server_id))
215
        except ClientError as err:
216
            raiseCLIError(err)
217
        except ValueError:
218
            raise CLIError(message='Server id must be positive integer', importance=1)
219

  
220
@command()
221
class server_console(_init_cyclades):
222
    """Get a VNC console"""
223

  
224
    def main(self, server_id):
225
        super(self.__class__, self).main()
226
        try:
227
            reply = self.client.get_server_console(int(server_id))
228
        except ClientError as err:
229
            raiseCLIError(err)
230
        except ValueError:
231
            raise CLIError(message='Server id must be positive integer', importance=1)
232
        print_dict(reply)
233

  
234
@command()
235
class server_firewall(_init_cyclades):
236
    """Set the server's firewall profile"""
237

  
238
    def main(self, server_id, profile):
239
        super(self.__class__, self).main()
240
        try:
241
            self.client.set_firewall_profile(int(server_id), profile)
242
        except ClientError as err:
243
            raiseCLIError(err)
244
        except ValueError:
245
            raise CLIError(message='Server id must be positive integer', importance=1)
246
@command()
247
class server_addr(_init_cyclades):
248
    """List a server's nic address"""
249

  
250
    def main(self, server_id):
251
        super(self.__class__, self).main()
252
        try:
253
            reply = self.client.list_server_nics(int(server_id))
254
        except ClientError as err:
255
            raiseCLIError(err)
256
        except ValueError:
257
            raise CLIError(message='Server id must be positive integer', importance=1)
258
        print_list(reply)
259

  
260
@command()
261
class server_meta(_init_cyclades):
262
    """Get a server's metadata"""
263

  
264
    def main(self, server_id, key=None):
265
        super(self.__class__, self).main()
266
        try:
267
            reply = self.client.get_server_metadata(int(server_id), key)
268
        except ClientError as err:
269
            raiseCLIError(err)
270
        except ValueError:
271
            raise CLIError(message='Server id must be positive integer', importance=1)
272
        print_dict(reply)
273

  
274
@command()
275
class server_addmeta(_init_cyclades):
276
    """Add server metadata"""
277

  
278
    def main(self, server_id, key, val):
279
        super(self.__class__, self).main()
280
        try:
281
            reply = self.client.create_server_metadata(int(server_id), key, val)
282
        except ClientError as err:
283
            raiseCLIError(err)
284
        except ValueError:
285
            raise CLIError(message='Server id must be positive integer', importance=1)
286
        print_dict(reply)
287

  
288
@command()
289
class server_setmeta(_init_cyclades):
290
    """Update server's metadata"""
291

  
292
    def main(self, server_id, key, val):
293
        super(self.__class__, self).main()
294
        metadata = {key: val}
295
        try:
296
            reply = self.client.update_server_metadata(int(server_id), **metadata)
297
        except ClientError as err:
298
            raiseCLIError(err)
299
        except ValueError:
300
            raise CLIError(message='Server id must be positive integer', importance=1)
301
        print_dict(reply)
302

  
303
@command()
304
class server_delmeta(_init_cyclades):
305
    """Delete server metadata"""
306

  
307
    def main(self, server_id, key):
308
        super(self.__class__, self).main()
309
        try:
310
            self.client.delete_server_metadata(int(server_id), key)
311
        except ClientError as err:
312
            raiseCLIError(err)
313
        except ValueError:
314
            raise CLIError(message='Server id must be positive integer', importance=1)
315

  
316
@command()
317
class server_stats(_init_cyclades):
318
    """Get server statistics"""
319

  
320
    def main(self, server_id):
321
        super(self.__class__, self).main()
322
        try:
323
            reply = self.client.get_server_stats(int(server_id))
324
        except ClientError as err:
325
            raiseCLIError(err)
326
        except ValueError:
327
            raise CLIError(message='Server id must be positive integer', importance=1)
328
        print_dict(reply, exclude=('serverRef',))
329

  
330
@command()
331
class flavor_list(_init_cyclades):
332
    """List flavors"""
333

  
334
    def update_parser(self, parser):
335
        parser.add_argument('-l', dest='detail', action='store_true',
336
                default=False, help='show detailed output')
337

  
338
    def main(self):
339
        super(self.__class__, self).main()
340
        try:
341
            flavors = self.client.list_flavors(self.args.detail)
342
        except ClientError as err:
343
            raiseCLIError(err)
344
        print_items(flavors)
345

  
346
@command()
347
class flavor_info(_init_cyclades):
348
    """Get flavor details"""
349

  
350
    def main(self, flavor_id):
351
        super(self.__class__, self).main()
352
        try:
353
            flavor = self.client.get_flavor_details(int(flavor_id))
354
        except ClientError as err:
355
            raiseCLIError(err)
356
        except ValueError:
357
            raise CLIError(message='Server id must be positive integer', importance=1)
358
        print_dict(flavor)
359

  
360
@command()
361
class image_list(_init_cyclades):
362
    """List images"""
363

  
364
    def update_parser(self, parser):
365
        parser.add_argument('-l', dest='detail', action='store_true',
366
                default=False, help='show detailed output')
367

  
368
    def main(self):
369
        super(self.__class__, self).main()
370
        try:
371
            images = self.client.list_images(self.args.detail)
372
        except ClientError as err:
373
            raiseCLIError(err)
374
        print_items(images)
375

  
376
@command()
377
class image_info(_init_cyclades):
378
    """Get image details"""
379

  
380
    def main(self, image_id):
381
        super(self.__class__, self).main()
382
        try:
383
            image = self.client.get_image_details(image_id)
384
        except ClientError as err:
385
            raiseCLIError(err)
386
        print_dict(image)
387

  
388
@command()
389
class image_delete(_init_cyclades):
390
    """Delete image"""
391

  
392
    def main(self, image_id):
393
        super(self.__class__, self).main()
394
        try:
395
            self.client.delete_image(image_id)
396
        except ClientError as err:
397
            raiseCLIError(err)
398

  
399
@command()
400
class image_properties(_init_cyclades):
401
    """Get image properties"""
402

  
403
    def main(self, image_id, key=None):
404
        super(self.__class__, self).main()
405
        try:
406
            reply = self.client.get_image_metadata(image_id, key)
407
        except ClientError as err:
408
            raiseCLIError(err)
409
        print_dict(reply)
410

  
411
@command()
412
class image_addproperty(_init_cyclades):
413
    """Add an image property"""
414

  
415
    def main(self, image_id, key, val):
416
        super(self.__class__, self).main()
417
        try:
418
            reply = self.client.create_image_metadata(image_id, key, val)
419
        except ClientError as err:
420
            raiseCLIError(err)
421
        print_dict(reply)
422

  
423
@command()
424
class image_setproperty(_init_cyclades):
425
    """Update an image property"""
426

  
427
    def main(self, image_id, key, val):
428
        super(self.__class__, self).main()
429
        metadata = {key: val}
430
        try:
431
            reply = self.client.update_image_metadata(image_id, **metadata)
432
        except ClientError as err:
433
            raiseCLIError(err)
434
        print_dict(reply)
435

  
436
@command()
437
class image_delproperty(_init_cyclades):
438
    """Delete an image property"""
439

  
440
    def main(self, image_id, key):
441
        super(self.__class__, self).main()
442
        try:
443
            self.client.delete_image_metadata(image_id, key)
444
        except ClientError as err:
445
            raiseCLIError(err)
446

  
447
@command()
448
class network_list(_init_cyclades):
449
    """List networks"""
450

  
451
    def update_parser(self, parser):
452
        parser.add_argument('-l', dest='detail', action='store_true',
453
                default=False, help='show detailed output')
454

  
455
    def print_networks(self, nets):
456
        for net in nets:
457
            netname = bold(net.pop('name'))
458
            netid = bold(unicode(net.pop('id')))
459
            print('%s (%s)'%(netname, netid))
460
            if getattr(self.args, 'detail'):
461
                network_info.print_network(net)
462
                print('- - -')
463

  
464
    def main(self):
465
        super(self.__class__, self).main()
466
        try:
467
            networks = self.client.list_networks(self.args.detail)
468
        except ClientError as err:
469
            raiseCLIError(err)
470
        self.print_networks(networks)
471

  
472
@command()
473
class network_create(_init_cyclades):
474
    """Create a network"""
475

  
476
    def update_parser(self, parser):
477
        try:
478
            super(self.__class__, self).update_parser(parser)
479
        except AttributeError:
480
            pass
481
        parser.add_argument('--with-cidr', action='store', dest='cidr', default=False,
482
            help='specific cidr for new network')
483
        parser.add_argument('--with-gateway', action='store', dest='gateway', default=False,
484
            help='specific getaway for new network')
485
        parser.add_argument('--with-dhcp', action='store', dest='dhcp', default=False,
486
            help='specific dhcp for new network')
487
        parser.add_argument('--with-type', action='store', dest='type', default=False,
488
            help='specific type for new network')
489

  
490
    def main(self, name):
491
        super(self.__class__, self).main()
492
        try:
493
            reply = self.client.create_network(name, cidr=getattr(self.args, 'cidr'),
494
                gateway=getattr(self.args, 'gateway'), dhcp=getattr(self.args, 'gateway'),
495
                type=getattr(self.args, 'type'))
496
        except ClientError as err:
497
            raiseCLIError(err)
498
        print_dict(reply)
499

  
500
@command()
501
class network_info(_init_cyclades):
502
    """Get network details"""
503

  
504
    @classmethod
505
    def print_network(self, net):
506
        if net.has_key('attachments'):
507
            att = net['attachments']['values']
508
            net['attachments'] = att if len(att) > 0 else None
509
        print_dict(net, ident=14)
510

  
511
    def main(self, network_id):
512
        super(self.__class__, self).main()
513
        try:
514
            network = self.client.get_network_details(network_id)
515
        except ClientError as err:
516
            raiseCLIError(err)
517
        network_info.print_network(network)
518

  
519
@command()
520
class network_rename(_init_cyclades):
521
    """Update network name"""
522

  
523
    def main(self, network_id, new_name):
524
        super(self.__class__, self).main()
525
        try:
526
            self.client.update_network_name(network_id, new_name)
527
        except ClientError as err:
528
            raiseCLIError(err)
529

  
530
@command()
531
class network_delete(_init_cyclades):
532
    """Delete a network"""
533

  
534
    def main(self, network_id):
535
        super(self.__class__, self).main()
536
        try:
537
            self.client.delete_network(network_id)
538
        except ClientError as err:
539
            raiseCLIError(err)
540

  
541
@command()
542
class network_connect(_init_cyclades):
543
    """Connect a server to a network"""
544

  
545
    def main(self, server_id, network_id):
546
        super(self.__class__, self).main()
547
        try:
548
            self.client.connect_server(server_id, network_id)
549
        except ClientError as err:
550
            raiseCLIError(err)
551

  
552
@command()
553
class network_disconnect(_init_cyclades):
554
    """Disconnect a nic that connects a server to a network"""
555

  
556
    def main(self, nic_id):
557
        super(self.__class__, self).main()
558
        try:
559
            server_id = nic_id.split('-')[1]
560
            self.client.disconnect_server(server_id, nic_id)
561
        except IndexError:
562
            raise CLIError(message='Incorrect nic format', importance=1,
563
                details='nid_id format: nic-<server_id>-<nic_index>')
564
        except ClientError as err:
565
            raiseCLIError(err)
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

  
34
from kamaki.cli import command, set_api_description
35
from kamaki.utils import print_dict, print_items
36
set_api_description('image', "Compute/Cyclades or Glance API image commands")
37
from kamaki.clients.image import ImageClient, ClientError
38
from .cli_utils import raiseCLIError
39

  
40
class _init_image(object):
41
    def main(self):
42
        try:
43
            token = self.config.get('image', 'token') or self.config.get('global', 'token')
44
            base_url = self.config.get('image', 'url') or self.config.get('global', 'url')
45
            self.client = ImageClient(base_url=base_url, token=token)
46
        except ClientError as err:
47
            raiseCLIError(err)
48

  
49
@command()
50
class image_public(_init_image):
51
    """List public images"""
52

  
53
    def update_parser(self, parser):
54
        parser.add_argument('-l', dest='detail', action='store_true',
55
                default=False, help='show detailed output')
56
        parser.add_argument('--container-format', dest='container_format',
57
                metavar='FORMAT', help='filter by container format')
58
        parser.add_argument('--disk-format', dest='disk_format',
59
                metavar='FORMAT', help='filter by disk format')
60
        parser.add_argument('--name', dest='name', metavar='NAME',
61
                help='filter by name')
62
        parser.add_argument('--size-min', dest='size_min', metavar='BYTES',
63
                help='filter by minimum size')
64
        parser.add_argument('--size-max', dest='size_max', metavar='BYTES',
65
                help='filter by maximum size')
66
        parser.add_argument('--status', dest='status', metavar='STATUS',
67
                help='filter by status')
68
        parser.add_argument('--order', dest='order', metavar='FIELD',
69
                help='order by FIELD (use a - prefix to reverse order)')
70

  
71
    def main(self):
72
    	super(self.__class__, self).main()
73
        filters = {}
74
        for filter in ('container_format', 'disk_format', 'name', 'size_min',
75
                       'size_max', 'status'):
76
            val = getattr(self.args, filter, None)
77
            if val is not None:
78
                filters[filter] = val
79

  
80
        order = self.args.order or ''
81
        try:
82
            images = self.client.list_public(self.args.detail,
83
                filters=filters, order=order)
84
        except ClientError as err:
85
            raiseCLIError(err)
86
        print_items(images, title=('name',))
87

  
88
@command()
89
class image_meta(_init_image):
90
    """Get image metadata"""
91

  
92
    def main(self, image_id):
93
    	super(self.__class__, self).main()
94
        try:
95
            image = self.client.get_meta(image_id)
96
        except ClientError as err:
97
            raiseCLIError(err)
98
        print_dict(image)
99

  
100
@command()
101
class image_register(_init_image):
102
    """Register an image"""
103

  
104
    def update_parser(self, parser):
105
        parser.add_argument('--checksum', dest='checksum', metavar='CHECKSUM',
106
                help='set image checksum')
107
        parser.add_argument('--container-format', dest='container_format',
108
                metavar='FORMAT', help='set container format')
109
        parser.add_argument('--disk-format', dest='disk_format',
110
                metavar='FORMAT', help='set disk format')
111
        parser.add_argument('--id', dest='id',
112
                metavar='ID', help='set image ID')
113
        parser.add_argument('--owner', dest='owner',
114
                metavar='USER', help='set image owner (admin only)')
115
        parser.add_argument('--property', dest='properties', action='append',
116
                metavar='KEY=VAL',
117
                help='add a property (can be used multiple times)')
118
        parser.add_argument('--public', dest='is_public', action='store_true',
119
                help='mark image as public')
120
        parser.add_argument('--size', dest='size', metavar='SIZE',
121
                help='set image size')
122

  
123
    def main(self, name, location):
124
    	super(self.__class__, self).main()
125
        if not location.startswith('pithos://'):
126
            account = self.config.get('storage', 'account').split()[0]
127
            if account[-1] == '/':
128
                account = account[:-1]
129
            container = self.config.get('storage', 'container')
130
            location = 'pithos://%s/%s'%(account, location) \
131
                if container is None or len(container) == 0 \
132
                else 'pithos://%s/%s/%s' % (account, container, location)
133

  
134
        params = {}
135
        for key in ('checksum', 'container_format', 'disk_format', 'id',
136
                    'owner', 'size'):
137
            val = getattr(self.args, key)
138
            if val is not None:
139
                params[key] = val
140

  
141
        if self.args.is_public:
142
            params['is_public'] = 'true'
143

  
144
        properties = {}
145
        for property in self.args.properties or []:
146
            key, sep, val = property.partition('=')
147
            if not sep:
148
                raise CLIError(message="Invalid property '%s'" % property, importance=1)
149
            properties[key.strip()] = val.strip()
150

  
151
        try:
152
            self.client.register(name, location, params, properties)
153
        except ClientError as err:
154
            raiseCLIError(err)
155

  
156
@command()
157
class image_members(_init_image):
158
    """Get image members"""
159

  
160
    def main(self, image_id):
161
    	super(self.__class__, self).main()
162
        try:
163
            members = self.client.list_members(image_id)
164
        except ClientError as err:
165
            raiseCLIError(err)
166
        for member in members:
167
            print(member['member_id'])
168

  
169
@command()
170
class image_shared(_init_image):
171
    """List shared images"""
172

  
173
    def main(self, member):
174
    	super(self.__class__, self).main()
175
        try:
176
            images = self.client.list_shared(member)
177
        except ClientError as err:
178
            raiseCLIError(err)
179
        for image in images:
180
            print(image['image_id'])
181

  
182
@command()
183
class image_addmember(_init_image):
184
    """Add a member to an image"""
185

  
186
    def main(self, image_id, member):
187
    	super(self.__class__, self).main()
188
        try:
189
            self.client.add_member(image_id, member)
190
        except ClientError as err:
191
            raiseCLIError(err)
192

  
193
@command()
194
class image_delmember(_init_image):
195
    """Remove a member from an image"""
196

  
197
    def main(self, image_id, member):
198
        super(self.__class__, self).main()
199
        try:
200
            self.client.remove_member(image_id, member)
201
        except ClientError as err:
202
            raiseCLIError(err)
203

  
204
@command()
205
class image_setmembers(_init_image):
206
    """Set the members of an image"""
207

  
208
    def main(self, image_id, *member):
209
        super(self.__class__, self).main()
210
        try:
211
            self.client.set_members(image_id, member)
212
        except ClientError as err:
213
            raiseCLIError(err)
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

  
34
from kamaki.cli import command, set_api_description, CLIError
35
from kamaki.clients.utils import filter_in
36
from kamaki.utils import format_size
37
set_api_description('store', 'Pithos+ storage commands')
38
from kamaki.clients.pithos import PithosClient, ClientError
39
from .cli_utils import raiseCLIError
40
from kamaki.utils import print_dict, pretty_keys, print_list
41
from colors import bold
42
from sys import stdout, exit
43
import signal
44

  
45
from progress.bar import IncrementalBar
46

  
47

  
48
class ProgressBar(IncrementalBar):
49
    #suffix = '%(percent)d%% - %(eta)ds'
50
    suffix = '%(percent)d%%'
51

  
52
class _pithos_init(object):
53
    def main(self):
54
        self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
55
        self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
56
        self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
57
        self.container = self.config.get('store', 'container') or self.config.get('global', 'container')
58
        self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
59
            container=self.container)
60

  
61
class _store_account_command(_pithos_init):
62
    """Base class for account level storage commands"""
63

  
64
    def update_parser(self, parser):
65
        parser.add_argument('--account', dest='account', metavar='NAME',
66
                          help="Specify an account to use")
67

  
68
    def progress(self, message):
69
        """Return a generator function to be used for progress tracking"""
70

  
71
        MESSAGE_LENGTH = 25
72

  
73
        def progress_gen(n):
74
            msg = message.ljust(MESSAGE_LENGTH)
75
            for i in ProgressBar(msg).iter(range(n)):
76
                yield
77
            yield
78

  
79
        return progress_gen
80

  
81
    def main(self):
82
        super(_store_account_command, self).main()
83
        if hasattr(self.args, 'account') and self.args.account is not None:
84
            self.client.account = self.args.account
85

  
86
class _store_container_command(_store_account_command):
87
    """Base class for container level storage commands"""
88

  
89
    def __init__(self):
90
        self.container = None
91
        self.path = None
92

  
93
    def update_parser(self, parser):
94
        super(_store_container_command, self).update_parser(parser)
95
        parser.add_argument('--container', dest='container', metavar='NAME', default=None,
96
            help="Specify a container to use")
97

  
98
    def extract_container_and_path(self, container_with_path, path_is_optional=True):
99
        assert isinstance(container_with_path, str)
100
        if ':' not in container_with_path:
101
            if hasattr(self.args, 'container'):
102
                self.container = getattr(self.args, 'container')
103
            else:
104
                self.container = self.client.container
105
            if self.container is None:
106
                self.container = container_with_path
107
            else:
108
                self.path = container_with_path
109
            if not path_is_optional and self.path is None:
110
                raise CLIError(message="Object path is missing", status=11)
111
            return
112
        cnp = container_with_path.split(':')
113
        self.container = cnp[0]
114
        try:
115
            self.path = cnp[1]
116
        except IndexError:
117
            if path_is_optional:
118
                self.path = None
119
            else:
120
                raise CLIError(message="Object path is missing", status=11)
121

  
122
    def main(self, container_with_path=None, path_is_optional=True):
123
        super(_store_container_command, self).main()
124
        if container_with_path is not None:
125
            self.extract_container_and_path(container_with_path, path_is_optional)
126
            self.client.container = self.container
127
        elif hasattr(self.args, 'container'):
128
            self.client.container = getattr(self.args,'container')
129
        self.container = self.client.container
130

  
131
"""
132
@command()
133
class store_test(_store_container_command):
134
    ""Test various stuff""
135

  
136
    def main(self):
137
        super(self.__class__, self).main('pithos')
138
        r = self.client.list_containers()
139
        for item in r:
140
            if item['name'].startswith('c1_') or item['name'].startswith('c2_') \
141
            or item['name'].startswith('c3_'):
142
                self.client.container = item['name']
143
                self.client.del_container(delimiter='/')
144
                self.client.del_container()
145
"""
146

  
147
@command()
148
class store_list(_store_container_command):
149
    """List containers, object trees or objects in a directory
150
    """
151

  
152
    def update_parser(self, parser):
153
        super(self.__class__, self).update_parser(parser)
154
        parser.add_argument('-l', action='store_true', dest='detail', default=False,
155
            help='show detailed output')
156
        parser.add_argument('-N', action='store', dest='show_size', default=1000,
157
            help='print output in chunks of size N')
158
        parser.add_argument('-n', action='store', dest='limit', default=None,
159
            help='show limited output')
160
        parser.add_argument('--marker', action='store', dest='marker', default=None,
161
            help='show output greater then marker')
162
        parser.add_argument('--prefix', action='store', dest='prefix', default=None,
163
            help='show output starting with prefix')
164
        parser.add_argument('--delimiter', action='store', dest='delimiter', default=None, 
165
            help='show output up to the delimiter')
166
        parser.add_argument('--path', action='store', dest='path', default=None, 
167
            help='show output starting with prefix up to /')
168
        parser.add_argument('--meta', action='store', dest='meta', default=None, 
169
            help='show output having the specified meta keys (e.g. --meta "meta1 meta2 ..."')
170
        parser.add_argument('--if-modified-since', action='store', dest='if_modified_since', 
171
            default=None, help='show output if modified since then')
172
        parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
173
            default=None, help='show output if not modified since then')
174
        parser.add_argument('--until', action='store', dest='until', default=None,
175
            help='show metadata until that date')
176
        dateformat = '%d/%m/%Y %H:%M:%S'
177
        parser.add_argument('--format', action='store', dest='format', default=dateformat,
178
            help='format to parse until date (default: d/m/Y H:M:S)')
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff