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

34
35
36
37
from json import load, dumps
from os.path import abspath
from logging import getLogger

38
from kamaki.cli import command
39
from kamaki.cli.command_tree import CommandTree
40
from kamaki.cli.utils import print_dict, print_json
41
from kamaki.clients.image import ImageClient
42
43
44
from kamaki.clients.pithos import PithosClient
from kamaki.clients.astakos import AstakosClient
from kamaki.clients import ClientError
Stavros Sachtouris's avatar
Stavros Sachtouris committed
45
46
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
from kamaki.cli.argument import IntArgument
47
from kamaki.cli.commands.cyclades import _init_cyclades
48
from kamaki.cli.errors import raiseCLIError
49
50
from kamaki.cli.commands import _command_init, errors
from kamaki.cli.commands import _optional_output_cmd, _optional_json
51

Stavros Sachtouris's avatar
Stavros Sachtouris committed
52

53
54
55
56
image_cmds = CommandTree(
    'image',
    'Cyclades/Plankton API image commands\n'
    'image compute:\tCyclades/Compute API image commands')
57
_commands = [image_cmds]
Stavros Sachtouris's avatar
Stavros Sachtouris committed
58
59


60
61
62
63
64
65
66
67
68
howto_image_file = [
    'Kamaki commands to:',
    ' get current user uuid: /user authenticate',
    ' check available containers: /file list',
    ' create a new container: /file create <container>',
    ' check container contents: /file list <container>',
    ' upload files: /file upload <image file> <container>']

about_image_id = ['To see a list of available image ids: /image list']
69
70


71
72
73
log = getLogger(__name__)


74
class _init_image(_command_init):
75
76
77
78
79
80
81
82
83
    @errors.generic.all
    def _run(self):
        token = self.config.get('image', 'token')\
            or self.config.get('compute', 'token')\
            or self.config.get('global', 'token')
        base_url = self.config.get('image', 'url')\
            or self.config.get('compute', 'url')\
            or self.config.get('global', 'url')
        self.client = ImageClient(base_url=base_url, token=token)
84
        self._set_log_params()
85
        self._update_max_threads()
86

87
    def main(self):
88
        self._run()
89

Stavros Sachtouris's avatar
Stavros Sachtouris committed
90

91
92
93
# Plankton Image Commands


94
def _validate_image_meta(json_dict, return_str=False):
95
96
97
98
99
100
    """
    :param json_dict" (dict) json-formated, of the form
        {"key1": "val1", "key2": "val2", ...}

    :param return_str: (boolean) if true, return a json dump

101
    :returns: (dict) if return_str is not True, else return str
102
103
104
105
106

    :raises TypeError, AttributeError: Invalid json format

    :raises AssertionError: Valid json but invalid image properties dict
    """
107
    json_str = dumps(json_dict, indent=2)
108
    for k, v in json_dict.items():
109
110
111
112
113
114
115
116
117
118
119
        if k.lower() == 'properties':
            for pk, pv in v.items():
                prop_ok = not (isinstance(pv, dict) or isinstance(pv, list))
                assert prop_ok, 'Invalid property value for key %s' % pk
                key_ok = not (' ' in k or '-' in k)
                assert key_ok, 'Invalid property key %s' % k
            continue
        meta_ok = not (isinstance(v, dict) or isinstance(v, list))
        assert meta_ok, 'Invalid value for meta key %s' % k
        meta_ok = ' ' not in k
        assert meta_ok, 'Invalid meta key [%s]' % k
120
121
122
123
        json_dict[k] = '%s' % v
    return json_str if return_str else json_dict


124
def _load_image_meta(filepath):
125
126
127
128
129
130
131
132
133
134
135
136
    """
    :param filepath: (str) the (relative) path of the metafile

    :returns: (dict) json_formated

    :raises TypeError, AttributeError: Invalid json format

    :raises AssertionError: Valid json but invalid image properties dict
    """
    with open(abspath(filepath)) as f:
        meta_dict = load(f)
        try:
137
            return _validate_image_meta(meta_dict)
138
139
140
141
142
        except AssertionError:
            log.debug('Failed to load properties from file %s' % filepath)
            raise


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def _validate_image_location(location):
    """
    :param location: (str) pithos://<uuid>/<container>/<img-file-path>

    :returns: (<uuid>, <container>, <img-file-path>)

    :raises AssertionError: if location is invalid
    """
    prefix = 'pithos://'
    msg = 'Invalid prefix for location %s , try: %s' % (location, prefix)
    assert location.startswith(prefix), msg
    service, sep, rest = location.partition('://')
    assert sep and rest, 'Location %s is missing uuid' % location
    uuid, sep, rest = rest.partition('/')
    assert sep and rest, 'Location %s is missing container' % location
    container, sep, img_path = rest.partition('/')
    assert sep and img_path, 'Location %s is missing image path' % location
    return uuid, container, img_path


163
@command(image_cmds)
164
class image_list(_init_image, _optional_json):
165
    """List images accessible by user"""
166

Stavros Sachtouris's avatar
Stavros Sachtouris committed
167
    arguments = dict(
168
        detail=FlagArgument('show detailed output', ('-l', '--details')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
169
170
171
172
173
        container_format=ValueArgument(
            'filter by container format',
            '--container-format'),
        disk_format=ValueArgument('filter by disk format', '--disk-format'),
        name=ValueArgument('filter by name', '--name'),
174
175
176
177
178
179
180
181
182
        name_pref=ValueArgument(
            'filter by name prefix (case insensitive)',
            '--name-prefix'),
        name_suff=ValueArgument(
            'filter by name suffix (case insensitive)',
            '--name-suffix'),
        name_like=ValueArgument(
            'print only if name contains this (case insensitive)',
            '--name-like'),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
183
184
185
        size_min=IntArgument('filter by minimum size', '--size-min'),
        size_max=IntArgument('filter by maximum size', '--size-max'),
        status=ValueArgument('filter by status', '--status'),
186
        owner=ValueArgument('filter by owner', '--owner'),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
187
188
189
        order=ValueArgument(
            'order by FIELD ( - to reverse order)',
            '--order',
Stavros Sachtouris's avatar
Stavros Sachtouris committed
190
            default=''),
191
        limit=IntArgument('limit number of listed images', ('-n', '--number')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
192
193
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
194
            '--more'),
195
        enum=FlagArgument('Enumerate results', '--enumerate')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
196
    )
197

198
199
200
201
202
203
204
205
206
207
208
209
    def _filtered_by_owner(self, detail, *list_params):
        images = []
        MINKEYS = set([
            'id', 'size', 'status', 'disk_format', 'container_format', 'name'])
        for img in self.client.list_public(True, *list_params):
            if img['owner'] == self['owner']:
                if not detail:
                    for key in set(img.keys()).difference(MINKEYS):
                        img.pop(key)
                images.append(img)
        return images

210
211
212
213
214
215
216
    def _filtered_by_name(self, images):
        np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
        return [img for img in images if (
            (not np) or img['name'].lower().startswith(np.lower())) and (
            (not ns) or img['name'].lower().endswith(ns.lower())) and (
            (not nl) or nl.lower() in img['name'].lower())]

217
218
219
220
    @errors.generic.all
    @errors.cyclades.connection
    def _run(self):
        super(self.__class__, self)._run()
221
        filters = {}
222
        for arg in set([
Stavros Sachtouris's avatar
Stavros Sachtouris committed
223
224
225
226
227
                'container_format',
                'disk_format',
                'name',
                'size_min',
                'size_max',
228
                'status']).intersection(self.arguments):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
229
230
231
232
            filters[arg] = self[arg]

        order = self['order']
        detail = self['detail']
233
234
235
236
237
        if self['owner']:
            images = self._filtered_by_owner(detail, filters, order)
        else:
            images = self.client.list_public(detail, filters, order)

238
        images = self._filtered_by_name(images)
239
        kwargs = dict(with_enumeration=self['enum'])
Stavros Sachtouris's avatar
Stavros Sachtouris committed
240
        if self['more']:
241
            kwargs['page_size'] = self['limit'] or 10
Stavros Sachtouris's avatar
Stavros Sachtouris committed
242
        elif self['limit']:
243
244
            images = images[:self['limit']]
        self._print(images, **kwargs)
245

246
247
248
249
    def main(self):
        super(self.__class__, self)._run()
        self._run()

Stavros Sachtouris's avatar
Stavros Sachtouris committed
250

251
@command(image_cmds)
252
class image_meta(_init_image, _optional_json):
253
254
    """Get image metadata
    Image metadata include:
255
256
257
    - image file information (location, size, etc.)
    - image information (id, name, etc.)
    - image os properties (os, fs, etc.)
258
    """
259

260
261
262
263
    @errors.generic.all
    @errors.plankton.connection
    @errors.plankton.id
    def _run(self, image_id):
264
        self._print([self.client.get_meta(image_id)])
265

266
267
    def main(self, image_id):
        super(self.__class__, self)._run()
268
        self._run(image_id=image_id)
269

Stavros Sachtouris's avatar
Stavros Sachtouris committed
270

271
@command(image_cmds)
272
class image_register(_init_image, _optional_json):
273
    """(Re)Register an image"""
274

Stavros Sachtouris's avatar
Stavros Sachtouris committed
275
276
277
278
279
280
281
282
283
    arguments = dict(
        checksum=ValueArgument('set image checksum', '--checksum'),
        container_format=ValueArgument(
            'set container format',
            '--container-format'),
        disk_format=ValueArgument('set disk format', '--disk-format'),
        owner=ValueArgument('set image owner (admin only)', '--owner'),
        properties=KeyValueArgument(
            'add property in key=value form (can be repeated)',
284
            ('-p', '--property')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
285
286
        is_public=FlagArgument('mark image as public', '--public'),
        size=IntArgument('set image size', '--size'),
287
288
289
290
291
292
293
294
295
296
        metafile=ValueArgument(
            'Load metadata from a json-formated file <img-file>.meta :'
            '{"key1": "val1", "key2": "val2", ..., "properties: {...}"}',
            ('--metafile')),
        metafile_force=FlagArgument(
            'Store remote metadata object, even if it already exists',
            ('-f', '--force')),
        no_metafile_upload=FlagArgument(
            'Do not store metadata in remote meta file',
            ('--no-metafile-upload')),
297

Stavros Sachtouris's avatar
Stavros Sachtouris committed
298
    )
299

300
301
302
303
304
    def _get_uuid(self):
        atoken = self.client.token
        user = AstakosClient(self.config.get('user', 'url'), atoken)
        return user.term('uuid')

305
306
307
    def _get_pithos_client(self, container):
        if self['no_metafile_upload']:
            return None
308
309
        purl = self.config.get('file', 'url')
        ptoken = self.client.token
310
        return PithosClient(purl, ptoken, self._get_uuid(), container)
311

312
    def _store_remote_metafile(self, pclient, remote_path, metadata):
313
        return pclient.upload_from_string(
314
            remote_path, _validate_image_meta(metadata, return_str=True))
315

316
317
318
319
    def _load_params_from_file(self, location):
        params, properties = dict(), dict()
        pfile = self['metafile']
        if pfile:
320
            try:
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
                for k, v in _load_image_meta(pfile).items():
                    key = k.lower().replace('-', '_')
                    if k == 'properties':
                        for pk, pv in v.items():
                            properties[pk.upper().replace('-', '_')] = pv
                    elif key == 'name':
                            continue
                    elif key == 'location':
                        if location:
                            continue
                        location = v
                    else:
                        params[key] = v
            except Exception as e:
                raiseCLIError(e, 'Invalid json metadata config file')
        return params, properties, location
337

338
    def _load_params_from_args(self, params, properties):
339
        for key in set([
Stavros Sachtouris's avatar
Stavros Sachtouris committed
340
341
342
343
344
                'checksum',
                'container_format',
                'disk_format',
                'owner',
                'size',
345
                'is_public']).intersection(self.arguments):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
346
            params[key] = self[key]
347
348
        for k, v in self['properties'].items():
            properties[k.upper().replace('-', '_')] = v
Stavros Sachtouris's avatar
Stavros Sachtouris committed
349

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
    def _validate_location(self, location):
        if not location:
            raiseCLIError(
                'No image file location provided',
                importance=2, details=[
                    'An image location is needed. Image location format:',
                    '  pithos://<uuid>/<container>/<path>',
                    ' an image file at the above location must exist.'
                    ] + howto_image_file)
        try:
            return _validate_image_location(location)
        except AssertionError as ae:
            raiseCLIError(
                ae, 'Invalid image location format',
                importance=1, details=[
                    'Valid image location format:',
                    '  pithos://<uuid>/<container>/<img-file-path>'
                    ] + howto_image_file)

    @errors.generic.all
    @errors.plankton.connection
    def _run(self, name, location):
        (params, properties, location) = self._load_params_from_file(location)
        uuid, container, img_path = self._validate_location(location)
        self._load_params_from_args(params, properties)
        pclient = self._get_pithos_client(container)

        #check if metafile exists
        meta_path = '%s.meta' % img_path
        if pclient and not self['metafile_force']:
380
            try:
381
382
383
384
385
386
387
388
389
390
391
392
                pclient.get_object_info(meta_path)
                raiseCLIError('Metadata file %s:%s already exists' % (
                    container, meta_path))
            except ClientError as ce:
                if ce.status != 404:
                    raise

        #register the image
        try:
            r = self.client.register(name, location, params, properties)
        except ClientError as ce:
            if ce.status in (400, ):
393
                raiseCLIError(
394
                    ce, 'Nonexistent image file location %s' % location,
395
                    details=[
396
397
398
                        'Make sure the image file exists'] + howto_image_file)
            raise
        self._print(r, print_dict)
399

400
        #upload the metadata file
401
        if pclient:
402
403
404
405
406
407
            try:
                meta_headers = pclient.upload_from_string(
                    meta_path, dumps(r, indent=2))
            except TypeError:
                print('Failed to dump metafile %s:%s' % (container, meta_path))
                return
408
409
            if self['json_output']:
                print_json(dict(
410
411
                    metafile_location='%s:%s' % (container, meta_path),
                    headers=meta_headers))
412
            else:
413
414
                print('Metadata file uploaded as %s:%s (version %s)' % (
                    container, meta_path, meta_headers['x-object-version']))
415

416
    def main(self, name, location=None):
417
        super(self.__class__, self)._run()
418
        self._run(name, location)
419

Stavros Sachtouris's avatar
Stavros Sachtouris committed
420

421
@command(image_cmds)
422
class image_unregister(_init_image, _optional_output_cmd):
423
424
425
426
427
428
    """Unregister an image (does not delete the image file)"""

    @errors.generic.all
    @errors.plankton.connection
    @errors.plankton.id
    def _run(self, image_id):
429
        self._optional_output(self.client.unregister(image_id))
430
431
432
433
434
435

    def main(self, image_id):
        super(self.__class__, self)._run()
        self._run(image_id=image_id)


436
@command(image_cmds)
437
class image_shared(_init_image, _optional_json):
438
439
    """List images shared by a member"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
440
441
    @errors.generic.all
    @errors.plankton.connection
442
    def _run(self, member):
443
        self._print(self.client.list_shared(member), title=('image_id',))
444

445
    def main(self, member):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
446
        super(self.__class__, self)._run()
447
        self._run(member)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
448

Stavros Sachtouris's avatar
Stavros Sachtouris committed
449

450
@command(image_cmds)
451
452
453
454
455
class image_members(_init_image):
    """Manage members. Members of an image are users who can modify it"""


@command(image_cmds)
456
class image_members_list(_init_image, _optional_json):
457
458
    """List members of an image"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
459
460
    @errors.generic.all
    @errors.plankton.connection
461
462
    @errors.plankton.id
    def _run(self, image_id):
463
        self._print(self.client.list_members(image_id), title=('member_id',))
464

465
    def main(self, image_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
466
        super(self.__class__, self)._run()
467
        self._run(image_id=image_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
468

Stavros Sachtouris's avatar
Stavros Sachtouris committed
469

470
@command(image_cmds)
471
class image_members_add(_init_image, _optional_output_cmd):
472
473
    """Add a member to an image"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
474
475
476
    @errors.generic.all
    @errors.plankton.connection
    @errors.plankton.id
477
    def _run(self, image_id=None, member=None):
478
            self._optional_output(self.client.add_member(image_id, member))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
479
480
481

    def main(self, image_id, member):
        super(self.__class__, self)._run()
482
        self._run(image_id=image_id, member=member)
483

Stavros Sachtouris's avatar
Stavros Sachtouris committed
484

485
@command(image_cmds)
486
class image_members_delete(_init_image, _optional_output_cmd):
487
488
    """Remove a member from an image"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
489
490
491
    @errors.generic.all
    @errors.plankton.connection
    @errors.plankton.id
492
    def _run(self, image_id=None, member=None):
493
            self._optional_output(self.client.remove_member(image_id, member))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
494
495
496

    def main(self, image_id, member):
        super(self.__class__, self)._run()
497
        self._run(image_id=image_id, member=member)
498

Stavros Sachtouris's avatar
Stavros Sachtouris committed
499

500
@command(image_cmds)
501
class image_members_set(_init_image, _optional_output_cmd):
502
503
    """Set the members of an image"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
504
505
506
    @errors.generic.all
    @errors.plankton.connection
    @errors.plankton.id
507
    def _run(self, image_id, members):
508
            self._optional_output(self.client.set_members(image_id, members))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
509
510
511

    def main(self, image_id, *members):
        super(self.__class__, self)._run()
512
        self._run(image_id=image_id, members=members)
513
514


515
516
517
# Compute Image Commands


518
@command(image_cmds)
519
class image_compute(_init_cyclades):
520
    """Cyclades/Compute API image commands"""
521
522
523


@command(image_cmds)
524
class image_compute_list(_init_cyclades, _optional_json):
525
526
    """List images"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
527
    arguments = dict(
528
529
        detail=FlagArgument('show detailed output', ('-l', '--details')),
        limit=IntArgument('limit number listed images', ('-n', '--number')),
530
531
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
532
            '--more'),
533
        enum=FlagArgument('Enumerate results', '--enumerate')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
534
    )
535

536
    def _make_results_pretty(self, images):
537
        for img in images:
538
539
            if 'metadata' in img:
                img['metadata'] = img['metadata']['values']
540

Stavros Sachtouris's avatar
Stavros Sachtouris committed
541
542
543
544
    @errors.generic.all
    @errors.cyclades.connection
    def _run(self):
        images = self.client.list_images(self['detail'])
545
        if self['detail'] and not self['json_output']:
Stavros Sachtouris's avatar
Stavros Sachtouris committed
546
            self._make_results_pretty(images)
547
        kwargs = dict(with_enumeration=self['enum'])
Stavros Sachtouris's avatar
Stavros Sachtouris committed
548
        if self['more']:
549
            kwargs['page_size'] = self['limit'] or 10
Stavros Sachtouris's avatar
Stavros Sachtouris committed
550
        else:
551
552
            images = images[:self['limit']]
        self._print(images, **kwargs)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
553

554
    def main(self):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
555
556
        super(self.__class__, self)._run()
        self._run()
557

Stavros Sachtouris's avatar
Stavros Sachtouris committed
558

559
@command(image_cmds)
560
class image_compute_info(_init_cyclades, _optional_json):
561
    """Get detailed information on an image"""
562

Stavros Sachtouris's avatar
Stavros Sachtouris committed
563
564
565
566
567
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
    def _run(self, image_id):
        image = self.client.get_image_details(image_id)
568
        if (not self['json_output']) and 'metadata' in image:
569
            image['metadata'] = image['metadata']['values']
570
        self._print([image])
571
572

    def main(self, image_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
573
        super(self.__class__, self)._run()
574
        self._run(image_id=image_id)
575

Stavros Sachtouris's avatar
Stavros Sachtouris committed
576

577
@command(image_cmds)
578
class image_compute_delete(_init_cyclades, _optional_output_cmd):
579
    """Delete an image (WARNING: image file is also removed)"""
580

Stavros Sachtouris's avatar
Stavros Sachtouris committed
581
582
583
584
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
    def _run(self, image_id):
585
        self._optional_output(self.client.delete_image(image_id))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
586

587
    def main(self, image_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
588
        super(self.__class__, self)._run()
589
        self._run(image_id=image_id)
590

Stavros Sachtouris's avatar
Stavros Sachtouris committed
591

592
@command(image_cmds)
593
class image_compute_properties(_init_cyclades):
594
    """Manage properties related to OS installation in an image"""
595
596
597


@command(image_cmds)
598
class image_compute_properties_list(_init_cyclades, _optional_json):
599
600
601
602
603
604
    """List all image properties"""

    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
    def _run(self, image_id):
605
        self._print(self.client.get_image_metadata(image_id), print_dict)
606
607
608
609
610
611
612

    def main(self, image_id):
        super(self.__class__, self)._run()
        self._run(image_id=image_id)


@command(image_cmds)
613
class image_compute_properties_get(_init_cyclades, _optional_json):
614
615
    """Get an image property"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
616
617
618
619
620
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
    @errors.plankton.metadata
    def _run(self, image_id, key):
621
        self._print(self.client.get_image_metadata(image_id, key), print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
622

623
    def main(self, image_id, key):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
624
        super(self.__class__, self)._run()
625
        self._run(image_id=image_id, key=key)
626

Stavros Sachtouris's avatar
Stavros Sachtouris committed
627

628
@command(image_cmds)
629
class image_compute_properties_add(_init_cyclades, _optional_json):
630
631
    """Add a property to an image"""

Stavros Sachtouris's avatar
Stavros Sachtouris committed
632
633
634
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
635
    @errors.plankton.metadata
Stavros Sachtouris's avatar
Stavros Sachtouris committed
636
    def _run(self, image_id, key, val):
637
638
        self._print(
            self.client.create_image_metadata(image_id, key, val), print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
639

640
    def main(self, image_id, key, val):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
641
        super(self.__class__, self)._run()
642
        self._run(image_id=image_id, key=key, val=val)
643

Stavros Sachtouris's avatar
Stavros Sachtouris committed
644

645
@command(image_cmds)
646
class image_compute_properties_set(_init_cyclades, _optional_json):
647
648
649
650
    """Add / update a set of properties for an image
    proeprties must be given in the form key=value, e.v.
    /image compute properties set <image-id> key1=val1 key2=val2
    """
651

Stavros Sachtouris's avatar
Stavros Sachtouris committed
652
653
654
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
655
    def _run(self, image_id, keyvals):
656
        meta = dict()
657
658
        for keyval in keyvals:
            key, val = keyval.split('=')
659
660
661
            meta[key] = val
        self._print(
            self.client.update_image_metadata(image_id, **meta), print_dict)
662
663

    def main(self, image_id, *key_equals_value):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
664
        super(self.__class__, self)._run()
665
        self._run(image_id=image_id, keyvals=key_equals_value)
666

Stavros Sachtouris's avatar
Stavros Sachtouris committed
667

668
@command(image_cmds)
669
670
class image_compute_properties_delete(_init_cyclades, _optional_output_cmd):
    """Delete a property from an image"""
671

Stavros Sachtouris's avatar
Stavros Sachtouris committed
672
673
674
675
676
    @errors.generic.all
    @errors.cyclades.connection
    @errors.plankton.id
    @errors.plankton.metadata
    def _run(self, image_id, key):
677
        self._optional_output(self.client.delete_image_metadata(image_id, key))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
678

679
    def main(self, image_id, key):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
680
        super(self.__class__, self)._run()
681
        self._run(image_id=image_id, key=key)