1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# -*- coding: utf-8 -*-
import argparse
import codecs
import collections
import locale
import logging
import sys
import os
# hack to avoid a bug in cmd2: https://bitbucket.org/catherinedevlin/cmd2/issue/1/silent-editor-check
# must be done before importing cliff
os.environ.setdefault('EDITOR', 'vi')
import cliff
import cliff.app
from cliff.app import LOG
import cliff.commandmanager
import slapos.version
# silence messages like 'Starting connection' that are logged with INFO
urllib3_logger = logging.getLogger('requests.packages.urllib3')
urllib3_logger.setLevel(logging.WARNING)
class SlapOSCommandManager(cliff.commandmanager.CommandManager):
def find_command(self, argv):
"""Given an argument list, find a command and
return the processor and any remaining arguments.
"""
# a little cheating, 'slapos node' is not documented by the help command
if argv == ['node']:
argv = ['node', 'status']
search_args = argv[:]
name = ''
while search_args:
if search_args[0].startswith('-'):
LOG.critical('slapos: invalid option %r' % search_args[0])
sys.exit(5)
next_val = search_args.pop(0)
name = '%s %s' % (name, next_val) if name else next_val
if name in self.commands:
cmd_ep = self.commands[name]
cmd_factory = cmd_ep.load()
return (cmd_factory, name, search_args)
else:
LOG.critical('slapos: the command %r does not exist or is not yet implemented.\n'
'\n'
'Available commands: %s\n\n'
'Please find documentation and forum at http://community.slapos.org\n'
'Please also make sure that the SlapOS Node package is up to date.',
' '.join(argv),
', '.join(sorted(repr(c) for c in self.commands)))
sys.exit(5)
class SlapOSHelpAction(argparse.Action):
"""
Adapted from cliff.help.HelpAction, this class detects
and outputs command groups, via the .command_group attribute
of the Command class. Must be a class attribute in case the class
cannot be instantiated ('Could not load' message).
"""
def __call__(self, parser, namespace, values, option_string=None):
app = self.default
parser.print_help(app.stdout)
command_manager = app.command_manager
groups = collections.defaultdict(list)
for name, ep in sorted(command_manager):
command_group, help_line = self._help_line(ep, name)
groups[command_group].append(help_line)
for group in sorted(groups):
app.stdout.write('\n%s commands:\n' % group)
for line in sorted(groups[group]):
app.stdout.write(line)
sys.exit(0)
def _help_line(self, ep, name):
try:
factory = ep.load()
except Exception as err:
return 'Could not load %r\n' % ep
try:
cmd = factory(self, None)
except Exception as err:
return 'Could not instantiate %r: %s\n' % (ep, err)
one_liner = cmd.get_description().split('\n')[0]
group = getattr(factory, 'command_group', 'other')
return group, ' %-13s %s\n' % (name, one_liner)
class SlapOSApp(cliff.app.App):
#
# self.options.verbose_level:
# -q -> 0 (WARNING)
# -> 1 (INFO)
# -v -> 2 (DEBUG)
# -vv -> 3 (...)
# etc.
#
log = logging.getLogger('slapos')
CONSOLE_MESSAGE_FORMAT = '%(message)s'
LOG_FILE_MESSAGE_FORMAT = '[%(asctime)s] %(levelname)-8s %(message)s'
def __init__(self):
super(SlapOSApp, self).__init__(
description='SlapOS client %s' % slapos.version.version,
version=slapos.version.version,
command_manager=SlapOSCommandManager('slapos.cli'),
)
def _set_streams(self, stdin, stdout, stderr):
try:
# SlapOS: might fail in some systems
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass
if sys.version_info[:2] == (2, 6):
# Configure the input and output streams. If a stream is
# provided, it must be configured correctly by the
# caller. If not, make sure the versions of the standard
# streams used by default are wrapped with encodings. This
# works around a problem with Python 2.6 fixed in 2.7 and
# later (http://hg.python.org/cpython/rev/e60ef17561dc/).
lang, encoding = locale.getdefaultlocale()
encoding = getattr(sys.stdout, 'encoding', None) or encoding
self.stdin = stdin or codecs.getreader(encoding)(sys.stdin)
self.stdout = stdout or codecs.getwriter(encoding)(sys.stdout)
self.stderr = stderr or codecs.getwriter(encoding)(sys.stderr)
else:
self.stdin = stdin or sys.stdin
self.stdout = stdout or sys.stdout
self.stderr = stderr or sys.stderr
def build_option_parser(self, *args, **kw):
kw.setdefault('argparse_kwargs', {})
kw['argparse_kwargs']['conflict_handler'] = 'resolve'
parser = super(SlapOSApp, self).build_option_parser(*args, **kw)
# add two aliases for --log-file (for compatibility with old commands)
parser.add_argument(
'--log-file', '--logfile', '--log_file',
action='store',
default=None,
help='Specify a file to log output (default: console only)',
)
parser.add_argument(
'-h', '--help',
action=SlapOSHelpAction,
nargs=0,
default=self, # tricky
help="show this help message and exit",
)
return parser
def initialize_app(self, argv):
if self.options.verbose_level > 2:
self.log.debug('initialize_app')
def prepare_to_run_command(self, cmd):
if self.options.verbose_level > 2:
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
def clean_up(self, cmd, result, err):
if self.options.verbose_level > 2:
self.log.debug('clean_up %s', cmd.__class__.__name__)
def run(self, argv):
# same as cliff.App.run except that it won't re-raise
# a logged exception, and doesn't use --debug
try:
self.options, remainder = self.parser.parse_known_args(argv)
self.configure_logging()
self.interactive_mode = not remainder
self.initialize_app(remainder)
except Exception as err:
LOG.exception(err)
return 1
if self.interactive_mode:
result = self.interact()
else:
result = self.run_subcommand(remainder)
return result
def run_subcommand(self, argv):
# same as cliff.App.run_subcommand except that it won't re-raise
# a logged exception, and doesn't use --debug
subcommand = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = subcommand
cmd = cmd_factory(self, self.options)
err = None
result = 1
try:
self.prepare_to_run_command(cmd)
full_name = (cmd_name
if self.interactive_mode
else ' '.join([self.NAME, cmd_name])
)
cmd_parser = cmd.get_parser(full_name)
parsed_args = cmd_parser.parse_args(sub_argv)
result = cmd.run(parsed_args)
except Exception as err:
LOG.exception(err)
try:
self.clean_up(cmd, result, err)
except Exception as err2:
LOG.exception(err2)
else:
try:
self.clean_up(cmd, result, None)
except Exception as err3:
LOG.exception(err3)
return result
def main(argv=sys.argv[1:]):
app = SlapOSApp()
if not argv:
argv = ['-h']
return app.run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))