Skip to content

Commit

Permalink
Merge pull request #27 from Erik-Lamers1/argeparser
Browse files Browse the repository at this point in the history
Move argeparser to separate file
  • Loading branch information
Erik-Lamers1 authored May 24, 2021
2 parents 920e889 + d6b515f commit 3b40a9a
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 87 deletions.
58 changes: 58 additions & 0 deletions vnet_manager/argeparser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from argparse import Namespace, ArgumentParser
from typing import Sequence

from vnet_manager.conf import settings


def parse_vnet_args(args: Sequence = None) -> Namespace:
parser = ArgumentParser(description="VNet-manager a virtual network manager - manages containers to create virtual networks")
parser.add_argument(
"action",
choices=sorted(settings.VALID_ACTIONS),
help="The action to preform on the virtual network, use '<action> help' for information about that action",
)
parser.add_argument("config", help="The yaml config file to use", nargs="?", default="default")

# Options
parser.add_argument(
"-m",
"--machines",
nargs="*",
help="Just apply the actions on the following machine names " "(default is all machines defined in the config file)",
)
parser.add_argument("-y", "--yes", action="store_true", help="Answer yes to all questions")
parser.add_argument("-nh", "--no-hosts", action="store_true", help="Disable creation of /etc/hosts")

start_group = parser.add_argument_group("Start options", "These options can be specified for the start action")
start_group.add_argument("-s", "--sniffer", action="store_true", help="Start a TCPdump sniffer on the VNet interfaces")

destroy_group = parser.add_argument_group("Destroy options", "These options can be specified for the destroy action")
destroy_group.add_argument("-b", "--base-image", action="store_true", help="Destroy the base image instead of the machines")

logging_group = parser.add_argument_group("Verbosity options", "Control output verbosity (can be supplied multiple times)")
logging_group.add_argument("-v", "--verbose", action="count", default=0, help="Be more verbose")
logging_group.add_argument("-q", "--quite", action="count", default=0, help="Be more quite")
return validate_argument_sanity(parser.parse_args(args=args), parser)


def validate_argument_sanity(args: Namespace, parser: ArgumentParser) -> Namespace:
"""
Validates the passed arguments for sanity
:param args: Namespace, The already processed user arguments
:param parser: ArgumentParser, The parser object
:return: Namespace, The validated arguments
:raises: SystemExit, if arguments are not sane
"""
# User input sanity checks
if args.action == "status":
# For people who are used to status calls
args.action = "show"
if args.config == "default" and args.action in settings.CONFIG_REQUIRED_ACTIONS:
parser.error("This action requires a config file to be passed")
if args.sniffer and not args.action == "start":
parser.error("The sniffer option only makes sense with the 'start' action")
if args.base_image and not args.action == "destroy":
parser.error("The base_image option only makes sense with the 'destroy' action")
if args.no_hosts and not args.action == "create":
parser.error("The no_hosts option only makes sense with the 'create' action")
return args
18 changes: 17 additions & 1 deletion vnet_manager/log.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import logging
import logging.config
from argparse import Namespace

from vnet_manager.conf import settings


def setup_console_logging(verbosity: int = logging.INFO):
def get_logging_verbosity(args: Namespace, default_verbosity: int = settings.LOGGING_DEFAULT_VERBOSITY) -> int:
"""
Get the logging verbosity based upon any passed verbosity arguments
:param args: Namespace, the parsed command line arguments
:param default_verbosity: int, the initial verbosity to manipulate
:return: int: The logging verbosity setting
"""
logging_verbs = {0: logging.CRITICAL, 1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}
# Compare the amount of verbosity args against the default verbosity
print(default_verbosity, args)
mod = default_verbosity + args.verbose - args.quite
# Return the appropriate logging level (must be within the defined logging levels)
return logging_verbs[max(min(len(logging_verbs) - 1, mod), 0)]


def setup_console_logging(verbosity: int = logging.INFO) -> None:
"""
:param int verbosity: Verbosity level logging.<verbosity>
"""
Expand Down
1 change: 1 addition & 0 deletions vnet_manager/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"pyroute2.ndb": {"level": "WARNING"},
},
}
LOGGING_DEFAULT_VERBOSITY = 3 # logging.INFO

# VNet Manager static settings / config
# provider config
Expand Down
44 changes: 44 additions & 0 deletions vnet_manager/tests/test_argeparser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from io import StringIO
from unittest.mock import patch

from vnet_manager.argeparser import parse_vnet_args
from vnet_manager.tests import VNetTestCase


default_args = ["list", "config"]


class TestParseArgs(VNetTestCase):
def test_parse_args_produces_known_args(self):
known_args = ("action", "config", "machines", "yes", "verbose", "sniffer", "base_image", "no_hosts")
args = parse_vnet_args(default_args)
for arg in known_args:
self.assertTrue(hasattr(args, arg), msg="Argument {} not found in parse_args return value".format(arg))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_config_required_action_without_config_is_passed(self, stderr):
with self.assertRaises(SystemExit):
parse_vnet_args(["create"])
self.assertTrue(stderr.getvalue().strip().endswith("This action requires a config file to be passed"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_sniffer_is_passed_without_start_action(self, stderr):
with self.assertRaises(SystemExit):
parse_vnet_args(["list", "config", "--sniffer"])
self.assertTrue(stderr.getvalue().strip().endswith("The sniffer option only makes sense with the 'start' action"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_base_image_passed_without_destroy_action(self, stderr):
with self.assertRaises(SystemExit):
parse_vnet_args(["list", "config", "--base-image"])
self.assertTrue(stderr.getvalue().strip().endswith("The base_image option only makes sense with the 'destroy' action"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_no_hosts_passed_without_create_action(self, stderr):
with self.assertRaises(SystemExit):
parse_vnet_args(["list", "config", "--no-hosts"])
self.assertTrue(stderr.getvalue().strip().endswith("The no_hosts option only makes sense with the 'create' action"))

def test_parse_args_sets_show_action_on_status_action(self):
args = parse_vnet_args(["status", "config"])
self.assertEqual(args.action, "show")
24 changes: 23 additions & 1 deletion vnet_manager/tests/test_log.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import logging
from argparse import ArgumentParser

from vnet_manager.log import setup_console_logging
from vnet_manager.log import setup_console_logging, get_logging_verbosity
from vnet_manager.tests import VNetTestCase
from vnet_manager.conf import settings


def my_test_args(args):
parser = ArgumentParser()
parser.add_argument("-q", "--quite", action="count", default=0)
parser.add_argument("-v", "--verbose", action="count", default=0)
return parser.parse_args(args=args)


class TestGetLoggingVerbosity(VNetTestCase):
def test_get_logging_verbosity_never_returns_higher_number_then_defined_verbs(self):
args = my_test_args(["-vvvvvvvvvvv", "--verbose", "-v"])
self.assertEqual(get_logging_verbosity(args), logging.DEBUG)

def test_get_logging_verbosity_never_returns_lower_number_then_defined_verbs(self):
args = my_test_args(["-qqqqqqqqqqq", "--quite", "-q"])
self.assertEqual(get_logging_verbosity(args), logging.CRITICAL)

def test_get_logging_verbosity_return_error_level_if_two_quites_passed(self):
args = my_test_args(["--quite", "-q"])
self.assertEqual(get_logging_verbosity(args), logging.ERROR)


class TestLog(VNetTestCase):
def test_setup_console_logging_sets_up_INFO_logging_by_default(self):
setup_console_logging()
Expand Down
41 changes: 2 additions & 39 deletions vnet_manager/tests/test_vnet_manager.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,14 @@
from unittest.mock import patch, Mock
from io import StringIO
from unittest.mock import Mock
from os import environ, EX_NOPERM
from logging import INFO, DEBUG

from vnet_manager.tests import VNetTestCase
from vnet_manager.conf import settings
from vnet_manager.vnet_manager import parse_args, main
from vnet_manager.vnet_manager import main

default_args = ["list", "config"]


class TestParseArgs(VNetTestCase):
def test_parse_args_produces_known_args(self):
known_args = ("action", "config", "machines", "yes", "verbose", "sniffer", "base_image", "no_hosts")
args = parse_args(default_args)
for arg in known_args:
self.assertTrue(hasattr(args, arg), msg="Argument {} not found in parse_args return value".format(arg))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_config_required_action_without_config_is_passed(self, stderr):
with self.assertRaises(SystemExit):
parse_args(["create"])
self.assertTrue(stderr.getvalue().strip().endswith("This action requires a config file to be passed"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_sniffer_is_passed_without_start_action(self, stderr):
with self.assertRaises(SystemExit):
parse_args(["list", "config", "--sniffer"])
self.assertTrue(stderr.getvalue().strip().endswith("The sniffer option only makes sense with the 'start' action"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_base_image_passed_without_destroy_action(self, stderr):
with self.assertRaises(SystemExit):
parse_args(["list", "config", "--base-image"])
self.assertTrue(stderr.getvalue().strip().endswith("The base_image option only makes sense with the 'destroy' action"))

@patch("sys.stderr", new_callable=StringIO)
def test_parse_args_exists_when_no_hosts_passed_without_create_action(self, stderr):
with self.assertRaises(SystemExit):
parse_args(["list", "config", "--no-hosts"])
self.assertTrue(stderr.getvalue().strip().endswith("The no_hosts option only makes sense with the 'create' action"))

def test_parse_args_sets_show_action_on_status_action(self):
args = parse_args(["status", "config"])
self.assertEqual(args.action, "show")


class TestVNetManagerMain(VNetTestCase):
def setUp(self) -> None:
self.setup_console_logging = self.set_up_patch("vnet_manager.vnet_manager.setup_console_logging")
Expand Down
51 changes: 5 additions & 46 deletions vnet_manager/vnet_manager.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,28 @@
import sys
from argparse import ArgumentParser, Namespace
from logging import INFO, DEBUG, getLogger
from logging import getLogger
from os import EX_NOPERM, environ
from typing import Sequence

from vnet_manager.conf import settings
from vnet_manager.log import setup_console_logging
from vnet_manager.log import setup_console_logging, get_logging_verbosity
from vnet_manager.actions.manager import ActionManager
from vnet_manager.utils.user import check_for_root_user
from vnet_manager.argeparser import parse_vnet_args

logger = getLogger(__name__)


def parse_args(args: Sequence = None) -> Namespace:
parser = ArgumentParser(description="VNet-manager a virtual network manager - manages containers to create virtual networks")
parser.add_argument(
"action",
choices=sorted(settings.VALID_ACTIONS),
help="The action to preform on the virtual network, use '<action> help' for information about that action",
)
parser.add_argument("config", help="The yaml config file to use", nargs="?", default="default")

# Options
parser.add_argument(
"-m",
"--machines",
nargs="*",
help="Just apply the actions on the following machine names " "(default is all machines defined in the config file)",
)
parser.add_argument("-y", "--yes", action="store_true", help="Answer yes to all questions")
parser.add_argument("-v", "--verbose", action="store_true", help="Print debug messages")
parser.add_argument("-nh", "--no-hosts", action="store_true", help="Disable creation of /etc/hosts")

start_group = parser.add_argument_group("Start options", "These options can be specified for the start action")
start_group.add_argument("-s", "--sniffer", action="store_true", help="Start a TCPdump sniffer on the VNet interfaces")
destroy_group = parser.add_argument_group("Destroy options", "These options can be specified for the destroy action")
destroy_group.add_argument("-b", "--base-image", action="store_true", help="Destroy the base image instead of the machines")
args = parser.parse_args(args=args)

# User input sanity checks
if args.action == "status":
# For people who are used to status calls
args.action = "show"
if args.config == "default" and args.action in settings.CONFIG_REQUIRED_ACTIONS:
parser.error("This action requires a config file to be passed")
if args.sniffer and not args.action == "start":
parser.error("The sniffer option only makes sense with the 'start' action")
if args.base_image and not args.action == "destroy":
parser.error("The base_image option only makes sense with the 'destroy' action")
if args.no_hosts and not args.action == "create":
parser.error("The no_hosts option only makes sense with the 'create' action")
return args


def main(args: Sequence = None) -> int:
"""
Program entry point
:param list args: The pre-cooked arguments to pass to the ArgParser
:return int: exit_code
"""
args = parse_args(args)
args = parse_vnet_args(args)
# Set the VNET_FORCE variable, if --yes is given this will answer yes to all questions
environ[settings.VNET_FORCE_ENV_VAR] = "true" if args.yes else "false"
# Setup logging
setup_console_logging(verbosity=DEBUG if args.verbose else INFO)
setup_console_logging(verbosity=get_logging_verbosity(args))
# Most VNet operation require root. So, do a root check
if not check_for_root_user():
logger.critical("This program should only be run as root")
Expand Down

0 comments on commit 3b40a9a

Please sign in to comment.