From a7b1f00dcb177e164a86fd4003aaa8210d1964dc Mon Sep 17 00:00:00 2001 From: dbagan Date: Tue, 24 Oct 2023 16:41:40 +0200 Subject: [PATCH] Set new feature for deployment --- CHANGELOG.txt | 4 ++++ README.md | 8 ++++++++ setup.py | 2 +- src/tentaclio/credentials/reader.py | 12 ++++++------ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d30f88a8..fb4d47bc 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2023-10-25 +### Added + - Add support for interpolating environment variables to tentaclio secrets file. + ## [1.2.2] - 2023-10-18 ### Added - Add support for connecting to SFTP via private key authentication. Replaces pysftp dependency with paramiko. diff --git a/README.md b/README.md index 0b483bc7..e36e6c6c 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,14 @@ And make it accessible to tentaclio by setting the environmental variable `TENTA Alternatively you can run `curl https://raw.githubusercontent.com/octoenergy/tentaclio/master/extras/init_tentaclio.sh` to create a secrets file in `~/.tentaclio.yml` and automatically configure your environment. +Environment variables can be included in the credentials file by using `${ENV_VARIABLE}` as it follows: +``` +secrets: + db: postgresql://${DB_USER}:${DB_PASS}@myhost.com/database +``` +Tentaclio will search `DB_USER` and `DB_PASS` in the environment and will interpolate their values with the secrets file content. + + ## Quick note on protocols structural subtyping. In order to abstract concrete dependencies from the implementation of data related functions (or in any part of the system really) we use typed [protocols](https://mypy.readthedocs.io/en/latest/protocols.html#simple-user-defined-protocols). This allows a more flexible dependency injection than using subclassing or [more complex approches](http://code.activestate.com/recipes/413268/). This idea is heavily inspired by how this exact thing is done in [go](https://www.youtube.com/watch?v=ifBUfIb7kdo). Learn more about this principle in our [tech blog](https://tech.octopus.energy/news/2019/03/21/python-interfaces-a-la-go.html). diff --git a/setup.py b/setup.py index 40929f6c..31b37f0f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools.command.install import install -VERSION = "1.2.2" +VERSION = "1.3.0" REPO_ROOT = pathlib.Path(__file__).parent diff --git a/src/tentaclio/credentials/reader.py b/src/tentaclio/credentials/reader.py index 2aff8698..7d4ebfe9 100644 --- a/src/tentaclio/credentials/reader.py +++ b/src/tentaclio/credentials/reader.py @@ -14,7 +14,7 @@ SECRETS = "secrets" TENTACLIO_SECRETS_FILE = "TENTACLIO__SECRETS_FILE" -ENV_VARIABLE_PATTERN = r"\${[a-zA-Z_]+[a-zA-Z0-9_]*}" +ENV_VARIABLE_PATTERN = re.compile(r"\${[a-zA-Z_]+[a-zA-Z0-9_]*}") NOT_VALID_ENV_VARIABLE_VALUES = "[${}]" @@ -90,19 +90,19 @@ def _load_from_file( raise TentaclioFileError("File not found") from e -def replace_env_variables(url: str) -> str: +def _replace_env_variables(url: str) -> str: """Replace the environment variables inside the url by its value in the environment. Environment variables match the following format: ${ENV_VARIABLE_NAME} """ - pattern = re.compile(ENV_VARIABLE_PATTERN) - env_variables = re.findall(pattern, url) + env_variables = re.findall(ENV_VARIABLE_PATTERN, url) for env_variable in env_variables: env_variable_name = re.sub(NOT_VALID_ENV_VARIABLE_VALUES, "", env_variable) env_value = os.getenv(env_variable_name) if not env_value: raise EnvironmentError( - f"Error while reading variable '{env_variable_name}' from the environment. " + f"Error while reading variable '{env_variable_name}' from the environment " + f"when interpolating it from the secrets file. " f"Check that the variable exists in your environment." ) url = url.replace(env_variable, env_value) @@ -123,7 +123,7 @@ def add_credentials_from_reader( creds = _load_creds_from_yaml(yaml_reader) for name, url in creds.items(): logger.info(f"Adding secret: {name}") - url = replace_env_variables(url) + url = _replace_env_variables(url) try: injector.register_credentials(urls.URL(url)) except Exception as e: