Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / image.py @ 545c6c29

History | View | Annotate | Download (16.2 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
42
from kamaki.cli.commands import _optional_output_cmd, _optional_json
43

    
44

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

    
51

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

    
55

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

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

    
72

    
73
# Plankton Image Commands
74

    
75

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

    
80
    arguments = dict(
81
        detail=FlagArgument('show detailed output', ('-l', '--details')),
82
        container_format=ValueArgument(
83
            'filter by container format',
84
            '--container-format'),
85
        disk_format=ValueArgument('filter by disk format', '--disk-format'),
86
        name=ValueArgument('filter by name', '--name'),
87
        name_pref=ValueArgument(
88
            'filter by name prefix (case insensitive)',
89
            '--name-prefix'),
90
        name_suff=ValueArgument(
91
            'filter by name suffix (case insensitive)',
92
            '--name-suffix'),
93
        name_like=ValueArgument(
94
            'print only if name contains this (case insensitive)',
95
            '--name-like'),
96
        size_min=IntArgument('filter by minimum size', '--size-min'),
97
        size_max=IntArgument('filter by maximum size', '--size-max'),
98
        status=ValueArgument('filter by status', '--status'),
99
        owner=ValueArgument('filter by owner', '--owner'),
100
        order=ValueArgument(
101
            'order by FIELD ( - to reverse order)',
102
            '--order',
103
            default=''),
104
        limit=IntArgument('limit number of listed images', ('-n', '--number')),
105
        more=FlagArgument(
106
            'output results in pages (-n to set items per page, default 10)',
107
            '--more'),
108
        enum=FlagArgument('Enumerate results', '--enumerate')
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
        images = self._filtered_by_name(images)
152
        kwargs = dict(with_enumeration=self['enum'])
153
        if self['more']:
154
            kwargs['page_size'] = self['limit'] or 10
155
        elif self['limit']:
156
            images = images[:self['limit']]
157
        self._print(images, **kwargs)
158

    
159
    def main(self):
160
        super(self.__class__, self)._run()
161
        self._run()
162

    
163

    
164
@command(image_cmds)
165
class image_meta(_init_image, _optional_json):
166
    """Get image metadata
167
    Image metadata include:
168
    - image file information (location, size, etc.)
169
    - image information (id, name, etc.)
170
    - image os properties (os, fs, etc.)
171
    """
172

    
173
    @errors.generic.all
174
    @errors.plankton.connection
175
    @errors.plankton.id
176
    def _run(self, image_id):
177
        self._print([self.client.get_meta(image_id)])
178

    
179
    def main(self, image_id):
180
        super(self.__class__, self)._run()
181
        self._run(image_id=image_id)
182

    
183

    
184
@command(image_cmds)
185
class image_register(_init_image, _optional_json):
186
    """(Re)Register an image"""
187

    
188
    arguments = dict(
189
        checksum=ValueArgument('set image checksum', '--checksum'),
190
        container_format=ValueArgument(
191
            'set container format',
192
            '--container-format'),
193
        disk_format=ValueArgument('set disk format', '--disk-format'),
194
        #id=ValueArgument('set image ID', '--id'),
195
        owner=ValueArgument('set image owner (admin only)', '--owner'),
196
        properties=KeyValueArgument(
197
            'add property in key=value form (can be repeated)',
198
            ('-p', '--property')),
199
        is_public=FlagArgument('mark image as public', '--public'),
200
        size=IntArgument('set image size', '--size')
201
    )
202

    
203
    @errors.generic.all
204
    @errors.plankton.connection
205
    def _run(self, name, location):
206
        if not location.startswith('pithos://'):
207
            account = self.config.get('file', 'account') \
208
                or self.config.get('global', 'account')
209
            assert account, 'No user account provided'
210
            if account[-1] == '/':
211
                account = account[:-1]
212
            container = self.config.get('file', 'container') \
213
                or self.config.get('global', 'container')
214
            if not container:
215
                location = 'pithos://%s/%s' % (account, location)
216
            else:
217
                location = 'pithos://%s/%s/%s' % (account, container, location)
218

    
219
        params = {}
220
        for key in set([
221
                'checksum',
222
                'container_format',
223
                'disk_format',
224
                'owner',
225
                'size',
226
                'is_public']).intersection(self.arguments):
227
            params[key] = self[key]
228
        properties = self['properties']
229

    
230
        self._print([self.client.register(name, location, params, properties)])
231

    
232
    def main(self, name, location):
233
        super(self.__class__, self)._run()
234
        self._run(name, location)
235

    
236

    
237
@command(image_cmds)
238
class image_unregister(_init_image, _optional_output_cmd):
239
    """Unregister an image (does not delete the image file)"""
240

    
241
    @errors.generic.all
242
    @errors.plankton.connection
243
    @errors.plankton.id
244
    def _run(self, image_id):
245
        self._optional_output(self.client.unregister(image_id))
246

    
247
    def main(self, image_id):
248
        super(self.__class__, self)._run()
249
        self._run(image_id=image_id)
250

    
251

    
252
@command(image_cmds)
253
class image_shared(_init_image, _optional_json):
254
    """List images shared by a member"""
255

    
256
    @errors.generic.all
257
    @errors.plankton.connection
258
    def _run(self, member):
259
        self._print(self.client.list_shared(member), title=('image_id',))
260

    
261
    def main(self, member):
262
        super(self.__class__, self)._run()
263
        self._run(member)
264

    
265

    
266
@command(image_cmds)
267
class image_members(_init_image):
268
    """Manage members. Members of an image are users who can modify it"""
269

    
270

    
271
@command(image_cmds)
272
class image_members_list(_init_image, _optional_json):
273
    """List members of an image"""
274

    
275
    @errors.generic.all
276
    @errors.plankton.connection
277
    @errors.plankton.id
278
    def _run(self, image_id):
279
        self._print(self.client.list_members(image_id), title=('member_id',))
280

    
281
    def main(self, image_id):
282
        super(self.__class__, self)._run()
283
        self._run(image_id=image_id)
284

    
285

    
286
@command(image_cmds)
287
class image_members_add(_init_image, _optional_output_cmd):
288
    """Add a member to an image"""
289

    
290
    @errors.generic.all
291
    @errors.plankton.connection
292
    @errors.plankton.id
293
    def _run(self, image_id=None, member=None):
294
            self._optional_output(self.client.add_member(image_id, member))
295

    
296
    def main(self, image_id, member):
297
        super(self.__class__, self)._run()
298
        self._run(image_id=image_id, member=member)
299

    
300

    
301
@command(image_cmds)
302
class image_members_delete(_init_image, _optional_output_cmd):
303
    """Remove a member from an image"""
304

    
305
    @errors.generic.all
306
    @errors.plankton.connection
307
    @errors.plankton.id
308
    def _run(self, image_id=None, member=None):
309
            self._optional_output(self.client.remove_member(image_id, member))
310

    
311
    def main(self, image_id, member):
312
        super(self.__class__, self)._run()
313
        self._run(image_id=image_id, member=member)
314

    
315

    
316
@command(image_cmds)
317
class image_members_set(_init_image, _optional_output_cmd):
318
    """Set the members of an image"""
319

    
320
    @errors.generic.all
321
    @errors.plankton.connection
322
    @errors.plankton.id
323
    def _run(self, image_id, members):
324
            self._optional_output(self.client.set_members(image_id, members))
325

    
326
    def main(self, image_id, *members):
327
        super(self.__class__, self)._run()
328
        self._run(image_id=image_id, members=members)
329

    
330

    
331
# Compute Image Commands
332

    
333

    
334
@command(image_cmds)
335
class image_compute(_init_cyclades):
336
    """Cyclades/Compute API image commands"""
337

    
338

    
339
@command(image_cmds)
340
class image_compute_list(_init_cyclades, _optional_json):
341
    """List images"""
342

    
343
    arguments = dict(
344
        detail=FlagArgument('show detailed output', ('-l', '--details')),
345
        limit=IntArgument('limit number listed images', ('-n', '--number')),
346
        more=FlagArgument(
347
            'output results in pages (-n to set items per page, default 10)',
348
            '--more'),
349
        enum=FlagArgument('Enumerate results', '--enumerate')
350
    )
351

    
352
    def _make_results_pretty(self, images):
353
        for img in images:
354
            if 'metadata' in img:
355
                img['metadata'] = img['metadata']['values']
356

    
357
    @errors.generic.all
358
    @errors.cyclades.connection
359
    def _run(self):
360
        images = self.client.list_images(self['detail'])
361
        if self['detail'] and not self['json_output']:
362
            self._make_results_pretty(images)
363
        kwargs = dict(with_enumeration=self['enum'])
364
        if self['more']:
365
            kwargs['page_size'] = self['limit'] or 10
366
        else:
367
            images = images[:self['limit']]
368
        self._print(images, **kwargs)
369

    
370
    def main(self):
371
        super(self.__class__, self)._run()
372
        self._run()
373

    
374

    
375
@command(image_cmds)
376
class image_compute_info(_init_cyclades, _optional_json):
377
    """Get detailed information on an image"""
378

    
379
    @errors.generic.all
380
    @errors.cyclades.connection
381
    @errors.plankton.id
382
    def _run(self, image_id):
383
        image = self.client.get_image_details(image_id)
384
        if (not self['json_output']) and 'metadata' in image:
385
            image['metadata'] = image['metadata']['values']
386
        self._print([image])
387

    
388
    def main(self, image_id):
389
        super(self.__class__, self)._run()
390
        self._run(image_id=image_id)
391

    
392

    
393
@command(image_cmds)
394
class image_compute_delete(_init_cyclades, _optional_output_cmd):
395
    """Delete an image (WARNING: image file is also removed)"""
396

    
397
    @errors.generic.all
398
    @errors.cyclades.connection
399
    @errors.plankton.id
400
    def _run(self, image_id):
401
        self._optional_output(self.client.delete_image(image_id))
402

    
403
    def main(self, image_id):
404
        super(self.__class__, self)._run()
405
        self._run(image_id=image_id)
406

    
407

    
408
@command(image_cmds)
409
class image_compute_properties(_init_cyclades):
410
    """Manage properties related to OS installation in an image"""
411

    
412

    
413
@command(image_cmds)
414
class image_compute_properties_list(_init_cyclades, _optional_json):
415
    """List all image properties"""
416

    
417
    @errors.generic.all
418
    @errors.cyclades.connection
419
    @errors.plankton.id
420
    def _run(self, image_id):
421
        self._print(self.client.get_image_metadata(image_id), print_dict)
422

    
423
    def main(self, image_id):
424
        super(self.__class__, self)._run()
425
        self._run(image_id=image_id)
426

    
427

    
428
@command(image_cmds)
429
class image_compute_properties_get(_init_cyclades, _optional_json):
430
    """Get an image property"""
431

    
432
    @errors.generic.all
433
    @errors.cyclades.connection
434
    @errors.plankton.id
435
    @errors.plankton.metadata
436
    def _run(self, image_id, key):
437
        self._print(self.client.get_image_metadata(image_id, key), print_dict)
438

    
439
    def main(self, image_id, key):
440
        super(self.__class__, self)._run()
441
        self._run(image_id=image_id, key=key)
442

    
443

    
444
@command(image_cmds)
445
class image_compute_properties_add(_init_cyclades, _optional_json):
446
    """Add a property to an image"""
447

    
448
    @errors.generic.all
449
    @errors.cyclades.connection
450
    @errors.plankton.id
451
    @errors.plankton.metadata
452
    def _run(self, image_id, key, val):
453
        self._print(
454
            self.client.create_image_metadata(image_id, key, val), print_dict)
455

    
456
    def main(self, image_id, key, val):
457
        super(self.__class__, self)._run()
458
        self._run(image_id=image_id, key=key, val=val)
459

    
460

    
461
@command(image_cmds)
462
class image_compute_properties_set(_init_cyclades, _optional_json):
463
    """Add / update a set of properties for an image
464
    proeprties must be given in the form key=value, e.v.
465
    /image compute properties set <image-id> key1=val1 key2=val2
466
    """
467

    
468
    @errors.generic.all
469
    @errors.cyclades.connection
470
    @errors.plankton.id
471
    def _run(self, image_id, keyvals):
472
        meta = dict()
473
        for keyval in keyvals:
474
            key, val = keyval.split('=')
475
            meta[key] = val
476
        self._print(
477
            self.client.update_image_metadata(image_id, **meta), print_dict)
478

    
479
    def main(self, image_id, *key_equals_value):
480
        super(self.__class__, self)._run()
481
        self._run(image_id=image_id, keyvals=key_equals_value)
482

    
483

    
484
@command(image_cmds)
485
class image_compute_properties_delete(_init_cyclades, _optional_output_cmd):
486
    """Delete a property from an image"""
487

    
488
    @errors.generic.all
489
    @errors.cyclades.connection
490
    @errors.plankton.id
491
    @errors.plankton.metadata
492
    def _run(self, image_id, key):
493
        self._optional_output(self.client.delete_image_metadata(image_id, key))
494

    
495
    def main(self, image_id, key):
496
        super(self.__class__, self)._run()
497
        self._run(image_id=image_id, key=key)