Skip to content

Feature/089 namespace on env vars with backward compatibility #191

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
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
8 changes: 4 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Environments variables of PyMS:

**CONFIGMAP_FILE**: The path to the configuration file. By default, PyMS searches for the configuration file in your
**PYMS_CONFIGMAP_FILE**: The path to the configuration file. By default, PyMS searches for the configuration file in your
current folder with the name "config.yml"
**KEY_FILE**: The path to the key file to decrypt your configuration. By default, PyMS searches for the configuration file in your
**PYMS_KEY_FILE**: The path to the key file to decrypt your configuration. By default, PyMS searches for the configuration file in your
current folder with the name "key.key"

## Create configuration
Expand Down Expand Up @@ -136,7 +136,7 @@ API = Api(

## Looking for Configuration file with Kubernetes Configmaps
By default, the Microservice class searches for a config.yml in the same path. You can set a different route or set a json file.
To change this path, you must define an environment variable called `CONFIGMAP_FILE`.
To change this path, you must define an environment variable called `PYMS_CONFIGMAP_FILE`.

This way of looking for the configuration is useful when you work with Docker and Kubernetes. For example, you could integrate
a configmap of Kubernetes, with this microservice and a deployment with:
Expand All @@ -154,7 +154,7 @@ spec:
- name: my-microservice
image: ...
env:
- name: CONFIGMAP_FILE
- name: PYMS_CONFIGMAP_FILE
value: "/usr/share/microservice/config.yaml"

volumeMounts:
Expand Down
4 changes: 2 additions & 2 deletions docs/encrypt_decryt_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Move your key, for example, to `mv key.key /home/my_user/keys/myproject.key`
then, store the key in a environment variable with:

```bash
export KEY_FILE=/home/my_user/keys/myproject.key
export PYMS_KEY_FILE=/home/my_user/keys/myproject.key
```

## 3. Encrypt your information and store it in config
Expand Down Expand Up @@ -89,7 +89,7 @@ can find the answer
## 4. Decrypt from your config file

Pyms knows if a variable is encrypted if this var start with the prefix `enc_` or `ENC_`. PyMS searches for your key file
in the `KEY_FILE` env variable and decrypts this value to store it in the same variable without the `enc_` prefix,
in the `PYMS_KEY_FILE` env variable and decrypts this value to store it in the same variable without the `enc_` prefix,
for example,

```yaml
Expand Down
2 changes: 1 addition & 1 deletion docs/ms_class.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pyms:
Check the [Configuration](configuration.md) section to learn how to create a configuration file.

The `Microservice` class searches for a `config.yml` in the directory you pass in `path` parameter or looks for the file in
`CONFIGMAP_FILE` env var.
`PYMS_CONFIGMAP_FILE` env var.

Each keyword in our configuration block, can be accessed in our Microservice object through the attribute `config`.

Expand Down
2 changes: 1 addition & 1 deletion examples/mininum_microservice_docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ RUN apk add --update curl gcc g++ git libffi-dev openssl-dev python3-dev build-b
&& rm -rf /var/cache/apk/*

ENV PYTHONUNBUFFERED=1 APP_HOME=/microservice/
ENV CONFIGMAP_FILE="$APP_HOME"config-docker.yml
ENV PYMS_CONFIGMAP_FILE="$APP_HOME"config-docker.yml

RUN mkdir $APP_HOME && adduser -S -D -H python
RUN chown -R python $APP_HOME
Expand Down
3 changes: 1 addition & 2 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,7 @@ min-similarity-lines=4

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
XXX


[TYPECHECK]
Expand Down
6 changes: 3 additions & 3 deletions pyms/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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


class Command:
Expand Down Expand Up @@ -117,15 +117,15 @@ def run(self):

@staticmethod
def print_ok(msg=""):
print('\033[92m\033[1m ' + msg + ' \033[0m\033[0m')
print(utils.colored_text(msg, utils.Colors.BRIGHT_GREEN, True))

def print_verbose(self, msg=""): # pragma: no cover
if self.verbose:
print(msg)

@staticmethod
def print_error(msg=""): # pragma: no cover
print('\033[91m\033[1m ' + msg + ' \033[0m\033[0m')
print(utils.colored_text(msg, utils.Colors.BRIGHT_RED, True))

def exit_with_error(self, msg=""): # pragma: no cover
self.print_error(msg)
Expand Down
42 changes: 41 additions & 1 deletion pyms/config/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import logging
import os

from pyms.config.confile import ConfFile
from pyms.constants import PYMS_CONFIG_WHITELIST_KEYWORDS
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
from pyms.exceptions import ServiceDoesNotExistException, ConfigErrorException, AttrDoesNotExistException
from pyms.utils import utils

logger = logging.getLogger(LOGGER_NAME)


def get_conf(*args, **kwargs):
Expand Down Expand Up @@ -42,6 +49,7 @@ def get_conf(*args, **kwargs):


def validate_conf(*args, **kwargs):

config = ConfFile(*args, **kwargs)
is_config_ok = True
try:
Expand Down Expand Up @@ -104,3 +112,35 @@ def validate_conf(*args, **kwargs):
config:
DEBUG: true
TESTING: true""".format(wrong_keywords))

# TODO Remove temporally deprecated warnings on future versions
__verify_deprecated_env_variables(config)


def __verify_deprecated_env_variables(config):
env_var_duplicated = "IMPORTANT: If you are using \"{}\" environment variable, \"{}\" value will be ignored."
env_var_deprecated = "IMPORTANT: \"{}\" environment variable is deprecated on this version, use \"{}\" instead."

if os.getenv(CONFIGMAP_FILE_ENVIRONMENT_LEGACY) is not None:
if os.getenv(CONFIGMAP_FILE_ENVIRONMENT) is not None:
msg = env_var_duplicated.format(CONFIGMAP_FILE_ENVIRONMENT, CONFIGMAP_FILE_ENVIRONMENT_LEGACY)
else:
msg = env_var_deprecated.format(CONFIGMAP_FILE_ENVIRONMENT_LEGACY, CONFIGMAP_FILE_ENVIRONMENT)
try:
if config.pyms.config.DEBUG:
msg = utils.colored_text(msg, utils.Colors.BRIGHT_YELLOW, True)
except AttrDoesNotExistException:
pass
logger.warning(msg)

if os.getenv(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY) is not None:
if os.getenv(CRYPT_FILE_KEY_ENVIRONMENT) is not None:
msg = env_var_duplicated.format(CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY)
else:
msg = env_var_deprecated.format(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, CRYPT_FILE_KEY_ENVIRONMENT)
try:
if config.pyms.config.DEBUG:
msg = utils.colored_text(msg, utils.Colors.BRIGHT_YELLOW, True)
except AttrDoesNotExistException:
pass
logger.warning(msg)
20 changes: 16 additions & 4 deletions pyms/config/confile.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Module to read yaml or json conf"""
import logging
import os
import re
from typing import Dict, Union, Text, Tuple, Iterable

import anyconfig

from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, LOGGER_NAME, DEFAULT_CONFIGMAP_FILENAME
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, LOGGER_NAME, DEFAULT_CONFIGMAP_FILENAME, \
CONFIGMAP_FILE_ENVIRONMENT_LEGACY
from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException
from pyms.utils.files import LoadFile

Expand All @@ -14,7 +16,7 @@

class ConfFile(dict):
"""Recursive get configuration from dictionary, a config file in JSON or YAML format from a path or
`CONFIGMAP_FILE` environment variable.
`PYMS_CONFIGMAP_FILE` environment variable.
**Atributes:**
* path: Path to find the `DEFAULT_CONFIGMAP_FILENAME` and `DEFAULT_KEY_FILENAME` if use encrypted vars
* empty_init: Allow blank variables
Expand All @@ -26,15 +28,18 @@ class ConfFile(dict):
def __init__(self, *args, **kwargs):
"""
Get configuration from a dictionary(variable `config`), from path (variable `path`) or from
environment with the constant `CONFIGMAP_FILE`
environment with the constant `PYMS_CONFIGMAP_FILE`
Set the configuration as upper case to inject the keys in flask config. Flask search for uppercase keys in
`app.config.from_object`
```python
if key.isupper():
self[key] = getattr(obj, key)
```
"""
self._loader = LoadFile(kwargs.get("path"), CONFIGMAP_FILE_ENVIRONMENT, DEFAULT_CONFIGMAP_FILENAME)
# TODO Remove temporally backward compatibility on future versions
configmap_file_env = self.__get_updated_configmap_file_env() # Temporally backward compatibility

self._loader = LoadFile(kwargs.get("path"), configmap_file_env, DEFAULT_CONFIGMAP_FILENAME)
self._crypt_cls = kwargs.get("crypt")
if self._crypt_cls:
self._crypt = self._crypt_cls(path=kwargs.get("path"))
Expand Down Expand Up @@ -125,3 +130,10 @@ def reload(self):

def __setattr__(self, name, value, *args, **kwargs):
super().__setattr__(name, value)

@staticmethod
def __get_updated_configmap_file_env() -> str:
result = CONFIGMAP_FILE_ENVIRONMENT
if (os.getenv(CONFIGMAP_FILE_ENVIRONMENT_LEGACY) is not None) and (os.getenv(CONFIGMAP_FILE_ENVIRONMENT) is None):
result = CONFIGMAP_FILE_ENVIRONMENT_LEGACY
return result
6 changes: 4 additions & 2 deletions pyms/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
CONFIGMAP_FILE_ENVIRONMENT = "CONFIGMAP_FILE"
CONFIGMAP_FILE_ENVIRONMENT = "PYMS_CONFIGMAP_FILE"
CONFIGMAP_FILE_ENVIRONMENT_LEGACY = "CONFIGMAP_FILE"

DEFAULT_CONFIGMAP_FILENAME = "config.yml"

CRYPT_FILE_KEY_ENVIRONMENT = "KEY_FILE"
CRYPT_FILE_KEY_ENVIRONMENT = "PYMS_KEY_FILE"
CRYPT_FILE_KEY_ENVIRONMENT_LEGACY = "KEY_FILE"

DEFAULT_KEY_FILENAME = "key.key"

Expand Down
17 changes: 14 additions & 3 deletions pyms/crypt/fernet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

from pyms.constants import CRYPT_FILE_KEY_ENVIRONMENT, DEFAULT_KEY_FILENAME
from pyms.constants import CRYPT_FILE_KEY_ENVIRONMENT, DEFAULT_KEY_FILENAME, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY
from pyms.crypt.driver import CryptAbstract
from pyms.exceptions import FileDoesNotExistException
from pyms.utils.files import LoadFile


class Crypt(CryptAbstract):
def __init__(self, *args, **kwargs):
self._loader = LoadFile(kwargs.get("path"), CRYPT_FILE_KEY_ENVIRONMENT, DEFAULT_KEY_FILENAME)
# TODO Remove temporally backward compatibility on future versions
crypt_file_key_env = self.__get_updated_crypt_file_key_env() # Temporally backward compatibility
self._loader = LoadFile(kwargs.get("path"), crypt_file_key_env, DEFAULT_KEY_FILENAME)
super().__init__(*args, **kwargs)

def generate_key(self, password: Text, write_to_file: bool = False) -> bytes:
Expand All @@ -36,9 +38,11 @@ def generate_key(self, password: Text, write_to_file: bool = False) -> bytes:
def read_key(self):
key = self._loader.get_file()
if not key:
# TODO Remove temporally backward compatibility on future versions
crypt_file_key_env = self.__get_updated_crypt_file_key_env() # Temporally backward compatibility
raise FileDoesNotExistException(
"Decrypt key {} not exists. You must set a correct env var {} "
"or run `pyms crypt create-key` command".format(self._loader.path, CRYPT_FILE_KEY_ENVIRONMENT))
"or run `pyms crypt create-key` command".format(self._loader.path, crypt_file_key_env))
return key

def encrypt(self, message):
Expand All @@ -57,3 +61,10 @@ def decrypt(self, encrypted):

def delete_key(self):
os.remove(self._loader.get_path_from_env())

@staticmethod
def __get_updated_crypt_file_key_env() -> str:
result = CRYPT_FILE_KEY_ENVIRONMENT
if (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY) is not None) and (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT) is None):
result = CRYPT_FILE_KEY_ENVIRONMENT_LEGACY
return result
2 changes: 1 addition & 1 deletion pyms/flask/app/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def example():
app.run()
```
Environments variables of PyMS:
**CONFIGMAP_FILE**: The path to the configuration file. By default, PyMS search the configuration file in your
**PYMS_CONFIGMAP_FILE**: The path to the configuration file. By default, PyMS search the configuration file in your
actual folder with the name "config.yml"

## Create configuration
Expand Down
20 changes: 20 additions & 0 deletions pyms/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
from pyms.exceptions import PackageNotExists


class Colors:
BLUE = "\033[34m"
GREEN = "\033[32m"
MAGENTA = "\033[35m"
RED = "\033[31m"
YELLOW = "\033[33m"
BRIGHT_BLUE = "\033[94m"
BRIGHT_GREEN = "\033[92m"
BRIGHT_MAGENTA = "\033[95m"
BRIGHT_RED = "\033[91m"
BRIGHT_YELLOW = "\033[93m"


def import_from(module: Text, name: Text):
module = __import__(module, fromlist=[name])
return getattr(module, name)
Expand All @@ -20,3 +33,10 @@ def check_package_exists(package_name: Text) -> Union[Exception, bool]:
raise PackageNotExists(
"{package} is not installed. try with pip install -U {package}".format(package=package_name))
return True


def colored_text(msg, color: Colors, bold=False):
result = "{}{}{}".format(color, msg, "\033[0m")
if bold:
result = "{}{}".format("\033[1m", result)
return result
21 changes: 21 additions & 0 deletions tests/config-tests-debug-off.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: false
testing: true
app_name: "Python Microservice"
test_var: general
subservice1:
test: input
subservice2:
test: output
21 changes: 21 additions & 0 deletions tests/config-tests-debug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: true
testing: true
app_name: "Python Microservice"
test_var: general
subservice1:
test: input
subservice2:
test: output
21 changes: 21 additions & 0 deletions tests/config-tests-deprecated.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: false
testing: true
app_name: "Python Microservice"
test_var: deprecated
subservice1:
test: input
subservice2:
test: output
2 changes: 1 addition & 1 deletion tests/test_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_crypt_file_error(self):
cmd = Command(arguments=arguments, autorun=False)
with pytest.raises(FileDoesNotExistException) as excinfo:
cmd.run()
assert ("Decrypt key None not exists. You must set a correct env var KEY_FILE or run "
assert ("Decrypt key None not exists. You must set a correct env var PYMS_KEY_FILE or run "
"`pyms crypt create-key` command") \
in str(excinfo.value)

Expand Down
Loading