Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / __init__.py @ c2332605

History | View | Annotate | Download (13.6 kB)

1
# Copyright 2011-2013 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,
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
import locale
43

    
44

    
45
log = get_logger(__name__)
46
pref_enc = locale.getpreferredencoding()
47

    
48

    
49
def _encode_nicely(somestr, encoding=pref_enc, replacement='?'):
50
    """Encode somestr as 'encoding', but don't raise errors (replace with ?)
51
        :param encoding: (str) encode every character in this encoding
52
        :param replacement: (char) replace each char raising encode-decode errs
53
    """
54
    newstr = ''
55
    for c in somestr:
56
        try:
57
            newc = c.encode(encoding)
58
            newstr = '%s%s' % (newstr, newc)
59
        except (UnicodeDecodeError, UnicodeEncodeError) as e:
60
            log.debug('Encoding(%s): %s' % (encoding, e))
61
            newstr = '%s%s' % (newstr, replacement)
62
    return newstr
63

    
64

    
65
def DontRaiseKeyError(func):
66
    def wrap(*args, **kwargs):
67
        try:
68
            return func(*args, **kwargs)
69
        except KeyError:
70
            return None
71
    return wrap
72

    
73

    
74
def addLogSettings(func):
75
    def wrap(self, *args, **kwargs):
76
        try:
77
            return func(self, *args, **kwargs)
78
        finally:
79
            self._set_log_params()
80
    return wrap
81

    
82

    
83
def dataModification(func):
84
    def wrap(self, inp):
85
        try:
86
            inp = func(self, inp)
87
        except Exception as e:
88
            log.warning('WARNING: Error while running %s: %s' % (func, e))
89
            log.warning('\tWARNING: Kamaki will use original data to go on')
90
        finally:
91
            return inp
92
    return wrap
93

    
94

    
95
class _command_init(object):
96

    
97
    # self.arguments (dict) contains all non-positional arguments
98
    # self.required (list or tuple) contains required argument keys
99
    #     if it is a list, at least one of these arguments is required
100
    #     if it is a tuple, all arguments are required
101
    #     Lists and tuples can nest other lists and/or tuples
102

    
103
    def __init__(
104
            self,
105
            arguments={}, auth_base=None, cloud=None,
106
            _in=None, _out=None, _err=None):
107
        self._in, self._out, self._err = (
108
            _in or stdin, _out or stdout, _err or stderr)
109
        self._in = codecs.getreader('utf-8')(_in or stdin)
110
        self._out = codecs.getwriter(pref_enc)(_out or stdout)
111
        self._err = codecs.getwriter(pref_enc)(_err or stderr)
112
        self.required = getattr(self, 'required', None)
113
        if hasattr(self, 'arguments'):
114
            arguments.update(self.arguments)
115
        if isinstance(self, _optional_output_cmd):
116
            arguments.update(self.oo_arguments)
117
        if isinstance(self, _optional_json):
118
            arguments.update(self.oj_arguments)
119
        if isinstance(self, _name_filter):
120
            arguments.update(self.nf_arguments)
121
        if isinstance(self, _id_filter):
122
            arguments.update(self.if_arguments)
123
        try:
124
            arguments.update(self.wait_arguments)
125
        except AttributeError:
126
            pass
127
        self.arguments = dict(arguments)
128
        try:
129
            self.config = self['config']
130
        except KeyError:
131
            pass
132
        self.auth_base = auth_base or getattr(self, 'auth_base', None)
133
        self.cloud = cloud or getattr(self, 'cloud', None)
134

    
135
    def write(self, s):
136
        self._out.write('%s' % s)
137
        self._out.flush()
138

    
139
    def writeln(self, s=''):
140
        self.write('%s\n' % s)
141

    
142
    def error(self, s=''):
143
        self._err.write('%s\n' % s)
144
        self._err.flush()
145

    
146
    def print_list(self, *args, **kwargs):
147
        kwargs.setdefault('out', self._out)
148
        return print_list(*args, **kwargs)
149

    
150
    def print_dict(self, *args, **kwargs):
151
        kwargs.setdefault('out', self._out)
152
        return print_dict(*args, **kwargs)
153

    
154
    def print_json(self, *args, **kwargs):
155
        kwargs.setdefault('out', self._out)
156
        return print_json(*args, **kwargs)
157

    
158
    def print_items(self, *args, **kwargs):
159
        kwargs.setdefault('out', self._out)
160
        return print_items(*args, **kwargs)
161

    
162
    def ask_user(self, *args, **kwargs):
163
        kwargs.setdefault('user_in', self._in)
164
        kwargs.setdefault('out', self._out)
165
        return ask_user(*args, **kwargs)
166

    
167
    @DontRaiseKeyError
168
    def _custom_url(self, service):
169
        return self.config.get_cloud(self.cloud, '%s_url' % service)
170

    
171
    @DontRaiseKeyError
172
    def _custom_token(self, service):
173
        return self.config.get_cloud(self.cloud, '%s_token' % service)
174

    
175
    @DontRaiseKeyError
176
    def _custom_type(self, service):
177
        return self.config.get_cloud(self.cloud, '%s_type' % service)
178

    
179
    @DontRaiseKeyError
180
    def _custom_version(self, service):
181
        return self.config.get_cloud(self.cloud, '%s_version' % service)
182

    
183
    def _uuids2usernames(self, uuids):
184
        return self.auth_base.post_user_catalogs(uuids)
185

    
186
    def _usernames2uuids(self, username):
187
        return self.auth_base.post_user_catalogs(displaynames=username)
188

    
189
    def _uuid2username(self, uuid):
190
        return self._uuids2usernames([uuid]).get(uuid, None)
191

    
192
    def _username2uuid(self, username):
193
        return self._usernames2uuids([username]).get(username, None)
194

    
195
    def _set_log_params(self):
196
        try:
197
            self.client.LOG_TOKEN = (
198
                self['config'].get('global', 'log_token').lower() == 'on')
199
        except Exception as e:
200
            log.debug('Failed to read custom log_token setting:'
201
                '%s\n default for log_token is off' % e)
202
        try:
203
            self.client.LOG_DATA = (
204
                self['config'].get('global', 'log_data').lower() == 'on')
205
        except Exception as e:
206
            log.debug('Failed to read custom log_data setting:'
207
                '%s\n default for log_data is off' % e)
208
        try:
209
            self.client.LOG_PID = (
210
                self['config'].get('global', 'log_pid').lower() == 'on')
211
        except Exception as e:
212
            log.debug('Failed to read custom log_pid setting:'
213
                '%s\n default for log_pid is off' % e)
214

    
215
    def _safe_progress_bar(
216
            self, msg, arg='progress_bar', countdown=False, timeout=100):
217
        """Try to get a progress bar, but do not raise errors"""
218
        try:
219
            progress_bar = self.arguments[arg]
220
            progress_bar.file = self._err
221
            gen = progress_bar.get_generator(
222
                msg, countdown=countdown, timeout=timeout)
223
        except Exception:
224
            return (None, None)
225
        return (progress_bar, gen)
226

    
227
    def _safe_progress_bar_finish(self, progress_bar):
228
        try:
229
            progress_bar.finish()
230
        except Exception:
231
            pass
232

    
233
    def __getitem__(self, argterm):
234
        """
235
        :param argterm: (str) the name/label of an argument in self.arguments
236

237
        :returns: the value of the corresponding Argument (not the argument
238
            object)
239

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

    
244
    def __setitem__(self, argterm, arg):
245
        """Install an argument as argterm
246
        If argterm points to another argument, the other argument is lost
247

248
        :param argterm: (str)
249

250
        :param arg: (Argument)
251
        """
252
        if not hasattr(self, 'arguments'):
253
            self.arguments = {}
254
        self.arguments[argterm] = arg
255

    
256
    def get_argument_object(self, argterm):
257
        """
258
        :param argterm: (str) the name/label of an argument in self.arguments
259

260
        :returns: the arument object
261

262
        :raises KeyError: if argterm not in self.arguments of this object
263
        """
264
        return self.arguments[argterm]
265

    
266
    def get_argument(self, argterm):
267
        """
268
        :param argterm: (str) the name/label of an argument in self.arguments
269

270
        :returns: the value of the arument object
271

272
        :raises KeyError: if argterm not in self.arguments of this object
273
        """
274
        return self[argterm]
275

    
276

    
277
#  feature classes - inherit them to get special features for your commands
278

    
279

    
280
class OutputFormatArgument(ValueArgument):
281
    """Accepted output formats: json (default)"""
282

    
283
    formats = ('json', )
284

    
285
    def ___init__(self, *args, **kwargs):
286
        super(OutputFormatArgument, self).___init__(*args, **kwargs)
287

    
288
    @property
289
    def value(self):
290
        return getattr(self, '_value', None)
291

    
292
    @value.setter
293
    def value(self, newvalue):
294
        if not newvalue:
295
            self._value = self.default
296
        elif newvalue.lower() in self.formats:
297
            self._value = newvalue.lower
298
        else:
299
            raise CLIInvalidArgument(
300
                'Invalid value %s for argument %s' % (
301
                    newvalue, self.lvalue),
302
                details=['Valid output formats: %s' % ', '.join(self.formats)])
303

    
304

    
305
class _optional_output_cmd(object):
306

    
307
    oo_arguments = dict(
308
        with_output=FlagArgument('show response headers', ('--with-output')),
309
        json_output=FlagArgument(
310
            'show headers in json (DEPRECATED from v0.12,'
311
            '  please use --output-format=json instead)', ('-j', '--json'))
312
    )
313

    
314
    def _optional_output(self, r):
315
        if self['json_output']:
316
            print_json(r, out=self._out)
317
        elif self['with_output']:
318
            print_items([r] if isinstance(r, dict) else r, out=self._out)
319

    
320

    
321
class _optional_json(object):
322

    
323
    oj_arguments = dict(
324
        output_format=OutputFormatArgument(
325
            'Show output in chosen output format (%s)' % ', '.join(
326
                OutputFormatArgument.formats),
327
            '--output-format'),
328
        json_output=FlagArgument(
329
            'show output in json (DEPRECATED from v0.12,'
330
            ' please use --output-format instead)', ('-j', '--json'))
331
    )
332

    
333
    def _print(self, output, print_method=print_items, **print_method_kwargs):
334
        if self['json_output'] or self['output_format']:
335
            print_json(output, out=self._out)
336
        else:
337
            print_method_kwargs.setdefault('out', self._out)
338
            print_method(output, **print_method_kwargs)
339

    
340

    
341
class _name_filter(object):
342

    
343
    nf_arguments = dict(
344
        name=ValueArgument('filter by name', '--name'),
345
        name_pref=ValueArgument(
346
            'filter by name prefix (case insensitive)', '--name-prefix'),
347
        name_suff=ValueArgument(
348
            'filter by name suffix (case insensitive)', '--name-suffix'),
349
        name_like=ValueArgument(
350
            'print only if name contains this (case insensitive)',
351
            '--name-like')
352
    )
353

    
354
    def _non_exact_name_filter(self, items):
355
        np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
356
        return [item for item in items if (
357
            (not np) or (item['name'] or '').lower().startswith(
358
                np.lower())) and (
359
            (not ns) or (item['name'] or '').lower().endswith(
360
                ns.lower())) and (
361
            (not nl) or nl.lower() in (item['name'] or '').lower())]
362

    
363
    def _exact_name_filter(self, items):
364
        return filter_dicts_by_dict(items, dict(name=self['name'] or '')) if (
365
            self['name']) else items
366

    
367
    def _filter_by_name(self, items):
368
        return self._non_exact_name_filter(self._exact_name_filter(items))
369

    
370

    
371
class _id_filter(object):
372

    
373
    if_arguments = dict(
374
        id=ValueArgument('filter by id', '--id'),
375
        id_pref=ValueArgument(
376
            'filter by id prefix (case insensitive)', '--id-prefix'),
377
        id_suff=ValueArgument(
378
            'filter by id suffix (case insensitive)', '--id-suffix'),
379
        id_like=ValueArgument(
380
            'print only if id contains this (case insensitive)', '--id-like')
381
    )
382

    
383
    def _non_exact_id_filter(self, items):
384
        np, ns, nl = self['id_pref'], self['id_suff'], self['id_like']
385
        return [item for item in items if (
386
            (not np) or (
387
                '%s' % item['id']).lower().startswith(np.lower())) and (
388
            (not ns) or ('%s' % item['id']).lower().endswith(ns.lower())) and (
389
            (not nl) or nl.lower() in ('%s' % item['id']).lower())]
390

    
391
    def _exact_id_filter(self, items):
392
        return filter_dicts_by_dict(items, dict(id=self['id'])) if (
393
            self['id']) else items
394

    
395
    def _filter_by_id(self, items):
396
        return self._non_exact_id_filter(self._exact_id_filter(items))