Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / __init__.py @ 23ea9475

History | View | Annotate | Download (12.8 kB)

1
# Copyright 2011-2014 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.logger import get_logger
35
from kamaki.cli.utils import (
36
    print_list, print_dict, print_json, print_items, ask_user, pref_enc,
37
    filter_dicts_by_dict)
38
from kamaki.cli.argument import FlagArgument, ValueArgument
39
from kamaki.cli.errors import CLIInvalidArgument
40
from sys import stdin, stdout, stderr
41
import codecs
42

    
43

    
44
log = get_logger(__name__)
45

    
46

    
47
def DontRaiseKeyError(func):
48
    def wrap(*args, **kwargs):
49
        try:
50
            return func(*args, **kwargs)
51
        except KeyError:
52
            return None
53
    return wrap
54

    
55

    
56
def addLogSettings(func):
57
    def wrap(self, *args, **kwargs):
58
        try:
59
            return func(self, *args, **kwargs)
60
        finally:
61
            self._set_log_params()
62
    return wrap
63

    
64

    
65
def dataModification(func):
66
    def wrap(self, inp):
67
        try:
68
            inp = func(self, inp)
69
        except Exception as e:
70
            log.warning('WARNING: Error while running %s: %s' % (func, e))
71
            log.warning('\tWARNING: Kamaki will use original data to go on')
72
        finally:
73
            return inp
74
    return wrap
75

    
76

    
77
class _command_init(object):
78

    
79
    # self.arguments (dict) contains all non-positional arguments
80
    # self.required (list or tuple) contains required argument keys
81
    #     if it is a list, at least one of these arguments is required
82
    #     if it is a tuple, all arguments are required
83
    #     Lists and tuples can nest other lists and/or tuples
84

    
85
    def __init__(
86
            self,
87
            arguments={}, auth_base=None, cloud=None,
88
            _in=None, _out=None, _err=None):
89
        self._in, self._out, self._err = (
90
            _in or stdin, _out or stdout, _err or stderr)
91
        self.required = getattr(self, 'required', None)
92
        if hasattr(self, 'arguments'):
93
            arguments.update(self.arguments)
94
        if isinstance(self, _optional_output_cmd):
95
            arguments.update(self.oo_arguments)
96
        if isinstance(self, _optional_json):
97
            arguments.update(self.oj_arguments)
98
        if isinstance(self, _name_filter):
99
            arguments.update(self.nf_arguments)
100
        if isinstance(self, _id_filter):
101
            arguments.update(self.if_arguments)
102
        try:
103
            arguments.update(self.wait_arguments)
104
        except AttributeError:
105
            pass
106
        self.arguments = dict(arguments)
107
        try:
108
            self.config = self['config']
109
        except KeyError:
110
            pass
111
        self.auth_base = auth_base or getattr(self, 'auth_base', None)
112
        self.cloud = cloud or getattr(self, 'cloud', None)
113

    
114
    def write(self, s):
115
        self._out.write(s.encode(pref_enc, errors='replace'))
116
        self._out.flush()
117

    
118
    def writeln(self, s=''):
119
        self.write('%s\n' % s)
120

    
121
    def error(self, s=''):
122
        self._err.write(('%s\n' % s).encode(pref_enc, errors='replace'))
123
        self._err.flush()
124

    
125
    def print_list(self, *args, **kwargs):
126
        kwargs.setdefault('out', self._out)
127
        return print_list(*args, **kwargs)
128

    
129
    def print_dict(self, *args, **kwargs):
130
        kwargs.setdefault('out', self)
131
        return print_dict(*args, **kwargs)
132

    
133
    def print_json(self, *args, **kwargs):
134
        kwargs.setdefault('out', self)
135
        return print_json(*args, **kwargs)
136

    
137
    def print_items(self, *args, **kwargs):
138
        kwargs.setdefault('out', self)
139
        return print_items(*args, **kwargs)
140

    
141
    def ask_user(self, *args, **kwargs):
142
        kwargs.setdefault('user_in', self._in)
143
        kwargs.setdefault('out', self)
144
        return ask_user(*args, **kwargs)
145

    
146
    @DontRaiseKeyError
147
    def _custom_url(self, service):
148
        return self.config.get_cloud(self.cloud, '%s_url' % service)
149

    
150
    @DontRaiseKeyError
151
    def _custom_token(self, service):
152
        return self.config.get_cloud(self.cloud, '%s_token' % service)
153

    
154
    @DontRaiseKeyError
155
    def _custom_type(self, service):
156
        return self.config.get_cloud(self.cloud, '%s_type' % service)
157

    
158
    @DontRaiseKeyError
159
    def _custom_version(self, service):
160
        return self.config.get_cloud(self.cloud, '%s_version' % service)
161

    
162
    def _uuids2usernames(self, uuids):
163
        return self.auth_base.post_user_catalogs(uuids)
164

    
165
    def _usernames2uuids(self, username):
166
        return self.auth_base.post_user_catalogs(displaynames=username)
167

    
168
    def _uuid2username(self, uuid):
169
        return self._uuids2usernames([uuid]).get(uuid, None)
170

    
171
    def _username2uuid(self, username):
172
        return self._usernames2uuids([username]).get(username, None)
173

    
174
    def _set_log_params(self):
175
        try:
176
            self.client.LOG_TOKEN = (
177
                self['config'].get('global', 'log_token').lower() == 'on')
178
        except Exception as e:
179
            log.debug('Failed to read custom log_token setting:'
180
                '%s\n default for log_token is off' % e)
181
        try:
182
            self.client.LOG_DATA = (
183
                self['config'].get('global', 'log_data').lower() == 'on')
184
        except Exception as e:
185
            log.debug('Failed to read custom log_data setting:'
186
                '%s\n default for log_data is off' % e)
187
        try:
188
            self.client.LOG_PID = (
189
                self['config'].get('global', 'log_pid').lower() == 'on')
190
        except Exception as e:
191
            log.debug('Failed to read custom log_pid setting:'
192
                '%s\n default for log_pid is off' % e)
193

    
194
    def _safe_progress_bar(
195
            self, msg, arg='progress_bar', countdown=False, timeout=100):
196
        """Try to get a progress bar, but do not raise errors"""
197
        try:
198
            progress_bar = self.arguments[arg]
199
            progress_bar.file = self._err
200
            gen = progress_bar.get_generator(
201
                msg, countdown=countdown, timeout=timeout)
202
        except Exception:
203
            return (None, None)
204
        return (progress_bar, gen)
205

    
206
    def _safe_progress_bar_finish(self, progress_bar):
207
        try:
208
            progress_bar.finish()
209
        except Exception:
210
            pass
211

    
212
    def __getitem__(self, argterm):
213
        """
214
        :param argterm: (str) the name/label of an argument in self.arguments
215

216
        :returns: the value of the corresponding Argument (not the argument
217
            object)
218

219
        :raises KeyError: if argterm not in self.arguments of this object
220
        """
221
        return self.arguments[argterm].value
222

    
223
    def __setitem__(self, argterm, arg):
224
        """Install an argument as argterm
225
        If argterm points to another argument, the other argument is lost
226

227
        :param argterm: (str)
228

229
        :param arg: (Argument)
230
        """
231
        if not hasattr(self, 'arguments'):
232
            self.arguments = {}
233
        self.arguments[argterm] = arg
234

    
235
    def get_argument_object(self, argterm):
236
        """
237
        :param argterm: (str) the name/label of an argument in self.arguments
238

239
        :returns: the arument object
240

241
        :raises KeyError: if argterm not in self.arguments of this object
242
        """
243
        return self.arguments[argterm]
244

    
245
    def get_argument(self, argterm):
246
        """
247
        :param argterm: (str) the name/label of an argument in self.arguments
248

249
        :returns: the value of the arument object
250

251
        :raises KeyError: if argterm not in self.arguments of this object
252
        """
253
        return self[argterm]
254

    
255

    
256
#  feature classes - inherit them to get special features for your commands
257

    
258

    
259
class OutputFormatArgument(ValueArgument):
260
    """Accepted output formats: json (default)"""
261

    
262
    formats = ('json', )
263

    
264
    def ___init__(self, *args, **kwargs):
265
        super(OutputFormatArgument, self).___init__(*args, **kwargs)
266

    
267
    @property
268
    def value(self):
269
        return getattr(self, '_value', None)
270

    
271
    @value.setter
272
    def value(self, newvalue):
273
        if not newvalue:
274
            self._value = self.default
275
        elif newvalue.lower() in self.formats:
276
            self._value = newvalue.lower
277
        else:
278
            raise CLIInvalidArgument(
279
                'Invalid value %s for argument %s' % (
280
                    newvalue, self.lvalue),
281
                details=['Valid output formats: %s' % ', '.join(self.formats)])
282

    
283

    
284
class _optional_output_cmd(object):
285

    
286
    oo_arguments = dict(
287
        with_output=FlagArgument('show response headers', ('--with-output')),
288
        json_output=FlagArgument(
289
            'show headers in json (DEPRECATED from v0.12,'
290
            '  please use --output-format=json instead)', ('-j', '--json'))
291
    )
292

    
293
    def _optional_output(self, r):
294
        if self['json_output']:
295
            print_json(r, out=self)
296
        elif self['with_output']:
297
            print_items([r] if isinstance(r, dict) else r, out=self)
298

    
299

    
300
class _optional_json(object):
301

    
302
    oj_arguments = dict(
303
        output_format=OutputFormatArgument(
304
            'Show output in chosen output format (%s)' % ', '.join(
305
                OutputFormatArgument.formats),
306
            '--output-format'),
307
        json_output=FlagArgument(
308
            'show output in json (DEPRECATED from v0.12,'
309
            ' please use --output-format instead)', ('-j', '--json'))
310
    )
311

    
312
    def _print(self, output, print_method=print_items, **print_method_kwargs):
313
        if self['json_output'] or self['output_format']:
314
            print_json(output, out=self)
315
        else:
316
            print_method_kwargs.setdefault('out', self)
317
            print_method(output, **print_method_kwargs)
318

    
319

    
320
class _name_filter(object):
321

    
322
    nf_arguments = dict(
323
        name=ValueArgument('filter by name', '--name'),
324
        name_pref=ValueArgument(
325
            'filter by name prefix (case insensitive)', '--name-prefix'),
326
        name_suff=ValueArgument(
327
            'filter by name suffix (case insensitive)', '--name-suffix'),
328
        name_like=ValueArgument(
329
            'print only if name contains this (case insensitive)',
330
            '--name-like')
331
    )
332

    
333
    def _non_exact_name_filter(self, items):
334
        np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
335
        return [item for item in items if (
336
            (not np) or (item['name'] or '').lower().startswith(
337
                np.lower())) and (
338
            (not ns) or (item['name'] or '').lower().endswith(
339
                ns.lower())) and (
340
            (not nl) or nl.lower() in (item['name'] or '').lower())]
341

    
342
    def _exact_name_filter(self, items):
343
        return filter_dicts_by_dict(items, dict(name=self['name'] or '')) if (
344
            self['name']) else items
345

    
346
    def _filter_by_name(self, items):
347
        return self._non_exact_name_filter(self._exact_name_filter(items))
348

    
349

    
350
class _id_filter(object):
351

    
352
    if_arguments = dict(
353
        id=ValueArgument('filter by id', '--id'),
354
        id_pref=ValueArgument(
355
            'filter by id prefix (case insensitive)', '--id-prefix'),
356
        id_suff=ValueArgument(
357
            'filter by id suffix (case insensitive)', '--id-suffix'),
358
        id_like=ValueArgument(
359
            'print only if id contains this (case insensitive)', '--id-like')
360
    )
361

    
362
    def _non_exact_id_filter(self, items):
363
        np, ns, nl = self['id_pref'], self['id_suff'], self['id_like']
364
        return [item for item in items if (
365
            (not np) or (
366
                '%s' % item['id']).lower().startswith(np.lower())) and (
367
            (not ns) or ('%s' % item['id']).lower().endswith(ns.lower())) and (
368
            (not nl) or nl.lower() in ('%s' % item['id']).lower())]
369

    
370
    def _exact_id_filter(self, items):
371
        return filter_dicts_by_dict(items, dict(id=self['id'])) if (
372
            self['id']) else items
373

    
374
    def _filter_by_id(self, items):
375
        return self._non_exact_id_filter(self._exact_id_filter(items))