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 sys
from typing import List
import click
from nacl.exceptions import CryptoError
from twisted.logger import Logger
import nucypher
from nucypher.blockchain.eth.registry import EthereumContractRegistry
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.characters.lawful import Ursula
from nucypher.cli.config import NucypherClickConfig
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.network.middleware import RestMiddleware
DESTRUCTION = '''
*Permanently and irreversibly delete all* nucypher files including
@ -24,36 +25,15 @@ Delete {}?'''
LOG = Logger('cli.actions')
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
console_emitter = NucypherClickConfig.emit
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()
if teacher_uris is None:
# 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:
teacher_node = Ursula.from_teacher_uri(teacher_uri=uri,
min_stake=min_stake,
federated_only=federated_only)
federated_only=federated_only,
network_middleware=network_middleware)
teacher_nodes.append(teacher_node)
return teacher_nodes
@ -96,12 +77,12 @@ def destroy_system_configuration(config_class,
character_config.destroy(force=force)
except FileNotFoundError:
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)
raise click.Abort()
else:
message = "Deleted configuration files at {}".format(character_config.config_root)
click.secho(message, fg='green')
console_emitter(message=message, color='green')
log.debug(message)
return config_root
@ -109,7 +90,7 @@ def destroy_system_configuration(config_class,
def unlock_keyring(configuration, password):
try:
click.secho("Decrypting keyring...", fg='blue')
console_emitter(message="Decrypting keyring...", color='blue')
configuration.keyring.unlock(password=password)
except CryptoError:
raise configuration.keyring.AuthenticationFailed
@ -126,8 +107,9 @@ def connect_to_blockchain(configuration, recompile_contracts: bool = False):
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)
configuration.forget_nodes()
message = "Removed all stored node node metadata and certificates"
console_emitter(message=message, color='red')
click.secho(message=message, fg='red')

View File

@ -19,7 +19,7 @@ import collections
import os
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 globalLogPublisher
@ -33,6 +33,9 @@ from nucypher.utilities.logging import (
class NucypherClickConfig:
# Output Sinks
emitters = list()
capture_stdout = False
__sentry_endpoint = NUCYPHER_SENTRY_ENDPOINT
# Environment Variables
@ -65,6 +68,11 @@ class NucypherClickConfig:
self.__keyring_password = keyring_password
return self.__keyring_password
@classmethod
def emit(cls, *args, **kwargs):
for emitter in cls.emitters:
emitter(*args, **kwargs)
class NucypherDeployerClickConfig(NucypherClickConfig):

View File

@ -20,38 +20,101 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import click
from nucypher.characters.banners import NUCYPHER_BANNER
from nucypher.characters.control.emitters import StdoutEmitter, IPCStdoutEmitter
from nucypher.cli import status
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.network.middleware import RestMiddleware
from nucypher.utilities.logging import GlobalConsoleLogger
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
@click.group()
@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('-J', '--json', help="Send all output to stdout as JSON", is_flag=True, default=False)
@click.option('--no-logs', help="Disable all logging output", is_flag=True, default=False)
@click.option('-Z', '--mock-networking', help="Use in-memory transport instead of networking", count=True)
@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
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:
GlobalConsoleLogger.start_if_not_started()
if not json:
click.echo(NUCYPHER_BANNER)
# CLI Session Configuration
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.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:
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)
nucypher_cli.add_command(bob.bob)
nucypher_cli.add_command(enrico.enrico)
nucypher_cli.add_command(moe.moe)
nucypher_cli.add_command(ursula.ursula)
#
# Character CLI Entry Points (Fan Out Input)
#
r"""
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 nucypher.characters.banners import NUCYPHER_BANNER
from nucypher.cli import actions
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.config.constants import SEEDNODES
emitter = StdoutEmitter()
def echo_version(ctx, param, value):
if not value or ctx.resilient_parsing:
@ -32,29 +34,23 @@ def echo_version(ctx, param, value):
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_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),
color='green',
quiet=quiet)
emitter(message="Generated keyring {}".format(new_configuration.keyring_dir), color='green')
actions.handle_control_output(message="Saved configuration file {}".format(new_configuration.config_file_location),
color='green',
quiet=quiet)
emitter(message="Saved configuration file {}".format(new_configuration.config_file_location), color='green')
# Give the use a suggestion as to what to do next...
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"
if config_root is not None:
config_file_location = os.path.join(config_root, config_file or character_config_class.CONFIG_FILENAME)
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),
color='green',
quiet=quiet)
return emitter(message=how_to_run_message.format(suggested_command), color='green')
def build_fleet_state_status(ursula) -> str: