Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (15.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
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

    
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
    )
109

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

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

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

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

    
151
        if self['more']:
152
            print_items(
153
                images,
154
                with_enumeration=self['enum'], page_size=self['limit'] or 10)
155
        elif self['limit']:
156
            print_items(images[:self['limit']], with_enumeration=self['enum'])
157
        else:
158
            print_items(images, with_enumeration=self['enum'])
159

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

    
164

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

    
174
    @errors.generic.all
175
    @errors.plankton.connection
176
    @errors.plankton.id
177
    def _run(self, image_id):
178
        image = self.client.get_meta(image_id)
179
        print_dict(image)
180

    
181
    def main(self, image_id):
182
        super(self.__class__, self)._run()
183
        self._run(image_id=image_id)
184

    
185

    
186
@command(image_cmds)
187
class image_register(_init_image):
188
    """(Re)Register an image"""
189

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

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

    
224
        params = {}
225
        for key in set([
226
                'checksum',
227
                'container_format',
228
                'disk_format',
229
                'owner',
230
                'size',
231
                'is_public']).intersection(self.arguments):
232
            params[key] = self[key]
233

    
234
            properties = self['properties']
235
        if self['update']:
236
            self.client.reregister(location, name, params, properties)
237
        else:
238
            r = self.client.register(name, location, params, properties)
239
            print_dict(r)
240

    
241
    def main(self, name, location):
242
        super(self.__class__, self)._run()
243
        self._run(name, location)
244

    
245

    
246
@command(image_cmds)
247
class image_unregister(_init_image):
248
    """Unregister an image (does not delete the image file)"""
249

    
250
    @errors.generic.all
251
    @errors.plankton.connection
252
    @errors.plankton.id
253
    def _run(self, image_id):
254
        self.client.unregister(image_id)
255

    
256
    def main(self, image_id):
257
        super(self.__class__, self)._run()
258
        self._run(image_id=image_id)
259

    
260

    
261
@command(image_cmds)
262
class image_members(_init_image):
263
    """Get image members"""
264

    
265
    @errors.generic.all
266
    @errors.plankton.connection
267
    @errors.plankton.id
268
    def _run(self, image_id):
269
        members = self.client.list_members(image_id)
270
        print_items(members)
271

    
272
    def main(self, image_id):
273
        super(self.__class__, self)._run()
274
        self._run(image_id=image_id)
275

    
276

    
277
@command(image_cmds)
278
class image_shared(_init_image):
279
    """List images shared by a member"""
280

    
281
    @errors.generic.all
282
    @errors.plankton.connection
283
    def _run(self, member):
284
        images = self.client.list_shared(member)
285
        print_items(images)
286

    
287
    def main(self, member):
288
        super(self.__class__, self)._run()
289
        self._run(member)
290

    
291

    
292
@command(image_cmds)
293
class image_addmember(_init_image):
294
    """Add a member to an image"""
295

    
296
    @errors.generic.all
297
    @errors.plankton.connection
298
    @errors.plankton.id
299
    def _run(self, image_id=None, member=None):
300
            self.client.add_member(image_id, member)
301

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

    
306

    
307
@command(image_cmds)
308
class image_delmember(_init_image):
309
    """Remove a member from an image"""
310

    
311
    @errors.generic.all
312
    @errors.plankton.connection
313
    @errors.plankton.id
314
    def _run(self, image_id=None, member=None):
315
            self.client.remove_member(image_id, member)
316

    
317
    def main(self, image_id, member):
318
        super(self.__class__, self)._run()
319
        self._run(image_id=image_id, member=member)
320

    
321

    
322
@command(image_cmds)
323
class image_setmembers(_init_image):
324
    """Set the members of an image"""
325

    
326
    @errors.generic.all
327
    @errors.plankton.connection
328
    @errors.plankton.id
329
    def _run(self, image_id, members):
330
            self.client.set_members(image_id, members)
331

    
332
    def main(self, image_id, *members):
333
        super(self.__class__, self)._run()
334
        self._run(image_id=image_id, members=members)
335

    
336

    
337
# Compute Image Commands
338

    
339

    
340
@command(image_cmds)
341
class image_compute(_init_cyclades):
342
    """Cyclades/Compute API image commands"""
343

    
344

    
345
@command(image_cmds)
346
class image_compute_list(_init_cyclades):
347
    """List images"""
348

    
349
    arguments = dict(
350
        detail=FlagArgument('show detailed output', ('-l', '--details')),
351
        limit=IntArgument('limit number listed images', ('-n', '--number')),
352
        more=FlagArgument(
353
            'output results in pages (-n to set items per page, default 10)',
354
            '--more'),
355
        enum=FlagArgument('Enumerate results', '--enumerate')
356
    )
357

    
358
    def _make_results_pretty(self, images):
359
        for img in images:
360
            if 'metadata' in img:
361
                img['metadata'] = img['metadata']['values']
362

    
363
    @errors.generic.all
364
    @errors.cyclades.connection
365
    def _run(self):
366
        images = self.client.list_images(self['detail'])
367
        if self['detail']:
368
            self._make_results_pretty(images)
369
        if self['more']:
370
            print_items(
371
                images,
372
                page_size=self['limit'] or 10, with_enumeration=self['enum'])
373
        else:
374
            print_items(images[:self['limit']], with_enumeration=self['enum'])
375

    
376
    def main(self):
377
        super(self.__class__, self)._run()
378
        self._run()
379

    
380

    
381
@command(image_cmds)
382
class image_compute_info(_init_cyclades):
383
    """Get detailed information on an image"""
384

    
385
    @errors.generic.all
386
    @errors.cyclades.connection
387
    @errors.plankton.id
388
    def _run(self, image_id):
389
        image = self.client.get_image_details(image_id)
390
        if 'metadata' in image:
391
            image['metadata'] = image['metadata']['values']
392
        print_dict(image)
393

    
394
    def main(self, image_id):
395
        super(self.__class__, self)._run()
396
        self._run(image_id=image_id)
397

    
398

    
399
@command(image_cmds)
400
class image_compute_delete(_init_cyclades):
401
    """Delete an image (WARNING: image file is also removed)"""
402

    
403
    @errors.generic.all
404
    @errors.cyclades.connection
405
    @errors.plankton.id
406
    def _run(self, image_id):
407
        self.client.delete_image(image_id)
408

    
409
    def main(self, image_id):
410
        super(self.__class__, self)._run()
411
        self._run(image_id=image_id)
412

    
413

    
414
@command(image_cmds)
415
class image_compute_properties(_init_cyclades):
416
    """Get properties related to OS installation in an image"""
417

    
418
    @errors.generic.all
419
    @errors.cyclades.connection
420
    @errors.plankton.id
421
    @errors.plankton.metadata
422
    def _run(self, image_id, key):
423
        r = self.client.get_image_metadata(image_id, key)
424
        print_dict(r)
425

    
426
    def main(self, image_id, key=''):
427
        super(self.__class__, self)._run()
428
        self._run(image_id=image_id, key=key)
429

    
430

    
431
@command(image_cmds)
432
class image_compute_addproperty(_init_cyclades):
433
    """Add an OS-related property to an image"""
434

    
435
    @errors.generic.all
436
    @errors.cyclades.connection
437
    @errors.plankton.id
438
    @errors.plankton.metadata
439
    def _run(self, image_id, key, val):
440
        r = self.client.create_image_metadata(image_id, key, val)
441
        print_dict(r)
442

    
443
    def main(self, image_id, key, val):
444
        super(self.__class__, self)._run()
445
        self._run(image_id=image_id, key=key, val=val)
446

    
447

    
448
@command(image_cmds)
449
class image_compute_setproperty(_init_cyclades):
450
    """Update an existing property in an image"""
451

    
452
    @errors.generic.all
453
    @errors.cyclades.connection
454
    @errors.plankton.id
455
    @errors.plankton.metadata
456
    def _run(self, image_id, key, val):
457
        metadata = {key: val}
458
        r = self.client.update_image_metadata(image_id, **metadata)
459
        print_dict(r)
460

    
461
    def main(self, image_id, key, val):
462
        super(self.__class__, self)._run()
463
        self._run(image_id=image_id, key=key, val=val)
464

    
465

    
466
@command(image_cmds)
467
class image_compute_delproperty(_init_cyclades):
468
    """Delete a property of an image"""
469

    
470
    @errors.generic.all
471
    @errors.cyclades.connection
472
    @errors.plankton.id
473
    @errors.plankton.metadata
474
    def _run(self, image_id, key):
475
        self.client.delete_image_metadata(image_id, key)
476

    
477
    def main(self, image_id, key):
478
        super(self.__class__, self)._run()
479
        self._run(image_id=image_id, key=key)