#!/usr/bin/env python3 from jinja2 import Environment, FileSystemLoader from ruamel.yaml import YAML from socket import gethostbyname from shlex import split from subprocess import Popen from time import sleep from configparser import ConfigParser import click from py import path from sys import exit from os import path as expander from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException import requests import json import re new_asset_data_url = ("https://raw.githubusercontent.com/patchkez/SuperNET/" "separate_json_data_dev_cherrypick/iguana/coins/tmp_cleanup/assetchains_data.yml") yamlname = 'assetchains_data.yaml' env = Environment(loader=FileSystemLoader('./dokomodo/templates/'), trim_blocks=True, lstrip_blocks=True) config_dir = path.local('/yaml_data') if config_dir.check() is False: config_dir = path.local('dokomodo/yaml') global dump # TEMPORARY CODE # Temporary - download yaml file with iguana addcoin methods and supplies def download_assets_data(): global dump file = requests.get(new_asset_data_url, stream=True) # dump = file.raw dump = file.text def save_assets_data(): global dump newyaml = path.local(config_dir).join(yamlname) try: myfile2 = newyaml.open(mode='w', ensure=True) myfile2.write(dump) except OSError as exception: click.echo('File could not be opened in write mode or be written: {}'.format(exception)) del dump click.echo('Downloading preparsed {} as {}'.format(new_asset_data_url, yamlname)) download_assets_data() save_assets_data() # This is common function for sending rpc requests to bitcoind and komodod def send_request(rpc_host, rpc_port, rpc_user, rpc_password): # assetchain_rpcuser = 'rpcuser' # assetchain_rpcpassword = 'rpcpassword' # request_url = ( # 'http://' + asset_rpcuser + ':' + asset_rpcpassword + '@' + assetchain_name + ':' + # assetchain_rpcport) return AuthServiceProxy("http://{}:{}@{}:{}".format(rpc_user, rpc_password, rpc_host, int(rpc_port))) # try: # # rpc_connection.sendmany("", ctx.sendtomany_recipients) # click.echo(rpc_connection.getinfo()) # except JSONRPCException as e: # click.echo("Error: %s" % e.error['message']) # Common fucntion for sending any http API request e.g. to iguana def post_rpc(url, payload, auth): try: r = requests.post(url, data=json.dumps(payload), auth=auth) return(json.loads(r.text)) except Exception as e: print("Couldn't connect to " + url, e) exit(0) # Common functions for getting data from web servers def get_rpc(url): try: r = requests.get(url) # return(json.loads(r.text)) return(r.text) except Exception as e: print("Couldn't connect to " + url, e) exit(0) class Config(object): def __init__(self, *args, **kwargs): self.config = config_dir.join('data.yaml') self.new_config = config_dir.join('assetchains_data.yaml') self.config_ini = config_dir.join('config.ini') super(Config, self).__init__(*args, **kwargs) def load(self): """Try to load the yaml""" # Configure yaml loader yaml = YAML(typ='safe', pure=True) yaml.default_flow_style = True yaml.width = 8096 # or some other big enough value to prevent line-wrap # Try to read yaml file try: self.config_data = yaml.load(self.config.read()) except OSError as exception: click.echo('{} yaml file could not be read: {}'.format(self.config.read, exception)) self.branches = self.config_data['assetchains'] self.seed_ip = gethostbyname(self.config_data['seed_host']) def load_new_asset(self): """Try to load the yaml""" # Configure yaml loader yaml = YAML(typ='safe', pure=True) yaml.default_flow_style = True # Try to read yaml file try: self.new_config_data = yaml.load(self.new_config.read()) except OSError as exception: click.echo('{} yaml file could not be read: {}'.format(self.config.read, exception)) # self.branches = self.new_config_data['assetchains'] # self.seed_ip = gethostbyname(self.config_data['seed_host']) # self.supply = self self.seed_ip2 = gethostbyname(self.new_config_data['seed_ip']) def load_ini(self): # initialize INI parser ini_parser = ConfigParser() # Try to read ini file try: ini_parser.read(str(self.config_ini)) except OSError as exception: click.echo('{} file could not be read: {}'.format(self.config_ini, exception)) self.btcpubkey = ini_parser['DEFAULT']['btcpubkey'] self.assetchains = ini_parser['ASSETCHAINS'] self.mined_coins = self.assetchains['mined_coins'].split() self.delay_asset = float(self.assetchains['delay_asset']) self.rpc_username = self.assetchains['rpc_username'] self.rpc_password = self.assetchains['rpc_password'] self.rpc_bind = self.assetchains['rpc_bind'] self.rpc_allowip = self.assetchains['rpc_allowip'] self.write_path_conf = self.assetchains['write_path_conf'] self.iguana = ini_parser['IGUANA'] self.production_coins = self.iguana['production_coins'] self.development_coins = self.iguana['development_coins'] self.iguana_url = self.iguana['iguana_url'] self.iguana_home_dir = self.iguana['iguana_home_dir'] self.scaling_tests = ini_parser['SCALING_TESTING'] self.sendtomany_recipients = self.scaling_tests['sendtomany_recipients'] self.number_of_requests = self.scaling_tests['number_of_requests'] self.delay_between_requests = self.scaling_tests['delay_between_requests'] def write_config(self, dirname, filename, templatized_config): # If directory is not set, set it to current directory if dirname is False: dirname = './' # Expand ~ to full path dirname_expanded = expander.expanduser(dirname) # Construct full path - directory + filename temppath = path.local(dirname_expanded).join(filename) # Try to open config in write mode try: myfile = temppath.open(mode='w', ensure=True) myfile.write(templatized_config) except OSError as exception: click.echo('File could not be opened in write mode or be written: {}'.format(exception)) # This is click thing, it will create decorator named pass_config from our Config class # This decorator is then passed to every function which needs to access attributes from Config class pass_config = click.make_pass_decorator(Config, ensure=True) @click.group() @pass_config def cli(config): config.load() config.load_new_asset() config.load_ini() @click.command('generate_docker_compose', short_help='OLD - Generates docker-compose file with all assetchains') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production', 'test']), prompt=True) @pass_config def generate_docker_compose(ctx, branch): """ TODO """ filename = 'docker-compose-assets-' + branch + '.yml' dirname = "./" click.echo('Writing new docker compose file into: {}'.format(filename)) template = env.get_template('docker-compose-template.conf.j2') templatized_config = template.render(items=ctx.config_data['assetchains'][branch], seed_ip=ctx.seed_ip, mined=ctx.mined_coins, btcpubkey=ctx.btcpubkey) ctx.write_config(dirname, filename=filename, templatized_config=templatized_config) @click.command('generate_new_docker_compose', short_help='PROD - Generates docker-compose file with all assetchains') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production', 'test']), prompt=True) @click.option('-a', '--asset', required=False, help='name of assetchain in capital \ letters e.g. SUPERNET') @click.option('-i', '--image', required=True, help='name of image used for assetchains, \ it must match image name you use for komodod e.g. kmdplatform_komodod_dev or \ kmdplatform_komodod') @pass_config def generate_new_docker_compose(ctx, branch, asset, image): """ TODO """ filename = 'docker-compose-assets-' + branch + '.yml' dirname = "./" click.echo('Writing new docker compose file into: {}'.format(filename)) yaml = YAML(typ='safe', pure=True) yaml.default_flow_style = True dic = {} def filtered_yaml(): coins = branch + '_coins_assets' for assetchain_key in ctx.assetchains[coins].split(', '): x = ctx.new_config_data['assetchains'][assetchain_key] dic[assetchain_key] = x if asset and asset == assetchain_key: pass elif asset: pass else: pass return dic template = env.get_template('docker-compose-new-template.conf.j2') templatized_config = template.render(items=filtered_yaml(), seed_ip=ctx.seed_ip2, mined=ctx.mined_coins, btcpubkey=ctx.btcpubkey, image_name=image) ctx.write_config(dirname, filename=filename, templatized_config=templatized_config) @click.command('assetchains', short_help='Replacement for assetchains script') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production', 'test']), prompt=True) @pass_config def assetchains(ctx, branch): bash_template = env.get_template('assetchains.j2') bash_templatized_config = bash_template.render(items=ctx.config_data['assetchains'][branch], seed_ip=ctx.seed_ip, mined=ctx.mined_coins, btcpubkey=ctx.btcpubkey) # Remove empty strings assetchains = list(filter(None, bash_templatized_config.split("\n"))) # Executed komodod commands with predefined sleep time for assetchain_command in assetchains: args = split(assetchain_command) try: Popen(args) except OSError as exception: click.echo(exception) exit(1) sleep(ctx.delay_asset) @click.command('generate_assetchains_conf', short_help='Generates configuration file for \ assetchains') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production', 'test'])) @click.option('-a', '--asset', required=False) @pass_config def generate_assetchains_conf(ctx, branch, asset): asset_template = env.get_template('assetchains_config.j2') def templatize(assetchain_name): dirname = ctx.write_path_conf + '/' + assetchain_name filename = assetchain_name + '.conf' asset_templatized_config = asset_template.render( rpcuser=ctx.rpc_username, rpcpassword=ctx.rpc_password, # rpcbind=ctx.rpc_bind, rpcbind=assetchain_name, rpcallowip=ctx.rpc_allowip, # rpcport=ctx.config_data['assetchains'][branch][assetchain_name]['rpc_port'], rpcport=ctx.new_config_data['assetchains'][assetchain_name]['iguana_payload'][ 'rpc'], btcpubkey=ctx.btcpubkey ) ctx.write_config(dirname, filename, asset_templatized_config) # return click.echo(asset_templatized_config) return asset_templatized_config coins = branch + '_coins_assets' for assetchain_key in ctx.assetchains[coins].split(', '): # for assetchain_name in ctx.config_data['assetchains'][branch]: if asset and asset == assetchain_key: click.echo('Writing CONFIG_FILE: {}'.format(assetchain_key)) templatize(assetchain_key) elif asset: pass else: click.echo('Writing CONFIG_FILE: {}'.format(assetchain_key)) templatize(assetchain_key) @click.command('importprivkey', short_help='Importprivkey into assetchains') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production', 'test'])) @click.option('-a', '--asset', required=False) @click.option('-h', '--rpchost', prompt=True, hide_input=False, confirmation_prompt=False, help='RPC host') @click.option('-u', '--rpcuser', prompt=True, hide_input=False, confirmation_prompt=False, help='RPC username') @click.option('-r', '--rpcpassword', prompt=True, hide_input=False, confirmation_prompt=False, help='RPC password') @click.option('-k', '--btcdprivkey', prompt=True, hide_input=True, confirmation_prompt=True, help='BTCD privkey') @pass_config def importprivkey(ctx, branch, asset, rpchost, rpcuser, rpcpassword, btcdprivkey): coins = branch + '_coins_assets' for assetchain_key in ctx.assetchains[coins].split(', '): # method = 'importprivkey' + '(privkey=' + btcdprivkey + ' , rescan=False)' rpcport = int(ctx.new_config_data['assetchains'][assetchain_key]['iguana_payload']['rpc']) click.echo(ctx.new_config_data['assetchains'][assetchain_key]['iguana_payload']['rpc']) rpc = send_request(rpc_host=rpchost, rpc_port=rpcport, rpc_user=rpcuser, rpc_password=rpcpassword) if asset and asset == assetchain_key: click.echo('Sending request to: {}'.format(assetchain_key)) # send_request(assetchain_name, rpc_port, method) click.echo(rpc.importprivkey(btcdprivkey, '', False)) elif asset: pass else: click.echo('Sending request to: {}'.format(assetchain_key)) click.echo(rpc.importprivkey(btcdprivkey, '', False)) # send_request(assetchain_name, rpc_port, method) # click.echo(rpc.getinfo()) sleep(1) @click.command('start_iguana', short_help='Add all methods into iguana') @click.option('-b', '--branch', required=True, type=click.Choice(['development', 'production'])) @click.option('-a', '--asset', required=False, help='name of assetchain in capital \ letters e.g. SUPERNET') @click.option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=True, help='iguana passphrase') @click.option('-m', '--myip', required=False, prompt=True, hide_input=False, confirmation_prompt=False, help='provide public IP of your NN if run via SSH session') @pass_config def start_iguana(ctx, branch, asset, password, myip): url = ctx.iguana_url # No authentication is needed for iguana auth = ('', '') # My IP if myip: response = myip else: # get public IP address of this host myip = 'http://' + ctx.new_config_data['check_my_ip'] response = get_rpc(myip).rstrip() click.echo('My IP address is: {}'.format(response)) myipmethod = ctx.new_config_data['misc_methods']['supernet_myip'] myipmethod['ipaddr'] = response # click.echo('MY IP method: {}'.format(myipmethod)) post_rpc(url, myipmethod, auth) sleep(3) # Add notaries for notary in ctx.new_config_data['misc_methods']['notaries']: click.echo('Adding notary: {}'.format(notary)) post_rpc(url, ctx.new_config_data['misc_methods']['notaries'][notary], auth) # Walletpassphrase click.echo('Adding walletpassphrase!') wallet = ctx.new_config_data['misc_methods']['wallet_passphrase'] # Replace password with the one provided by user wallet['params'][0] = password # click.echo(wallet) post_rpc(url, wallet, auth) # Add coins + DPOW coins = branch + '_coins' for assetchain_key in ctx.iguana[coins].split(', '): # Read only assetchains payloads payload = ctx.new_config_data['assetchains'][assetchain_key]['iguana_payload'] # Replace ${HOME#/} with value in our INI file # remove first '/' home = re.sub(r"\/", "", ctx.iguana_home_dir, 1) # Read value of 'path' key line = payload['path'] # Substitute newline = re.sub(r"\$\{HOME\#\/\}", home, line) # Update value in loaded dictionary ctx.new_config_data['assetchains'][assetchain_key]['iguana_payload']['path'] = newline # Dpow dpow = ctx.new_config_data['misc_methods']['dpow'] dpow['pubkey'] = ctx.btcpubkey dpow['symbol'] = assetchain_key # click.echo(wallet) if asset and asset == assetchain_key: click.echo('Sending addcoin request to: {}'.format(assetchain_key)) post_rpc(url, payload, auth) # sleep(3) click.echo('Sending dpow {} request to: {}'.format(dpow, assetchain_key)) post_rpc(url, dpow, auth) elif asset: pass else: click.echo('Sending addcoin request to: {}'.format(assetchain_key)) post_rpc(url, payload, auth) # sleep(10) click.echo('Sending dpow {} request to: {}'.format(dpow, assetchain_key)) post_rpc(url, dpow, auth) # sleep(10) # Add functions into cli() function which is main group for all commands cli.add_command(generate_docker_compose) cli.add_command(generate_new_docker_compose) cli.add_command(assetchains) cli.add_command(generate_assetchains_conf) cli.add_command(importprivkey) cli.add_command(start_iguana) if __name__ == "__main__": cli()