Revision ca5528f1

b/kamaki/cli/argument/__init__.py
274 274
            print('kamaki %s' % kamaki.__version__)
275 275

  
276 276

  
277
class RepeatableArgument(Argument):
278
    """A value argument that can be repeated"""
279

  
280
    def __init__(self, help='', parsed_name=None, default=[]):
281
        super(RepeatableArgument, self).__init__(
282
            -1, help, parsed_name, default)
283

  
284

  
277 285
class KeyValueArgument(Argument):
278
    """A Value Argument that can be repeated
286
    """A Key=Value Argument that can be repeated
279 287

  
280 288
    :syntax: --<arg> key1=value1 --<arg> key2=value2 ...
281 289
    """
b/kamaki/cli/argument/test.py
291 291
        self.assertEqual(va.value, 'some value')
292 292

  
293 293

  
294
class RepeatableArgument(TestCase):
295

  
296
    @patch('%s.Argument.__init__' % arg_path)
297
    def test___init__(self, init):
298
        help, pname, default = 'help', 'pname', 'default'
299
        kva = argument.RepeatableArgument(help, pname, default)
300
        self.assertTrue(isinstance(kva, argument.RepeatableArgument))
301
        self.assertEqual(init.mock_calls[-1], call(-1, help, pname, default))
302

  
303

  
294 304
class KeyValueArgument(TestCase):
295 305

  
296 306
    @patch('%s.Argument.__init__' % arg_path)
......
497 507
    runTestCase(IntArgument, 'IntArgument', argv[1:])
498 508
    runTestCase(DateArgument, 'DateArgument', argv[1:])
499 509
    runTestCase(VersionArgument, 'VersionArgument', argv[1:])
510
    runTestCase(RepeatableArgument, 'RepeatableArgument', argv[1:])
500 511
    runTestCase(KeyValueArgument, 'KeyValueArgument', argv[1:])
501 512
    runTestCase(ProgressBarArgument, 'ProgressBarArgument', argv[1:])
502 513
    runTestCase(ArgumentParseManager, 'ArgumentParseManager', argv[1:])
b/kamaki/cli/commands/image.py
42 42
from kamaki.clients.pithos import PithosClient
43 43
from kamaki.clients.astakos import AstakosClient
44 44
from kamaki.clients import ClientError
45
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
46
from kamaki.cli.argument import IntArgument, ProgressBarArgument
45
from kamaki.cli.argument import (
46
    FlagArgument, ValueArgument, RepeatableArgument, KeyValueArgument,
47
    IntArgument, ProgressBarArgument)
47 48
from kamaki.cli.commands.cyclades import _init_cyclades
48 49
from kamaki.cli.errors import raiseCLIError, CLIBaseUrlError
49 50
from kamaki.cli.commands import _command_init, errors, addLogSettings
......
277 278

  
278 279

  
279 280
@command(image_cmds)
280
class image_meta(_init_image, _optional_json):
281
class image_meta(_init_image):
282
    """Manage image metadata and custom properties"""
283

  
284

  
285
@command(image_cmds)
286
class image_meta_list(_init_image, _optional_json):
281 287
    """Get image metadata
282 288
    Image metadata include:
283 289
    - image file information (location, size, etc.)
......
289 295
    @errors.plankton.connection
290 296
    @errors.plankton.id
291 297
    def _run(self, image_id):
292
        self._print([self.client.get_meta(image_id)])
298
        meta = self.client.get_meta(image_id)
299
        if not self['json_output']:
300
            meta['owner'] += ' (%s)' % self._uuid2username(meta['owner'])
301
        self._print(meta, print_dict)
302

  
303
    def main(self, image_id):
304
        super(self.__class__, self)._run()
305
        self._run(image_id=image_id)
306

  
307

  
308
@command(image_cmds)
309
class image_meta_set(_init_image, _optional_output_cmd):
310
    """Add / update metadata and properties for an image
311
    The original image preserves the values that are not affected
312
    """
313

  
314
    arguments = dict(
315
        name=ValueArgument('Set a new name', ('--name')),
316
        disk_format=ValueArgument('Set a new disk format', ('--disk-format')),
317
        container_format=ValueArgument(
318
            'Set a new container format', ('--container-format')),
319
        status=ValueArgument('Set a new status', ('--status')),
320
        publish=FlagArgument('publish the image', ('--publish')),
321
        unpublish=FlagArgument('unpublish the image', ('--unpublish')),
322
        properties=KeyValueArgument(
323
            'set property in key=value form (can be repeated)',
324
            ('-p', '--property'))
325
    )
326

  
327
    def _check_empty(self):
328
        for term in (
329
                'name', 'disk_format', 'container_format', 'status', 'publish',
330
                'unpublish', 'properties'):
331
            if self['term']:
332
                if self['publish'] and self['unpublish']:
333
                    raiseCLIError(
334
                        '--publish and --unpublish are mutually exclusive')
335
                return
336
        raiseCLIError(
337
            'Nothing to update, please use arguments (-h for a list)')
338

  
339
    @errors.generic.all
340
    @errors.plankton.connection
341
    @errors.plankton.id
342
    def _run(self, image_id):
343
        self._check_empty()
344
        meta = self.client.get_meta(image_id)
345
        for k, v in self['properties'].items():
346
            meta['properties'][k.upper()] = v
347
        self._optional_output(self.client.update_image(
348
            image_id,
349
            name=self['name'],
350
            disk_format=self['disk_format'],
351
            container_format=self['container_format'],
352
            status=self['status'],
353
            public=self['publish'] or self['unpublish'] or None,
354
            **meta['properties']))
355

  
356
    def main(self, image_id):
357
        super(self.__class__, self)._run()
358
        self._run(image_id=image_id)
359

  
360

  
361
@command(image_cmds)
362
class image_meta_delete(_init_image, _optional_output_cmd):
363
    """Remove/empty image metadata and/or custom properties"""
364

  
365
    arguments = dict(
366
        disk_format=FlagArgument('Empty disk format', ('--disk-format')),
367
        container_format=FlagArgument(
368
            'Empty container format', ('--container-format')),
369
        status=FlagArgument('Empty status', ('--status')),
370
        properties=RepeatableArgument(
371
            'Property keys to remove', ('-p', '--property'))
372
    )
373

  
374
    def _check_empty(self):
375
        for term in (
376
                'disk_format', 'container_format', 'status', 'properties'):
377
            if self[term]:
378
                return
379
        raiseCLIError(
380
            'Nothing to update, please use arguments (-h for a list)')
381

  
382
    @errors.generic.all
383
    @errors.plankton.connection
384
    @errors.plankton.id
385
    def _run(self, image_id):
386
        self._check_empty()
387
        meta = self.client.get_meta(image_id)
388
        for k in self['properties']:
389
            meta['properties'].pop(k.upper(), None)
390
        self._optional_output(self.client.update_image(
391
            image_id,
392
            disk_format='' if self['disk_format'] else None,
393
            container_format='' if self['container_format'] else None,
394
            status='' if self['status'] else None,
395
            **meta['properties']))
293 396

  
294 397
    def main(self, image_id):
295 398
        super(self.__class__, self)._run()
......
308 411
            'set container format',
309 412
            '--container-format'),
310 413
        disk_format=ValueArgument('set disk format', '--disk-format'),
311
        owner=ValueArgument('set image owner (admin only)', '--owner'),
414
        #owner=ValueArgument('set image owner (admin only)', '--owner'),
312 415
        properties=KeyValueArgument(
313 416
            'add property in key=value form (can be repeated)',
314 417
            ('-p', '--property')),
......
610 713
        super(self.__class__, self)._run()
611 714
        self._run(image_id=image_id, members=member_ids)
612 715

  
613

  
614 716
# Compute Image Commands
615 717

  
616 718

  
......
768 870
        self._run(image_id=image_id, key=key)
769 871

  
770 872

  
771
@command(image_cmds)
772
class image_compute_properties_add(_init_cyclades, _optional_json):
773
    """Add a property to an image"""
774

  
775
    @errors.generic.all
776
    @errors.cyclades.connection
777
    @errors.plankton.id
778
    @errors.plankton.metadata
779
    def _run(self, image_id, key, val):
780
        self._print(
781
            self.client.create_image_metadata(image_id, key, val), print_dict)
782

  
783
    def main(self, image_id, key, val):
784
        super(self.__class__, self)._run()
785
        self._run(image_id=image_id, key=key, val=val)
873
#@command(image_cmds)
874
#class image_compute_properties_add(_init_cyclades, _optional_json):
875
#    """Add a property to an image"""
876
#
877
#    @errors.generic.all
878
#    @errors.cyclades.connection
879
#    @errors.plankton.id
880
#    @errors.plankton.metadata
881
#    def _run(self, image_id, key, val):
882
#        self._print(
883
#            self.client.create_image_metadata(image_id, key, val), print_dict)
884
#
885
#    def main(self, image_id, key, val):
886
#        super(self.__class__, self)._run()
887
#        self._run(image_id=image_id, key=key, val=val)
786 888

  
787 889

  
788 890
@command(image_cmds)
b/kamaki/cli/test.py
41 41
from kamaki.cli.argument.test import (
42 42
    Argument, ConfigArgument, RuntimeConfigArgument, FlagArgument,
43 43
    ValueArgument, IntArgument, DateArgument, VersionArgument,
44
    KeyValueArgument, ProgressBarArgument, ArgumentParseManager)
44
    RepeatableArgument, KeyValueArgument, ProgressBarArgument,
45
    ArgumentParseManager)
45 46
from kamaki.cli.utils.test import UtilsMethods
46 47

  
47 48

  
b/kamaki/clients/compute/__init__.py
370 370
            response_headers[k] = r.headers.get(k, v)
371 371
        return r.json['meta' if key else 'metadata']
372 372

  
373
    def create_image_metadata(self, image_id, key, val):
374
        """
375
        :param image_id: integer (str or int)
373
    # def create_image_metadata(self, image_id, key, val):
374
    #     """
375
    #     :param image_id: integer (str or int)
376 376

  
377
        :param key: (str) metadatum key
377
    #     :param key: (str) metadatum key
378 378

  
379
        :param val: (str) metadatum value
379
    #     :param val: (str) metadatum value
380 380

  
381
        :returns: (dict) updated metadata
382
        """
383
        req = {'meta': {key: val}}
384
        r = self.images_metadata_put(image_id, key, json_data=req)
385
        return r.json['meta']
381
    #     :returns: (dict) updated metadata
382
    #     """
383
    #     req = {'meta': {key: val}}
384
    #     r = self.images_metadata_put(image_id, key, json_data=req)
385
    #     return r.json['meta']
386 386

  
387 387
    def update_image_metadata(
388 388
            self, image_id,
b/kamaki/clients/compute/test.py
703 703
        self.client.delete_image(img_ref)
704 704
        ID.assert_called_once_with(img_ref)
705 705

  
706
    @patch('%s.images_metadata_put' % compute_pkg, return_value=FR())
707
    def test_create_image_metadata(self, IP):
708
        (key, val) = ('k1', 'v1')
709
        FR.json = dict(meta=img_recv['image'])
710
        r = self.client.create_image_metadata(img_ref, key, val)
711
        IP.assert_called_once_with(
712
            img_ref, '%s' % key,
713
            json_data=dict(meta={key: val}))
714
        self.assert_dicts_are_equal(r, img_recv['image'])
706
    # @patch('%s.images_metadata_put' % compute_pkg, return_value=FR())
707
    # def test_create_image_metadata(self, IP):
708
    #     (key, val) = ('k1', 'v1')
709
    #     FR.json = dict(meta=img_recv['image'])
710
    #     r = self.client.create_image_metadata(img_ref, key, val)
711
    #     IP.assert_called_once_with(
712
    #         img_ref, '%s' % key,
713
    #         json_data=dict(meta={key: val}))
714
    #     self.assert_dicts_are_equal(r, img_recv['image'])
715 715

  
716 716
    @patch('%s.images_metadata_post' % compute_pkg, return_value=FR())
717 717
    def test_update_image_metadata(self, IP):
b/kamaki/clients/image/__init__.py
218 218
            self.set_header('X-Image-Meta-Owner', owner_id)
219 219
        for k, v in properties.items():
220 220
            self.set_header('X-Image-Meta-Property-%s' % k, v)
221
        self.set_header('Content-Length', 0)
221 222
        r = self.put(path, success=200)
222 223
        return r.headers
b/kamaki/clients/image/test.py
304 304
            (image_id, name, disk_format, container_format,
305 305
            status, public, owner_id, properties) = args
306 306
            self.assertEqual(r, FR.headers)
307
            header_calls = []
307
            header_calls = [call('Content-Length', 0), ]
308 308
            prf = 'X-Image-Meta-'
309 309
            if name:
310 310
                header_calls.append(call('%sName' % prf, name))

Also available in: Unified diff