Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / image.py @ 16d7b9ff

History | View | Annotate | Download (34.6 kB)

1 e3f01d64 Stavros Sachtouris
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2 7493ccb6 Stavros Sachtouris
#
3 7493ccb6 Stavros Sachtouris
# Redistribution and use in source and binary forms, with or
4 7493ccb6 Stavros Sachtouris
# without modification, are permitted provided that the following
5 7493ccb6 Stavros Sachtouris
# conditions are met:
6 7493ccb6 Stavros Sachtouris
#
7 7493ccb6 Stavros Sachtouris
#   1. Redistributions of source code must retain the above
8 7493ccb6 Stavros Sachtouris
#      copyright notice, this list of conditions and the following
9 7493ccb6 Stavros Sachtouris
#      disclaimer.
10 7493ccb6 Stavros Sachtouris
#
11 7493ccb6 Stavros Sachtouris
#   2. Redistributions in binary form must reproduce the above
12 7493ccb6 Stavros Sachtouris
#      copyright notice, this list of conditions and the following
13 7493ccb6 Stavros Sachtouris
#      disclaimer in the documentation and/or other materials
14 7493ccb6 Stavros Sachtouris
#      provided with the distribution.
15 7493ccb6 Stavros Sachtouris
#
16 7493ccb6 Stavros Sachtouris
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 7493ccb6 Stavros Sachtouris
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 7493ccb6 Stavros Sachtouris
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 7493ccb6 Stavros Sachtouris
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 7493ccb6 Stavros Sachtouris
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 7493ccb6 Stavros Sachtouris
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 7493ccb6 Stavros Sachtouris
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 7493ccb6 Stavros Sachtouris
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 7493ccb6 Stavros Sachtouris
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 7493ccb6 Stavros Sachtouris
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 7493ccb6 Stavros Sachtouris
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 7493ccb6 Stavros Sachtouris
# POSSIBILITY OF SUCH DAMAGE.
28 7493ccb6 Stavros Sachtouris
#
29 7493ccb6 Stavros Sachtouris
# The views and conclusions contained in the software and
30 7493ccb6 Stavros Sachtouris
# documentation are those of the authors and should not be
31 7493ccb6 Stavros Sachtouris
# interpreted as representing official policies, either expressed
32 7493ccb6 Stavros Sachtouris
# or implied, of GRNET S.A.command
33 7493ccb6 Stavros Sachtouris
34 623a4ceb Stavros Sachtouris
from json import load, dumps
35 d77e33d4 Stavros Sachtouris
from os import path
36 623a4ceb Stavros Sachtouris
from logging import getLogger
37 6430d3a0 Stavros Sachtouris
from io import StringIO
38 76f58e2e Stavros Sachtouris
from pydoc import pager
39 623a4ceb Stavros Sachtouris
40 f3e94e06 Stavros Sachtouris
from kamaki.cli import command
41 d486baec Stavros Sachtouris
from kamaki.cli.command_tree import CommandTree
42 76f58e2e Stavros Sachtouris
from kamaki.cli.utils import filter_dicts_by_dict
43 1395c40e Stavros Sachtouris
from kamaki.clients.image import ImageClient
44 00336c85 Stavros Sachtouris
from kamaki.clients.pithos import PithosClient
45 00336c85 Stavros Sachtouris
from kamaki.clients.astakos import AstakosClient
46 00336c85 Stavros Sachtouris
from kamaki.clients import ClientError
47 ca5528f1 Stavros Sachtouris
from kamaki.cli.argument import (
48 ca5528f1 Stavros Sachtouris
    FlagArgument, ValueArgument, RepeatableArgument, KeyValueArgument,
49 ca5528f1 Stavros Sachtouris
    IntArgument, ProgressBarArgument)
50 bfb54881 Stavros Sachtouris
from kamaki.cli.commands.cyclades import _init_cyclades
51 8cec3671 Stavros Sachtouris
from kamaki.cli.errors import raiseCLIError, CLIBaseUrlError
52 b4f69041 Stavros Sachtouris
from kamaki.cli.commands import _command_init, errors, addLogSettings
53 6d190dd1 Stavros Sachtouris
from kamaki.cli.commands import (
54 6d190dd1 Stavros Sachtouris
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
55 0b368c8c Stavros Sachtouris
56 234954d1 Stavros Sachtouris
57 a29d2f88 Stavros Sachtouris
image_cmds = CommandTree(
58 a29d2f88 Stavros Sachtouris
    'image',
59 a29d2f88 Stavros Sachtouris
    'Cyclades/Plankton API image commands\n'
60 a29d2f88 Stavros Sachtouris
    'image compute:\tCyclades/Compute API image commands')
61 d486baec Stavros Sachtouris
_commands = [image_cmds]
62 234954d1 Stavros Sachtouris
63 234954d1 Stavros Sachtouris
64 aa82dd5a Stavros Sachtouris
howto_image_file = [
65 aa82dd5a Stavros Sachtouris
    'Kamaki commands to:',
66 c626151a Stavros Sachtouris
    ' get current user id: /user authenticate',
67 aa82dd5a Stavros Sachtouris
    ' check available containers: /file list',
68 aa82dd5a Stavros Sachtouris
    ' create a new container: /file create <container>',
69 aa82dd5a Stavros Sachtouris
    ' check container contents: /file list <container>',
70 7bbfeb1a Stavros Sachtouris
    ' upload files: /file upload <image file> <container>',
71 7bbfeb1a Stavros Sachtouris
    ' register an image: /image register <image name> <container>:<path>']
72 aa82dd5a Stavros Sachtouris
73 aa82dd5a Stavros Sachtouris
about_image_id = ['To see a list of available image ids: /image list']
74 15142309 Stavros Sachtouris
75 15142309 Stavros Sachtouris
76 623a4ceb Stavros Sachtouris
log = getLogger(__name__)
77 623a4ceb Stavros Sachtouris
78 623a4ceb Stavros Sachtouris
79 5eae854d Stavros Sachtouris
class _init_image(_command_init):
80 a03ade9e Stavros Sachtouris
    @errors.generic.all
81 b4f69041 Stavros Sachtouris
    @addLogSettings
82 a03ade9e Stavros Sachtouris
    def _run(self):
83 b4f69041 Stavros Sachtouris
        if getattr(self, 'cloud', None):
84 b4f69041 Stavros Sachtouris
            img_url = self._custom_url('image') or self._custom_url('plankton')
85 b4f69041 Stavros Sachtouris
            if img_url:
86 1d0f1ffa Stavros Sachtouris
                token = self._custom_token('image') or self._custom_token(
87 1d0f1ffa Stavros Sachtouris
                    'plankton') or self.config.get_cloud(self.cloud, 'token')
88 b4f69041 Stavros Sachtouris
                self.client = ImageClient(base_url=img_url, token=token)
89 b4f69041 Stavros Sachtouris
                return
90 8cec3671 Stavros Sachtouris
        if getattr(self, 'auth_base', False):
91 8cec3671 Stavros Sachtouris
            plankton_endpoints = self.auth_base.get_service_endpoints(
92 72fa0010 Stavros Sachtouris
                self._custom_type('image') or self._custom_type(
93 72fa0010 Stavros Sachtouris
                    'plankton') or 'image',
94 72fa0010 Stavros Sachtouris
                self._custom_version('image') or self._custom_version(
95 72fa0010 Stavros Sachtouris
                    'plankton') or '')
96 8cec3671 Stavros Sachtouris
            base_url = plankton_endpoints['publicURL']
97 b4f69041 Stavros Sachtouris
            token = self.auth_base.token
98 8cec3671 Stavros Sachtouris
        else:
99 8cec3671 Stavros Sachtouris
            raise CLIBaseUrlError(service='plankton')
100 a03ade9e Stavros Sachtouris
        self.client = ImageClient(base_url=base_url, token=token)
101 a03ade9e Stavros Sachtouris
102 7493ccb6 Stavros Sachtouris
    def main(self):
103 a03ade9e Stavros Sachtouris
        self._run()
104 7493ccb6 Stavros Sachtouris
105 234954d1 Stavros Sachtouris
106 573be34f Stavros Sachtouris
# Plankton Image Commands
107 573be34f Stavros Sachtouris
108 573be34f Stavros Sachtouris
109 aa82dd5a Stavros Sachtouris
def _validate_image_meta(json_dict, return_str=False):
110 623a4ceb Stavros Sachtouris
    """
111 623a4ceb Stavros Sachtouris
    :param json_dict" (dict) json-formated, of the form
112 623a4ceb Stavros Sachtouris
        {"key1": "val1", "key2": "val2", ...}
113 623a4ceb Stavros Sachtouris

114 623a4ceb Stavros Sachtouris
    :param return_str: (boolean) if true, return a json dump
115 623a4ceb Stavros Sachtouris

116 aa82dd5a Stavros Sachtouris
    :returns: (dict) if return_str is not True, else return str
117 623a4ceb Stavros Sachtouris

118 623a4ceb Stavros Sachtouris
    :raises TypeError, AttributeError: Invalid json format
119 623a4ceb Stavros Sachtouris

120 623a4ceb Stavros Sachtouris
    :raises AssertionError: Valid json but invalid image properties dict
121 623a4ceb Stavros Sachtouris
    """
122 9553da85 Stavros Sachtouris
    json_str = dumps(json_dict, indent=2)
123 623a4ceb Stavros Sachtouris
    for k, v in json_dict.items():
124 aa82dd5a Stavros Sachtouris
        if k.lower() == 'properties':
125 aa82dd5a Stavros Sachtouris
            for pk, pv in v.items():
126 aa82dd5a Stavros Sachtouris
                prop_ok = not (isinstance(pv, dict) or isinstance(pv, list))
127 aa82dd5a Stavros Sachtouris
                assert prop_ok, 'Invalid property value for key %s' % pk
128 aa82dd5a Stavros Sachtouris
                key_ok = not (' ' in k or '-' in k)
129 aa82dd5a Stavros Sachtouris
                assert key_ok, 'Invalid property key %s' % k
130 aa82dd5a Stavros Sachtouris
            continue
131 aa82dd5a Stavros Sachtouris
        meta_ok = not (isinstance(v, dict) or isinstance(v, list))
132 aa82dd5a Stavros Sachtouris
        assert meta_ok, 'Invalid value for meta key %s' % k
133 aa82dd5a Stavros Sachtouris
        meta_ok = ' ' not in k
134 aa82dd5a Stavros Sachtouris
        assert meta_ok, 'Invalid meta key [%s]' % k
135 623a4ceb Stavros Sachtouris
        json_dict[k] = '%s' % v
136 623a4ceb Stavros Sachtouris
    return json_str if return_str else json_dict
137 623a4ceb Stavros Sachtouris
138 623a4ceb Stavros Sachtouris
139 aa82dd5a Stavros Sachtouris
def _load_image_meta(filepath):
140 623a4ceb Stavros Sachtouris
    """
141 623a4ceb Stavros Sachtouris
    :param filepath: (str) the (relative) path of the metafile
142 623a4ceb Stavros Sachtouris

143 623a4ceb Stavros Sachtouris
    :returns: (dict) json_formated
144 623a4ceb Stavros Sachtouris

145 623a4ceb Stavros Sachtouris
    :raises TypeError, AttributeError: Invalid json format
146 623a4ceb Stavros Sachtouris

147 623a4ceb Stavros Sachtouris
    :raises AssertionError: Valid json but invalid image properties dict
148 623a4ceb Stavros Sachtouris
    """
149 d77e33d4 Stavros Sachtouris
    with open(path.abspath(filepath)) as f:
150 623a4ceb Stavros Sachtouris
        meta_dict = load(f)
151 623a4ceb Stavros Sachtouris
        try:
152 aa82dd5a Stavros Sachtouris
            return _validate_image_meta(meta_dict)
153 623a4ceb Stavros Sachtouris
        except AssertionError:
154 623a4ceb Stavros Sachtouris
            log.debug('Failed to load properties from file %s' % filepath)
155 623a4ceb Stavros Sachtouris
            raise
156 623a4ceb Stavros Sachtouris
157 623a4ceb Stavros Sachtouris
158 aa82dd5a Stavros Sachtouris
def _validate_image_location(location):
159 aa82dd5a Stavros Sachtouris
    """
160 f2ea1314 Stavros Sachtouris
    :param location: (str) pithos://<user-id>/<container>/<image-path>
161 aa82dd5a Stavros Sachtouris

162 f2ea1314 Stavros Sachtouris
    :returns: (<user-id>, <container>, <image-path>)
163 aa82dd5a Stavros Sachtouris

164 aa82dd5a Stavros Sachtouris
    :raises AssertionError: if location is invalid
165 aa82dd5a Stavros Sachtouris
    """
166 aa82dd5a Stavros Sachtouris
    prefix = 'pithos://'
167 aa82dd5a Stavros Sachtouris
    msg = 'Invalid prefix for location %s , try: %s' % (location, prefix)
168 aa82dd5a Stavros Sachtouris
    assert location.startswith(prefix), msg
169 aa82dd5a Stavros Sachtouris
    service, sep, rest = location.partition('://')
170 c626151a Stavros Sachtouris
    assert sep and rest, 'Location %s is missing user-id' % location
171 aa82dd5a Stavros Sachtouris
    uuid, sep, rest = rest.partition('/')
172 aa82dd5a Stavros Sachtouris
    assert sep and rest, 'Location %s is missing container' % location
173 aa82dd5a Stavros Sachtouris
    container, sep, img_path = rest.partition('/')
174 aa82dd5a Stavros Sachtouris
    assert sep and img_path, 'Location %s is missing image path' % location
175 aa82dd5a Stavros Sachtouris
    return uuid, container, img_path
176 aa82dd5a Stavros Sachtouris
177 aa82dd5a Stavros Sachtouris
178 d486baec Stavros Sachtouris
@command(image_cmds)
179 6d190dd1 Stavros Sachtouris
class image_list(_init_image, _optional_json, _name_filter, _id_filter):
180 573be34f Stavros Sachtouris
    """List images accessible by user"""
181 7493ccb6 Stavros Sachtouris
182 2d1202ee Stavros Sachtouris
    PERMANENTS = (
183 2d1202ee Stavros Sachtouris
        'id', 'name',
184 2d1202ee Stavros Sachtouris
        'status', 'container_format', 'disk_format', 'size')
185 2d1202ee Stavros Sachtouris
186 1ae79e60 Stavros Sachtouris
    arguments = dict(
187 f40f0cb7 Stavros Sachtouris
        detail=FlagArgument('show detailed output', ('-l', '--details')),
188 1ae79e60 Stavros Sachtouris
        container_format=ValueArgument(
189 1ae79e60 Stavros Sachtouris
            'filter by container format',
190 1ae79e60 Stavros Sachtouris
            '--container-format'),
191 1ae79e60 Stavros Sachtouris
        disk_format=ValueArgument('filter by disk format', '--disk-format'),
192 1ae79e60 Stavros Sachtouris
        size_min=IntArgument('filter by minimum size', '--size-min'),
193 1ae79e60 Stavros Sachtouris
        size_max=IntArgument('filter by maximum size', '--size-max'),
194 1ae79e60 Stavros Sachtouris
        status=ValueArgument('filter by status', '--status'),
195 f9457c89 Stavros Sachtouris
        owner=ValueArgument('filter by owner', '--owner'),
196 95641ecc Stavros Sachtouris
        owner_name=ValueArgument('filter by owners username', '--owner-name'),
197 1ae79e60 Stavros Sachtouris
        order=ValueArgument(
198 1ae79e60 Stavros Sachtouris
            'order by FIELD ( - to reverse order)',
199 1ae79e60 Stavros Sachtouris
            '--order',
200 83c3ba87 Stavros Sachtouris
            default=''),
201 f40f0cb7 Stavros Sachtouris
        limit=IntArgument('limit number of listed images', ('-n', '--number')),
202 83c3ba87 Stavros Sachtouris
        more=FlagArgument(
203 83c3ba87 Stavros Sachtouris
            'output results in pages (-n to set items per page, default 10)',
204 ed9af02c Stavros Sachtouris
            '--more'),
205 2d1202ee Stavros Sachtouris
        enum=FlagArgument('Enumerate results', '--enumerate'),
206 5576a4eb Stavros Sachtouris
        prop=KeyValueArgument('filter by property key=value', ('--property')),
207 5576a4eb Stavros Sachtouris
        prop_like=KeyValueArgument(
208 5576a4eb Stavros Sachtouris
            'fliter by property key=value where value is part of actual value',
209 1716a15d Stavros Sachtouris
            ('--property-like')),
210 1ae79e60 Stavros Sachtouris
    )
211 7493ccb6 Stavros Sachtouris
212 6d190dd1 Stavros Sachtouris
    def _filter_by_owner(self, images):
213 95641ecc Stavros Sachtouris
        ouuid = self['owner'] or self._username2uuid(self['owner_name'])
214 854222c7 Stavros Sachtouris
        return filter_dicts_by_dict(images, dict(owner=ouuid))
215 f9457c89 Stavros Sachtouris
216 5576a4eb Stavros Sachtouris
    def _add_owner_name(self, images):
217 5576a4eb Stavros Sachtouris
        uuids = self._uuids2usernames(
218 5576a4eb Stavros Sachtouris
            list(set([img['owner'] for img in images])))
219 5576a4eb Stavros Sachtouris
        for img in images:
220 5576a4eb Stavros Sachtouris
            img['owner'] += ' (%s)' % uuids[img['owner']]
221 5576a4eb Stavros Sachtouris
        return images
222 f1e5b343 Stavros Sachtouris
223 6d190dd1 Stavros Sachtouris
    def _filter_by_properties(self, images):
224 2d1202ee Stavros Sachtouris
        new_images = []
225 2d1202ee Stavros Sachtouris
        for img in images:
226 854222c7 Stavros Sachtouris
            props = [dict(img['properties'])]
227 854222c7 Stavros Sachtouris
            if self['prop']:
228 854222c7 Stavros Sachtouris
                props = filter_dicts_by_dict(props, self['prop'])
229 854222c7 Stavros Sachtouris
            if props and self['prop_like']:
230 854222c7 Stavros Sachtouris
                props = filter_dicts_by_dict(
231 854222c7 Stavros Sachtouris
                    props, self['prop_like'], exact_match=False)
232 854222c7 Stavros Sachtouris
            if props:
233 854222c7 Stavros Sachtouris
                new_images.append(img)
234 2d1202ee Stavros Sachtouris
        return new_images
235 2d1202ee Stavros Sachtouris
236 a03ade9e Stavros Sachtouris
    @errors.generic.all
237 a03ade9e Stavros Sachtouris
    @errors.cyclades.connection
238 a03ade9e Stavros Sachtouris
    def _run(self):
239 a03ade9e Stavros Sachtouris
        super(self.__class__, self)._run()
240 7493ccb6 Stavros Sachtouris
        filters = {}
241 fa984c2c Stavros Sachtouris
        for arg in set([
242 1ae79e60 Stavros Sachtouris
                'container_format',
243 1ae79e60 Stavros Sachtouris
                'disk_format',
244 1ae79e60 Stavros Sachtouris
                'name',
245 1ae79e60 Stavros Sachtouris
                'size_min',
246 1ae79e60 Stavros Sachtouris
                'size_max',
247 de73876b Stavros Sachtouris
                'status']).intersection(self.arguments):
248 1ae79e60 Stavros Sachtouris
            filters[arg] = self[arg]
249 1ae79e60 Stavros Sachtouris
250 1ae79e60 Stavros Sachtouris
        order = self['order']
251 854222c7 Stavros Sachtouris
        detail = self['detail'] or (
252 854222c7 Stavros Sachtouris
            self['prop'] or self['prop_like']) or (
253 854222c7 Stavros Sachtouris
            self['owner'] or self['owner_name'])
254 f9457c89 Stavros Sachtouris
255 854222c7 Stavros Sachtouris
        images = self.client.list_public(detail, filters, order)
256 854222c7 Stavros Sachtouris
257 854222c7 Stavros Sachtouris
        if self['owner'] or self['owner_name']:
258 6d190dd1 Stavros Sachtouris
            images = self._filter_by_owner(images)
259 854222c7 Stavros Sachtouris
        if self['prop'] or self['prop_like']:
260 6d190dd1 Stavros Sachtouris
            images = self._filter_by_properties(images)
261 6d190dd1 Stavros Sachtouris
        images = self._filter_by_id(images)
262 6d190dd1 Stavros Sachtouris
        images = self._non_exact_name_filter(images)
263 854222c7 Stavros Sachtouris
264 5576a4eb Stavros Sachtouris
        if self['detail'] and not self['json_output']:
265 5576a4eb Stavros Sachtouris
            images = self._add_owner_name(images)
266 854222c7 Stavros Sachtouris
        elif detail and not self['detail']:
267 854222c7 Stavros Sachtouris
            for img in images:
268 854222c7 Stavros Sachtouris
                for key in set(img).difference(self.PERMANENTS):
269 854222c7 Stavros Sachtouris
                    img.pop(key)
270 545c6c29 Stavros Sachtouris
        kwargs = dict(with_enumeration=self['enum'])
271 6430d3a0 Stavros Sachtouris
        if self['limit']:
272 545c6c29 Stavros Sachtouris
            images = images[:self['limit']]
273 6430d3a0 Stavros Sachtouris
        if self['more']:
274 6430d3a0 Stavros Sachtouris
            kwargs['out'] = StringIO()
275 6430d3a0 Stavros Sachtouris
            kwargs['title'] = ()
276 545c6c29 Stavros Sachtouris
        self._print(images, **kwargs)
277 6430d3a0 Stavros Sachtouris
        if self['more']:
278 6430d3a0 Stavros Sachtouris
            pager(kwargs['out'].getvalue())
279 7493ccb6 Stavros Sachtouris
280 a03ade9e Stavros Sachtouris
    def main(self):
281 a03ade9e Stavros Sachtouris
        super(self.__class__, self)._run()
282 a03ade9e Stavros Sachtouris
        self._run()
283 a03ade9e Stavros Sachtouris
284 234954d1 Stavros Sachtouris
285 d486baec Stavros Sachtouris
@command(image_cmds)
286 ca5528f1 Stavros Sachtouris
class image_meta(_init_image):
287 ca5528f1 Stavros Sachtouris
    """Manage image metadata and custom properties"""
288 ca5528f1 Stavros Sachtouris
289 ca5528f1 Stavros Sachtouris
290 ca5528f1 Stavros Sachtouris
@command(image_cmds)
291 82cc4b8f Stavros Sachtouris
class image_info(_init_image, _optional_json):
292 15142309 Stavros Sachtouris
    """Get image metadata
293 15142309 Stavros Sachtouris
    Image metadata include:
294 439826ec Stavros Sachtouris
    - image file information (location, size, etc.)
295 439826ec Stavros Sachtouris
    - image information (id, name, etc.)
296 439826ec Stavros Sachtouris
    - image os properties (os, fs, etc.)
297 15142309 Stavros Sachtouris
    """
298 7493ccb6 Stavros Sachtouris
299 a03ade9e Stavros Sachtouris
    @errors.generic.all
300 a03ade9e Stavros Sachtouris
    @errors.plankton.connection
301 a03ade9e Stavros Sachtouris
    @errors.plankton.id
302 a03ade9e Stavros Sachtouris
    def _run(self, image_id):
303 ca5528f1 Stavros Sachtouris
        meta = self.client.get_meta(image_id)
304 ca5528f1 Stavros Sachtouris
        if not self['json_output']:
305 ca5528f1 Stavros Sachtouris
            meta['owner'] += ' (%s)' % self._uuid2username(meta['owner'])
306 76f58e2e Stavros Sachtouris
        self._print(meta, self.print_dict)
307 ca5528f1 Stavros Sachtouris
308 ca5528f1 Stavros Sachtouris
    def main(self, image_id):
309 ca5528f1 Stavros Sachtouris
        super(self.__class__, self)._run()
310 ca5528f1 Stavros Sachtouris
        self._run(image_id=image_id)
311 ca5528f1 Stavros Sachtouris
312 ca5528f1 Stavros Sachtouris
313 ca5528f1 Stavros Sachtouris
@command(image_cmds)
314 ca5528f1 Stavros Sachtouris
class image_meta_set(_init_image, _optional_output_cmd):
315 ca5528f1 Stavros Sachtouris
    """Add / update metadata and properties for an image
316 ca5528f1 Stavros Sachtouris
    The original image preserves the values that are not affected
317 ca5528f1 Stavros Sachtouris
    """
318 ca5528f1 Stavros Sachtouris
319 ca5528f1 Stavros Sachtouris
    arguments = dict(
320 ca5528f1 Stavros Sachtouris
        name=ValueArgument('Set a new name', ('--name')),
321 ca5528f1 Stavros Sachtouris
        disk_format=ValueArgument('Set a new disk format', ('--disk-format')),
322 ca5528f1 Stavros Sachtouris
        container_format=ValueArgument(
323 ca5528f1 Stavros Sachtouris
            'Set a new container format', ('--container-format')),
324 ca5528f1 Stavros Sachtouris
        status=ValueArgument('Set a new status', ('--status')),
325 ca5528f1 Stavros Sachtouris
        publish=FlagArgument('publish the image', ('--publish')),
326 ca5528f1 Stavros Sachtouris
        unpublish=FlagArgument('unpublish the image', ('--unpublish')),
327 ca5528f1 Stavros Sachtouris
        properties=KeyValueArgument(
328 ca5528f1 Stavros Sachtouris
            'set property in key=value form (can be repeated)',
329 ca5528f1 Stavros Sachtouris
            ('-p', '--property'))
330 ca5528f1 Stavros Sachtouris
    )
331 ca5528f1 Stavros Sachtouris
332 ca5528f1 Stavros Sachtouris
    def _check_empty(self):
333 ca5528f1 Stavros Sachtouris
        for term in (
334 ca5528f1 Stavros Sachtouris
                'name', 'disk_format', 'container_format', 'status', 'publish',
335 ca5528f1 Stavros Sachtouris
                'unpublish', 'properties'):
336 ca5528f1 Stavros Sachtouris
            if self['term']:
337 ca5528f1 Stavros Sachtouris
                if self['publish'] and self['unpublish']:
338 ca5528f1 Stavros Sachtouris
                    raiseCLIError(
339 ca5528f1 Stavros Sachtouris
                        '--publish and --unpublish are mutually exclusive')
340 ca5528f1 Stavros Sachtouris
                return
341 ca5528f1 Stavros Sachtouris
        raiseCLIError(
342 ca5528f1 Stavros Sachtouris
            'Nothing to update, please use arguments (-h for a list)')
343 ca5528f1 Stavros Sachtouris
344 ca5528f1 Stavros Sachtouris
    @errors.generic.all
345 ca5528f1 Stavros Sachtouris
    @errors.plankton.connection
346 ca5528f1 Stavros Sachtouris
    @errors.plankton.id
347 ca5528f1 Stavros Sachtouris
    def _run(self, image_id):
348 ca5528f1 Stavros Sachtouris
        self._check_empty()
349 ca5528f1 Stavros Sachtouris
        meta = self.client.get_meta(image_id)
350 ca5528f1 Stavros Sachtouris
        for k, v in self['properties'].items():
351 ca5528f1 Stavros Sachtouris
            meta['properties'][k.upper()] = v
352 ca5528f1 Stavros Sachtouris
        self._optional_output(self.client.update_image(
353 ca5528f1 Stavros Sachtouris
            image_id,
354 ca5528f1 Stavros Sachtouris
            name=self['name'],
355 ca5528f1 Stavros Sachtouris
            disk_format=self['disk_format'],
356 ca5528f1 Stavros Sachtouris
            container_format=self['container_format'],
357 ca5528f1 Stavros Sachtouris
            status=self['status'],
358 ca5528f1 Stavros Sachtouris
            public=self['publish'] or self['unpublish'] or None,
359 ca5528f1 Stavros Sachtouris
            **meta['properties']))
360 ca5528f1 Stavros Sachtouris
361 ca5528f1 Stavros Sachtouris
    def main(self, image_id):
362 ca5528f1 Stavros Sachtouris
        super(self.__class__, self)._run()
363 ca5528f1 Stavros Sachtouris
        self._run(image_id=image_id)
364 ca5528f1 Stavros Sachtouris
365 ca5528f1 Stavros Sachtouris
366 ca5528f1 Stavros Sachtouris
@command(image_cmds)
367 ca5528f1 Stavros Sachtouris
class image_meta_delete(_init_image, _optional_output_cmd):
368 ca5528f1 Stavros Sachtouris
    """Remove/empty image metadata and/or custom properties"""
369 ca5528f1 Stavros Sachtouris
370 ca5528f1 Stavros Sachtouris
    arguments = dict(
371 ca5528f1 Stavros Sachtouris
        disk_format=FlagArgument('Empty disk format', ('--disk-format')),
372 ca5528f1 Stavros Sachtouris
        container_format=FlagArgument(
373 ca5528f1 Stavros Sachtouris
            'Empty container format', ('--container-format')),
374 ca5528f1 Stavros Sachtouris
        status=FlagArgument('Empty status', ('--status')),
375 ca5528f1 Stavros Sachtouris
        properties=RepeatableArgument(
376 ca5528f1 Stavros Sachtouris
            'Property keys to remove', ('-p', '--property'))
377 ca5528f1 Stavros Sachtouris
    )
378 ca5528f1 Stavros Sachtouris
379 ca5528f1 Stavros Sachtouris
    def _check_empty(self):
380 1d0f1ffa Stavros Sachtouris
        for t in ('disk_format', 'container_format', 'status', 'properties'):
381 1d0f1ffa Stavros Sachtouris
            if self[t]:
382 ca5528f1 Stavros Sachtouris
                return
383 ca5528f1 Stavros Sachtouris
        raiseCLIError(
384 ca5528f1 Stavros Sachtouris
            'Nothing to update, please use arguments (-h for a list)')
385 ca5528f1 Stavros Sachtouris
386 ca5528f1 Stavros Sachtouris
    @errors.generic.all
387 ca5528f1 Stavros Sachtouris
    @errors.plankton.connection
388 ca5528f1 Stavros Sachtouris
    @errors.plankton.id
389 ca5528f1 Stavros Sachtouris
    def _run(self, image_id):
390 ca5528f1 Stavros Sachtouris
        self._check_empty()
391 ca5528f1 Stavros Sachtouris
        meta = self.client.get_meta(image_id)
392 ca5528f1 Stavros Sachtouris
        for k in self['properties']:
393 ca5528f1 Stavros Sachtouris
            meta['properties'].pop(k.upper(), None)
394 ca5528f1 Stavros Sachtouris
        self._optional_output(self.client.update_image(
395 ca5528f1 Stavros Sachtouris
            image_id,
396 ca5528f1 Stavros Sachtouris
            disk_format='' if self['disk_format'] else None,
397 ca5528f1 Stavros Sachtouris
            container_format='' if self['container_format'] else None,
398 ca5528f1 Stavros Sachtouris
            status='' if self['status'] else None,
399 ca5528f1 Stavros Sachtouris
            **meta['properties']))
400 7493ccb6 Stavros Sachtouris
401 a03ade9e Stavros Sachtouris
    def main(self, image_id):
402 a03ade9e Stavros Sachtouris
        super(self.__class__, self)._run()
403 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id)
404 a03ade9e Stavros Sachtouris
405 234954d1 Stavros Sachtouris
406 d486baec Stavros Sachtouris
@command(image_cmds)
407 545c6c29 Stavros Sachtouris
class image_register(_init_image, _optional_json):
408 38db356b Stavros Sachtouris
    """(Re)Register an image file to an Image service
409 38db356b Stavros Sachtouris
    The image file must be stored at a pithos repository
410 16d7b9ff Stavros Sachtouris
    Some metadata can be set by user (e.g., disk-format) while others are set
411 16d7b9ff Stavros Sachtouris
    only automatically (e.g., image id). There are also some custom user
412 38db356b Stavros Sachtouris
    metadata, called properties.
413 38db356b Stavros Sachtouris
    A register command creates a remote meta file at
414 97086fcd Stavros Sachtouris
    .  <container>:<image path>.meta
415 38db356b Stavros Sachtouris
    Users may download and edit this file and use it to re-register one or more
416 38db356b Stavros Sachtouris
    images.
417 38db356b Stavros Sachtouris
    In case of a meta file, runtime arguments for metadata or properties
418 38db356b Stavros Sachtouris
    override meta file settings.
419 38db356b Stavros Sachtouris
    """
420 7493ccb6 Stavros Sachtouris
421 d77e33d4 Stavros Sachtouris
    container_info_cache = {}
422 d77e33d4 Stavros Sachtouris
423 1ae79e60 Stavros Sachtouris
    arguments = dict(
424 38db356b Stavros Sachtouris
        checksum=ValueArgument('Set image checksum', '--checksum'),
425 1ae79e60 Stavros Sachtouris
        container_format=ValueArgument(
426 1d0f1ffa Stavros Sachtouris
            'Set container format', '--container-format'),
427 38db356b Stavros Sachtouris
        disk_format=ValueArgument('Set disk format', '--disk-format'),
428 38db356b Stavros Sachtouris
        owner_name=ValueArgument('Set user uuid by user name', '--owner-name'),
429 1ae79e60 Stavros Sachtouris
        properties=KeyValueArgument(
430 38db356b Stavros Sachtouris
            'Add property (user-specified metadata) in key=value form'
431 38db356b Stavros Sachtouris
            '(can be repeated)',
432 1736e06d Stavros Sachtouris
            ('-p', '--property')),
433 38db356b Stavros Sachtouris
        is_public=FlagArgument('Mark image as public', '--public'),
434 38db356b Stavros Sachtouris
        size=IntArgument('Set image size in bytes', '--size'),
435 aa82dd5a Stavros Sachtouris
        metafile=ValueArgument(
436 aa82dd5a Stavros Sachtouris
            'Load metadata from a json-formated file <img-file>.meta :'
437 aa82dd5a Stavros Sachtouris
            '{"key1": "val1", "key2": "val2", ..., "properties: {...}"}',
438 aa82dd5a Stavros Sachtouris
            ('--metafile')),
439 aa82dd5a Stavros Sachtouris
        metafile_force=FlagArgument(
440 38db356b Stavros Sachtouris
            'Overide remote metadata file', ('-f', '--force')),
441 aa82dd5a Stavros Sachtouris
        no_metafile_upload=FlagArgument(
442 aa82dd5a Stavros Sachtouris
            'Do not store metadata in remote meta file',
443 aa82dd5a Stavros Sachtouris
            ('--no-metafile-upload')),
444 f2ea1314 Stavros Sachtouris
        container=ValueArgument(
445 f2ea1314 Stavros Sachtouris
            'Pithos+ container containing the image file',
446 f2ea1314 Stavros Sachtouris
            ('-C', '--container')),
447 d77e33d4 Stavros Sachtouris
        uuid=ValueArgument('Custom user uuid', '--uuid'),
448 d77e33d4 Stavros Sachtouris
        local_image_path=ValueArgument(
449 d77e33d4 Stavros Sachtouris
            'Local image file path to upload and register '
450 d77e33d4 Stavros Sachtouris
            '(still need target file in the form container:remote-path )',
451 d77e33d4 Stavros Sachtouris
            '--upload-image-file'),
452 d77e33d4 Stavros Sachtouris
        progress_bar=ProgressBarArgument(
453 d77e33d4 Stavros Sachtouris
            'Do not use progress bar', '--no-progress-bar', default=False)
454 1ae79e60 Stavros Sachtouris
    )
455 7493ccb6 Stavros Sachtouris
456 f5c28bfa Stavros Sachtouris
    def _get_user_id(self):
457 00336c85 Stavros Sachtouris
        atoken = self.client.token
458 ef00bc31 Stavros Sachtouris
        if getattr(self, 'auth_base', False):
459 9d8737a2 Stavros Sachtouris
            return self.auth_base.term('id', atoken)
460 ef00bc31 Stavros Sachtouris
        else:
461 1d0f1ffa Stavros Sachtouris
            astakos_url = self.config.get('user', 'url') or self.config.get(
462 1d0f1ffa Stavros Sachtouris
                'astakos', 'url')
463 ef00bc31 Stavros Sachtouris
            if not astakos_url:
464 ef00bc31 Stavros Sachtouris
                raise CLIBaseUrlError(service='astakos')
465 ef00bc31 Stavros Sachtouris
            user = AstakosClient(astakos_url, atoken)
466 819311d3 Stavros Sachtouris
            return user.term('id')
467 00336c85 Stavros Sachtouris
468 aa82dd5a Stavros Sachtouris
    def _get_pithos_client(self, container):
469 aa82dd5a Stavros Sachtouris
        if self['no_metafile_upload']:
470 aa82dd5a Stavros Sachtouris
            return None
471 00336c85 Stavros Sachtouris
        ptoken = self.client.token
472 ef00bc31 Stavros Sachtouris
        if getattr(self, 'auth_base', False):
473 ef00bc31 Stavros Sachtouris
            pithos_endpoints = self.auth_base.get_service_endpoints(
474 df0045d8 Stavros Sachtouris
                'object-store')
475 ef00bc31 Stavros Sachtouris
            purl = pithos_endpoints['publicURL']
476 ef00bc31 Stavros Sachtouris
        else:
477 df0045d8 Stavros Sachtouris
            purl = self.config.get_cloud('pithos', 'url')
478 df0045d8 Stavros Sachtouris
        if not purl:
479 df0045d8 Stavros Sachtouris
            raise CLIBaseUrlError(service='pithos')
480 f5c28bfa Stavros Sachtouris
        return PithosClient(purl, ptoken, self._get_user_id(), container)
481 00336c85 Stavros Sachtouris
482 aa82dd5a Stavros Sachtouris
    def _store_remote_metafile(self, pclient, remote_path, metadata):
483 00336c85 Stavros Sachtouris
        return pclient.upload_from_string(
484 d77e33d4 Stavros Sachtouris
            remote_path, _validate_image_meta(metadata, return_str=True),
485 d77e33d4 Stavros Sachtouris
            container_info_cache=self.container_info_cache)
486 00336c85 Stavros Sachtouris
487 aa82dd5a Stavros Sachtouris
    def _load_params_from_file(self, location):
488 aa82dd5a Stavros Sachtouris
        params, properties = dict(), dict()
489 aa82dd5a Stavros Sachtouris
        pfile = self['metafile']
490 aa82dd5a Stavros Sachtouris
        if pfile:
491 00336c85 Stavros Sachtouris
            try:
492 aa82dd5a Stavros Sachtouris
                for k, v in _load_image_meta(pfile).items():
493 aa82dd5a Stavros Sachtouris
                    key = k.lower().replace('-', '_')
494 1d0f1ffa Stavros Sachtouris
                    if key == 'properties':
495 aa82dd5a Stavros Sachtouris
                        for pk, pv in v.items():
496 aa82dd5a Stavros Sachtouris
                            properties[pk.upper().replace('-', '_')] = pv
497 aa82dd5a Stavros Sachtouris
                    elif key == 'name':
498 aa82dd5a Stavros Sachtouris
                            continue
499 aa82dd5a Stavros Sachtouris
                    elif key == 'location':
500 aa82dd5a Stavros Sachtouris
                        if location:
501 aa82dd5a Stavros Sachtouris
                            continue
502 aa82dd5a Stavros Sachtouris
                        location = v
503 aa82dd5a Stavros Sachtouris
                    else:
504 aa82dd5a Stavros Sachtouris
                        params[key] = v
505 aa82dd5a Stavros Sachtouris
            except Exception as e:
506 aa82dd5a Stavros Sachtouris
                raiseCLIError(e, 'Invalid json metadata config file')
507 aa82dd5a Stavros Sachtouris
        return params, properties, location
508 7493ccb6 Stavros Sachtouris
509 aa82dd5a Stavros Sachtouris
    def _load_params_from_args(self, params, properties):
510 f769a16a Stavros Sachtouris
        for key in set([
511 1ae79e60 Stavros Sachtouris
                'checksum',
512 1ae79e60 Stavros Sachtouris
                'container_format',
513 1ae79e60 Stavros Sachtouris
                'disk_format',
514 1ae79e60 Stavros Sachtouris
                'owner',
515 1ae79e60 Stavros Sachtouris
                'size',
516 de73876b Stavros Sachtouris
                'is_public']).intersection(self.arguments):
517 1ae79e60 Stavros Sachtouris
            params[key] = self[key]
518 aa82dd5a Stavros Sachtouris
        for k, v in self['properties'].items():
519 aa82dd5a Stavros Sachtouris
            properties[k.upper().replace('-', '_')] = v
520 1ae79e60 Stavros Sachtouris
521 aa82dd5a Stavros Sachtouris
    def _validate_location(self, location):
522 aa82dd5a Stavros Sachtouris
        if not location:
523 aa82dd5a Stavros Sachtouris
            raiseCLIError(
524 aa82dd5a Stavros Sachtouris
                'No image file location provided',
525 aa82dd5a Stavros Sachtouris
                importance=2, details=[
526 aa82dd5a Stavros Sachtouris
                    'An image location is needed. Image location format:',
527 38db356b Stavros Sachtouris
                    '  <container>:<path>',
528 f2ea1314 Stavros Sachtouris
                    ' where an image file at the above location must exist.'
529 aa82dd5a Stavros Sachtouris
                    ] + howto_image_file)
530 aa82dd5a Stavros Sachtouris
        try:
531 aa82dd5a Stavros Sachtouris
            return _validate_image_location(location)
532 aa82dd5a Stavros Sachtouris
        except AssertionError as ae:
533 aa82dd5a Stavros Sachtouris
            raiseCLIError(
534 aa82dd5a Stavros Sachtouris
                ae, 'Invalid image location format',
535 aa82dd5a Stavros Sachtouris
                importance=1, details=[
536 aa82dd5a Stavros Sachtouris
                    'Valid image location format:',
537 38db356b Stavros Sachtouris
                    '  <container>:<img-file-path>'
538 aa82dd5a Stavros Sachtouris
                    ] + howto_image_file)
539 aa82dd5a Stavros Sachtouris
540 38db356b Stavros Sachtouris
    @staticmethod
541 38db356b Stavros Sachtouris
    def _old_location_format(location):
542 38db356b Stavros Sachtouris
        prefix = 'pithos://'
543 38db356b Stavros Sachtouris
        try:
544 38db356b Stavros Sachtouris
            if location.startswith(prefix):
545 38db356b Stavros Sachtouris
                uuid, sep, rest = location[len(prefix):].partition('/')
546 38db356b Stavros Sachtouris
                container, sep, path = rest.partition('/')
547 38db356b Stavros Sachtouris
                return (uuid, container, path)
548 38db356b Stavros Sachtouris
        except Exception as e:
549 38db356b Stavros Sachtouris
            raiseCLIError(e, 'Invalid location format', details=[
550 38db356b Stavros Sachtouris
                'Correct location format:', '  <container>:<image path>'])
551 38db356b Stavros Sachtouris
        return ()
552 38db356b Stavros Sachtouris
553 f2ea1314 Stavros Sachtouris
    def _mine_location(self, container_path):
554 38db356b Stavros Sachtouris
        old_response = self._old_location_format(container_path)
555 38db356b Stavros Sachtouris
        if old_response:
556 38db356b Stavros Sachtouris
            return old_response
557 38db356b Stavros Sachtouris
        uuid = self['uuid'] or (self._username2uuid(self['owner_name']) if (
558 38db356b Stavros Sachtouris
                    self['owner_name']) else self._get_user_id())
559 38db356b Stavros Sachtouris
        if not uuid:
560 38db356b Stavros Sachtouris
            if self['owner_name']:
561 38db356b Stavros Sachtouris
                raiseCLIError('No user with username %s' % self['owner_name'])
562 38db356b Stavros Sachtouris
            raiseCLIError('Failed to get user uuid', details=[
563 38db356b Stavros Sachtouris
                'For details on current user:',
564 38db356b Stavros Sachtouris
                '  /user whoami',
565 38db356b Stavros Sachtouris
                'To authenticate a new user through a user token:',
566 38db356b Stavros Sachtouris
                '  /user authenticate <token>'])
567 f2ea1314 Stavros Sachtouris
        if self['container']:
568 f2ea1314 Stavros Sachtouris
            return uuid, self['container'], container_path
569 f2ea1314 Stavros Sachtouris
        container, sep, path = container_path.partition(':')
570 f2ea1314 Stavros Sachtouris
        if not (bool(container) and bool(path)):
571 f2ea1314 Stavros Sachtouris
            raiseCLIError(
572 f2ea1314 Stavros Sachtouris
                'Incorrect container-path format', importance=1, details=[
573 f2ea1314 Stavros Sachtouris
                'Use : to seperate container form path',
574 f2ea1314 Stavros Sachtouris
                '  <container>:<image-path>',
575 f2ea1314 Stavros Sachtouris
                'OR',
576 f2ea1314 Stavros Sachtouris
                'Use -C to specifiy a container',
577 f2ea1314 Stavros Sachtouris
                '  -C <container> <image-path>'] + howto_image_file)
578 f2ea1314 Stavros Sachtouris
579 f2ea1314 Stavros Sachtouris
        return uuid, container, path
580 f2ea1314 Stavros Sachtouris
581 aa82dd5a Stavros Sachtouris
    @errors.generic.all
582 aa82dd5a Stavros Sachtouris
    @errors.plankton.connection
583 7bbfeb1a Stavros Sachtouris
    @errors.pithos.container
584 7bbfeb1a Stavros Sachtouris
    def _run(self, name, uuid, dst_cont, img_path):
585 d77e33d4 Stavros Sachtouris
        if self['local_image_path']:
586 d77e33d4 Stavros Sachtouris
            with open(self['local_image_path']) as f:
587 7bbfeb1a Stavros Sachtouris
                pithos = self._get_pithos_client(dst_cont)
588 d77e33d4 Stavros Sachtouris
                (pbar, upload_cb) = self._safe_progress_bar('Uploading')
589 d77e33d4 Stavros Sachtouris
                if pbar:
590 d77e33d4 Stavros Sachtouris
                    hash_bar = pbar.clone()
591 d77e33d4 Stavros Sachtouris
                    hash_cb = hash_bar.get_generator('Calculating hashes')
592 d77e33d4 Stavros Sachtouris
                pithos.upload_object(
593 d77e33d4 Stavros Sachtouris
                    img_path, f,
594 d77e33d4 Stavros Sachtouris
                    hash_cb=hash_cb, upload_cb=upload_cb,
595 d77e33d4 Stavros Sachtouris
                    container_info_cache=self.container_info_cache)
596 d77e33d4 Stavros Sachtouris
                pbar.finish()
597 d77e33d4 Stavros Sachtouris
598 7bbfeb1a Stavros Sachtouris
        location = 'pithos://%s/%s/%s' % (uuid, dst_cont, img_path)
599 f2ea1314 Stavros Sachtouris
        (params, properties, new_loc) = self._load_params_from_file(location)
600 f2ea1314 Stavros Sachtouris
        if location != new_loc:
601 7bbfeb1a Stavros Sachtouris
            uuid, dst_cont, img_path = self._validate_location(new_loc)
602 aa82dd5a Stavros Sachtouris
        self._load_params_from_args(params, properties)
603 7bbfeb1a Stavros Sachtouris
        pclient = self._get_pithos_client(dst_cont)
604 aa82dd5a Stavros Sachtouris
605 aa82dd5a Stavros Sachtouris
        #check if metafile exists
606 aa82dd5a Stavros Sachtouris
        meta_path = '%s.meta' % img_path
607 aa82dd5a Stavros Sachtouris
        if pclient and not self['metafile_force']:
608 c4aefeaf Stavros Sachtouris
            try:
609 aa82dd5a Stavros Sachtouris
                pclient.get_object_info(meta_path)
610 81b0838d Stavros Sachtouris
                raiseCLIError(
611 81b0838d Stavros Sachtouris
                    'Metadata file %s:%s already exists, abort' % (
612 7bbfeb1a Stavros Sachtouris
                        dst_cont, meta_path),
613 81b0838d Stavros Sachtouris
                    details=['Registration ABORTED', 'Try -f to overwrite'])
614 aa82dd5a Stavros Sachtouris
            except ClientError as ce:
615 aa82dd5a Stavros Sachtouris
                if ce.status != 404:
616 aa82dd5a Stavros Sachtouris
                    raise
617 aa82dd5a Stavros Sachtouris
618 aa82dd5a Stavros Sachtouris
        #register the image
619 aa82dd5a Stavros Sachtouris
        try:
620 aa82dd5a Stavros Sachtouris
            r = self.client.register(name, location, params, properties)
621 aa82dd5a Stavros Sachtouris
        except ClientError as ce:
622 7bbfeb1a Stavros Sachtouris
            if ce.status in (400, 404):
623 c4aefeaf Stavros Sachtouris
                raiseCLIError(
624 7bbfeb1a Stavros Sachtouris
                    ce, 'Nonexistent image file location\n\t%s' % location,
625 c4aefeaf Stavros Sachtouris
                    details=[
626 7bbfeb1a Stavros Sachtouris
                        'Does the image file %s exist at container %s ?' % (
627 7bbfeb1a Stavros Sachtouris
                            img_path, dst_cont)] + howto_image_file)
628 aa82dd5a Stavros Sachtouris
            raise
629 6b8a403c Stavros Sachtouris
        r['owner'] += ' (%s)' % self._uuid2username(r['owner'])
630 76f58e2e Stavros Sachtouris
        self._print(r, self.print_dict)
631 a03ade9e Stavros Sachtouris
632 aa82dd5a Stavros Sachtouris
        #upload the metadata file
633 00336c85 Stavros Sachtouris
        if pclient:
634 aa82dd5a Stavros Sachtouris
            try:
635 aa82dd5a Stavros Sachtouris
                meta_headers = pclient.upload_from_string(
636 d77e33d4 Stavros Sachtouris
                    meta_path, dumps(r, indent=2),
637 d77e33d4 Stavros Sachtouris
                    container_info_cache=self.container_info_cache)
638 aa82dd5a Stavros Sachtouris
            except TypeError:
639 1d0f1ffa Stavros Sachtouris
                self.error(
640 1d0f1ffa Stavros Sachtouris
                    'Failed to dump metafile %s:%s' % (dst_cont, meta_path))
641 aa82dd5a Stavros Sachtouris
                return
642 9553da85 Stavros Sachtouris
            if self['json_output']:
643 76f58e2e Stavros Sachtouris
                self.print_json(dict(
644 7bbfeb1a Stavros Sachtouris
                    metafile_location='%s:%s' % (dst_cont, meta_path),
645 aa82dd5a Stavros Sachtouris
                    headers=meta_headers))
646 9553da85 Stavros Sachtouris
            else:
647 1d0f1ffa Stavros Sachtouris
                self.error('Metadata file uploaded as %s:%s (version %s)' % (
648 7bbfeb1a Stavros Sachtouris
                    dst_cont, meta_path, meta_headers['x-object-version']))
649 00336c85 Stavros Sachtouris
650 f2ea1314 Stavros Sachtouris
    def main(self, name, container___image_path):
651 a03ade9e Stavros Sachtouris
        super(self.__class__, self)._run()
652 f2ea1314 Stavros Sachtouris
        self._run(name, *self._mine_location(container___image_path))
653 7493ccb6 Stavros Sachtouris
654 234954d1 Stavros Sachtouris
655 d486baec Stavros Sachtouris
@command(image_cmds)
656 f5f35422 Stavros Sachtouris
class image_unregister(_init_image, _optional_output_cmd):
657 4a17d307 Stavros Sachtouris
    """Unregister an image (does not delete the image file)"""
658 4a17d307 Stavros Sachtouris
659 4a17d307 Stavros Sachtouris
    @errors.generic.all
660 4a17d307 Stavros Sachtouris
    @errors.plankton.connection
661 4a17d307 Stavros Sachtouris
    @errors.plankton.id
662 4a17d307 Stavros Sachtouris
    def _run(self, image_id):
663 f5f35422 Stavros Sachtouris
        self._optional_output(self.client.unregister(image_id))
664 4a17d307 Stavros Sachtouris
665 4a17d307 Stavros Sachtouris
    def main(self, image_id):
666 4a17d307 Stavros Sachtouris
        super(self.__class__, self)._run()
667 4a17d307 Stavros Sachtouris
        self._run(image_id=image_id)
668 4a17d307 Stavros Sachtouris
669 4a17d307 Stavros Sachtouris
670 4a17d307 Stavros Sachtouris
@command(image_cmds)
671 545c6c29 Stavros Sachtouris
class image_shared(_init_image, _optional_json):
672 f5f35422 Stavros Sachtouris
    """List images shared by a member"""
673 f5f35422 Stavros Sachtouris
674 236e7d08 Stavros Sachtouris
    @errors.generic.all
675 236e7d08 Stavros Sachtouris
    @errors.plankton.connection
676 f5f35422 Stavros Sachtouris
    def _run(self, member):
677 cf115aed Stavros Sachtouris
        r = self.client.list_shared(member)
678 cf115aed Stavros Sachtouris
        if r:
679 cf115aed Stavros Sachtouris
            uuid = self._username2uuid(member)
680 cf115aed Stavros Sachtouris
            r = self.client.list_shared(uuid) if uuid else []
681 cf115aed Stavros Sachtouris
        self._print(r, title=('image_id',))
682 7493ccb6 Stavros Sachtouris
683 cf115aed Stavros Sachtouris
    def main(self, member_id_or_username):
684 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
685 cf115aed Stavros Sachtouris
        self._run(member_id_or_username)
686 236e7d08 Stavros Sachtouris
687 234954d1 Stavros Sachtouris
688 d486baec Stavros Sachtouris
@command(image_cmds)
689 f5f35422 Stavros Sachtouris
class image_members(_init_image):
690 f5f35422 Stavros Sachtouris
    """Manage members. Members of an image are users who can modify it"""
691 f5f35422 Stavros Sachtouris
692 f5f35422 Stavros Sachtouris
693 f5f35422 Stavros Sachtouris
@command(image_cmds)
694 545c6c29 Stavros Sachtouris
class image_members_list(_init_image, _optional_json):
695 f5f35422 Stavros Sachtouris
    """List members of an image"""
696 f5f35422 Stavros Sachtouris
697 236e7d08 Stavros Sachtouris
    @errors.generic.all
698 236e7d08 Stavros Sachtouris
    @errors.plankton.connection
699 f5f35422 Stavros Sachtouris
    @errors.plankton.id
700 f5f35422 Stavros Sachtouris
    def _run(self, image_id):
701 cf115aed Stavros Sachtouris
        members = self.client.list_members(image_id)
702 cf115aed Stavros Sachtouris
        if not self['json_output']:
703 cf115aed Stavros Sachtouris
            uuids = [member['member_id'] for member in members]
704 cf115aed Stavros Sachtouris
            usernames = self._uuids2usernames(uuids)
705 cf115aed Stavros Sachtouris
            for member in members:
706 cf115aed Stavros Sachtouris
                member['member_id'] += ' (%s)' % usernames[member['member_id']]
707 cf115aed Stavros Sachtouris
        self._print(members, title=('member_id',))
708 7493ccb6 Stavros Sachtouris
709 f5f35422 Stavros Sachtouris
    def main(self, image_id):
710 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
711 f5f35422 Stavros Sachtouris
        self._run(image_id=image_id)
712 236e7d08 Stavros Sachtouris
713 234954d1 Stavros Sachtouris
714 d486baec Stavros Sachtouris
@command(image_cmds)
715 f5f35422 Stavros Sachtouris
class image_members_add(_init_image, _optional_output_cmd):
716 7493ccb6 Stavros Sachtouris
    """Add a member to an image"""
717 7493ccb6 Stavros Sachtouris
718 236e7d08 Stavros Sachtouris
    @errors.generic.all
719 236e7d08 Stavros Sachtouris
    @errors.plankton.connection
720 236e7d08 Stavros Sachtouris
    @errors.plankton.id
721 b04288f7 Stavros Sachtouris
    def _run(self, image_id=None, member=None):
722 f5f35422 Stavros Sachtouris
            self._optional_output(self.client.add_member(image_id, member))
723 236e7d08 Stavros Sachtouris
724 cf115aed Stavros Sachtouris
    def main(self, image_id, member_id):
725 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
726 cf115aed Stavros Sachtouris
        self._run(image_id=image_id, member=member_id)
727 7493ccb6 Stavros Sachtouris
728 234954d1 Stavros Sachtouris
729 d486baec Stavros Sachtouris
@command(image_cmds)
730 f5f35422 Stavros Sachtouris
class image_members_delete(_init_image, _optional_output_cmd):
731 7493ccb6 Stavros Sachtouris
    """Remove a member from an image"""
732 7493ccb6 Stavros Sachtouris
733 236e7d08 Stavros Sachtouris
    @errors.generic.all
734 236e7d08 Stavros Sachtouris
    @errors.plankton.connection
735 236e7d08 Stavros Sachtouris
    @errors.plankton.id
736 b04288f7 Stavros Sachtouris
    def _run(self, image_id=None, member=None):
737 f5f35422 Stavros Sachtouris
            self._optional_output(self.client.remove_member(image_id, member))
738 236e7d08 Stavros Sachtouris
739 236e7d08 Stavros Sachtouris
    def main(self, image_id, member):
740 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
741 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id, member=member)
742 7493ccb6 Stavros Sachtouris
743 234954d1 Stavros Sachtouris
744 d486baec Stavros Sachtouris
@command(image_cmds)
745 f5f35422 Stavros Sachtouris
class image_members_set(_init_image, _optional_output_cmd):
746 7493ccb6 Stavros Sachtouris
    """Set the members of an image"""
747 7493ccb6 Stavros Sachtouris
748 236e7d08 Stavros Sachtouris
    @errors.generic.all
749 236e7d08 Stavros Sachtouris
    @errors.plankton.connection
750 236e7d08 Stavros Sachtouris
    @errors.plankton.id
751 b04288f7 Stavros Sachtouris
    def _run(self, image_id, members):
752 f5f35422 Stavros Sachtouris
            self._optional_output(self.client.set_members(image_id, members))
753 236e7d08 Stavros Sachtouris
754 cf115aed Stavros Sachtouris
    def main(self, image_id, *member_ids):
755 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
756 cf115aed Stavros Sachtouris
        self._run(image_id=image_id, members=member_ids)
757 f3e94e06 Stavros Sachtouris
758 573be34f Stavros Sachtouris
# Compute Image Commands
759 573be34f Stavros Sachtouris
760 573be34f Stavros Sachtouris
761 d486baec Stavros Sachtouris
@command(image_cmds)
762 8741c407 Stavros Sachtouris
class image_compute(_init_cyclades):
763 a29d2f88 Stavros Sachtouris
    """Cyclades/Compute API image commands"""
764 8741c407 Stavros Sachtouris
765 8741c407 Stavros Sachtouris
766 8741c407 Stavros Sachtouris
@command(image_cmds)
767 6d190dd1 Stavros Sachtouris
class image_compute_list(
768 6d190dd1 Stavros Sachtouris
        _init_cyclades, _optional_json, _name_filter, _id_filter):
769 f3e94e06 Stavros Sachtouris
    """List images"""
770 f3e94e06 Stavros Sachtouris
771 1716a15d Stavros Sachtouris
    PERMANENTS = ('id', 'name')
772 1716a15d Stavros Sachtouris
773 1ae79e60 Stavros Sachtouris
    arguments = dict(
774 f40f0cb7 Stavros Sachtouris
        detail=FlagArgument('show detailed output', ('-l', '--details')),
775 f40f0cb7 Stavros Sachtouris
        limit=IntArgument('limit number listed images', ('-n', '--number')),
776 1d0f1ffa Stavros Sachtouris
        more=FlagArgument('handle long lists of results', '--more'),
777 1716a15d Stavros Sachtouris
        enum=FlagArgument('Enumerate results', '--enumerate'),
778 fc48b144 Stavros Sachtouris
        user_id=ValueArgument('filter by user_id', '--user-id'),
779 fc48b144 Stavros Sachtouris
        user_name=ValueArgument('filter by username', '--user-name'),
780 1716a15d Stavros Sachtouris
        meta=KeyValueArgument(
781 1716a15d Stavros Sachtouris
            'filter by metadata key=value (can be repeated)', ('--metadata')),
782 1716a15d Stavros Sachtouris
        meta_like=KeyValueArgument(
783 1716a15d Stavros Sachtouris
            'filter by metadata key=value (can be repeated)',
784 1716a15d Stavros Sachtouris
            ('--metadata-like'))
785 1ae79e60 Stavros Sachtouris
    )
786 f3e94e06 Stavros Sachtouris
787 1716a15d Stavros Sachtouris
    def _filter_by_metadata(self, images):
788 1716a15d Stavros Sachtouris
        new_images = []
789 1716a15d Stavros Sachtouris
        for img in images:
790 854222c7 Stavros Sachtouris
            meta = [dict(img['metadata'])]
791 854222c7 Stavros Sachtouris
            if self['meta']:
792 854222c7 Stavros Sachtouris
                meta = filter_dicts_by_dict(meta, self['meta'])
793 854222c7 Stavros Sachtouris
            if meta and self['meta_like']:
794 854222c7 Stavros Sachtouris
                meta = filter_dicts_by_dict(
795 854222c7 Stavros Sachtouris
                    meta, self['meta_like'], exact_match=False)
796 854222c7 Stavros Sachtouris
            if meta:
797 854222c7 Stavros Sachtouris
                new_images.append(img)
798 1716a15d Stavros Sachtouris
        return new_images
799 1716a15d Stavros Sachtouris
800 fc48b144 Stavros Sachtouris
    def _filter_by_user(self, images):
801 fc48b144 Stavros Sachtouris
        uuid = self['user_id'] or self._username2uuid(self['user_name'])
802 fc48b144 Stavros Sachtouris
        return filter_dicts_by_dict(images, dict(user_id=uuid))
803 fc48b144 Stavros Sachtouris
804 1716a15d Stavros Sachtouris
    def _add_name(self, images, key='user_id'):
805 1716a15d Stavros Sachtouris
        uuids = self._uuids2usernames(
806 1716a15d Stavros Sachtouris
            list(set([img[key] for img in images])))
807 1716a15d Stavros Sachtouris
        for img in images:
808 1716a15d Stavros Sachtouris
            img[key] += ' (%s)' % uuids[img[key]]
809 1716a15d Stavros Sachtouris
        return images
810 1716a15d Stavros Sachtouris
811 236e7d08 Stavros Sachtouris
    @errors.generic.all
812 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
813 236e7d08 Stavros Sachtouris
    def _run(self):
814 1716a15d Stavros Sachtouris
        withmeta = bool(self['meta'] or self['meta_like'])
815 fc48b144 Stavros Sachtouris
        withuser = bool(self['user_id'] or self['user_name'])
816 fc48b144 Stavros Sachtouris
        detail = self['detail'] or withmeta or withuser
817 1716a15d Stavros Sachtouris
        images = self.client.list_images(detail)
818 6d190dd1 Stavros Sachtouris
        images = self._filter_by_name(images)
819 6d190dd1 Stavros Sachtouris
        images = self._filter_by_id(images)
820 fc48b144 Stavros Sachtouris
        if withuser:
821 fc48b144 Stavros Sachtouris
            images = self._filter_by_user(images)
822 1716a15d Stavros Sachtouris
        if withmeta:
823 1716a15d Stavros Sachtouris
            images = self._filter_by_metadata(images)
824 1716a15d Stavros Sachtouris
        if self['detail'] and not self['json_output']:
825 1716a15d Stavros Sachtouris
            images = self._add_name(self._add_name(images, 'tenant_id'))
826 fc48b144 Stavros Sachtouris
        elif detail and not self['detail']:
827 fc48b144 Stavros Sachtouris
            for img in images:
828 fc48b144 Stavros Sachtouris
                for key in set(img).difference(self.PERMANENTS):
829 fc48b144 Stavros Sachtouris
                    img.pop(key)
830 545c6c29 Stavros Sachtouris
        kwargs = dict(with_enumeration=self['enum'])
831 6430d3a0 Stavros Sachtouris
        if self['limit']:
832 545c6c29 Stavros Sachtouris
            images = images[:self['limit']]
833 6430d3a0 Stavros Sachtouris
        if self['more']:
834 6430d3a0 Stavros Sachtouris
            kwargs['out'] = StringIO()
835 6430d3a0 Stavros Sachtouris
            kwargs['title'] = ()
836 545c6c29 Stavros Sachtouris
        self._print(images, **kwargs)
837 6430d3a0 Stavros Sachtouris
        if self['more']:
838 6430d3a0 Stavros Sachtouris
            pager(kwargs['out'].getvalue())
839 236e7d08 Stavros Sachtouris
840 f3e94e06 Stavros Sachtouris
    def main(self):
841 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
842 236e7d08 Stavros Sachtouris
        self._run()
843 f3e94e06 Stavros Sachtouris
844 234954d1 Stavros Sachtouris
845 d486baec Stavros Sachtouris
@command(image_cmds)
846 545c6c29 Stavros Sachtouris
class image_compute_info(_init_cyclades, _optional_json):
847 15142309 Stavros Sachtouris
    """Get detailed information on an image"""
848 f3e94e06 Stavros Sachtouris
849 236e7d08 Stavros Sachtouris
    @errors.generic.all
850 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
851 236e7d08 Stavros Sachtouris
    @errors.plankton.id
852 236e7d08 Stavros Sachtouris
    def _run(self, image_id):
853 236e7d08 Stavros Sachtouris
        image = self.client.get_image_details(image_id)
854 cf115aed Stavros Sachtouris
        uuids = [image['user_id'], image['tenant_id']]
855 cf115aed Stavros Sachtouris
        usernames = self._uuids2usernames(uuids)
856 cf115aed Stavros Sachtouris
        image['user_id'] += ' (%s)' % usernames[image['user_id']]
857 cf115aed Stavros Sachtouris
        image['tenant_id'] += ' (%s)' % usernames[image['tenant_id']]
858 76f58e2e Stavros Sachtouris
        self._print(image, self.print_dict)
859 f3e94e06 Stavros Sachtouris
860 f3e94e06 Stavros Sachtouris
    def main(self, image_id):
861 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
862 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id)
863 f3e94e06 Stavros Sachtouris
864 234954d1 Stavros Sachtouris
865 d486baec Stavros Sachtouris
@command(image_cmds)
866 f5f35422 Stavros Sachtouris
class image_compute_delete(_init_cyclades, _optional_output_cmd):
867 24ff0a35 Stavros Sachtouris
    """Delete an image (WARNING: image file is also removed)"""
868 f3e94e06 Stavros Sachtouris
869 236e7d08 Stavros Sachtouris
    @errors.generic.all
870 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
871 236e7d08 Stavros Sachtouris
    @errors.plankton.id
872 236e7d08 Stavros Sachtouris
    def _run(self, image_id):
873 f5f35422 Stavros Sachtouris
        self._optional_output(self.client.delete_image(image_id))
874 236e7d08 Stavros Sachtouris
875 f3e94e06 Stavros Sachtouris
    def main(self, image_id):
876 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
877 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id)
878 f3e94e06 Stavros Sachtouris
879 234954d1 Stavros Sachtouris
880 d486baec Stavros Sachtouris
@command(image_cmds)
881 8741c407 Stavros Sachtouris
class image_compute_properties(_init_cyclades):
882 395fbf9e Stavros Sachtouris
    """Manage properties related to OS installation in an image"""
883 f5f35422 Stavros Sachtouris
884 f5f35422 Stavros Sachtouris
885 f5f35422 Stavros Sachtouris
@command(image_cmds)
886 545c6c29 Stavros Sachtouris
class image_compute_properties_list(_init_cyclades, _optional_json):
887 f5f35422 Stavros Sachtouris
    """List all image properties"""
888 f5f35422 Stavros Sachtouris
889 f5f35422 Stavros Sachtouris
    @errors.generic.all
890 f5f35422 Stavros Sachtouris
    @errors.cyclades.connection
891 f5f35422 Stavros Sachtouris
    @errors.plankton.id
892 f5f35422 Stavros Sachtouris
    def _run(self, image_id):
893 76f58e2e Stavros Sachtouris
        self._print(self.client.get_image_metadata(image_id), self.print_dict)
894 f5f35422 Stavros Sachtouris
895 f5f35422 Stavros Sachtouris
    def main(self, image_id):
896 f5f35422 Stavros Sachtouris
        super(self.__class__, self)._run()
897 f5f35422 Stavros Sachtouris
        self._run(image_id=image_id)
898 f5f35422 Stavros Sachtouris
899 f5f35422 Stavros Sachtouris
900 f5f35422 Stavros Sachtouris
@command(image_cmds)
901 545c6c29 Stavros Sachtouris
class image_compute_properties_get(_init_cyclades, _optional_json):
902 f5f35422 Stavros Sachtouris
    """Get an image property"""
903 f5f35422 Stavros Sachtouris
904 236e7d08 Stavros Sachtouris
    @errors.generic.all
905 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
906 236e7d08 Stavros Sachtouris
    @errors.plankton.id
907 236e7d08 Stavros Sachtouris
    @errors.plankton.metadata
908 236e7d08 Stavros Sachtouris
    def _run(self, image_id, key):
909 76f58e2e Stavros Sachtouris
        self._print(
910 76f58e2e Stavros Sachtouris
            self.client.get_image_metadata(image_id, key), self.print_dict)
911 236e7d08 Stavros Sachtouris
912 f5f35422 Stavros Sachtouris
    def main(self, image_id, key):
913 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
914 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id, key=key)
915 f3e94e06 Stavros Sachtouris
916 234954d1 Stavros Sachtouris
917 d486baec Stavros Sachtouris
@command(image_cmds)
918 545c6c29 Stavros Sachtouris
class image_compute_properties_set(_init_cyclades, _optional_json):
919 f5f35422 Stavros Sachtouris
    """Add / update a set of properties for an image
920 466636c9 Stavros Sachtouris
    properties must be given in the form key=value, e.v.
921 f5f35422 Stavros Sachtouris
    /image compute properties set <image-id> key1=val1 key2=val2
922 f5f35422 Stavros Sachtouris
    """
923 f3e94e06 Stavros Sachtouris
924 236e7d08 Stavros Sachtouris
    @errors.generic.all
925 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
926 236e7d08 Stavros Sachtouris
    @errors.plankton.id
927 f5f35422 Stavros Sachtouris
    def _run(self, image_id, keyvals):
928 545c6c29 Stavros Sachtouris
        meta = dict()
929 f5f35422 Stavros Sachtouris
        for keyval in keyvals:
930 89ea97e1 Stavros Sachtouris
            key, sep, val = keyval.partition('=')
931 545c6c29 Stavros Sachtouris
            meta[key] = val
932 545c6c29 Stavros Sachtouris
        self._print(
933 76f58e2e Stavros Sachtouris
            self.client.update_image_metadata(image_id, **meta),
934 76f58e2e Stavros Sachtouris
            self.print_dict)
935 f5f35422 Stavros Sachtouris
936 f5f35422 Stavros Sachtouris
    def main(self, image_id, *key_equals_value):
937 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
938 f5f35422 Stavros Sachtouris
        self._run(image_id=image_id, keyvals=key_equals_value)
939 f3e94e06 Stavros Sachtouris
940 234954d1 Stavros Sachtouris
941 d486baec Stavros Sachtouris
@command(image_cmds)
942 f5f35422 Stavros Sachtouris
class image_compute_properties_delete(_init_cyclades, _optional_output_cmd):
943 f5f35422 Stavros Sachtouris
    """Delete a property from an image"""
944 f3e94e06 Stavros Sachtouris
945 236e7d08 Stavros Sachtouris
    @errors.generic.all
946 236e7d08 Stavros Sachtouris
    @errors.cyclades.connection
947 236e7d08 Stavros Sachtouris
    @errors.plankton.id
948 236e7d08 Stavros Sachtouris
    @errors.plankton.metadata
949 236e7d08 Stavros Sachtouris
    def _run(self, image_id, key):
950 f5f35422 Stavros Sachtouris
        self._optional_output(self.client.delete_image_metadata(image_id, key))
951 236e7d08 Stavros Sachtouris
952 f3e94e06 Stavros Sachtouris
    def main(self, image_id, key):
953 236e7d08 Stavros Sachtouris
        super(self.__class__, self)._run()
954 b04288f7 Stavros Sachtouris
        self._run(image_id=image_id, key=key)