Skip to content

Commit

Permalink
more guided approach, pep8
Browse files Browse the repository at this point in the history
  • Loading branch information
vepkenez committed Mar 31, 2022
1 parent 8949fbc commit 763b9ed
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 290 deletions.
1 change: 0 additions & 1 deletion nucypher_ops/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@
__license__ = "GNU Affero General Public License, Version 3"

__copyright__ = 'Copyright (C) 2022 NuCypher'

2 changes: 1 addition & 1 deletion nucypher_ops/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .ops.fleet_ops import CloudDeployers
from .ops.fleet_ops import CloudDeployers
11 changes: 5 additions & 6 deletions nucypher_ops/cli/ethereum.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK
from nucypher_ops.ops.fleet_ops import CloudDeployers
import os
import click
emitter = click

from nucypher_ops.ops.fleet_ops import CloudDeployers

from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK


@click.group('ethereum')
def cli():
"""deploy and update geth nodes"""


@cli.command('deploy')
@click.option('--image', help="The geth image to deploy", default='ethereum/client-go:stable')
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
Expand All @@ -28,12 +27,12 @@ def deploy(image, namespace, network, include_hosts, envvars, cliargs):
envvars=envvars,
cliargs=cliargs,
resource_name='ethereum'
)
)

hostnames = deployer.config['instances'].keys()
if include_hosts:
hostnames = include_hosts
for name, hostdata in [(n, d) for n, d in deployer.config['instances'].items() if n in hostnames]:
emitter.echo(f'\t{name}: {hostdata["publicaddress"]}', color="yellow")
os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
deployer.deploy_image_on_existing_nodes(hostnames, 'ethereum')
deployer.deploy_image_on_existing_nodes(hostnames, 'ethereum')
6 changes: 4 additions & 2 deletions nucypher_ops/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# hello.py
import click
import click

from nucypher_ops.cli.nodes import cli as nodes
from nucypher_ops.cli.ursula import cli as ursula
from nucypher_ops.cli.ethereum import cli as ethereum


@click.group()
def index():
pass


index.add_command(nodes)
index.add_command(ursula)
index.add_command(ethereum)
index.add_command(ethereum)
76 changes: 46 additions & 30 deletions nucypher_ops/cli/nodes.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK, NETWORKS
from nucypher_ops.ops.fleet_ops import CloudDeployers
import os
import click
emitter = click

from nucypher_ops.ops.fleet_ops import CloudDeployers

from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK


@click.group('nodes')
def cli():
"""Manage the machinery"""


@cli.command('create')
@click.option('--region', help="provider specific region name (like us-east-1 or SFO3", default=None)
@click.option('--instance-type', help="provider specific instance size like `s-1vcpu-2gb` or `t3.small`", default=None)
@click.option('--cloudprovider', help="aws or digitalocean", default='aws')
@click.option('--count', help="Create this many nodes.", type=click.INT, default=1)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--nickname', help="A nickname by which to remember the created hosts", type=click.STRING, required=False)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
def create(instance_type, cloudprovider, count, nickname, namespace, network):
def create(region, instance_type, cloudprovider, count, nickname, namespace, network):
"""Creates the required number of workers to be staked later under a namespace"""

if cloudprovider == 'aws':
try:
import boto3
except ImportError:
raise click.BadOptionUsage('cloudprovider', "You must have boto3 installed to create aws nodes. run `pip install boto3` or use `--cloudprovider digitalocean`")
raise click.BadOptionUsage(
'cloudprovider', "You must have boto3 installed to create aws nodes. run `pip install boto3` or use `--cloudprovider digitalocean`")

deployer = CloudDeployers.get_deployer(cloudprovider)(emitter,
namespace=namespace, network=network, instance_type=instance_type, action='create')
deployer = CloudDeployers.get_deployer(cloudprovider)(emitter,
namespace=namespace, network=network, instance_type=instance_type, action='create', region=region)

names = []
i = 1
Expand All @@ -39,7 +39,8 @@ def create(instance_type, cloudprovider, count, nickname, namespace, network):
names.append(name)
i += 1
deployer.create_nodes(names)
emitter.echo(f"done. created {count} nodes. list existing nodes with `nucypher-ops nodes list`")
emitter.echo(
f"done. created {count} nodes. list existing nodes with `nucypher-ops nodes list`")


@cli.command('add')
Expand All @@ -55,24 +56,35 @@ def add(host_address, login_name, key_path, ssh_port, nickname, namespace, netwo

name = nickname

deployer = CloudDeployers.get_deployer('generic')(emitter, None, None, namespace=namespace, network=network, action='add')
deployer = CloudDeployers.get_deployer('generic')(
emitter, None, None, namespace=namespace, network=network, action='add')
deployer.create_nodes([name], host_address, login_name, key_path, ssh_port)


@cli.command('list')
@click.option('--network', help="The network whose hosts you want to see.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--network', help="The network whose hosts you want to see.", type=click.STRING, default=None)
@click.option('--namespace', help="The network whose hosts you want to see.", type=click.STRING, default=DEFAULT_NAMESPACE)
def list(network, namespace):
"""Prints local config info about known hosts"""

deployer = CloudDeployers.get_deployer('generic')(emitter, network=network, namespace=namespace)
hosts = deployer.get_all_hosts()
if not hosts:
emitter.echo(f"No nodes in the {network}/{namespace} namespace")
for name, data in hosts:
emitter.echo(name)
for k, v in data.items():
emitter.echo(f"\t{k}: {v}")
if not network:
networks = NETWORKS.keys()
else:
networks = [network]

for network in networks:
emitter.echo(network)
deployer = CloudDeployers.get_deployer('generic')(
emitter, network=network, namespace=namespace, read_only=True)
if deployer.config_path.exists():
hosts = deployer.get_all_hosts()
if not hosts:
emitter.echo(
f"No nodes in the {network}/{namespace} namespace")
for name, data in hosts:
emitter.echo(name)
for k, v in data.items():
emitter.echo(f"\t{k}: {v}")


@cli.command('destroy')
Expand All @@ -84,22 +96,24 @@ def destroy(cloudprovider, namespace, network, include_hosts):
"""Cleans up all previously created resources for the given network for the same cloud provider"""

if not cloudprovider:
hosts = CloudDeployers.get_deployer('generic')(emitter, network=network, namespace=namespace).get_all_hosts()
if len(set(host['provider'] for address, host in hosts)) == 1: # check if there are hosts in this namespace
hosts = CloudDeployers.get_deployer('generic')(
emitter, network=network, namespace=namespace).get_all_hosts()
# check if there are hosts in this namespace
if len(set(host['provider'] for address, host in hosts)) == 1:
cloudprovider = hosts[0][1]['provider']
else:
emitter.echo("Found hosts from multiple cloudproviders.")
emitter.echo("We can only destroy hosts from one cloudprovider at a time.")
emitter.echo("Please specify which provider's hosts you'd like to destroy using --cloudprovider (digitalocean or aws)")
emitter.echo(
"We can only destroy hosts from one cloudprovider at a time.")
emitter.echo(
"Please specify which provider's hosts you'd like to destroy using --cloudprovider (digitalocean or aws)")
return
deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, network=network, namespace=namespace)
deployer = CloudDeployers.get_deployer(cloudprovider)(
emitter, network=network, namespace=namespace)

hostnames = [name for name, data in deployer.get_provider_hosts()]
if include_hosts:
hostnames = include_hosts
emitter.echo(f"\nAbout to destroy the following: {', '.join(hostnames)}, including all local data about these nodes.")
emitter.echo("\ntype 'y' to continue")
if click.getchar(echo=False) == 'y':
deployer.destroy_resources(hostnames)


Expand All @@ -110,12 +124,14 @@ def destroy(cloudprovider, namespace, network, include_hosts):
def remove(namespace, network, include_hosts):
"""Removes managed resources for the given network/namespace"""

deployer = CloudDeployers.get_deployer('generic')(emitter, network=network, namespace=namespace)
deployer = CloudDeployers.get_deployer('generic')(
emitter, network=network, namespace=namespace)

hostnames = [name for name, data in deployer.get_all_hosts()]
if include_hosts:
hostnames = include_hosts
emitter.echo(f"\nAbout to remove information about the following: {', '.join(hostnames)}, including all local data about these nodes.")
emitter.echo(
f"\nAbout to remove information about the following: {', '.join(hostnames)}, including all local data about these nodes.")
emitter.echo("\ntype 'y' to continue")
if click.getchar(echo=False) == 'y':
deployer.remove_resources(hostnames)
60 changes: 11 additions & 49 deletions nucypher_ops/cli/ursula.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK
from nucypher_ops.ops.fleet_ops import CloudDeployers
import os
import click
emitter = click

from nucypher_ops.ops.fleet_ops import CloudDeployers

from nucypher_ops.constants import DEFAULT_NAMESPACE, DEFAULT_NETWORK, PAYMENT_NETWORK_CHOICES


class EnumMenuPromptFromDict(click.Option):

def __init__(self, *args, **kwargs):
super(EnumMenuPromptFromDict, self).__init__(*args, **kwargs)
if 'prompt' not in kwargs:
raise TypeError(
"'prompt' keyword required for '{}' option".format(
args[0][0]))

self.choices_dict = self.prompt
self.prompt_menu = '\n'.join('[{}] {}'.format(i + 1, name)
for i, name in enumerate(self.prompt))
self.prompt = 'Choose a payment network from,\n{}\n{}'.format(
self.prompt_menu, self.name)

def prompt_for_value(self, ctx):
"""Get entered value and then validate"""
while True:
value = super(EnumMenuPromptFromDict, self).prompt_for_value(ctx)
try:
choice = int(value)
if choice > 0:
return list(self.choices_dict)[choice - 1]
except (ValueError, IndexError):
if value in self.choices_dict:
return value
click.echo('Error: {} is not a valid choice'.format(value))

def full_process_value(self, ctx, value):
"""Convert the entered value to the value from the choices dict"""
value = super(EnumMenuPromptFromDict, self).full_process_value(
ctx, value)
try:
return self.choices_dict[value]
except (KeyError, IndexError):
raise click.UsageError(
"'{}' is not a valid choice".format(value), ctx)


@click.group('ursula')
def cli():
"""deploy and update ursula nodes"""


@cli.command('deploy')
@click.option('--payment-network', cls=EnumMenuPromptFromDict, prompt=PAYMENT_NETWORK_CHOICES)
@click.option('--payment-network', help="Payment network name", type=click.STRING, default=None)
@click.option('--payment-provider', help="The remote blockchain provider for the payment network.", default=None)
@click.option('--eth-provider', help="The remote blockchain provider for policies on the remote node.", default=None)
@click.option('--nucypher-image', help="The docker image containing the nucypher code to run on the remote nodes.", default='nucypher/nucypher:latest')
Expand All @@ -78,15 +38,16 @@ def deploy(payment_network, payment_provider, eth_provider, nucypher_image, seed
docker_image=nucypher_image,
payment_provider=payment_provider,
payment_network=payment_network
)
)

hostnames = deployer.config['instances'].keys()
if include_hosts:
hostnames = include_hosts
for name, hostdata in [(n, d) for n, d in deployer.config['instances'].items() if n in hostnames]:
emitter.echo(f'\t{name}: {hostdata["publicaddress"]}', color="yellow")
os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
deployer.deploy_nucypher_on_existing_nodes(hostnames, migrate_nucypher=migrate, init=init)
deployer.deploy_nucypher_on_existing_nodes(
hostnames, migrate_nucypher=migrate, init=init)


@cli.command('update')
Expand All @@ -106,7 +67,7 @@ def deploy(nucypher_image, namespace, network, include_hosts, envvars, cliargs):
cliargs=cliargs,
resource_name='nucypher',
docker_image=nucypher_image
)
)

hostnames = deployer.config['instances'].keys()
if include_hosts:
Expand All @@ -124,10 +85,11 @@ def deploy(nucypher_image, namespace, network, include_hosts, envvars, cliargs):
def status(namespace, network, include_hosts):
"""Displays ursula status and updates worker data in stakeholder config"""

deployer = CloudDeployers.get_deployer('generic')(emitter, namespace=namespace, network=network)
deployer = CloudDeployers.get_deployer('generic')(
emitter, namespace=namespace, network=network)

hostnames = deployer.config['instances'].keys()
if include_hosts:
hostnames = include_hosts

deployer.get_worker_status(hostnames)
deployer.get_worker_status(hostnames)
12 changes: 7 additions & 5 deletions nucypher_ops/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from pathlib import Path

APP_DIR = AppDirs("nucypher-ops")
DEFAULT_CONFIG_ROOT = Path(os.getenv('NUCYPHER_OPS_CONFIG_ROOT', default=APP_DIR.user_data_dir))
DEFAULT_CONFIG_ROOT = Path(
os.getenv('NUCYPHER_OPS_CONFIG_ROOT', default=APP_DIR.user_data_dir))

MAINNET = 1
ROPSTEN = 3
Expand All @@ -26,13 +27,14 @@
REVERSE_LOOKUP_CHAIN_NAMES = {v: k for k, v in CHAIN_NAMES.items()}


NETWORKS = {
NETWORKS = {
'mainnet': {'policy': MAINNET, 'payment': POLYGON_MAINNET},
'ibex': {'policy': RINKEBY, 'payment': POLYGON_MUMBAI},
'lynx': {'policy': GOERLI, 'payment': POLYGON_MUMBAI}
}

PAYMENT_NETWORK_CHOICES = {k:k for k in ('polygon', 'mumbai')}
PAYMENT_NETWORKS = ('polygon', 'mumbai')
PAYMENT_NETWORK_CHOICES = '\n\t'.join(PAYMENT_NETWORKS)

BASE_DIR = os.path.dirname(__file__)

Expand All @@ -45,5 +47,5 @@
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD = "NUCYPHER_OPERATOR_ETH_PASSWORD"
NUCYPHER_ENVVAR_PROVIDER_URI = "NUCYPHER_PROVIDER_URI"

DEFAULT_NAMESPACE=os.getenv('NUCYPHER_OPS_DEFAULT_NAMESPACE', 'nucypher')
DEFAULT_NETWORK=os.getenv('NUCYPHER_OPS_DEFAULT_NETWORK', 'mainnet')
DEFAULT_NAMESPACE = os.getenv('NUCYPHER_OPS_DEFAULT_NAMESPACE', 'nucypher')
DEFAULT_NETWORK = os.getenv('NUCYPHER_OPS_DEFAULT_NETWORK', 'mainnet')
10 changes: 6 additions & 4 deletions nucypher_ops/ops/ansible_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
}
)


class AnsiblePlayBookResultsCollector(CallbackBase):
"""
Expand Down Expand Up @@ -55,7 +56,8 @@ def v2_runner_on_ok(self, result, *args, **kwargs):
data = '[{}]=> changed'.format(result._host.name)
else:
data = '[{}]=> ok'.format(result._host.name)
self.send_save(data, color='yellow' if result.is_changed() else 'green')
self.send_save(
data, color='yellow' if result.is_changed() else 'green')
if 'msg' in result._task_fields['args']:
self.send_save('\n')
msg = result._task_fields['args']['msg']
Expand All @@ -65,8 +67,8 @@ def v2_runner_on_ok(self, result, *args, **kwargs):
regex = fr'{k}:\s*(?P<data>.*)'
match = re.search(regex, msg, flags=re.MULTILINE)
if match:
self.results[k].append((result._host.name, match.groupdict()['data']))

self.results[k].append(
(result._host.name, match.groupdict()['data']))

def v2_runner_on_failed(self, result, *args, **kwargs):
if self.filter_output is not None:
Expand Down Expand Up @@ -115,4 +117,4 @@ def v2_playbook_on_stats(self, stats):

def send_save(self, data, color=None):
self.sock.echo(data, color=color)
self.playbook_results.append(data)
self.playbook_results.append(data)
Loading

0 comments on commit 763b9ed

Please sign in to comment.