mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-03-29 16:48:41 +08:00
395 lines
12 KiB
Python
395 lines
12 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import print_function
|
|
|
|
"""
|
|
Blockstack-client
|
|
~~~~~
|
|
copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
|
|
copyright: (c) 2016 by Blockstack.org
|
|
|
|
This file is part of Blockstack-client.
|
|
|
|
Blockstack-client is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Blockstack-client is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
"""
|
|
IMPROTANT: READ THIS FIRST
|
|
|
|
Do NOT add CLI commands to this file.
|
|
Instead, define the appropriate method in the `actions.py` file
|
|
in this module.
|
|
|
|
This module will load and register each appropriate method from `actions.py`
|
|
as a command-line option.
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
import requests
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
import logging
|
|
logging.disable(logging.CRITICAL)
|
|
|
|
from blockstack_client import config
|
|
from blockstack_client.client import session, analytics_user_register
|
|
from blockstack_client.config import CONFIG_PATH, VERSION, semver_match, get_config, client_uuid_path, get_or_set_uuid
|
|
from blockstack_client.method_parser import parse_methods, build_method_subparsers
|
|
|
|
from wallet import *
|
|
from utils import exit_with_error, print_result
|
|
|
|
log = config.get_logger()
|
|
|
|
|
|
def get_methods(prefix, module):
|
|
"""
|
|
Get the built-in CLI methods
|
|
"""
|
|
methods = []
|
|
for attr in dir(module):
|
|
if not attr.startswith(prefix):
|
|
continue
|
|
|
|
method = getattr(module, attr)
|
|
|
|
if callable(method) or hasattr(method, '__call__'):
|
|
methods.append(method)
|
|
|
|
return methods
|
|
|
|
|
|
def get_plugin_methods(module_name, prefix):
|
|
"""
|
|
Load methods from a given module
|
|
Return the list on success
|
|
Return None on error
|
|
"""
|
|
try:
|
|
module = __import__(module_name)
|
|
except ImportError:
|
|
log.error('Failed to import "{}"'.format(module_name))
|
|
return None
|
|
|
|
return get_methods(prefix, module)
|
|
|
|
|
|
def get_cli_methods():
|
|
"""
|
|
Get built-in CLI methods
|
|
"""
|
|
import blockstack_client.actions as builtin_methods
|
|
all_methods = get_methods('cli_', builtin_methods)
|
|
return all_methods
|
|
|
|
|
|
def prompt_args(arginfolist, prompt_func):
|
|
"""
|
|
Prompt for args, using parsed method information
|
|
Use prompt_func(help, name) to do the prompt
|
|
Return a list of parsed arguments
|
|
Return None on error
|
|
"""
|
|
parsed_args = []
|
|
for arg in arginfolist:
|
|
name, help = arg['name'], arg['help']
|
|
|
|
try:
|
|
parsed_arg = None
|
|
while True:
|
|
try:
|
|
parsed_arg = prompt_func(help, name)
|
|
break
|
|
except ValueError:
|
|
print('Invalid args. Please try again. {}:{}'.format(name, help))
|
|
continue
|
|
|
|
parsed_args.append(parsed_arg)
|
|
except KeyboardInterrupt:
|
|
print('Keyboard interrupt')
|
|
return None
|
|
except Exception as e:
|
|
log.exception(e)
|
|
return None
|
|
|
|
return parsed_args
|
|
|
|
|
|
def find_arg(argv, has_arg, short_opt, long_opt):
|
|
"""
|
|
Find an option in an argument vector.
|
|
If @has_arg is True, then the argument will be removed as well.
|
|
Otherwise, the argument is assumed to be True
|
|
|
|
Return (new argv, argument) on success. The option and its argument will be removed.
|
|
Return (None, None) if the option is present, but no argument is given and has_arg is True.
|
|
If the option is not found, then argv will be unchanged, and None will be returned
|
|
"""
|
|
arg = False
|
|
if short_opt in argv or long_opt in argv:
|
|
i = 1
|
|
while i < len(argv):
|
|
if argv[i] == short_opt or argv[i] == long_opt:
|
|
if has_arg:
|
|
if i + 1 >= len(argv) or argv[i+1].startswith('-'):
|
|
# print('{}: missing argument'.format(argv[i], file=sys.stderr))
|
|
return (None, None)
|
|
|
|
arg = argv[i + 1]
|
|
argv.pop(i)
|
|
argv.pop(i)
|
|
# print('found {}/{} at {} ({})'.format(short_opt, long_opt, i, arg))
|
|
return (argv, arg)
|
|
|
|
else:
|
|
argv.pop(i)
|
|
arg = True
|
|
# print('found {}/{} at {} ({})'.format(short_opt, long_opt, i, arg))
|
|
|
|
else:
|
|
i += 1
|
|
|
|
return (argv, arg)
|
|
|
|
|
|
def run_cli(argv=None, config_path=CONFIG_PATH):
|
|
"""
|
|
Run a CLI command from arguments (defaults to sys.argv)
|
|
Return the result of the command on success.
|
|
The result will be a dict, and will have 'error' defined on error condition.
|
|
"""
|
|
|
|
if argv is None:
|
|
argv = sys.argv
|
|
|
|
cli_debug = False
|
|
cli_config_argv = False
|
|
cli_default_yes = False
|
|
cli_api_pass = None
|
|
|
|
if '-v' in argv or '--version' in argv:
|
|
print(VERSION)
|
|
sys.exit(0)
|
|
|
|
# debug?
|
|
new_argv, cli_debug = find_arg(argv, False, '-d', '--debug')
|
|
if new_argv is None:
|
|
# invalid
|
|
sys.exit(1)
|
|
|
|
if cli_debug:
|
|
os.environ['BLOCKSTACK_DEBUG'] = '1'
|
|
log.setLevel(logging.DEBUG)
|
|
log.debug("Activated debugging")
|
|
|
|
# alternative config path?
|
|
new_argv, cli_config_path = find_arg(argv, True, '-c', '--config')
|
|
if new_argv is None:
|
|
# invalid
|
|
sys.exit(1)
|
|
|
|
argv = new_argv
|
|
if cli_config_path:
|
|
cli_config_argv = True
|
|
config_path = cli_config_path
|
|
log.debug('Use config file {}'.format(config_path))
|
|
|
|
os.environ['BLOCKSTACK_CLIENT_CONFIG'] = config_path
|
|
|
|
# CLI-given password?
|
|
new_argv, cli_password = find_arg(argv, True, '-p', '--password')
|
|
if new_argv is None:
|
|
# invalid
|
|
sys.exit(1)
|
|
|
|
argv = new_argv
|
|
if cli_password and os.environ.get('BLOCKSTACK_CLIENT_WALLET_PASSWORD') is None:
|
|
log.debug("Use CLI password")
|
|
os.environ["BLOCKSTACK_CLIENT_WALLET_PASSWORD"] = cli_password
|
|
|
|
# assume YES to all prompts?
|
|
new_argv, cli_default_yes = find_arg(argv, False, '-y', '--yes')
|
|
if new_argv is None:
|
|
# invalid
|
|
sys.exit(1)
|
|
|
|
if cli_default_yes or os.environ.get("BLOCKSTACK_CLIENT_INTERACTIVE_YES") == "1":
|
|
print("Assume YES to all interactive prompts", file=sys.stderr)
|
|
os.environ["BLOCKSTACK_CLIENT_INTERACTIVE_YES"] = '1'
|
|
|
|
# API password?
|
|
new_argv, cli_api_pass = find_arg(argv, True, '-a', '--api_password')
|
|
if new_argv is None:
|
|
# invalid
|
|
sys.exit(1)
|
|
|
|
argv = new_argv
|
|
if cli_api_pass:
|
|
os.environ['BLOCKSTACK_API_PASSWORD'] = cli_api_pass
|
|
|
|
if cli_config_argv or cli_debug:
|
|
# re-exec to reset variables
|
|
print("Re-exec {} with {}".format(argv[0], argv))
|
|
os.execv( argv[0], argv )
|
|
|
|
# do one-time opt-in request
|
|
uuid_path = client_uuid_path(config_dir=os.path.dirname(config_path))
|
|
first_time = False
|
|
client_uuid = None
|
|
|
|
if not os.path.exists(uuid_path):
|
|
first_time = True
|
|
client_uuid = get_or_set_uuid(config_dir=os.path.dirname(config_path))
|
|
|
|
if os.environ.get('BLOCKSTACK_CLIENT_INTERACTIVE_YES') != '1':
|
|
# interactive allowed
|
|
# prompt for email
|
|
print("Would you like to receive an email when there is a new release of this software available?")
|
|
email_addr = raw_input("Email address (leave blank to opt out): ")
|
|
|
|
# will only process real email addresses when we email announcements out
|
|
if len(email_addr) > 0:
|
|
analytics_user_register( client_uuid, email_addr )
|
|
|
|
conf = config.get_config(path=config_path, interactive=(os.environ.get('BLOCKSTACK_CLIENT_INTERACTIVE_YES') != '1'))
|
|
if conf is None:
|
|
return {'error': 'Failed to load config'}
|
|
|
|
conf_version = conf.get('client_version', '')
|
|
if not semver_match(conf_version, VERSION):
|
|
# back up the config file
|
|
if not cli_config_argv:
|
|
# default config file
|
|
backup_path = config.backup_config_file(config_path=config_path)
|
|
if not backup_path:
|
|
exit_with_error("Failed to back up legacy configuration file {}".format(config_path))
|
|
|
|
else:
|
|
exit_with_error("Backed up legacy configuration file from {} to {} and re-generated a new, default configuration. Please restart.".format(config_path, backup_path))
|
|
|
|
advanced_mode = conf.get('advanced_mode', False)
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Blockstack cli version {}'.format(config.VERSION)
|
|
)
|
|
|
|
all_methods = []
|
|
subparsers = parser.add_subparsers(dest='action')
|
|
|
|
# add basic methods
|
|
all_method_names = get_cli_methods()
|
|
all_methods = parse_methods(all_method_names)
|
|
build_method_subparsers(subparsers, all_methods)
|
|
|
|
if not advanced_mode:
|
|
# remove advanced methods
|
|
all_methods = filter( lambda m: 'advanced' not in m['pragmas'], all_methods )
|
|
|
|
# Print default help message, if no argument is given
|
|
if len(argv) == 1 or '-h' in argv or '--help' in argv:
|
|
parser.print_help()
|
|
return {}
|
|
|
|
interactive, args, directive = False, None, None
|
|
|
|
try:
|
|
args, unknown_args = parser.parse_known_args(args=argv[1:])
|
|
directive = args.action
|
|
except SystemExit:
|
|
# bad arguments
|
|
# special case: if the method is specified, but no method arguments are given,
|
|
# then switch to prompting the user for individual arguments.
|
|
try:
|
|
directive_parser = argparse.ArgumentParser(
|
|
description='Blockstack cli version {}'.format(config.VERSION)
|
|
)
|
|
directive_subparsers = directive_parser.add_subparsers(
|
|
dest='action'
|
|
)
|
|
|
|
# only parse the directive
|
|
build_method_subparsers(
|
|
directive_subparsers, all_methods, include_args=False, include_opts=False
|
|
)
|
|
directive_args, directive_unknown_args = directive_parser.parse_known_args(
|
|
args=argv[1:]
|
|
)
|
|
|
|
# want interactive prompting
|
|
interactive, directive = True, directive_args.action
|
|
|
|
except SystemExit:
|
|
# still invalid
|
|
parser.print_help()
|
|
return {'error': 'Invalid arguments. Try passing "-h".'}
|
|
|
|
result = {}
|
|
|
|
blockstack_server, blockstack_port = conf['server'], conf['port']
|
|
|
|
# initialize blockstack connection
|
|
session(
|
|
conf=conf, server_host=blockstack_server,
|
|
server_port=blockstack_port, set_global=True
|
|
)
|
|
|
|
prompt_func = lambda help, name: raw_input('optional: {} ("{}"): '.format(help, name))
|
|
|
|
# dispatch to the apporpriate method
|
|
for method_info in all_methods:
|
|
if directive != method_info['command']:
|
|
continue
|
|
|
|
method = method_info['method']
|
|
pragmas = method_info['pragmas']
|
|
|
|
# interactive?
|
|
if interactive:
|
|
print('')
|
|
print('Interactive prompt engaged. Press Ctrl+C to quit')
|
|
print('Help for "{}": {}'.format(method_info['command'], method_info['help']))
|
|
print('')
|
|
|
|
required_args = prompt_args(method_info['args'], prompt_func)
|
|
if required_args is None:
|
|
return {'error': 'Failed to prompt for arguments'}
|
|
|
|
optional_args = prompt_args(method_info['opts'], prompt_func)
|
|
if optional_args is None:
|
|
return {'error': 'Failed to prompt for arguments'}
|
|
|
|
full_args = [method_info['command']] + required_args + optional_args
|
|
try:
|
|
args, unknown_args = parser.parse_known_args(args=full_args)
|
|
except SystemExit:
|
|
# invalid arguments
|
|
return {'error': 'Invalid arguments. Please try again.'}
|
|
|
|
result = method(args, config_path=config_path)
|
|
return result
|
|
|
|
# not found
|
|
return {'error': 'No such command "{}"'.format(args.action)}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
result = run_cli()
|
|
if 'error' in result:
|
|
exit_with_error(result['error'])
|
|
else:
|
|
print_result(result)
|
|
sys.exit(0)
|