PK ! Kc) tmn/__init__.pyimport logging
__version__ = '0.4.0'
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('[%(levelname)s] %(message)s'))
logger = logging.getLogger('tmn')
logger.addHandler(handler)
logger.setLevel('CRITICAL')
PK ! wI tmn/configuration.pyimport logging
import sys
from clint import resources
from eth_keys import keys
from slugify import slugify
import docker
from tmn import display
from tmn.elements.network import Network
from tmn.elements.service import Service
from tmn.elements.volume import Volume
from tmn.environments import environments
logger = logging.getLogger('tmn')
resources.init('tomochain', 'tmn')
class Configuration:
"""docstring for Configuration."""
def __init__(self, name: str = None, net: str = None,
pkey: str = None, start: bool = False,
docker_url: str = None, api: bool = False) -> None:
self.networks = {}
self.services = {}
self.volumes = {}
self.name = name
self.net = net
self.pkey = pkey or ''
self.id = None
self.force_recreate = False
if not docker_url:
self.client = docker.from_env()
else:
self.client = docker.DockerClient(base_url=docker_url)
self.api = 'True' if api else 'False'
try:
self.client.ping()
except Exception as e:
logger.error(e)
display.error_docker()
sys.exit('\n')
if resources.user.read('name'):
self._load()
elif start:
self._write()
else:
display.error_start_not_initialized()
sys.exit('\n')
self._compose()
def _get_address(self) -> str:
pk = keys.PrivateKey(bytes.fromhex(self.pkey))
return pk.public_key.to_address()[2:]
def _load(self) -> None:
if self.name or self.net or self.pkey:
display.warning_ignoring_start_options(resources.user.read('name'))
self.id = resources.user.read('id')
self.name = resources.user.read('name')
self.net = resources.user.read('net')
self.api = resources.user.read('api')
#######################################################################
# this is a dirty fix for retro compatiblity #
# can be removed in some future version #
# old `tmn` don't write the `id` or `net` option to disk #
# screw with `tmn update` #
# will ask to recreate as this is a breaking change #
#######################################################################
if not self.id or not self.net:
self.force_recreate = True
self.net = 'devnet'
#######################################################################
def _write(self) -> None:
if not self.name:
display.error_start_option_required('--name')
sys.exit('\n')
elif not self.net:
display.error_start_option_required('--net')
sys.exit('\n')
elif not self.pkey:
display.error_start_option_required('--pkey')
sys.exit('\n')
self._validate()
self.id = self._get_address()
resources.user.write('id', self.id)
resources.user.write('name', self.name)
resources.user.write('net', self.net)
resources.user.write('api', self.api)
def _compose(self) -> None:
self.networks['tmn'] = Network(
name='{}_tmn'.format(self.name),
client=self.client
)
self.volumes['chaindata'] = Volume(
name='{}_chaindata'.format(self.name),
client=self.client
)
if self.net == 'mainnet':
tag = 'stable'
elif self.net == 'testnet':
tag = 'testnet'
else:
tag = 'devnet'
if self.api == 'True': # this is dirty, should be refactored
tomochain_ports = {'30303/udp': 30303, '30303/tcp': 30303,
8545: 8545, 8546: 8546}
else:
tomochain_ports = {'30303/udp': 30303, '30303/tcp': 30303}
self.services['metrics'] = Service(
name='{}_metrics'.format(self.name),
hostname='{}'.format(self.id),
image='tomochain/telegraf:{}'.format(tag),
network=self.networks['tmn'].name,
volumes={
'/var/run/docker.sock': {
'bind': '/var/run/docker.sock', 'mode': 'ro'
},
'/sys': {'bind': '/rootfs/sys', 'mode': 'ro'},
'/proc': {'bind': '/rootfs/proc', 'mode': 'ro'},
'/etc': {'bind': '/rootfs/etc', 'mode': 'ro'}
},
client=self.client
)
self.services['tomochain'] = Service(
name='{}_tomochain'.format(self.name),
image='tomochain/node:{}'.format(tag),
network=self.networks['tmn'].name,
environment={
'IDENTITY': '{}'.format(self.name),
'PRIVATE_KEY': '{}'.format(self.pkey)
},
volumes={
self.volumes['chaindata'].name: {
'bind': '/tomochain', 'mode': 'rw'
}
},
ports=tomochain_ports,
client=self.client
)
for container, variables in environments[self.net].items():
for variable, value in variables.items():
self.services[container].add_environment(
name=variable,
value=value
)
def _validate(self) -> None:
self.name = slugify(self.name)
if len(self.name) < 5 or len(self.name) > 30:
display.error_validation_option('--name', '5 to 30 characters '
'slug')
sys.exit('\n')
if len(self.pkey) != 64:
display.error_validation_option('--pkey', '64 characters hex '
'string')
sys.exit('\n')
try:
bytes.fromhex(self.pkey)
except ValueError:
display.error_validation_option('--pkey', '64 characters hex '
'string')
sys.exit('\n')
def remove(self) -> None:
resources.user.delete('id')
resources.user.delete('name')
resources.user.delete('net')
PK ! 3" " tmn/display.pyimport sys
import pastel
pastel.add_style('hg', 'green')
pastel.add_style('hgb', 'green', options=['bold'])
pastel.add_style('hy', 'yellow')
pastel.add_style('hyb', 'yellow', options=['bold'])
pastel.add_style('link', 'yellow', options=['underscore'])
pastel.add_style('und', options=['underscore'])
pastel.add_style('warning', 'yellow')
pastel.add_style('error', 'red')
help_url = 'https://docs.tomochain.com/masternode/tmn/'
def newline(number: int = 1) -> None:
"Print newlines"
print('\n'*number, end='')
def style(function):
"Print and colorize strings with `pastel`"
def wrapper(*args, **kwargs) -> None:
print(pastel.colorize(function(*args, **kwargs)))
return wrapper
def style_no_new_line(function):
"Print and colorize strings with `pastel`. No newline."
def wrapper(*args) -> None:
print(pastel.colorize(function(*args)), end='', flush=True)
return wrapper
@style
def link(msg: str, url: str) -> str:
"Return a pastel formated string for browser links"
return '{msg} {url}'.format(
msg=msg,
url=url
)
def link_docs() -> None:
"Custom link message for documentation"
link('Documentation on running a masternode:', help_url)
@style
def title(msg: str) -> str:
"Return a pastel formated title string"
return '{msg}\n'.format(
msg=msg
)
def title_start_masternode(name: str) -> None:
"Title when starting a masternode"
title('Starting masternode {}:'.format(name))
def title_stop_masternode(name: str) -> None:
"Title when stopping a masternode"
title('Stopping masternode {}:'.format(name))
def title_status_masternode(name: str) -> None:
"Title when viewing a masternode status"
title('Masternode {} status:'.format(name))
def title_inspect_masternode(name: str) -> None:
"Title when inspecting a masternode"
title('Masternode {} details:'.format(name))
def title_update_masternode(name: str) -> None:
"Title when updating a masternode"
title('Updating masternode {}:'.format(name))
def title_remove_masternode(name: str) -> None:
"Title when removing a masternode"
title('Removing masternode {}:'.format(name))
@style
def subtitle(msg: str) -> str:
"Return a pastel formated subtitle string"
return '{msg}\n'.format(
msg=msg
)
def subtitle_create_volumes() -> None:
"Subtitle when creating volumes"
subtitle('Volumes')
def subtitle_remove_volumes() -> None:
"Subtitle when removing volumes"
subtitle('Volumes')
def subtitle_create_networks() -> None:
"Subtitle when creating networks"
subtitle('Networks')
def subtitle_remove_networks() -> None:
"Subtitle when removing networks"
subtitle('Networks')
def subtitle_create_containers() -> None:
"Subtitle when creating containers"
subtitle('Containers')
def subtitle_remove_containers() -> None:
"Subtitle when removing containers"
subtitle('Containers')
@style
def detail(msg, content: str, indent: int = 1) -> str:
"Return a pastel formated detail"
return (' '*indent
+ '{msg}:\n'.format(msg=msg)
+ ' '*indent
+ '{content}'.format(content=content))
def detail_identity(content: str) -> None:
"Custom detail message for the masternode identity"
detail('Unique identity', content)
def detail_coinbase(content: str) -> None:
"Custom detail message for the masternode coinbase address"
detail('Coinbase address (account public key)', content)
@style_no_new_line
def step(msg: str, indent: int = 1) -> str:
"Return a pastel formated step with indentation."
step = ' '*indent + '- {msg}... '.format(
msg=msg
)
return step
def step_create_volume(name: str) -> None:
"Custom step message for docker volumes creation"
step('Creating {name}'.format(
name=name
))
def step_remove_volume(name: str) -> None:
"Custom step message for docker volumes removal"
step('Removing {name}'.format(
name=name
))
def step_create_network(name: str) -> None:
"Custom step message for docker networks creation"
step('Creating {name}'.format(
name=name
))
def step_remove_network(name: str) -> None:
"Custom step message for docker networks creation"
step('Removing {name}'.format(
name=name
))
def step_create_container(name: str) -> None:
"Custom step message for docker container creation"
step('Creating {name}'.format(
name=name
))
def step_start_container(name: str) -> None:
"Custom step message for docker container starting"
step('Starting {name}'.format(
name=name
))
def step_remove_container(name: str) -> None:
"Custom step message for docker container starting"
step('Removing {name}'.format(
name=name
))
def step_stop_container(name: str) -> None:
"Custom step message for docker container stopping"
step('Stopping {name}'.format(
name=name
))
@style
def step_close(msg: str, color: str = 'green') -> str:
"Return a pastel formated end of step"
return '{msg}>'.format(
msg=msg,
color=color
)
def step_close_ok() -> None:
"Custom close message when all ok"
msg = 'ok'
if sys.stdout.encoding == 'UTF-8':
msg = '✔'
step_close(msg)
def step_close_nok() -> None:
"Custom close message when all ok"
msg = 'error'
if sys.stdout.encoding == 'UTF-8':
msg = '✗'
step_close(msg, 'red')
@style
def status(name: str = '', status: str = 'absent', id: str = '',
status_color: str = 'red') -> str:
"Return a pastel formated end of step"
if id:
return ' {name}\t{status}(>{id})>'.format(
name=name,
status=status,
color=status_color,
id=id
)
else:
return ' {name}\t{status}>'.format(
name=name,
status=status,
color=status_color,
)
@style
def warning(msg: str, newline: bool = True) -> str:
"Return a pastel formated string for warnings"
before = ''
if newline:
before = '\n'
return before + '! warning: {msg}\n'.format(
msg=msg
)
def warning_ignoring_start_options(name: str) -> None:
"Custom warning when tmn is ignoring the start options"
warning(
'masternode {} is already configured\n'.format(name)
+ ' '
+ 'ignoring start options\n'
)
def warning_remove_masternode(name: str) -> None:
"Custom warning when tmn is removing masternode but no confirmation"
warning(
'you are about to remove masternode {}\n'.format(name)
+ ' '
+ 'this will permanently delete its data\n'
+ ' '
+ 'to confirm use the --confirm flag'
)
@style
def error(msg: str) -> str:
"Return a pastel formated string for errors"
return (
'\n! error: {msg}\n'.format(msg=msg)
+ ' '
+ 'need help? {}'.format(help_url)
)
def error_docker() -> None:
"Custom error when docker is not accessible"
error('could not access the docker daemon')
def error_docker_api() -> None:
"Custom error when docker is not accessible"
error('something went wrong while doing stuff with docker')
def error_start_not_initialized() -> None:
"Custom error when `tmn start` has never been used with `--name` option"
error(
'tmn doesn\'t manage any masternode yet\n'
' please use '
'tmn start --name to get started'
)
def error_start_option_required(option: str) -> None:
"Custom error when `tmn start` is used with name but not the other options"
error(
'{} is required when starting a new masternode'
.format(option)
)
def error_validation_option(option: str, format: str) -> None:
"Custom error when an option format is not valide"
error(
'{} is not valid\n'.format(option)
+ ' it should be a {}'.format(format)
)
def error_breaking_change() -> None:
"Custom error when breaking changes need to recreate the node"
error(
'latest update introduced some non-retrocompatible changes\n'
' '
'please recreate your node by deleting it\n'
' '
'tmn remove --confirm\n'
' '
'and creating it back with the same options as the old one\n'
' '
'tmn start --name ... --net ... --pkey ...'
)
PK ! kK tmn/elements/__init__.pyfrom tmn.elements.network import Network
from tmn.elements.service import Service
from tmn.elements.volume import Volume
__all__ = ['Network', 'Service', 'Volume']
PK ! `H tmn/elements/network.pyimport logging
import docker
logger = logging.getLogger('tmn')
class Network:
"""docstring for Network."""
def __init__(self, client: docker.DockerClient, name: str) -> None:
self.name = name
self.network = None
self.client = client
try:
self.network = self.client.networks.get(self.name)
except docker.errors.NotFound as e:
logger.debug('network {} not yet created ({})'
.format(self.name, e))
except docker.errors.APIError as e:
logger.error(e)
def create(self) -> bool:
"create docker network"
try:
if self.network:
return True
else:
self.client.networks.create(self.name)
return True
except docker.errors.APIError as e:
logger.error(e)
except docker.errors.ConnectionRefusedError as e:
logger.error(e)
def remove(self) -> bool:
"delete docker network"
try:
if self.network:
self.network.remove()
self.network = None
return True
else:
return True
except docker.errors.APIError as e:
logger.error(e)
return False
PK ! Js s tmn/elements/service.pyimport logging
from typing import Dict, Union
import docker
logger = logging.getLogger('tmn')
class Service:
"""docstring for Service."""
def __init__(
self,
client: docker.DockerClient,
name: str,
image: str = None,
hostname: str = None,
network: str = None,
environment: Dict[str, str] = {},
volumes: Dict[str, Dict[str, str]] = {},
ports: Dict[str, Dict[str, str]] = {},
log_driver: str = 'json-file',
log_opts: Dict[str, str] = {'max-size': '3g'}
) -> None:
self.container = False
self.image = image
self.name = name
self.environment = environment
self.network = network
self.hostname = hostname
self.volumes = volumes
self.ports = ports
self.log_driver = log_driver
self.log_opts = log_opts
self.client = client
try:
self.container = self.client.containers.get(self.name)
except docker.errors.NotFound as e:
logger.debug('container {} not yet created ({})'
.format(self.name, e))
except docker.errors.APIError as e:
logger.error(e)
def add_environment(self, name: str, value: str) -> None:
"add a new environment to the service"
self.environment[name] = value
def add_volume(self, source: str, target: str, mode: str = 'rw') -> None:
"add a new volume to the service"
self.volumes[source] = {'bind': target, 'mode': mode}
def add_port(self, source: str, target: str) -> None:
"add a new port mapping to the service"
self.ports[source] = target
def create(self) -> bool:
"create the service container"
try:
if self.container:
return True
else:
self.client.images.pull(self.image)
self.container = self.client.containers.create(
image=self.image,
name=self.name,
hostname=self.hostname,
network=self.network,
environment=self.environment,
volumes=self.volumes,
log_config={'type': self.log_driver,
'config': self.log_opts},
detach=True
)
return True
except docker.errors.APIError as e:
logger.error(e)
return False
def start(self) -> bool:
"start the service container"
try:
if self.container:
self.container.reload()
if self.container.status in ['running', 'restarting']:
return True
elif self.container.status in ['paused']:
self.container.unpause()
return True
else:
self.container.start()
return True
else:
return False
except docker.errors.APIError as e:
logger.error(e)
return False
def status(self) -> Union[str, bool]:
"return the status of the service container"
try:
if self.container:
self.container.reload()
return self.container.status
else:
return 'absent'
except docker.errors.APIError as e:
logger.error(e)
return False
def execute(self, command: str) -> Union[str, bool]:
"return the result of a command on the service container"
try:
if self.container:
self.container.reload()
return self.container.exec_run(
'/bin/sh -c "{}"'.format(command)
).output.decode("utf-8")
else:
return False
except docker.errors.APIError as e:
logger.error(e)
return False
def stop(self) -> bool:
"stop the service container"
try:
if self.container:
self.container.reload()
if self.container.status in ['created', 'exited', 'dead']:
return True
else:
self.container.stop()
return True
else:
return True
except docker.errors.APIError as e:
logger.error(e)
return False
def remove(self) -> bool:
"stop the service container"
try:
if self.container:
self.container.remove(force=True)
self.container = None
return True
else:
return True
except docker.errors.APIError as e:
logger.error(e)
return False
def update(self) -> bool:
"update the service container"
try:
if self.container:
self.container.remove(force=True)
return self.create() and self.start()
else:
return False
except docker.errors.APIError as e:
logger.error(e)
return False
PK ! C tmn/elements/volume.pyimport logging
import docker
logger = logging.getLogger('tmn')
class Volume:
"""docstring for Volume."""
def __init__(self, client: docker.DockerClient, name: str) -> None:
self.name = name
self.volume = None
self.client = client
try:
self.volume = self.client.volumes.get(self.name)
except docker.errors.NotFound as e:
logger.debug('volume {} not yet created ({})'
.format(self.name, e))
except docker.errors.APIError as e:
logger.error(e)
def create(self) -> bool:
"create docker volumes"
try:
if self.volume:
return True
else:
self.client.volumes.create(self.name)
return True
except docker.errors.APIError as e:
logger.error(e)
return False
def remove(self) -> bool:
"delete docker volume"
try:
if self.volume:
self.volume.remove(force=True)
self.volume = None
return True
else:
return True
except docker.errors.APIError as e:
logger.error(e)
return False
PK ! f} } tmn/environments.pyenvironments = {
'mainnet': {
'tomochain': {
'BOOTNODES': (
'enode://97f0ca95a653e3c44d5df2674e19e9324ea4bf4d47a46b1d8560f'
'3ed4ea328f725acec3fcfcb37eb11706cf07da669e9688b091f1543f89b24'
'25700a68bc8876@104.248.98.78:30301,enode://b72927f349f3a27b78'
'9d0ca615ffe3526f361665b496c80e7cc19dace78bd94785fdadc270054ab'
'727dbb172d9e3113694600dd31b2558dd77ad85a869032dea@188.166.207'
'.189:30301,enode://c8f2f0643527d4efffb8cb10ef9b6da4310c5ac9f2'
'e988a7f85363e81d42f1793f64a9aa127dbaff56b1e8011f90fe9ff57fa02'
'a36f73220da5ff81d8b8df351@104.248.98.60:30301'
),
'NETSTATS_HOST': 'stats.tomochain.com',
'NETSTATS_PORT': '443',
'NETWORK_ID': '88',
'WS_SECRET': (
'getty-site-pablo-auger-room-sos-blair-shin-whiz-delhi'
)
},
'metrics': {
'METRICS_ENDPOINT': 'https://metrics.tomochain.com'
}
},
'testnet': {
'tomochain': {
'BOOTNODES': (
'enode://4d3c2cc0ce7135c1778c6f1cfda623ab44b4b6db55289543d48ec'
'fde7d7111fd420c42174a9f2fea511a04cf6eac4ec69b4456bfaaae0e5bd2'
'36107d3172b013@52.221.28.223:30301,enode://298780104303fcdb37'
'a84c5702ebd9ec660971629f68a933fd91f7350c54eea0e294b0857f1fd2e'
'8dba2869fcc36b83e6de553c386cf4ff26f19672955d9f312@13.251.101.'
'216:30301,enode://46dba3a8721c589bede3c134d755eb1a38ae7c5a4c6'
'9249b8317c55adc8d46a369f98b06514ecec4b4ff150712085176818d18f5'
'9a9e6311a52dbe68cff5b2ae@13.250.94.232:30301'
),
'NETSTATS_HOST': 'stats.testnet.tomochain.com',
'NETSTATS_PORT': '443',
'NETWORK_ID': '89',
'WS_SECRET': (
'anna-coal-flee-carrie-zip-hhhh-tarry-laue-felon-rhine'
)
},
'metrics': {
'METRICS_ENDPOINT': 'https://metrics.testnet.tomochain.com'
}
},
'devnet': {
'tomochain': {
'BOOTNODES': (
'enode://f3d3d5d6cd0fdde8996722ff5b5a92f331029b2dcbdb9748f50db'
'1421851a939eb660bf81a7ec7f359454aa0fd65fe4c03ae5c6bb2382b34df'
'aaca7eb6ecaf4e@52.77.194.164:30301,enode://34b923ddfcba1bfafd'
'd1ac7a030436f9fbdc565919189f5e62c8cadd798c239b5807a26ab7f6b96'
'a44200eb0399d1ebc2d9c1be94d2a774c8cc7660ff4c10367@13.228.93.2'
'32:30301,enode://e2604862d18049e025f294d63d537f9f54309ff09e45'
'ed69ff4f18c984831f5ef45370053355301e3a4da95aba2698c6116f4d2a3'
'47e5a5e0a3152ac0ae0f574@18.136.42.72:30301'
),
'NETSTATS_HOST': 'stats.devnet.tomochain.com',
'NETSTATS_PORT': '443',
'NETWORK_ID': '90',
'WS_SECRET': (
'torn-fcc-caper-drool-jelly-zip-din-fraud-rater-darn'
)
},
'metrics': {
'METRICS_ENDPOINT': 'https://metrics.devnet.tomochain.com'
}
}
}
PK ! ]! !
tmn/tmn.pyimport logging
import sys
import click
from tmn import display
from tmn import __version__
from tmn.configuration import Configuration
logger = logging.getLogger('tmn')
docker_url = None
@click.group(help=('Tomo MasterNode (tmn) is a cli tool to help you run a Tomo'
'chain masternode'))
@click.option('--debug', is_flag=True, help='Enable debug mode')
@click.option('--docker', metavar='URL', help='Url to the docker server')
@click.version_option(version=__version__)
def main(debug: bool, docker: str) -> None:
"Cli entrypoint"
global docker_url
if debug:
logger.setLevel('DEBUG')
logger.debug('Debugging enabled')
docker_url = docker
@click.command(help='Display TomoChain documentation link')
def docs() -> None:
"Link to the documentation"
display.link_docs()
@click.command(help='Start your TomoChain masternode')
@click.option('--name', metavar='NAME', help='Your masternode\'s name')
@click.option('--net', type=click.Choice(['mainnet', 'testnet', 'devnet']),
help='The environment your masternode will connect to')
@click.option('--pkey', metavar='KEY', help=('Private key of the account your '
'masternode will collect rewards '
'on'))
@click.option('--api', is_flag=True)
def start(name: str, net: str, pkey: str, api: bool) -> None:
"Start the containers needed to run a masternode"
configuration = Configuration(name=name, net=net, pkey=pkey, start=True,
docker_url=docker_url, api=api)
if configuration.force_recreate:
display.error_breaking_change()
sys.exit('\n')
display.title_start_masternode(configuration.name)
# volumes
display.subtitle_create_volumes()
for _, value in configuration.volumes.items():
display.step_create_volume(value.name)
if value.create():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# networks
display.subtitle_create_networks()
for _, value in configuration.networks.items():
display.step_create_network(value.name)
if value.create():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# container
# create
display.subtitle_create_containers()
for _, value in configuration.services.items():
display.step_create_container(value.name)
if value.create():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# start
for _, value in configuration.services.items():
display.step_start_container(value.name)
if value.start():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
@click.command(help='Stop your TomoChain masternode')
def stop() -> None:
"Stop the masternode containers"
configuration = Configuration(docker_url=docker_url)
if configuration.force_recreate:
display.error_breaking_change()
sys.exit('\n')
display.title_stop_masternode(configuration.name)
for _, service in configuration.services.items():
display.step_stop_container(service.name)
if service.stop():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
@click.command(help='Show the status of your TomoChain masternode')
def status() -> None:
"Show the status of the masternode containers"
configuration = Configuration(docker_url=docker_url)
if configuration.force_recreate:
display.error_breaking_change()
sys.exit('\n')
display.title_status_masternode(configuration.name)
for _, service in configuration.services.items():
status = service.status()
if status and status == 'absent':
display.status(
name=service.name
)
if status and status in ['running']:
display.status(
name=service.name,
status=status,
id=service.container.short_id,
status_color='green'
)
elif status:
display.status(
name=service.name,
status=status,
id=service.container.short_id,
)
else:
display.status(
name=service.name,
status='error'
)
display.newline()
@click.command(help='Show details about your TomoChain masternode')
def inspect() -> None:
"Show details about the tomochain masternode"
configuration = Configuration(docker_url=docker_url)
if configuration.force_recreate:
display.error_breaking_change()
sys.exit('\n')
display.title_inspect_masternode(configuration.name)
identity = configuration.services['tomochain'].execute(
'echo $IDENTITY'
) or 'container not running'
display.detail_identity(identity)
display.newline()
coinbase = configuration.services['tomochain'].execute(
'tomo account list --keystore keystore 2> /dev/null | head -n 1 | cut '
'-d"{" -f 2 | cut -d"}" -f 1'
)
if coinbase:
coinbase = '0x{}'.format(coinbase)
else:
coinbase = 'container not running'
display.detail_coinbase(coinbase)
display.newline()
@click.command(help='Update your masternode')
def update() -> None:
"Update the tomochain masternode with the lastest images"
configuration = Configuration(docker_url=docker_url)
if configuration.force_recreate:
display.error_breaking_change()
sys.exit('\n')
display.title_update_masternode(configuration.name)
display.subtitle_remove_containers()
# containers
# stop
for _, service in configuration.services.items():
display.step_stop_container(service.name)
if service.stop():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# remove
for _, service in configuration.services.items():
display.step_remove_container(service.name)
if service.remove():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# create
for _, value in configuration.services.items():
display.step_create_container(value.name)
if value.create():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# start
for _, value in configuration.services.items():
display.step_start_container(value.name)
if value.start():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
@click.command(help='Remove your TomoChain masternode')
@click.option('--confirm', is_flag=True)
def remove(confirm: bool) -> None:
"Remove the masternode (containers, networks volumes)"
configuration = Configuration(docker_url=docker_url)
if not confirm:
display.warning_remove_masternode(configuration.name)
sys.exit('\n')
display.title_remove_masternode(configuration.name)
display.subtitle_remove_containers()
# containers
# stop
for _, service in configuration.services.items():
display.step_stop_container(service.name)
if service.stop():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# remove
for _, service in configuration.services.items():
display.step_remove_container(service.name)
if service.remove():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# networks
display.subtitle_remove_networks()
for _, network in configuration.networks.items():
display.step_remove_network(network.name)
if network.remove():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
# volumes
display.subtitle_remove_volumes()
for _, volume in configuration.volumes.items():
display.step_remove_volume(volume.name)
if volume.remove():
display.step_close_ok()
else:
display.step_close_nok()
display.newline()
configuration.remove()
main.add_command(docs)
main.add_command(start)
main.add_command(stop)
main.add_command(status)
main.add_command(inspect)
main.add_command(update)
main.add_command(remove)
PK !H
# $ $ tmn-0.4.0.dist-info/entry_points.txtN+I/N.,()*ͳb= M PK !HnHT U tmn-0.4.0.dist-info/WHEEL
A
н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK !H% " tmn-0.4.0.dist-info/METADATAXioH_Qk8#R=Yw;k|l0EݜlœXxկ^}!
/H=$=Nw.KaZya=5T
f(i%ư0rnכt29WOӽl߲y2vs&&c{+KQB={RG2ת{tua`/{%DžpN6Fʚ=J-(*KOX*W8t(Kt.Ll3W{V>OkD9ߣxW?g;d;k$g@Fj#0Y#&$7y67Çh}vo={b(&Z]'@I]Qըa7ng?d.£:y]R]Ev?U372NycztX59.bӻƔ<$N}A+GKIq1BU g@exיJa+3Btxjci/]ojU[OaT4Wdn: x=hov7KyxQ.Sk9SmnVDx^b:_hLNT@FKgTam\UY9IMdQ$"[kbc6