Move modifier flags to main CLI; Session based CLI configuration StdoutEmitter for pre and post character handling

pull/802/head
Kieran R. Prasch 2019-02-25 21:41:14 -07:00 committed by Kieran Prasch
parent 43513cd3b4
commit 0ed8c6c0a4
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
4 changed files with 113 additions and 64 deletions

View File

@ -1,15 +1,16 @@
import shutil import shutil
import sys
from typing import List from typing import List
import click import click
from nacl.exceptions import CryptoError from nacl.exceptions import CryptoError
from twisted.logger import Logger from twisted.logger import Logger
import nucypher
from nucypher.blockchain.eth.registry import EthereumContractRegistry from nucypher.blockchain.eth.registry import EthereumContractRegistry
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.characters.lawful import Ursula from nucypher.characters.lawful import Ursula
from nucypher.cli.config import NucypherClickConfig
from nucypher.config.constants import DEFAULT_CONFIG_ROOT from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.network.middleware import RestMiddleware
DESTRUCTION = ''' DESTRUCTION = '''
*Permanently and irreversibly delete all* nucypher files including *Permanently and irreversibly delete all* nucypher files including
@ -24,36 +25,15 @@ Delete {}?'''
LOG = Logger('cli.actions') LOG = Logger('cli.actions')
console_emitter = NucypherClickConfig.emit
def handle_control_output(response: dict = None,
message: str = None,
json: bool = False,
quiet: bool = False,
color: str = 'white',
bold: bool = False,
) -> None:
try:
if not quiet and not json:
if response:
for k, v in response.items():
click.secho(message=f'{k} ...... {v}', fg=color, bold=bold)
elif message:
if json:
sys.stdout({'result': message, 'version': nucypher.__version__})
click.secho(message=message, fg=color, bold=bold)
else:
click.secho(message=message, fg=color, bold=bold)
else:
raise ValueError('Either "response" or "message" is required, but got neither.')
elif json:
sys.stdout(response)
except Exception:
LOG.debug("Error while formatting nucypher console output")
raise
def load_seednodes(min_stake: int, federated_only: bool, teacher_uris: list = None) -> List[Ursula]: def load_seednodes(min_stake: int,
federated_only: bool,
network_middleware: RestMiddleware = None,
teacher_uris: list = None
) -> List[Ursula]:
teacher_nodes = list() teacher_nodes = list()
if teacher_uris is None: if teacher_uris is None:
# Default teacher nodes can be placed here # Default teacher nodes can be placed here
@ -61,7 +41,8 @@ def load_seednodes(min_stake: int, federated_only: bool, teacher_uris: list = No
for uri in teacher_uris: for uri in teacher_uris:
teacher_node = Ursula.from_teacher_uri(teacher_uri=uri, teacher_node = Ursula.from_teacher_uri(teacher_uri=uri,
min_stake=min_stake, min_stake=min_stake,
federated_only=federated_only) federated_only=federated_only,
network_middleware=network_middleware)
teacher_nodes.append(teacher_node) teacher_nodes.append(teacher_node)
return teacher_nodes return teacher_nodes
@ -96,12 +77,12 @@ def destroy_system_configuration(config_class,
character_config.destroy(force=force) character_config.destroy(force=force)
except FileNotFoundError: except FileNotFoundError:
message = 'Failed: No nucypher files found at {}'.format(character_config.config_root) message = 'Failed: No nucypher files found at {}'.format(character_config.config_root)
click.secho(message, fg='red') console_emitter(message=message, color='red')
log.debug(message) log.debug(message)
raise click.Abort() raise click.Abort()
else: else:
message = "Deleted configuration files at {}".format(character_config.config_root) message = "Deleted configuration files at {}".format(character_config.config_root)
click.secho(message, fg='green') console_emitter(message=message, color='green')
log.debug(message) log.debug(message)
return config_root return config_root
@ -109,7 +90,7 @@ def destroy_system_configuration(config_class,
def unlock_keyring(configuration, password): def unlock_keyring(configuration, password):
try: try:
click.secho("Decrypting keyring...", fg='blue') console_emitter(message="Decrypting keyring...", color='blue')
configuration.keyring.unlock(password=password) configuration.keyring.unlock(password=password)
except CryptoError: except CryptoError:
raise configuration.keyring.AuthenticationFailed raise configuration.keyring.AuthenticationFailed
@ -126,8 +107,9 @@ def connect_to_blockchain(configuration, recompile_contracts: bool = False):
def forget(configuration): def forget(configuration):
"""Forget all known nodes via storages""" """Forget all known nodes via storage"""
click.confirm("Permanently delete all known node data?", abort=True) click.confirm("Permanently delete all known node data?", abort=True)
configuration.forget_nodes() configuration.forget_nodes()
message = "Removed all stored node node metadata and certificates" message = "Removed all stored node node metadata and certificates"
console_emitter(message=message, color='red')
click.secho(message=message, fg='red') click.secho(message=message, fg='red')

View File

@ -19,7 +19,7 @@ import collections
import os import os
import click import click
from constant_sorrow.constants import NO_PASSWORD from constant_sorrow.constants import NO_PASSWORD, NO_EMITTER
from twisted.logger import Logger from twisted.logger import Logger
from twisted.logger import globalLogPublisher from twisted.logger import globalLogPublisher
@ -33,6 +33,9 @@ from nucypher.utilities.logging import (
class NucypherClickConfig: class NucypherClickConfig:
# Output Sinks
emitters = list()
capture_stdout = False
__sentry_endpoint = NUCYPHER_SENTRY_ENDPOINT __sentry_endpoint = NUCYPHER_SENTRY_ENDPOINT
# Environment Variables # Environment Variables
@ -65,6 +68,11 @@ class NucypherClickConfig:
self.__keyring_password = keyring_password self.__keyring_password = keyring_password
return self.__keyring_password return self.__keyring_password
@classmethod
def emit(cls, *args, **kwargs):
for emitter in cls.emitters:
emitter(*args, **kwargs)
class NucypherDeployerClickConfig(NucypherClickConfig): class NucypherDeployerClickConfig(NucypherClickConfig):

View File

@ -20,38 +20,101 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import click import click
from nucypher.characters.banners import NUCYPHER_BANNER from nucypher.characters.banners import NUCYPHER_BANNER
from nucypher.characters.control.emitters import StdoutEmitter, IPCStdoutEmitter
from nucypher.cli import status from nucypher.cli import status
from nucypher.cli.characters import moe, ursula, alice, bob, enrico from nucypher.cli.characters import moe, ursula, alice, bob, enrico
from nucypher.cli.config import nucypher_click_config from nucypher.cli.config import nucypher_click_config, NucypherClickConfig
from nucypher.cli.painting import echo_version from nucypher.cli.painting import echo_version
from nucypher.network.middleware import RestMiddleware
from nucypher.utilities.logging import GlobalConsoleLogger from nucypher.utilities.logging import GlobalConsoleLogger
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
@click.group() @click.group()
@click.option('--version', help="Echo the CLI version", is_flag=True, callback=echo_version, expose_value=False, is_eager=True) @click.option('--version', help="Echo the CLI version", is_flag=True, callback=echo_version, expose_value=False, is_eager=True)
@click.option('-v', '--verbose', help="Specify verbosity level", count=True) @click.option('-v', '--verbose', help="Specify verbosity level", count=True)
@click.option('-J', '--json', help="Send all output to stdout as JSON", is_flag=True, default=False) @click.option('-Z', '--mock-networking', help="Use in-memory transport instead of networking", count=True)
@click.option('--no-logs', help="Disable all logging output", is_flag=True, default=False) @click.option('-J', '--json-ipc', help="Send all output to stdout as JSON", is_flag=True, default=False)
@click.option('-Q', '--quiet', help="Disable console printing", is_flag=True, default=False)
@click.option('-L', '--no-logs', help="Disable all logging output", is_flag=True, default=False)
@click.option('-D', '--debug', help="Enable debugging mode", is_flag=True)
@click.option('--no-registry', help="Skip importing the default contract registry", is_flag=True)
@nucypher_click_config @nucypher_click_config
def nucypher_cli(click_config, verbose, json, no_logs): def nucypher_cli(click_config,
verbose,
mock_networking,
json_ipc,
no_logs,
quiet,
debug,
no_registry):
# Session Emitter for pre and post character control engagement.
if json_ipc:
emitter = IPCStdoutEmitter(quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout)
else:
emitter = StdoutEmitter(quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout)
NucypherClickConfig.emitter = emitter
click_config.emitter(message=NUCYPHER_BANNER)
# Logging
if not no_logs: if not no_logs:
GlobalConsoleLogger.start_if_not_started() GlobalConsoleLogger.start_if_not_started()
if not json: # CLI Session Configuration
click.echo(NUCYPHER_BANNER)
click_config.verbose = verbose click_config.verbose = verbose
click_config.json = json click_config.mock_networking = mock_networking
click_config.json_ipc = json_ipc
click_config.no_logs = no_logs click_config.no_logs = no_logs
click_config.quiet = quiet
click_config.no_registry = no_registry
click_config.debug = debug
# only used for testing outputs;
# Redirects outputs to in-memory python containers.
if mock_networking:
click_config.emitter(message="WARNING: Mock networking is enabled")
click_config.middleware = MockRestMiddleware()
else:
click_config.middleware = RestMiddleware()
# Global Warnings
if click_config.verbose: if click_config.verbose:
click.secho("Verbose mode is enabled", fg='blue') click_config.emitter("Verbose mode is enabled", color='blue')
nucypher_cli.add_command(status.status) #
nucypher_cli.add_command(alice.alice) # Character CLI Entry Points (Fan Out Input)
nucypher_cli.add_command(bob.bob) #
nucypher_cli.add_command(enrico.enrico)
nucypher_cli.add_command(moe.moe) r"""
nucypher_cli.add_command(ursula.ursula) ursula
|
| moe
| /
| /
stdin --> cli.main --- alice
| \
| \
| bob
|
enrico
"""
# New character CLI modules must be added here
# for the entry point to be attached to the nucypher base command.
# Inversely, commenting out an entry point will disable it.
ENTRTY_POINTS = (
status.status,
alice.alice,
bob.bob,
enrico.enrico,
moe.moe,
ursula.ursula
)
for entry_point in ENTRTY_POINTS:
nucypher_cli.add_command(entry_point)

View File

@ -21,9 +21,11 @@ import maya
from constant_sorrow.constants import NO_KNOWN_NODES from constant_sorrow.constants import NO_KNOWN_NODES
from nucypher.characters.banners import NUCYPHER_BANNER from nucypher.characters.banners import NUCYPHER_BANNER
from nucypher.cli import actions from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.config.constants import SEEDNODES from nucypher.config.constants import SEEDNODES
emitter = StdoutEmitter()
def echo_version(ctx, param, value): def echo_version(ctx, param, value):
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
@ -32,29 +34,23 @@ def echo_version(ctx, param, value):
ctx.exit() ctx.exit()
def paint_new_installation_help(new_configuration, config_root=None, config_file=None, quiet: bool = False): def paint_new_installation_help(new_configuration, config_root=None, config_file=None):
character_config_class = new_configuration.__class__ character_config_class = new_configuration.__class__
character_name = character_config_class._CHARACTER_CLASS.__name__.lower() character_name = character_config_class._NAME.lower()
actions.handle_control_output(message="Generated keyring {}".format(new_configuration.keyring_dir), emitter(message="Generated keyring {}".format(new_configuration.keyring_dir), color='green')
color='green',
quiet=quiet)
actions.handle_control_output(message="Saved configuration file {}".format(new_configuration.config_file_location), emitter(message="Saved configuration file {}".format(new_configuration.config_file_location), color='green')
color='green',
quiet=quiet)
# Give the use a suggestion as to what to do next... # Give the use a suggestion as to what to do next...
suggested_command = f'nucypher {character_name} run' suggested_command = f'nucypher {character_name} run'
how_to_run_message = f"\nTo run an {character_name.capitalize()} node from the default configuration filepath run: \n\n'{suggested_command}'\n" how_to_run_message = f"\nTo run an {character_name.capitalize()} node from the default configuration filepath run: \n\n'{suggested_command}'\n"
if config_root is not None: if config_root is not None:
config_file_location = os.path.join(config_root, config_file or character_config_class.CONFIG_FILENAME) config_file_location = os.path.join(config_root, config_file or character_config_class.CONFIG_FILENAME)
suggested_command += ' --config-file {}'.format(config_file_location) suggested_command += ' --config-file {}'.format(config_file_location)
click.secho(how_to_run_message.format(suggested_command), fg='green')
return actions.handle_control_output(message=how_to_run_message.format(suggested_command), return emitter(message=how_to_run_message.format(suggested_command), color='green')
color='green',
quiet=quiet)
def build_fleet_state_status(ursula) -> str: def build_fleet_state_status(ursula) -> str: