Skip to content

Added command create-config (#169) #204

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

Merged
merged 1 commit into from
Nov 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ valid-metaclass-classmethod-first-arg=mcs
max-args=7

# Maximum number of attributes for a class (see R0902).
max-attributes=7
max-attributes=8

# Maximum number of boolean expressions in a if statement
max-bool-expr=5
Expand Down
32 changes: 32 additions & 0 deletions pyms/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import argparse
import os
import sys
from distutils.util import strtobool

from pyms.crypt.fernet import Crypt
from pyms.flask.services.swagger import merge_swagger_file
from pyms.utils import check_package_exists, import_from, utils
from pyms.config import create_conf_file


class Command:
Expand All @@ -18,6 +20,7 @@ class Command:

args = []

# flake8: noqa: C901
def __init__(self, *args, **kwargs):
arguments = kwargs.get("arguments", False)
autorun = kwargs.get("autorun", True)
Expand Down Expand Up @@ -53,6 +56,9 @@ def __init__(self, *args, **kwargs):
"-f", "--file", default=os.path.join('project', 'swagger', 'swagger.yaml'),
help='Swagger file path')

parser_create_config = commands.add_parser('create-config', help='Generate a config file')
parser_create_config.add_argument("create_config", action='store_true', help='Generate a config file')

parser.add_argument("-v", "--verbose", default="", type=str, help="Verbose ")

args = parser.parse_args(arguments)
Expand All @@ -74,6 +80,10 @@ def __init__(self, *args, **kwargs):
self.file = args.file
except AttributeError:
self.merge_swagger = False
try:
self.create_config = args.create_config
except Exception:
self.create_config = False
self.verbose = len(args.verbose)
if autorun: # pragma: no cover
result = self.run()
Expand All @@ -91,6 +101,8 @@ def run(self):
if self.create_key:
path = crypt._loader.get_path_from_env() # pylint: disable=protected-access
pwd = self.get_input('Type a password to generate the key file: ')
# Should use yes_no_input insted of get input below
# the result should be validated for Yes (Y|y) rather allowing anything other than 'n'
generate_file = self.get_input('Do you want to generate a file in {}? [Y/n]'.format(path))
generate_file = generate_file.lower() != "n"
key = crypt.generate_key(pwd, generate_file)
Expand All @@ -99,6 +111,8 @@ def run(self):
else:
self.print_ok("Key generated: {}".format(key))
if self.encrypt:
# Spoted Unhandle exceptions - The encrypt function throws FileDoesNotExistException, ValueError
# which are not currently handled
encrypted = crypt.encrypt(self.encrypt)
self.print_ok("Encrypted OK: {}".format(encrypted))
if self.startproject:
Expand All @@ -113,8 +127,26 @@ def run(self):
except FileNotFoundError as ex:
self.print_error(ex.__str__())
return False
if self.create_config:
use_requests = self.yes_no_input('Do you want to use request')
use_swagger = self.yes_no_input('Do you want to use swagger')
try:
conf_file_path = create_conf_file(use_requests, use_swagger)
self.print_ok(f'Config file "{conf_file_path}" created')
return True
except Exception as ex:
self.print_error(ex.__str__())
return False
return True

def yes_no_input(self, msg=""): # pragma: no cover
answer = input(utils.colored_text(f'{msg}{"?" if not msg.endswith("?") else ""} [Y/n] :', utils.Colors.BLUE, True)) # nosec
try:
return strtobool(answer)
except ValueError:
self.print_error('Invalid input, Please answer with a "Y" or "n"')
self.yes_no_input(msg)

@staticmethod
def print_ok(msg=""):
print(utils.colored_text(msg, utils.Colors.BRIGHT_GREEN, True))
Expand Down
4 changes: 2 additions & 2 deletions pyms/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .conf import get_conf
from .confile import ConfFile
from .conf import get_conf, create_conf_file

__all__ = ['get_conf', 'ConfFile']
__all__ = ['get_conf', 'create_conf_file', 'ConfFile']
56 changes: 52 additions & 4 deletions pyms/config/conf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import logging
import os

import logging
from typing import Union
import yaml
from pyms.utils import utils
from pyms.config.confile import ConfFile
from pyms.constants import PYMS_CONFIG_WHITELIST_KEYWORDS, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, \
CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, LOGGER_NAME
CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, LOGGER_NAME, \
DEFAULT_CONFIGMAP_FILENAME
from pyms.exceptions import ServiceDoesNotExistException, ConfigErrorException, AttrDoesNotExistException
from pyms.utils import utils

logger = logging.getLogger(LOGGER_NAME)

Expand Down Expand Up @@ -144,3 +146,49 @@ def __verify_deprecated_env_variables(config):
except AttrDoesNotExistException:
pass
logger.warning(msg)


def create_conf_file(use_requests: bool = False, use_swagger: bool = False) -> Union[Exception, str]:
"""
Creates a configuration file defining

:param use_requests: Do you want to use requests, defaults to False
:type use_requests: bool, optional
:param use_swagger: Do you want to use swagger, defaults to False
:type use_swagger: bool, optional
:raises FileExistsError: Config file already exists
:raises IOError: Config file creation failed.
:return: Raises FileExistsError or IOError OR returns config_file_path
:rtype: Union[Exception, str]
"""
# Try using env value for config file, if not found use default
CONFIG_FILE = os.getenv(CONFIGMAP_FILE_ENVIRONMENT, None)
if not CONFIG_FILE:
CONFIG_FILE = DEFAULT_CONFIGMAP_FILENAME
# Prevent overwriting existing file
if os.path.exists(CONFIG_FILE):
raise FileExistsError("Config file already exists at '{}'".format(os.path.abspath(CONFIG_FILE)))
# Create config dict
config = {"pyms": {}}
# add services
if use_requests:
if not config["pyms"].get("services", None):
config["pyms"]["services"] = {}
config["pyms"]["services"]["requests"] = {"data": ""}
if use_swagger:
if not config["pyms"].get("services", None):
config["pyms"]["services"] = {}
config["pyms"]["services"]["swagger"] = {"path": "", "file": "swagger.yaml"}
# add Basic Flask config
config["pyms"]["config"] = {
"DEBUG": True,
"TESTING": False,
"APP_NAME": "Python Microservice",
"APPLICATION_ROOT": ""
}
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as config_file:
config_file.write(yaml.dump(config, default_flow_style=False, default_style=None, sort_keys=False))
except Exception as ex:
raise ex
return CONFIG_FILE
14 changes: 14 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import os
from pyms.flask.app import Microservice
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, DEFAULT_CONFIGMAP_FILENAME

class MyMicroserviceNoSingleton(Microservice):
_singleton = False


class MyMicroservice(Microservice):
pass


def remove_conf_file():
"""
Remove the YAML config file
"""
CONFIG_FILE = os.getenv(CONFIGMAP_FILE_ENVIRONMENT, None)
if not CONFIG_FILE:
CONFIG_FILE = DEFAULT_CONFIGMAP_FILENAME
# Dlete file, if exists
if os.path.exists(CONFIG_FILE):
os.remove(CONFIG_FILE)
11 changes: 11 additions & 0 deletions tests/test_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pyms.exceptions import FileDoesNotExistException, PackageNotExists
from pyms.crypt.fernet import Crypt
from pyms.flask.services.swagger import get_bundled_specs
from tests.common import remove_conf_file


class TestCmd(unittest.TestCase):
Expand Down Expand Up @@ -75,3 +76,13 @@ def test_merge_swagger_error(self):
cmd = Command(arguments=arguments, autorun=False)
with pytest.raises(ResolutionError) as excinfo:
cmd.run()

@patch('pyms.cmd.main.Command.yes_no_input', return_value=True)
def test_create_config_all(self, input):
# Remove config file if already exists for test
remove_conf_file()
arguments = ["create-config"]
cmd = Command(arguments=arguments, autorun=False)
assert cmd.run()
assert not cmd.run()
remove_conf_file()
24 changes: 23 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import unittest
from unittest import mock

from pyms.config import get_conf, ConfFile
from pyms.config import get_conf, ConfFile, create_conf_file
from pyms.config.conf import validate_conf
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, LOGGER_NAME, CONFIG_BASE, \
CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY
Expand Down Expand Up @@ -190,3 +190,25 @@ def all_validate_conf_combinations(self, legacy=False):
validate_conf()
os.environ[config_env] = os.path.join(self.BASE_DIR, "config-tests-debug-off.yml")
validate_conf()


class ConfiFileTest(unittest.TestCase):
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

def setUp(self):
os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-file.yml")

def tearDown(self):
del os.environ[CONFIGMAP_FILE_ENVIRONMENT]

def test_create_config(self):
create_conf_file(use_requests=True, use_swagger=True)
self.assertTrue(os.path.exists(os.environ[CONFIGMAP_FILE_ENVIRONMENT]))
validate_conf()

def test_create_config_failure(self):
with self.assertRaises(FileExistsError):
create_conf_file(use_requests=True, use_swagger=True)
# Delete the file
if os.path.exists(os.environ[CONFIGMAP_FILE_ENVIRONMENT]):
os.remove(os.environ[CONFIGMAP_FILE_ENVIRONMENT])