Enrich documentation with network-related examples
[kamaki] / kamaki / cli / commands / __init__.py
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
42 log = get_logger(__name__)
43
44
45 def DontRaiseKeyError(func):
46     def wrap(*args, **kwargs):
47         try:
48             return func(*args, **kwargs)
49         except KeyError:
50             return None
51     return wrap
52
53
54 def addLogSettings(func):
55     def wrap(self, *args, **kwargs):
56         try:
57             return func(self, *args, **kwargs)
58         finally:
59             self._set_log_params()
60     return wrap
61
62
63 class _command_init(object):
64
65     # self.arguments (dict) contains all non-positional arguments
66     # self.required (list or tuple) contains required argument keys
67     #     if it is a list, at least one of these arguments is required
68     #     if it is a tuple, all arguments are required
69     #     Lists and tuples can nest other lists and/or tuples
70
71     def __init__(
72             self,
73             arguments={}, auth_base=None, cloud=None,
74             _in=None, _out=None, _err=None):
75         self._in, self._out, self._err = (
76             _in or stdin, _out or stdout, _err or stderr)
77         self.required = getattr(self, 'required', None)
78         if hasattr(self, 'arguments'):
79             arguments.update(self.arguments)
80         if isinstance(self, _optional_output_cmd):
81             arguments.update(self.oo_arguments)
82         if isinstance(self, _optional_json):
83             arguments.update(self.oj_arguments)
84         if isinstance(self, _name_filter):
85             arguments.update(self.nf_arguments)
86         if isinstance(self, _id_filter):
87             arguments.update(self.if_arguments)
88         try:
89             arguments.update(self.wait_arguments)
90         except AttributeError:
91             pass
92         self.arguments = dict(arguments)
93         try:
94             self.config = self['config']
95         except KeyError:
96             pass
97         self.auth_base = auth_base or getattr(self, 'auth_base', None)
98         self.cloud = cloud or getattr(self, 'cloud', None)
99
100     def write(self, s):
101         self._out.write('%s' % s)
102         self._out.flush()
103
104     def writeln(self, s=''):
105         self.write('%s\n' % s)
106
107     def error(self, s=''):
108         self._err.write('%s\n' % s)
109         self._err.flush()
110
111     def print_list(self, *args, **kwargs):
112         kwargs.setdefault('out', self._out)
113         return print_list(*args, **kwargs)
114
115     def print_dict(self, *args, **kwargs):
116         kwargs.setdefault('out', self._out)
117         return print_dict(*args, **kwargs)
118
119     def print_json(self, *args, **kwargs):
120         kwargs.setdefault('out', self._out)
121         return print_json(*args, **kwargs)
122
123     def print_items(self, *args, **kwargs):
124         kwargs.setdefault('out', self._out)
125         return print_items(*args, **kwargs)
126
127     def ask_user(self, *args, **kwargs):
128         kwargs.setdefault('user_in', self._in)
129         kwargs.setdefault('out', self._out)
130         return ask_user(*args, **kwargs)
131
132     @DontRaiseKeyError
133     def _custom_url(self, service):
134         return self.config.get_cloud(self.cloud, '%s_url' % service)
135
136     @DontRaiseKeyError
137     def _custom_token(self, service):
138         return self.config.get_cloud(self.cloud, '%s_token' % service)
139
140     @DontRaiseKeyError
141     def _custom_type(self, service):
142         return self.config.get_cloud(self.cloud, '%s_type' % service)
143
144     @DontRaiseKeyError
145     def _custom_version(self, service):
146         return self.config.get_cloud(self.cloud, '%s_version' % service)
147
148     def _uuids2usernames(self, uuids):
149         return self.auth_base.post_user_catalogs(uuids)
150
151     def _usernames2uuids(self, username):
152         return self.auth_base.post_user_catalogs(displaynames=username)
153
154     def _uuid2username(self, uuid):
155         return self._uuids2usernames([uuid]).get(uuid, None)
156
157     def _username2uuid(self, username):
158         return self._usernames2uuids([username]).get(username, None)
159
160     def _set_log_params(self):
161         try:
162             self.client.LOG_TOKEN = (
163                 self['config'].get('global', 'log_token').lower() == 'on')
164         except Exception as e:
165             log.debug('Failed to read custom log_token setting:'
166                 '%s\n default for log_token is off' % e)
167         try:
168             self.client.LOG_DATA = (
169                 self['config'].get('global', 'log_data').lower() == 'on')
170         except Exception as e:
171             log.debug('Failed to read custom log_data setting:'
172                 '%s\n default for log_data is off' % e)
173         try:
174             self.client.LOG_PID = (
175                 self['config'].get('global', 'log_pid').lower() == 'on')
176         except Exception as e:
177             log.debug('Failed to read custom log_pid setting:'
178                 '%s\n default for log_pid is off' % e)
179
180     def _safe_progress_bar(
181             self, msg, arg='progress_bar', countdown=False, timeout=100):
182         """Try to get a progress bar, but do not raise errors"""
183         try:
184             progress_bar = self.arguments[arg]
185             progress_bar.file = self._err
186             gen = progress_bar.get_generator(
187                 msg, countdown=countdown, timeout=timeout)
188         except Exception:
189             return (None, None)
190         return (progress_bar, gen)
191
192     def _safe_progress_bar_finish(self, progress_bar):
193         try:
194             progress_bar.finish()
195         except Exception:
196             pass
197
198     def __getitem__(self, argterm):
199         """
200         :param argterm: (str) the name/label of an argument in self.arguments
201
202         :returns: the value of the corresponding Argument (not the argument
203             object)
204
205         :raises KeyError: if argterm not in self.arguments of this object
206         """
207         return self.arguments[argterm].value
208
209     def __setitem__(self, argterm, arg):
210         """Install an argument as argterm
211         If argterm points to another argument, the other argument is lost
212
213         :param argterm: (str)
214
215         :param arg: (Argument)
216         """
217         if not hasattr(self, 'arguments'):
218             self.arguments = {}
219         self.arguments[argterm] = arg
220
221     def get_argument_object(self, argterm):
222         """
223         :param argterm: (str) the name/label of an argument in self.arguments
224
225         :returns: the arument object
226
227         :raises KeyError: if argterm not in self.arguments of this object
228         """
229         return self.arguments[argterm]
230
231     def get_argument(self, argterm):
232         """
233         :param argterm: (str) the name/label of an argument in self.arguments
234
235         :returns: the value of the arument object
236
237         :raises KeyError: if argterm not in self.arguments of this object
238         """
239         return self[argterm]
240
241
242 #  feature classes - inherit them to get special features for your commands
243
244
245 class OutputFormatArgument(ValueArgument):
246     """Accepted output formats: json (default)"""
247
248     formats = ('json', )
249
250     def ___init__(self, *args, **kwargs):
251         super(OutputFormatArgument, self).___init__(*args, **kwargs)
252
253     @property
254     def value(self):
255         return getattr(self, '_value', None)
256
257     @value.setter
258     def value(self, newvalue):
259         if not newvalue:
260             self._value = self.default
261         elif newvalue.lower() in self.formats:
262             self._value = newvalue.lower
263         else:
264             raise CLIInvalidArgument(
265                 'Invalid value %s for argument %s' % (
266                     newvalue, self.lvalue),
267                 details=['Valid output formats: %s' % ', '.join(self.formats)])
268
269
270 class _optional_output_cmd(object):
271
272     oo_arguments = dict(
273         with_output=FlagArgument('show response headers', ('--with-output')),
274         json_output=FlagArgument(
275             'show headers in json (DEPRECATED from v0.12,'
276             '  please use --output-format=json instead)', ('-j', '--json'))
277     )
278
279     def _optional_output(self, r):
280         if self['json_output']:
281             print_json(r, out=self._out)
282         elif self['with_output']:
283             print_items([r] if isinstance(r, dict) else r, out=self._out)
284
285
286 class _optional_json(object):
287
288     oj_arguments = dict(
289         output_format=OutputFormatArgument(
290             'Show output in chosen output format (%s)' % ', '.join(
291                 OutputFormatArgument.formats),
292             '--output-format'),
293         json_output=FlagArgument(
294             'show output in json (DEPRECATED from v0.12,'
295             ' please use --output-format instead)', ('-j', '--json'))
296     )
297
298     def _print(self, output, print_method=print_items, **print_method_kwargs):
299         if self['json_output'] or self['output_format']:
300             print_json(output, out=self._out)
301         else:
302             print_method_kwargs.setdefault('out', self._out)
303             print_method(output, **print_method_kwargs)
304
305
306 class _name_filter(object):
307
308     nf_arguments = dict(
309         name=ValueArgument('filter by name', '--name'),
310         name_pref=ValueArgument(
311             'filter by name prefix (case insensitive)', '--name-prefix'),
312         name_suff=ValueArgument(
313             'filter by name suffix (case insensitive)', '--name-suffix'),
314         name_like=ValueArgument(
315             'print only if name contains this (case insensitive)',
316             '--name-like')
317     )
318
319     def _non_exact_name_filter(self, items):
320         np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
321         return [item for item in items if (
322             (not np) or (item['name'] or '').lower().startswith(
323                 np.lower())) and (
324             (not ns) or (item['name'] or '').lower().endswith(
325                 ns.lower())) and (
326             (not nl) or nl.lower() in (item['name'] or '').lower())]
327
328     def _exact_name_filter(self, items):
329         return filter_dicts_by_dict(items, dict(name=self['name'] or '')) if (
330             self['name']) else items
331
332     def _filter_by_name(self, items):
333         return self._non_exact_name_filter(self._exact_name_filter(items))
334
335
336 class _id_filter(object):
337
338     if_arguments = dict(
339         id=ValueArgument('filter by id', '--id'),
340         id_pref=ValueArgument(
341             'filter by id prefix (case insensitive)', '--id-prefix'),
342         id_suff=ValueArgument(
343             'filter by id suffix (case insensitive)', '--id-suffix'),
344         id_like=ValueArgument(
345             'print only if id contains this (case insensitive)', '--id-like')
346     )
347
348     def _non_exact_id_filter(self, items):
349         np, ns, nl = self['id_pref'], self['id_suff'], self['id_like']
350         return [item for item in items if (
351             (not np) or (
352                 '%s' % item['id']).lower().startswith(np.lower())) and (
353             (not ns) or ('%s' % item['id']).lower().endswith(ns.lower())) and (
354             (not nl) or nl.lower() in ('%s' % item['id']).lower())]
355
356     def _exact_id_filter(self, items):
357         return filter_dicts_by_dict(items, dict(id=self['id'])) if (
358             self['id']) else items
359
360     def _filter_by_id(self, items):
361         return self._non_exact_id_filter(self._exact_id_filter(items))