Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / image.py @ cd295a1d

History | View | Annotate | Download (18 kB)

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.command
33

    
34
from kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.utils import print_dict, print_items, print_json
37
from kamaki.clients.image import ImageClient
38
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
39
from kamaki.cli.argument import IntArgument
40
from kamaki.cli.commands.cyclades import _init_cyclades
41
from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
42

    
43

    
44
image_cmds = CommandTree(
45
    'image',
46
    'Cyclades/Plankton API image commands\n'
47
    'image compute:\tCyclades/Compute API image commands')
48
_commands = [image_cmds]
49

    
50

    
51
about_image_id = [
52
    'To see a list of available image ids: /image list']
53

    
54

    
55
class _init_image(_command_init):
56
    @errors.generic.all
57
    def _run(self):
58
        token = self.config.get('image', 'token')\
59
            or self.config.get('compute', 'token')\
60
            or self.config.get('global', 'token')
61
        base_url = self.config.get('image', 'url')\
62
            or self.config.get('compute', 'url')\
63
            or self.config.get('global', 'url')
64
        self.client = ImageClient(base_url=base_url, token=token)
65
        self._set_log_params()
66
        self._update_max_threads()
67

    
68
    def main(self):
69
        self._run()
70

    
71

    
72
# Plankton Image Commands
73

    
74

    
75
@command(image_cmds)
76
class image_list(_init_image):
77
    """List images accessible by user"""
78

    
79
    arguments = dict(
80
        detail=FlagArgument('show detailed output', ('-l', '--details')),
81
        container_format=ValueArgument(
82
            'filter by container format',
83
            '--container-format'),
84
        disk_format=ValueArgument('filter by disk format', '--disk-format'),
85
        name=ValueArgument('filter by name', '--name'),
86
        name_pref=ValueArgument(
87
            'filter by name prefix (case insensitive)',
88
            '--name-prefix'),
89
        name_suff=ValueArgument(
90
            'filter by name suffix (case insensitive)',
91
            '--name-suffix'),
92
        name_like=ValueArgument(
93
            'print only if name contains this (case insensitive)',
94
            '--name-like'),
95
        size_min=IntArgument('filter by minimum size', '--size-min'),
96
        size_max=IntArgument('filter by maximum size', '--size-max'),
97
        status=ValueArgument('filter by status', '--status'),
98
        owner=ValueArgument('filter by owner', '--owner'),
99
        order=ValueArgument(
100
            'order by FIELD ( - to reverse order)',
101
            '--order',
102
            default=''),
103
        limit=IntArgument('limit number of listed images', ('-n', '--number')),
104
        more=FlagArgument(
105
            'output results in pages (-n to set items per page, default 10)',
106
            '--more'),
107
        enum=FlagArgument('Enumerate results', '--enumerate'),
108
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
109
    )
110

    
111
    def _filtered_by_owner(self, detail, *list_params):
112
        images = []
113
        MINKEYS = set([
114
            'id', 'size', 'status', 'disk_format', 'container_format', 'name'])
115
        for img in self.client.list_public(True, *list_params):
116
            if img['owner'] == self['owner']:
117
                if not detail:
118
                    for key in set(img.keys()).difference(MINKEYS):
119
                        img.pop(key)
120
                images.append(img)
121
        return images
122

    
123
    def _filtered_by_name(self, images):
124
        np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
125
        return [img for img in images if (
126
            (not np) or img['name'].lower().startswith(np.lower())) and (
127
            (not ns) or img['name'].lower().endswith(ns.lower())) and (
128
            (not nl) or nl.lower() in img['name'].lower())]
129

    
130
    @errors.generic.all
131
    @errors.cyclades.connection
132
    def _run(self):
133
        super(self.__class__, self)._run()
134
        filters = {}
135
        for arg in set([
136
                'container_format',
137
                'disk_format',
138
                'name',
139
                'size_min',
140
                'size_max',
141
                'status']).intersection(self.arguments):
142
            filters[arg] = self[arg]
143

    
144
        order = self['order']
145
        detail = self['detail']
146
        if self['owner']:
147
            images = self._filtered_by_owner(detail, filters, order)
148
        else:
149
            images = self.client.list_public(detail, filters, order)
150

    
151
        if self['json_output']:
152
            print_json(images)
153
            return
154
        images = self._filtered_by_name(images)
155
        if self['more']:
156
            print_items(
157
                images,
158
                with_enumeration=self['enum'], page_size=self['limit'] or 10)
159
        elif self['limit']:
160
            print_items(images[:self['limit']], with_enumeration=self['enum'])
161
        else:
162
            print_items(images, with_enumeration=self['enum'])
163

    
164
    def main(self):
165
        super(self.__class__, self)._run()
166
        self._run()
167

    
168

    
169
@command(image_cmds)
170
class image_meta(_init_image):
171
    """Get image metadata
172
    Image metadata include:
173
    - image file information (location, size, etc.)
174
    - image information (id, name, etc.)
175
    - image os properties (os, fs, etc.)
176
    """
177

    
178
    arguments = dict(
179
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
180
    )
181

    
182
    @errors.generic.all
183
    @errors.plankton.connection
184
    @errors.plankton.id
185
    def _run(self, image_id):
186
        printer = print_json if self['json_output'] else print_dict
187
        printer(self.client.get_meta(image_id))
188

    
189
    def main(self, image_id):
190
        super(self.__class__, self)._run()
191
        self._run(image_id=image_id)
192

    
193

    
194
@command(image_cmds)
195
class image_register(_init_image):
196
    """(Re)Register an image"""
197

    
198
    arguments = dict(
199
        checksum=ValueArgument('set image checksum', '--checksum'),
200
        container_format=ValueArgument(
201
            'set container format',
202
            '--container-format'),
203
        disk_format=ValueArgument('set disk format', '--disk-format'),
204
        #id=ValueArgument('set image ID', '--id'),
205
        owner=ValueArgument('set image owner (admin only)', '--owner'),
206
        properties=KeyValueArgument(
207
            'add property in key=value form (can be repeated)',
208
            ('-p', '--property')),
209
        is_public=FlagArgument('mark image as public', '--public'),
210
        size=IntArgument('set image size', '--size'),
211
        #update=FlagArgument(
212
        #    'update existing image properties',
213
        #    ('-u', '--update')),
214
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
215
    )
216

    
217
    @errors.generic.all
218
    @errors.plankton.connection
219
    def _run(self, name, location):
220
        if not location.startswith('pithos://'):
221
            account = self.config.get('file', 'account') \
222
                or self.config.get('global', 'account')
223
            assert account, 'No user account provided'
224
            if account[-1] == '/':
225
                account = account[:-1]
226
            container = self.config.get('file', 'container') \
227
                or self.config.get('global', 'container')
228
            if not container:
229
                location = 'pithos://%s/%s' % (account, location)
230
            else:
231
                location = 'pithos://%s/%s/%s' % (account, container, location)
232

    
233
        params = {}
234
        for key in set([
235
                'checksum',
236
                'container_format',
237
                'disk_format',
238
                'owner',
239
                'size',
240
                'is_public']).intersection(self.arguments):
241
            params[key] = self[key]
242

    
243
            properties = self['properties']
244

    
245
        printer = print_json if self['json_output'] else print_dict
246
        printer(self.client.register(name, location, params, properties))
247

    
248
    def main(self, name, location):
249
        super(self.__class__, self)._run()
250
        self._run(name, location)
251

    
252

    
253
@command(image_cmds)
254
class image_unregister(_init_image, _optional_output_cmd):
255
    """Unregister an image (does not delete the image file)"""
256

    
257
    @errors.generic.all
258
    @errors.plankton.connection
259
    @errors.plankton.id
260
    def _run(self, image_id):
261
        self._optional_output(self.client.unregister(image_id))
262

    
263
    def main(self, image_id):
264
        super(self.__class__, self)._run()
265
        self._run(image_id=image_id)
266

    
267

    
268
@command(image_cmds)
269
class image_shared(_init_image):
270
    """List images shared by a member"""
271

    
272
    arguments = dict(
273
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
274
    )
275

    
276
    @errors.generic.all
277
    @errors.plankton.connection
278
    def _run(self, member):
279
        r = self.client.list_shared(member)
280
        if self['json_output']:
281
            print_json(r)
282
        else:
283
            print_items(r, title=('image_id',))
284

    
285
    def main(self, member):
286
        super(self.__class__, self)._run()
287
        self._run(member)
288

    
289

    
290
@command(image_cmds)
291
class image_members(_init_image):
292
    """Manage members. Members of an image are users who can modify it"""
293

    
294

    
295
@command(image_cmds)
296
class image_members_list(_init_image):
297
    """List members of an image"""
298

    
299
    arguments = dict(
300
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
301
    )
302

    
303
    @errors.generic.all
304
    @errors.plankton.connection
305
    @errors.plankton.id
306
    def _run(self, image_id):
307
        members = self.client.list_members(image_id)
308
        if self['json_output']:
309
            print_json(members)
310
        else:
311
            print_items(members, title=('member_id',), with_redundancy=True)
312

    
313
    def main(self, image_id):
314
        super(self.__class__, self)._run()
315
        self._run(image_id=image_id)
316

    
317

    
318
@command(image_cmds)
319
class image_members_add(_init_image, _optional_output_cmd):
320
    """Add a member to an image"""
321

    
322
    @errors.generic.all
323
    @errors.plankton.connection
324
    @errors.plankton.id
325
    def _run(self, image_id=None, member=None):
326
            self._optional_output(self.client.add_member(image_id, member))
327

    
328
    def main(self, image_id, member):
329
        super(self.__class__, self)._run()
330
        self._run(image_id=image_id, member=member)
331

    
332

    
333
@command(image_cmds)
334
class image_members_delete(_init_image, _optional_output_cmd):
335
    """Remove a member from an image"""
336

    
337
    @errors.generic.all
338
    @errors.plankton.connection
339
    @errors.plankton.id
340
    def _run(self, image_id=None, member=None):
341
            self._optional_output(self.client.remove_member(image_id, member))
342

    
343
    def main(self, image_id, member):
344
        super(self.__class__, self)._run()
345
        self._run(image_id=image_id, member=member)
346

    
347

    
348
@command(image_cmds)
349
class image_members_set(_init_image, _optional_output_cmd):
350
    """Set the members of an image"""
351

    
352
    @errors.generic.all
353
    @errors.plankton.connection
354
    @errors.plankton.id
355
    def _run(self, image_id, members):
356
            self._optional_output(self.client.set_members(image_id, members))
357

    
358
    def main(self, image_id, *members):
359
        super(self.__class__, self)._run()
360
        self._run(image_id=image_id, members=members)
361

    
362

    
363
# Compute Image Commands
364

    
365

    
366
@command(image_cmds)
367
class image_compute(_init_cyclades):
368
    """Cyclades/Compute API image commands"""
369

    
370

    
371
@command(image_cmds)
372
class image_compute_list(_init_cyclades):
373
    """List images"""
374

    
375
    arguments = dict(
376
        detail=FlagArgument('show detailed output', ('-l', '--details')),
377
        limit=IntArgument('limit number listed images', ('-n', '--number')),
378
        more=FlagArgument(
379
            'output results in pages (-n to set items per page, default 10)',
380
            '--more'),
381
        enum=FlagArgument('Enumerate results', '--enumerate'),
382
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
383
    )
384

    
385
    def _make_results_pretty(self, images):
386
        for img in images:
387
            if 'metadata' in img:
388
                img['metadata'] = img['metadata']['values']
389

    
390
    @errors.generic.all
391
    @errors.cyclades.connection
392
    def _run(self):
393
        images = self.client.list_images(self['detail'])
394
        if self['json_output']:
395
            print_json(images)
396
            return
397
        if self['detail']:
398
            self._make_results_pretty(images)
399
        if self['more']:
400
            print_items(
401
                images,
402
                page_size=self['limit'] or 10, with_enumeration=self['enum'])
403
        else:
404
            print_items(images[:self['limit']], with_enumeration=self['enum'])
405

    
406
    def main(self):
407
        super(self.__class__, self)._run()
408
        self._run()
409

    
410

    
411
@command(image_cmds)
412
class image_compute_info(_init_cyclades):
413
    """Get detailed information on an image"""
414

    
415
    arguments = dict(
416
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
417
    )
418

    
419
    @errors.generic.all
420
    @errors.cyclades.connection
421
    @errors.plankton.id
422
    def _run(self, image_id):
423
        image = self.client.get_image_details(image_id)
424
        if self['json_output']:
425
            print_json(image)
426
            return
427
        if 'metadata' in image:
428
            image['metadata'] = image['metadata']['values']
429
        print_dict(image)
430

    
431
    def main(self, image_id):
432
        super(self.__class__, self)._run()
433
        self._run(image_id=image_id)
434

    
435

    
436
@command(image_cmds)
437
class image_compute_delete(_init_cyclades, _optional_output_cmd):
438
    """Delete an image (WARNING: image file is also removed)"""
439

    
440
    @errors.generic.all
441
    @errors.cyclades.connection
442
    @errors.plankton.id
443
    def _run(self, image_id):
444
        self._optional_output(self.client.delete_image(image_id))
445

    
446
    def main(self, image_id):
447
        super(self.__class__, self)._run()
448
        self._run(image_id=image_id)
449

    
450

    
451
@command(image_cmds)
452
class image_compute_properties(_init_cyclades):
453
    """Manage properties related to OS installation in an image"""
454

    
455

    
456
@command(image_cmds)
457
class image_compute_properties_list(_init_cyclades):
458
    """List all image properties"""
459

    
460
    arguments = dict(
461
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
462
    )
463

    
464
    @errors.generic.all
465
    @errors.cyclades.connection
466
    @errors.plankton.id
467
    def _run(self, image_id):
468
        printer = print_json if self['json_output'] else print_dict
469
        printer(self.client.get_image_metadata(image_id))
470

    
471
    def main(self, image_id):
472
        super(self.__class__, self)._run()
473
        self._run(image_id=image_id)
474

    
475

    
476
@command(image_cmds)
477
class image_compute_properties_get(_init_cyclades):
478
    """Get an image property"""
479

    
480
    arguments = dict(
481
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
482
    )
483

    
484
    @errors.generic.all
485
    @errors.cyclades.connection
486
    @errors.plankton.id
487
    @errors.plankton.metadata
488
    def _run(self, image_id, key):
489
        printer = print_json if self['json_output'] else print_dict
490
        printer(self.client.get_image_metadata(image_id, key))
491

    
492
    def main(self, image_id, key):
493
        super(self.__class__, self)._run()
494
        self._run(image_id=image_id, key=key)
495

    
496

    
497
@command(image_cmds)
498
class image_compute_properties_add(_init_cyclades):
499
    """Add a property to an image"""
500

    
501
    arguments = dict(
502
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
503
    )
504

    
505
    @errors.generic.all
506
    @errors.cyclades.connection
507
    @errors.plankton.id
508
    @errors.plankton.metadata
509
    def _run(self, image_id, key, val):
510
        printer = print_json if self['json_output'] else print_dict
511
        printer(self.client.create_image_metadata(image_id, key, val))
512

    
513
    def main(self, image_id, key, val):
514
        super(self.__class__, self)._run()
515
        self._run(image_id=image_id, key=key, val=val)
516

    
517

    
518
@command(image_cmds)
519
class image_compute_properties_set(_init_cyclades):
520
    """Add / update a set of properties for an image
521
    proeprties must be given in the form key=value, e.v.
522
    /image compute properties set <image-id> key1=val1 key2=val2
523
    """
524
    arguments = dict(
525
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
526
    )
527

    
528
    @errors.generic.all
529
    @errors.cyclades.connection
530
    @errors.plankton.id
531
    def _run(self, image_id, keyvals):
532
        metadata = dict()
533
        for keyval in keyvals:
534
            key, val = keyval.split('=')
535
            metadata[key] = val
536
        printer = print_json if self['json_output'] else print_dict
537
        printer(self.client.update_image_metadata(image_id, **metadata))
538

    
539
    def main(self, image_id, *key_equals_value):
540
        super(self.__class__, self)._run()
541
        self._run(image_id=image_id, keyvals=key_equals_value)
542

    
543

    
544
@command(image_cmds)
545
class image_compute_properties_delete(_init_cyclades, _optional_output_cmd):
546
    """Delete a property from an image"""
547

    
548
    @errors.generic.all
549
    @errors.cyclades.connection
550
    @errors.plankton.id
551
    @errors.plankton.metadata
552
    def _run(self, image_id, key):
553
        self._optional_output(self.client.delete_image_metadata(image_id, key))
554

    
555
    def main(self, image_id, key):
556
        super(self.__class__, self)._run()
557
        self._run(image_id=image_id, key=key)