mirror of https://github.com/nucypher/nucypher.git
Move modifier flags to main CLI; Session based CLI configuration StdoutEmitter for pre and post character handling
parent
43513cd3b4
commit
0ed8c6c0a4
|
@ -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')
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue