Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aca-py startup arg updates #739

Merged
merged 12 commits into from
Oct 7, 2020
17 changes: 15 additions & 2 deletions aries_cloudagent/commands/tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from asynctest import mock as async_mock
import pytest

from ...config.error import ArgsParseError
from .. import provision as command


class TestProvision(AsyncTestCase):
def test_bad_calls(self):
with self.assertRaises(command.ProvisionError):
with self.assertRaises(ArgsParseError):
command.execute([])

with self.assertRaises(SystemExit):
Expand All @@ -16,7 +17,19 @@ def test_bad_calls(self):
@pytest.mark.indy
def test_provision_wallet(self):
test_seed = "testseed000000000000000000000001"
command.execute(["--wallet-type", "indy", "--seed", test_seed])
command.execute(
[
"--wallet-type",
"indy",
"--wallet-name",
"test_wallet",
"--wallet-key",
"key",
"--seed",
test_seed,
"--no-ledger",
]
)

async def test_provision_ledger_configured(self):
with async_mock.patch.object(
Expand Down
24 changes: 17 additions & 7 deletions aries_cloudagent/commands/tests/test_start.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from asynctest import TestCase as AsyncTestCase
from asynctest import mock as async_mock

from ...config.error import ArgsParseError
from .. import start as command


class TestStart(AsyncTestCase):
def test_bad_args(self):
with async_mock.patch.object(
command.ArgumentParser, "print_usage"
) as print_usage:
with self.assertRaises(SystemExit):
command.execute([])
print_usage.assert_called_once()
with self.assertRaises(ArgsParseError):
command.execute([])

with self.assertRaises(SystemExit):
command.execute(["bad"])
Expand All @@ -24,7 +21,20 @@ def test_exec_start(self):
) as run_loop, async_mock.patch.object(
command, "shutdown_app", autospec=True
) as shutdown_app:
command.execute(["-it", "http", "0.0.0.0", "80", "-ot", "http"])
command.execute(
[
"-it",
"http",
"0.0.0.0",
"80",
"-ot",
"http",
"--endpoint",
"0.0.0.0",
"80",
"--no-ledger",
]
)
start_app.assert_called_once()
assert isinstance(start_app.call_args[0][0], command.Conductor)
shutdown_app.assert_called_once()
Expand Down
158 changes: 111 additions & 47 deletions aries_cloudagent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from os import environ

from argparse import ArgumentParser, Namespace
from argparse import Action as ArgparseAction
from typing import Type

from .error import ArgsParseError
Expand Down Expand Up @@ -69,8 +70,12 @@ def load_argument_groups(parser: ArgumentParser, *groups: Type[ArgumentGroup]):

def get_settings(args: Namespace):
settings = {}
for group in group_inst:
settings.update(group.get_settings(args))
try:
for group in group_inst:
settings.update(group.get_settings(args))
except ArgsParseError as e:
parser.print_help()
raise e
return settings

return get_settings
Expand Down Expand Up @@ -352,6 +357,16 @@ def get_settings(self, args: Namespace) -> dict:
return settings


class LoadFromFile(ArgparseAction):
"""Utility to load args from a file."""

def __call__(self, parser, namespace, values, option_string=None):
"""Load arg values from the specified file."""
with values as f:
fargs = f.read().split()
parser.parse_args(fargs, namespace)


@group(CAT_PROVISION, CAT_START)
class GeneralGroup(ArgumentGroup):
"""General settings."""
Expand All @@ -360,6 +375,12 @@ class GeneralGroup(ArgumentGroup):

def add_arguments(self, parser: ArgumentParser):
"""Add general command line arguments to the parser."""
parser.add_argument(
"--arg-file",
type=open,
action=LoadFromFile,
help="Load aca-py arguments from the specified file.",
)
parser.add_argument(
"--plugin",
dest="external_plugins",
Expand All @@ -374,31 +395,11 @@ def add_arguments(self, parser: ArgumentParser):
"--storage-type",
type=str,
metavar="<storage-type>",
default="basic",
help="Specifies the type of storage provider to use for the internal\
storage engine. This storage interface is used to store internal state.\
Supported internal storage types are 'basic' (memory) and 'indy'.",
)
parser.add_argument(
"-e",
"--endpoint",
type=str,
nargs="+",
metavar="<endpoint>",
help="Specifies the endpoints to put into DIDDocs\
to inform other agents of where they should send messages destined\
for this agent. Each endpoint could be one of the specified inbound\
transports for this agent, or the endpoint could be that of\
another agent (e.g. 'https://example.com/agent-endpoint') if the\
routing of messages to this agent by a mediator is configured.\
The first endpoint specified will be used in invitations.\
The endpoints are used in the formation of a connection\
with another agent.",
)
parser.add_argument(
"--profile-endpoint",
type=str,
metavar="<profile_endpoint>",
help="Specifies the profile endpoint for the (public) DID.",
Supported internal storage types are 'basic' (memory)\
and 'indy'. The default (if not specified) is 'basic'.",
)
parser.add_argument(
"--read-only-ledger",
Expand All @@ -420,11 +421,6 @@ def get_settings(self, args: Namespace) -> dict:
settings["external_plugins"] = args.external_plugins
if args.storage_type:
settings["storage_type"] = args.storage_type
if args.endpoint:
settings["default_endpoint"] = args.endpoint[0]
settings["additional_endpoints"] = args.endpoint[1:]
if args.profile_endpoint:
settings["profile_endpoint"] = args.profile_endpoint
if args.read_only_ledger:
settings["read_only_ledger"] = True
if args.tails_server_base_url:
Expand Down Expand Up @@ -473,18 +469,32 @@ def add_arguments(self, parser: ArgumentParser):
the URL might be 'http://localhost:9000/genesis'.\
Genesis transactions URLs are available for the Sovrin test/main networks.",
)
parser.add_argument(
"--no-ledger",
action="store_true",
help="Specifies that aca-py will run with no ledger configured.\
This must be set if running in no-ledger mode. Overrides any\
specified ledger or genesis configurations. Default: false.",
)

def get_settings(self, args: Namespace) -> dict:
"""Extract ledger settings."""
settings = {}
if args.genesis_url:
settings["ledger.genesis_url"] = args.genesis_url
elif args.genesis_file:
settings["ledger.genesis_file"] = args.genesis_file
elif args.genesis_transactions:
settings["ledger.genesis_transactions"] = args.genesis_transactions
if args.ledger_pool_name:
settings["ledger.pool_name"] = args.ledger_pool_name
if not args.no_ledger:
if args.genesis_url:
settings["ledger.genesis_url"] = args.genesis_url
elif args.genesis_file:
settings["ledger.genesis_file"] = args.genesis_file
elif args.genesis_transactions:
settings["ledger.genesis_transactions"] = args.genesis_transactions
else:
raise ArgsParseError(
"One of --genesis-url --genesis-file or --genesis-transactions "
+ "must be specified (unless --no-ledger is specified to "
+ "explicitely configure aca-py to run with no ledger)."
)
if args.ledger_pool_name:
settings["ledger.pool_name"] = args.ledger_pool_name
return settings


Expand Down Expand Up @@ -579,9 +589,7 @@ def add_arguments(self, parser: ArgumentParser):
help="Write timing information to a given log file.",
)
parser.add_argument(
"--trace",
action="store_true",
help="Generate tracing events.",
"--trace", action="store_true", help="Generate tracing events.",
)
parser.add_argument(
"--trace-target",
Expand Down Expand Up @@ -683,14 +691,35 @@ class TransportGroup(ArgumentGroup):

def add_arguments(self, parser: ArgumentParser):
"""Add transport-specific command line arguments to the parser."""
parser.add_argument(
"-e",
"--endpoint",
type=str,
nargs="+",
metavar="<endpoint>",
help="Specifies the endpoints to put into DIDDocs\
to inform other agents of where they should send messages destined\
for this agent. Each endpoint could be one of the specified inbound\
transports for this agent, or the endpoint could be that of\
another agent (e.g. 'https://example.com/agent-endpoint') if the\
routing of messages to this agent by a mediator is configured.\
The first endpoint specified will be used in invitations.\
The endpoints are used in the formation of a connection\
with another agent.",
)
parser.add_argument(
"--profile-endpoint",
type=str,
metavar="<profile_endpoint>",
help="Specifies the profile endpoint for the (public) DID.",
)
parser.add_argument(
"-it",
"--inbound-transport",
dest="inbound_transports",
type=str,
action="append",
nargs=3,
required=True,
metavar=("<module>", "<host>", "<port>"),
help="REQUIRED. Defines the inbound transport(s) on which the agent\
listens for receiving messages from other agents. This parameter can\
Expand All @@ -705,7 +734,6 @@ def add_arguments(self, parser: ArgumentParser):
dest="outbound_transports",
type=str,
action="append",
required=True,
metavar="<module>",
help="REQUIRED. Defines the outbound transport(s) on which the agent\
will send outgoing messages to other agents. This parameter can be passed\
Expand Down Expand Up @@ -746,10 +774,24 @@ def add_arguments(self, parser: ArgumentParser):
def get_settings(self, args: Namespace):
"""Extract transport settings."""
settings = {}
settings["transport.inbound_configs"] = args.inbound_transports
settings["transport.outbound_configs"] = args.outbound_transports
if args.inbound_transports:
settings["transport.inbound_configs"] = args.inbound_transports
else:
raise ArgsParseError("-it/--inbound-transport is required")
if args.outbound_transports:
settings["transport.outbound_configs"] = args.outbound_transports
else:
raise ArgsParseError("-ot/--outbound-transport is required")
settings["transport.enable_undelivered_queue"] = args.enable_undelivered_queue

if args.endpoint:
settings["default_endpoint"] = args.endpoint[0]
settings["additional_endpoints"] = args.endpoint[1:]
else:
raise ArgsParseError("-e/--endpoint is required")
if args.profile_endpoint:
settings["profile_endpoint"] = args.profile_endpoint

if args.label:
settings["default_label"] = args.label
if args.max_message_size:
Expand Down Expand Up @@ -808,16 +850,20 @@ def add_arguments(self, parser: ArgumentParser):
"--wallet-type",
type=str,
metavar="<wallet-type>",
default="basic",
help="Specifies the type of Indy wallet provider to use.\
Supported internal storage types are 'basic' (memory) and 'indy'.",
Supported internal storage types are 'basic' (memory) and 'indy'.\
The default (if not specified) is 'basic'.",
)
parser.add_argument(
"--wallet-storage-type",
type=str,
metavar="<storage-type>",
default="default",
help="Specifies the type of Indy wallet backend to use.\
Supported internal storage types are 'basic' (memory),\
'default' (sqlite), and 'postgres_storage'.",
'default' (sqlite), and 'postgres_storage'. The default,\
if not specified, is 'default'.",
)
parser.add_argument(
"--wallet-storage-config",
Expand Down Expand Up @@ -874,4 +920,22 @@ def get_settings(self, args: Namespace) -> dict:
settings["wallet.storage_creds"] = args.wallet_storage_creds
if args.replace_public_did:
settings["wallet.replace_public_did"] = True
# check required settings for 'indy' wallets
if settings["wallet.type"] == "indy":
# requires name, key
if not args.wallet_name or not args.wallet_key:
raise ArgsParseError(
"Parameters --wallet-name and --wallet-key must be provided"
+ " for indy wallets"
)
# postgres storage requires additional configuration
if (
"wallet.storage_config" in settings
and settings["wallet.storage_config"] == "postgres_storage"
):
if not args.wallet_storage_config or not args.wallet_storage_creds:
raise ArgsParseError(
"Parameters --wallet-storage-config and --wallet-storage-creds"
+ " must be provided for indy postgres wallets"
)
return settings
1 change: 1 addition & 0 deletions aries_cloudagent/config/tests/test-general-args.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--plugin foo --storage-type bar
27 changes: 26 additions & 1 deletion aries_cloudagent/config/tests/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def test_transport_settings(self):
group.add_arguments(parser)

with async_mock.patch.object(parser, "exit") as exit_parser:
parser.parse_args([])
parser.parse_args(["-h"])
exit_parser.assert_called_once()

result = parser.parse_args(
Expand All @@ -42,6 +42,8 @@ async def test_transport_settings(self):
"http",
"--max-outbound-retry",
"5",
"--endpoint",
"http://0.0.0.0:80",
]
)

Expand All @@ -54,6 +56,29 @@ async def test_transport_settings(self):
assert settings.get("transport.outbound_configs") == ["http"]
assert result.max_outbound_retry == 5

async def test_transport_settings_file(self):
"""Test file argument parsing."""

parser = ArgumentParser()
group = argparse.GeneralGroup()
group.add_arguments(parser)

with async_mock.patch.object(parser, "exit") as exit_parser:
parser.parse_args(["-h"])
exit_parser.assert_called_once()

result = parser.parse_args(
["--arg-file", "./aries_cloudagent/config/tests/test-general-args.cfg",]
)

assert result.external_plugins == ["foo"]
assert result.storage_type == "bar"

settings = group.get_settings(result)

assert settings.get("external_plugins") == ["foo"]
assert settings.get("storage_type") == "bar"

def test_bytesize(self):
bs = ByteSize()
with self.assertRaises(ArgumentTypeError):
Expand Down
Loading