Merge pull request #186 from bigchaindb/feat/179/cli-export-import-public-keys

Added CLI command to export public key
This commit is contained in:
Troy McConaghy 2016-04-20 15:15:03 +02:00
commit dadde325b8
11 changed files with 181 additions and 68 deletions

View File

@ -1,5 +1,7 @@
"""Command line interface for the `bigchain` command."""
"""Implementation of the `bigchaindb` command,
which is one of the commands in the BigchainDB
command-line interface.
"""
import os
import sys
@ -38,7 +40,7 @@ def run_configure(args, skip_if_exists=False):
"""Run a script to configure the current node.
Args:
skip_if_exists (bool): skip the function if a conf file already exists
skip_if_exists (bool): skip the function if a config file already exists
"""
config_path = args.config or bigchaindb.config_utils.CONFIG_DEFAULT_PATH
config_file_exists = os.path.exists(config_path)
@ -48,7 +50,7 @@ def run_configure(args, skip_if_exists=False):
if config_file_exists and not args.yes:
want = input('Config file `{}` exists, do you want to override it? '
'(cannot be undone) [y/n]: '.format(config_path))
'(cannot be undone) [y/N]: '.format(config_path))
if want != 'y':
return
@ -56,25 +58,49 @@ def run_configure(args, skip_if_exists=False):
conf = copy.deepcopy(bigchaindb._config)
print('Generating keypair')
conf['keypair']['private'], conf['keypair']['public'] = crypto.generate_key_pair()
conf['keypair']['private'], conf['keypair']['public'] = \
crypto.generate_key_pair()
if not args.yes:
for key in ('bind', ):
val = conf['server'][key]
conf['server'][key] = input('API Server {}? (default `{}`): '.format(key, val)) or val
conf['server'][key] = \
input('API Server {}? (default `{}`): '.format(key, val)) \
or val
for key in ('host', 'port', 'name'):
val = conf['database'][key]
conf['database'][key] = input('Database {}? (default `{}`): '.format(key, val)) or val
conf['database'][key] = \
input('Database {}? (default `{}`): '.format(key, val)) \
or val
for key in ('host', 'port', 'rate'):
val = conf['statsd'][key]
conf['statsd'][key] = input('Statsd {}? (default `{}`): '.format(key, val)) or val
conf['statsd'][key] = \
input('Statsd {}? (default `{}`): '.format(key, val)) \
or val
bigchaindb.config_utils.write_config(conf, config_path)
print('Configuration written to {}'.format(config_path))
print('Ready to go!')
def run_export_my_pubkey(args):
"""Export this node's public key to standard output
"""
logger.debug('bigchaindb args = {}'.format(args))
bigchaindb.config_utils.autoconfigure(filename=args.config, force=True)
pubkey = bigchaindb.config['keypair']['public']
if pubkey is not None:
print(pubkey)
else:
sys.exit("This node's public key wasn't set anywhere "
"so it can't be exported")
# raises SystemExit exception
# message is sent to stderr
# exits with exit code 1 (signals tha an error happened)
def run_init(args):
"""Initialize the database"""
bigchaindb.config_utils.autoconfigure(filename=args.config, force=True)
@ -103,16 +129,18 @@ def run_start(args):
except DatabaseAlreadyExists:
pass
except KeypairNotFoundException:
sys.exit('Cannot start BigchainDB, no keypair found. Did you run `bigchaindb configure`?')
sys.exit("Can't start BigchainDB, no keypair found. "
'Did you run `bigchaindb configure`?')
processes = Processes()
logger.info('Start bigchaindb main process')
logger.info('Starting BigchainDB main process')
processes.start()
def main():
parser = argparse.ArgumentParser(description='Control your bigchain node.',
parents=[base_parser])
parser = argparse.ArgumentParser(
description='Control your BigchainDB node.',
parents=[base_parser])
# all the commands are contained in the subparsers object,
# the command selected by the user will be stored in `args.command`
@ -121,22 +149,28 @@ def main():
subparsers = parser.add_subparsers(title='Commands',
dest='command')
# parser for writing a config file
subparsers.add_parser('configure',
help='Prepare the config file and create the node keypair')
help='Prepare the config file '
'and create the node keypair')
# parser for database level commands
# parsers for showing/exporting config values
subparsers.add_parser('show-config',
help='Show the current configuration')
subparsers.add_parser('export-my-pubkey',
help="Export this node's public key")
# parser for database-level commands
subparsers.add_parser('init',
help='Init the database')
subparsers.add_parser('drop',
help='Drop the database')
# TODO how about just config, or info?
subparsers.add_parser('show-config',
help='Show the current configuration')
# parser for starting BigchainDB
subparsers.add_parser('start',
help='Start bigchain')
help='Start BigchainDB')
start(parser, globals())

View File

@ -1,4 +1,5 @@
'''Command line interface for the `bigchain-benchmark` command.'''
"""Command line interface for the `bigchaindb-benchmark` command."""
import logging
import argparse

View File

@ -1,4 +1,6 @@
"""Utility functions and basic common arguments for ``argparse.ArgumentParser``."""
"""Utility functions and basic common arguments
for ``argparse.ArgumentParser``.
"""
import argparse
import multiprocessing as mp
@ -7,7 +9,8 @@ import multiprocessing as mp
def start(parser, scope):
"""Utility function to execute a subcommand.
The function will look up in the ``scope`` if there is a function called ``run_<parser.args.command>``
The function will look up in the ``scope``
if there is a function called ``run_<parser.args.command>``
and will run it using ``parser.args`` as first positional argument.
Args:
@ -15,7 +18,8 @@ def start(parser, scope):
scope (dict): map containing (eventually) the functions to be called.
Raises:
NotImplementedError: if ``scope`` doesn't contain a function called ``run_<parser.args.command>``.
NotImplementedError: if ``scope`` doesn't contain a function called
``run_<parser.args.command>``.
"""
args = parser.parse_args()
@ -29,7 +33,8 @@ def start(parser, scope):
# if no command has been found, raise a `NotImplementedError`
if not func:
raise NotImplementedError('Command `{}` not yet implemented'.format(args.command))
raise NotImplementedError('Command `{}` not yet implemented'.
format(args.command))
args.multiprocess = getattr(args, 'multiprocess', False)

View File

@ -1,8 +1,8 @@
"""Utils to configure Bigchain.
"""Utils to configure BigchainDB.
By calling `file_config`, the global configuration (stored in
`bigchain.config`) will be updated with the values contained in the
configuration file.
`$HOME/.bigchaindb`) will be updated with the values contained
in the configuration file.
Note that there is a precedence in reading configuration values:
- local config file;
@ -54,7 +54,20 @@ def map_leafs(func, mapping):
# Thanks Alex <3
# http://stackoverflow.com/a/3233356/597097
def update(d, u):
"""Recursively update a mapping."""
"""Recursively update a mapping (i.e. a dict, list, set, or tuple).
Conceptually, d and u are two sets trees (with nodes and edges).
This function goes through all the nodes of u. For each node in u,
if d doesn't have that node yet, then this function adds the node from u,
otherwise this function overwrites the node already in d with u's node.
Args:
d (mapping): The mapping to overwrite and add to.
u (mapping): The mapping to read for changes.
Returns:
mapping: An updated version of d (updated by u).
"""
for k, v in u.items():
if isinstance(v, collections.Mapping):
r = update(d.get(k, {}), v)
@ -65,19 +78,21 @@ def update(d, u):
def file_config(filename=None):
"""Returns the values found in a configuration file.
"""Returns the config values found in a configuration file.
Args:
filename (str): the JSON file with the configuration. Defaults to ``None``.
If ``None``, the HOME of the current user and the string ``.bigchaindb`` will be used.
filename (str): the JSON file with the configuration values.
If ``None``, CONFIG_DEFAULT_PATH will be used.
Note:
The function merges the values in ``filename`` with the **default configuration**,
so any update made to ``bigchaindb.config`` will be lost.
Returns:
dict: The config values in the specified config file (or the
file at CONFIG_DEFAULT_PATH, if filename == None)
"""
logger.debug('On entry into file_config(), filename = {}'.format(filename))
if not filename:
filename = CONFIG_DEFAULT_PATH
logger.debug('file_config() will try to open `{}`'.format(filename))
with open(filename) as f:
config = json.load(f)
@ -145,17 +160,21 @@ def update_types(config, reference, list_sep=':'):
return map_leafs(_update_type, config)
def dict_config(config):
"""Merge the provided configuration with the default one.
def set_config(config):
"""Set bigchaindb.config equal to the default config dict,
then update that with whatever is in the provided config dict,
and then set bigchaindb.config['CONFIGURED'] = True
Args:
newconfig (dict): a dictionary with the configuration to load.
config (dict): the config dict to read for changes
to the default config
Note:
The function merges ``newconfig`` with the **default configuration**, so any
update made to ``bigchaindb.config`` will be lost.
Any previous changes made to ``bigchaindb.config`` will be lost.
"""
# Deep copy the default config into bigchaindb.config
bigchaindb.config = copy.deepcopy(bigchaindb._config)
# Update the default config with whatever is in the passed config
update(bigchaindb.config, update_types(config, bigchaindb.config))
bigchaindb.config['CONFIGURED'] = True
@ -193,8 +212,7 @@ def autoconfigure(filename=None, config=None, force=False):
if config:
newconfig = update(newconfig, config)
dict_config(newconfig)
return newconfig
set_config(newconfig) # sets bigchaindb.config
def load_consensus_plugin(name=None):

View File

@ -1,27 +1,46 @@
# The BigchainDB Command Line Interfaces (CLIs)
# The BigchainDB Command Line Interface (CLI)
BigchainDB has some Command Line Interfaces (CLIs). One of them is the `bigchaindb` command which we already saw when we first started BigchainDB using:
```text
$ bigchaindb configure
$ bigchaindb start
```
There are some command-line commands for working with BigchainDB: `bigchaindb` and `bigchaindb-benchmark`. This section provides an overview of those commands.
When you run `bigchaindb configure`, it creates a default configuration file in `$HOME/.bigchaindb`. You can check that configuration using:
```text
$ bigchaindb show-config
```
## bigchaindb
To find out what else you can do with the `bigchain` command, use:
```text
$ bigchaindb -h
```
### bigchaindb --help
There's another command named `bigchaindb-benchmark`. It's used to run benchmarking tests. You can learn more about it using:
One can get basic help with the `bigchaindb` command using `bigchaindb --help` or `bigchaindb -h`.
### bigchaindb configure
This command generates a public/private keypair for the node, and writes a BigchainDB configuration file to the node's file system. It's documented in the section [Configuring a BigchainDB Node](configuration.html).
If you want to force-generate a new configuration file regardless of whether one already exists (i.e. skipping the yes/no prompt), then use `bigchaindb -y configure`.
### bigchaindb show-config
This command shows the values of the configuration settings, which can come from a variety of sources. See [the section on configuring BigchainDB](configuration.html) for more details and examples.
### bigchaindb export-my-pubkey
This command writes the node's public key (i.e. one of its configuration values) to standard output (stdout).
### bigchaindb init
This command creates a RethinkDB database, two RethinkDB database tables (backlog and bigchain), various RethinkDB database indexes, and the genesis block.
Note: The `bigchaindb start` command (see below) always starts by trying a `bigchaindb init` first. If it sees that the RethinkDB database already exists, then it doesn't re-initialize the database. One doesn't have to do `bigchaindb init` before `bigchaindb start`. `bigchaindb init` is useful if you only want to initialize (but not start).
### bigchaindb drop
This command drops (erases) the RethinkDB database. You will be prompted to make sure. If you want to force-drop the database (i.e. skipping the yes/no prompt), then use `bigchaindb -y drop`
### bigchaindb start
This command starts BigchainDB. It always begins by trying a `bigchaindb init` first. See the note in the documentation for `bigchaindb init`.
## bigchaindb-benchmark
The `bigchaindb-benchmark` command is used to run benchmarking tests. You can learn more about it using:
```text
$ bigchaindb-benchmark -h
$ bigchaindb-benchmark load -h
```
Note that you can always start `bigchaindb` using a different config file using the `-c` option.
For more information check the help with `bigchaindb -h`.

View File

@ -95,16 +95,17 @@ $ rethinkdb
Then open a different terminal and run:
```text
$ bigchaindb -y configure
$ bigchaindb init
```
That creates a configuration file in `$HOME/.bigchaindb` (documented in [the section on configuration](configuration.html)), initializes the database, creates the tables, creates the indexes, and generates the genesis block.
That creates a configuration file in `$HOME/.bigchaindb` (documented in [the section on configuration](configuration.html)). More documentation about the `bigchaindb` command is in the section on [the BigchainDB Command Line Interface (CLI)](bigchaindb-cli.html).
You can start BigchainDB Server using:
```text
$ bigchaindb start
```
If it's the first time you've run `bigchaindb start`, then it creates the database (a RethinkDB database), the tables, the indexes, and the genesis block. It then starts BigchainDB. If you're run `bigchaindb start` or `bigchaindb init` before (and you haven't dropped the database), then `bigchaindb start` just starts BigchainDB.
## Run BigchainDB with Docker

View File

@ -45,7 +45,7 @@ def ignore_local_config_file(monkeypatch):
@pytest.fixture(scope='function', autouse=True)
def restore_config(request, node_config):
from bigchaindb import config_utils
config_utils.dict_config(node_config)
config_utils.set_config(node_config)
@pytest.fixture(scope='module')

View File

@ -17,7 +17,7 @@ from bigchaindb.db import get_conn
@pytest.fixture(autouse=True)
def restore_config(request, node_config):
from bigchaindb import config_utils
config_utils.dict_config(node_config)
config_utils.set_config(node_config)
@pytest.fixture(scope='module', autouse=True)

View File

@ -82,7 +82,7 @@ def test_bigchain_run_start_assume_yes_create_default_config(monkeypatch, mock_p
value['return'] = newconfig
monkeypatch.setattr(config_utils, 'write_config', mock_write_config)
monkeypatch.setattr(config_utils, 'file_config', lambda x: config_utils.dict_config(expected_config))
monkeypatch.setattr(config_utils, 'file_config', lambda x: config_utils.set_config(expected_config))
monkeypatch.setattr('os.path.exists', lambda path: False)
args = Namespace(config=None, yes=True)
@ -108,6 +108,42 @@ def test_bigchain_show_config(capsys):
assert output_config == config
def test_bigchain_export_my_pubkey_when_pubkey_set(capsys, monkeypatch):
from bigchaindb import config
from bigchaindb.commands.bigchain import run_export_my_pubkey
args = Namespace(config='dummy')
# so in run_export_my_pubkey(args) below,
# filename=args.config='dummy' is passed to autoconfigure().
# We just assume autoconfigure() works and sets
# config['keypair']['public'] correctly (tested elsewhere).
# We force-set config['keypair']['public'] using monkeypatch.
monkeypatch.setitem(config['keypair'], 'public', 'Charlie_Bucket')
_, _ = capsys.readouterr() # has the effect of clearing capsys
run_export_my_pubkey(args)
out, err = capsys.readouterr()
assert out == config['keypair']['public'] + '\n'
assert out == 'Charlie_Bucket\n'
def test_bigchain_export_my_pubkey_when_pubkey_not_set(monkeypatch):
from bigchaindb import config
from bigchaindb.commands.bigchain import run_export_my_pubkey
args = Namespace(config='dummy')
monkeypatch.setitem(config['keypair'], 'public', None)
# assert that run_export_my_pubkey(args) raises SystemExit:
with pytest.raises(SystemExit) as exc_info:
run_export_my_pubkey(args)
# exc_info is an object of class ExceptionInfo
# https://pytest.org/latest/builtin.html#_pytest._code.ExceptionInfo
assert exc_info.type == SystemExit
# exc_info.value is an object of class SystemExit
# https://docs.python.org/3/library/exceptions.html#SystemExit
assert exc_info.value.code == \
"This node's public key wasn't set anywhere so it can't be exported"
def test_bigchain_run_init_when_db_exists(mock_db_init_with_existing_db):
from bigchaindb.commands.bigchain import run_init
args = Namespace(config=None)
@ -159,4 +195,3 @@ def test_run_configure_when_config_does_exist(monkeypatch,
args = Namespace(config='foo', yes=None)
run_configure(args)
assert value == {}

View File

@ -18,7 +18,7 @@ def test_bigchain_instance_is_initialized_when_conf_provided():
from bigchaindb import config_utils
assert 'CONFIGURED' not in bigchaindb.config
config_utils.dict_config({'keypair': {'public': 'a', 'private': 'b'}})
config_utils.set_config({'keypair': {'public': 'a', 'private': 'b'}})
assert bigchaindb.config['CONFIGURED'] is True
b = bigchaindb.Bigchain()

View File

@ -5,7 +5,7 @@ from ..db import conftest
@pytest.fixture(autouse=True)
def restore_config(request, node_config):
from bigchaindb import config_utils
config_utils.dict_config(node_config)
config_utils.set_config(node_config)
@pytest.fixture(scope='module', autouse=True)