Revision 362adf50
b/kamaki/cli/__init__.py | ||
---|---|---|
195 | 195 |
def _check_config_version(cnf): |
196 | 196 |
guess = cnf.guess_version() |
197 | 197 |
if guess < 3.0: |
198 |
print('Configuration file "%s" is not updated to v3.0' % ( |
|
198 |
print('Config file format version >= 3.0 is required') |
|
199 |
print('Configuration file "%s" format is not up to date' % ( |
|
199 | 200 |
cnf.path)) |
200 |
print('Calculating changes while preserving information ...') |
|
201 |
print('but kamaki can fix this:') |
|
202 |
print('Calculating changes while preserving information') |
|
201 | 203 |
lost_terms = cnf.rescue_old_file() |
202 |
if lost_terms: |
|
203 |
print 'The following information will not be preserved:' |
|
204 |
print '...', '\n... '.join(lost_terms) |
|
205 | 204 |
print('... DONE') |
206 |
print('Kamaki is ready to transform config file to version 3.0') |
|
205 |
if lost_terms: |
|
206 |
print 'The following information will NOT be preserved:' |
|
207 |
print '\t', '\n\t'.join(lost_terms) |
|
208 |
print('Kamaki is ready to convert the config file to version 3.0') |
|
207 | 209 |
stdout.write('Overwrite file %s ? [Y, y] ' % cnf.path) |
208 | 210 |
from sys import stdin |
209 | 211 |
reply = stdin.readline() |
... | ... | |
214 | 216 |
print('... ABORTING') |
215 | 217 |
raise CLIError( |
216 | 218 |
'Invalid format for config file %s' % cnf.path, |
217 |
importance=3, details=['Please, update config file to v3.0']) |
|
219 |
importance=3, details=[ |
|
220 |
'Please, update config file to v3.0', |
|
221 |
'For automatic conversion, rerun and say Y']) |
|
218 | 222 |
|
219 | 223 |
|
220 |
def _init_session(arguments): |
|
224 |
def _init_session(arguments, is_non_API=False):
|
|
221 | 225 |
global _help |
222 | 226 |
_help = arguments['help'].value |
223 | 227 |
global _debug |
... | ... | |
228 | 232 |
_verbose = arguments['verbose'].value |
229 | 233 |
_cnf = arguments['config'] |
230 | 234 |
_check_config_version(_cnf.value) |
231 |
raise CLIError( |
|
232 |
'Your file is OK, but i am not ready to proceed', |
|
233 |
importance=3, details=['DO NOT PANIC, EXIT THE BUILDING QUIETLY']) |
|
234 | 235 |
|
235 | 236 |
global _colors |
236 |
_colors = _cnf.get('global', 'colors')
|
|
237 |
_colors = _cnf.value.get_global('colors')
|
|
237 | 238 |
if not (stdout.isatty() and _colors == 'on'): |
238 | 239 |
from kamaki.cli.utils import remove_colors |
239 | 240 |
remove_colors() |
240 | 241 |
_silent = arguments['silent'].value |
241 | 242 |
_setup_logging(_silent, _debug, _verbose, _include) |
242 |
picked_cloud = arguments['cloud'].value |
|
243 |
if picked_cloud: |
|
244 |
global_url = _cnf.get('remotes', picked_cloud) |
|
245 |
if not global_url: |
|
246 |
raise CLIError( |
|
247 |
'No remote cloud "%s" in kamaki configuration' % picked_cloud, |
|
248 |
importance=3, details=[ |
|
249 |
'To check if this remote cloud alias is declared:', |
|
250 |
' /config get remotes.%s' % picked_cloud, |
|
251 |
'To set a remote authentication URI aliased as "%s"' % ( |
|
252 |
picked_cloud), |
|
253 |
' /config set remotes.%s <URI>' % picked_cloud |
|
254 |
]) |
|
255 |
else: |
|
256 |
global_url = _cnf.get('global', 'auth_url') |
|
257 |
global_token = _cnf.get('global', 'token') |
|
243 |
|
|
244 |
if _help or is_non_API: |
|
245 |
return None |
|
246 |
|
|
247 |
cloud = arguments['cloud'].value or 'default' |
|
248 |
if not cloud in _cnf.value.keys('remote'): |
|
249 |
raise CLIError( |
|
250 |
'No cloud remote "%s" is configured' % cloud, |
|
251 |
importance=3, details=[ |
|
252 |
'To configure a new cloud remote, find and set the', |
|
253 |
'single authentication URL and token:', |
|
254 |
' kamaki config set remote.%s.url <URL>' % cloud, |
|
255 |
' kamaki config set remote.%s.token <t0k3n>' % cloud]) |
|
256 |
url = _cnf.get_remote(cloud, 'url') |
|
257 |
if not url: |
|
258 |
kloger.warning( |
|
259 |
'WARNING: No remote.%s.url, use service urls instead' % cloud) |
|
260 |
return cloud |
|
261 |
token = _cnf.get_remote(cloud, 'token') |
|
262 |
if not token: |
|
263 |
raise CLIError( |
|
264 |
'No authentication token provided for %s cloud' % cloud, |
|
265 |
importance=3, details=[ |
|
266 |
'Get and set a token for %s cloud:' % cloud, |
|
267 |
' kamaki config set remote.%s.token <t0k3n>' % cloud]) |
|
268 |
|
|
258 | 269 |
from kamaki.clients.astakos import AstakosClient as AuthCachedClient |
259 | 270 |
try: |
260 |
return AuthCachedClient(global_url, global_token)
|
|
271 |
return AuthCachedClient(url, token)
|
|
261 | 272 |
except AssertionError as ae: |
262 |
kloger.warning('WARNING: Failed to load auth_url %s [ %s ]' % (
|
|
263 |
global_url, ae))
|
|
273 |
kloger.warning( |
|
274 |
'WARNING: Failed to load auth_url %s [ %s ]' % (url, ae))
|
|
264 | 275 |
return None |
265 | 276 |
|
266 | 277 |
|
267 | 278 |
def _load_spec_module(spec, arguments, module): |
268 |
#spec_name = arguments['config'].get('cli', spec) |
|
269 | 279 |
if not spec: |
270 | 280 |
return None |
271 | 281 |
pkg = None |
... | ... | |
304 | 314 |
|
305 | 315 |
def _load_all_commands(cmd_tree, arguments): |
306 | 316 |
_cnf = arguments['config'] |
307 |
#specs = [spec for spec in _cnf.get_groups() if _cnf.get(spec, 'cli')] |
|
308 | 317 |
for cmd_group, spec in _cnf.get_cli_specs(): |
309 | 318 |
try: |
310 | 319 |
spec_module = _load_spec_module(spec, arguments, '_commands') |
... | ... | |
411 | 420 |
def run_one_cmd(exe_string, parser, auth_base): |
412 | 421 |
global _history |
413 | 422 |
_history = History( |
414 |
parser.arguments['config'].get('history', 'file'))
|
|
423 |
parser.arguments['config'].get_global('history_file'))
|
|
415 | 424 |
_history.add(' '.join([exe_string] + argv[1:])) |
416 | 425 |
from kamaki.cli import one_command |
417 | 426 |
one_command.run(auth_base, parser, _help) |
... | ... | |
424 | 433 |
shell.run(auth_base, parser) |
425 | 434 |
|
426 | 435 |
|
436 |
def is_non_API(parser): |
|
437 |
nonAPIs = ('history', 'config') |
|
438 |
for term in parser.unparsed: |
|
439 |
if not term.startswith('-'): |
|
440 |
if term in nonAPIs: |
|
441 |
return True |
|
442 |
return False |
|
443 |
return False |
|
444 |
|
|
445 |
|
|
427 | 446 |
def main(): |
428 | 447 |
try: |
429 | 448 |
exe = basename(argv[0]) |
... | ... | |
432 | 451 |
if parser.arguments['version'].value: |
433 | 452 |
exit(0) |
434 | 453 |
|
435 |
log_file = parser.arguments['config'].get('global', 'log_file')
|
|
454 |
log_file = parser.arguments['config'].get_global('log_file')
|
|
436 | 455 |
if log_file: |
437 | 456 |
logger.set_log_filename(log_file) |
438 | 457 |
global filelog |
439 | 458 |
filelog = logger.add_file_logger(__name__.split('.')[0]) |
440 | 459 |
filelog.info('* Initial Call *\n%s\n- - -' % ' '.join(argv)) |
441 | 460 |
|
442 |
auth_base = _init_session(parser.arguments)
|
|
461 |
remote_base = _init_session(parser.arguments, is_non_API(parser))
|
|
443 | 462 |
|
444 | 463 |
from kamaki.cli.utils import suggest_missing |
445 | 464 |
suggest_missing() |
446 | 465 |
|
447 | 466 |
if parser.unparsed: |
448 |
run_one_cmd(exe, parser, auth_base)
|
|
467 |
run_one_cmd(exe, parser, remote_base)
|
|
449 | 468 |
elif _help: |
450 | 469 |
parser.parser.print_help() |
451 | 470 |
_groups_help(parser.arguments) |
452 | 471 |
else: |
453 |
run_shell(exe, parser, auth_base)
|
|
472 |
run_shell(exe, parser, remote_base)
|
|
454 | 473 |
except CLIError as err: |
455 | 474 |
print_error_message(err) |
456 | 475 |
if _debug: |
b/kamaki/cli/argument.py | ||
---|---|---|
167 | 167 |
return self.value.get(group, term) |
168 | 168 |
|
169 | 169 |
def get_groups(self): |
170 |
return self.value.keys('cli') |
|
170 |
suffix = '_cli' |
|
171 |
slen = len(suffix) |
|
172 |
return [term[:-slen] for term in self.value.keys('global') if ( |
|
173 |
term.endswith(suffix))] |
|
171 | 174 |
|
172 | 175 |
def get_cli_specs(self): |
173 |
return self.value.items('cli') |
|
176 |
suffix = '_cli' |
|
177 |
slen = len(suffix) |
|
178 |
return [(k[:-slen], v) for k, v in self.value.items('global') if ( |
|
179 |
k.endswith(suffix))] |
|
180 |
|
|
181 |
def get_global(self, option): |
|
182 |
return self.value.get_global(option) |
|
183 |
|
|
184 |
def get_remote(self, remote, option): |
|
185 |
return self.value.get_remote(remote, option) |
|
174 | 186 |
|
175 | 187 |
_config_arg = ConfigArgument( |
176 |
1, 'Path to configuration file', |
|
177 |
('-c', '--config')) |
|
188 |
1, 'Path to configuration file', ('-c', '--config')) |
|
178 | 189 |
|
179 | 190 |
|
180 | 191 |
class CmdLineConfigArgument(Argument): |
b/kamaki/cli/command_shell.py | ||
---|---|---|
302 | 302 |
self.auth_base = auth_base |
303 | 303 |
self._parser = parser |
304 | 304 |
self._history = History( |
305 |
parser.arguments['config'].get('history', 'file'))
|
|
305 |
parser.arguments['config'].get_global('history_file'))
|
|
306 | 306 |
if path: |
307 | 307 |
cmd = self.cmd_tree.get_command(path) |
308 | 308 |
intro = cmd.path.replace('_', ' ') |
b/kamaki/cli/commands/__init__.py | ||
---|---|---|
34 | 34 |
from kamaki.cli.logger import get_logger |
35 | 35 |
from kamaki.cli.utils import print_json, print_items |
36 | 36 |
from kamaki.cli.argument import FlagArgument |
37 |
from kamaki.cli.errors import CLIError |
|
38 |
from kamaki.clients import Client |
|
37 | 39 |
|
38 | 40 |
log = get_logger(__name__) |
39 | 41 |
|
40 | 42 |
|
41 | 43 |
class _command_init(object): |
42 | 44 |
|
43 |
def __init__(self, arguments={}, auth_base=None): |
|
45 |
def __init__(self, arguments={}, auth_base_or_remote=None):
|
|
44 | 46 |
if hasattr(self, 'arguments'): |
45 | 47 |
arguments.update(self.arguments) |
46 | 48 |
if isinstance(self, _optional_output_cmd): |
... | ... | |
52 | 54 |
self.config = self['config'] |
53 | 55 |
except KeyError: |
54 | 56 |
pass |
55 |
self.auth_base = auth_base or getattr(self, 'auth_base', None) |
|
57 |
if isinstance(auth_base_or_remote, Client): |
|
58 |
self.auth_base = auth_base_or_remote |
|
59 |
elif not getattr(self, 'auth_base', None): |
|
60 |
self.remote = auth_base_or_remote |
|
61 |
if not self.remote: |
|
62 |
raise CLIError('CRITICAL: No cloud specified', 3) |
|
56 | 63 |
|
57 | 64 |
def _set_log_params(self): |
58 | 65 |
try: |
59 | 66 |
self.client.LOG_TOKEN, self.client.LOG_DATA = ( |
60 |
self['config'].get('global', 'log_token') == 'on',
|
|
61 |
self['config'].get('global', 'log_data') == 'on')
|
|
67 |
self['config'].get_global('log_token').lower() == 'on',
|
|
68 |
self['config'].get_global('log_data').lower() == 'on')
|
|
62 | 69 |
except Exception as e: |
63 | 70 |
log.warning('Failed to read custom log settings:' |
64 | 71 |
'%s\n defaults for token and data logging are off' % e) |
65 | 72 |
|
66 | 73 |
def _update_max_threads(self): |
67 | 74 |
try: |
68 |
max_threads = int(self['config'].get('global', 'max_threads'))
|
|
75 |
max_threads = int(self['config'].get_global('max_threads'))
|
|
69 | 76 |
assert max_threads > 0 |
70 | 77 |
self.client.MAX_THREADS = max_threads |
71 | 78 |
except Exception as e: |
b/kamaki/cli/config.py | ||
---|---|---|
79 | 79 |
# 'livetest_cli': 'livetest', |
80 | 80 |
# 'astakos_cli': 'snf-astakos' |
81 | 81 |
}, |
82 |
'remotes':
|
|
82 |
'remote': |
|
83 | 83 |
{ |
84 | 84 |
'default': { |
85 | 85 |
'url': '', |
... | ... | |
220 | 220 |
return 2.0 |
221 | 221 |
log.warning('........ nope') |
222 | 222 |
log.warning('Config file heuristic 2: at least 1 remote section ?') |
223 |
if 'remotes' in sections:
|
|
224 |
for r in self.keys('remotes'):
|
|
223 |
if 'remote' in sections: |
|
224 |
for r in self.keys('remote'): |
|
225 | 225 |
log.warning('... found remote "%s"' % r) |
226 | 226 |
return 3.0 |
227 | 227 |
log.warning('........ nope') |
... | ... | |
238 | 238 |
|
239 | 239 |
:raises KeyError: if remote or remote's option does not exist |
240 | 240 |
""" |
241 |
r = self.get('remotes', remote)
|
|
241 |
r = self.get('remote', remote) |
|
242 | 242 |
if not r: |
243 | 243 |
raise KeyError('Remote "%s" does not exist' % remote) |
244 | 244 |
return r[option] |
245 | 245 |
|
246 |
def get_global(self, option): |
|
247 |
return self.get('global', option) |
|
248 |
|
|
246 | 249 |
def set_remote(self, remote, option, value): |
247 | 250 |
try: |
248 |
d = self.get('remotes', remote)
|
|
251 |
d = self.get('remote', remote) |
|
249 | 252 |
except KeyError: |
250 | 253 |
pass |
251 | 254 |
d[option] = value |
252 |
self.set('remotes', remote, d) |
|
255 |
self.set('remote', remote, d) |
|
256 |
|
|
257 |
def set_global(self, option, value): |
|
258 |
self.set('global', option, value) |
|
253 | 259 |
|
254 | 260 |
def _load_defaults(self): |
255 | 261 |
for section, options in DEFAULTS.items(): |
... | ... | |
305 | 311 |
self._overrides[section][option] = value |
306 | 312 |
|
307 | 313 |
def write(self): |
308 |
for r, d in self.items('remotes'):
|
|
314 |
for r, d in self.items('remote'): |
|
309 | 315 |
for k, v in d.items(): |
310 | 316 |
self.set('remote "%s"' % r, k, v) |
311 |
self.remove_section('remotes')
|
|
317 |
self.remove_section('remote') |
|
312 | 318 |
|
313 | 319 |
with open(self.path, 'w') as f: |
314 | 320 |
os.chmod(self.path, 0600) |
b/kamaki/cli/errors.py | ||
---|---|---|
55 | 55 |
self.importance = 0 |
56 | 56 |
|
57 | 57 |
|
58 |
class CLIUnimplemented(CLIError): |
|
59 |
def __init__( |
|
60 |
self, |
|
61 |
message='I \'M SORRY, DAVE.\nI \'M AFRAID I CAN\'T DO THAT.', |
|
62 |
details=[ |
|
63 |
' _ |', |
|
64 |
' _-- --_ |', |
|
65 |
' -- -- |', |
|
66 |
' -- . -- |', |
|
67 |
' -_ _- |', |
|
68 |
' -_ _- |', |
|
69 |
' - |'], |
|
70 |
importance=3): |
|
71 |
super(CLIUnimplemented, self).__init__(message, details, importance) |
|
72 |
|
|
73 |
|
|
58 | 74 |
class CLIBaseUrlError(CLIError): |
59 | 75 |
def __init__(self, message='', details=[], importance=2, service=None): |
60 | 76 |
message = message or 'No url for %s' % service.lower() |
61 | 77 |
details = details or [ |
62 | 78 |
'Two options to resolve this:', |
63 | 79 |
'A. (recommended) Let kamaki discover the endpoint URLs for all', |
64 |
'services by setting a single Authentication URL:', |
|
65 |
' /config set auth_url <AUTH_URL>', |
|
80 |
'services by setting a single Authentication URL and token:', |
|
81 |
' /config set remote.default.url <AUTH_URL>', |
|
82 |
' /config set remote.default.token <t0k3n>', |
|
66 | 83 |
'B. (advanced users) Explicitly set a valid %s endpoint URL' % ( |
67 | 84 |
service.upper()), |
68 |
'Note: auth_url option has a higher priority, so delete it to',
|
|
85 |
'Note: url option has a higher priority, so delete it to', |
|
69 | 86 |
'make that work', |
70 |
' /config delete auth_url', |
|
71 |
' /config set %s.url <%s_URL>' % (service, service.upper())] |
|
87 |
' /config delete remote.default.url', |
|
88 |
' /config set remote.%s.url <%s_URL>' % ( |
|
89 |
service, service.upper())] |
|
72 | 90 |
super(CLIBaseUrlError, self).__init__(message, details, importance) |
73 | 91 |
|
74 | 92 |
|
b/kamaki/cli/one_command.py | ||
---|---|---|
55 | 55 |
return None |
56 | 56 |
|
57 | 57 |
|
58 |
def run(auth_base, parser, _help):
|
|
58 |
def run(remote_base, parser, _help):
|
|
59 | 59 |
group = get_command_group(list(parser.unparsed), parser.arguments) |
60 | 60 |
if not group: |
61 | 61 |
parser.parser.print_help() |
... | ... | |
68 | 68 |
global _best_match |
69 | 69 |
_best_match = [] |
70 | 70 |
|
71 |
group_spec = parser.arguments['config'].get('cli', group)
|
|
71 |
group_spec = parser.arguments['config'].get('global', '%s_cli' % group)
|
|
72 | 72 |
spec_module = _load_spec_module(group_spec, parser.arguments, '_commands') |
73 | 73 |
if spec_module is None: |
74 | 74 |
raise CLIUnknownCommand( |
... | ... | |
96 | 96 |
exit(0) |
97 | 97 |
|
98 | 98 |
cls = cmd.get_class() |
99 |
executable = cls(parser.arguments, auth_base)
|
|
99 |
executable = cls(parser.arguments, remote_base)
|
|
100 | 100 |
parser.update_arguments(executable.arguments) |
101 | 101 |
#parsed, unparsed = parse_known_args(parser, executable.arguments) |
102 | 102 |
for term in _best_match: |
b/kamaki/cli/utils.py | ||
---|---|---|
39 | 39 |
|
40 | 40 |
from kamaki.cli.errors import raiseCLIError |
41 | 41 |
|
42 |
suggest = dict( |
|
43 |
ansicolors=dict( |
|
42 |
suggest = dict(ansicolors=dict( |
|
44 | 43 |
active=False, |
45 | 44 |
url='#install-ansicolors-progress', |
46 |
description='Add colors to console responses'), |
|
47 |
progress=dict( |
|
48 |
active=False, |
|
49 |
url='#install-ansicolors-progress', |
|
50 |
description='Add progress bars to some commands')) |
|
45 |
description='Add colors to console responses')) |
|
51 | 46 |
|
52 | 47 |
try: |
53 | 48 |
from colors import magenta, red, yellow, bold |
Also available in: Unified diff