modular_sdk/services/environment_service.py (89 lines of code) (raw):
import os
from typing import Optional, List
from modular_sdk.commons.constants import PARAM_ASSUME_ROLE_ARN, \
MODULAR_AWS_CREDENTIALS_EXPIRATION_ENV, REGION_ENV, ENVS_TO_HIDE, \
HIDDEN_ENV_PLACEHOLDER, MODULAR_AWS_SESSION_TOKEN_ENV, \
MODULAR_AWS_ACCESS_KEY_ID_ENV, MODULAR_AWS_SECRET_ACCESS_KEY_ENV, \
MODULAR_REGION_ENV, MODULAR_SERVICE_MODE_ENV, SERVICE_MODE_DOCKER, \
DEFAULT_REGION_ENV, ENV_INNER_CACHE_TTL_SECONDS, \
DEFAULT_INNER_CACHE_TTL_SECONDS
from modular_sdk.commons.log_helper import get_logger
_LOG = get_logger(__name__)
class EnvironmentService:
# TODO make it decent and put envs' names to constants
def __init__(self):
self._environment = os.environ
def __repr__(self) -> str:
return ', '.join([
f'{k}={v if k not in ENVS_TO_HIDE else HIDDEN_ENV_PLACEHOLDER}'
for k, v in self._environment.items()
])
def set(self, name: str, value: str):
self._environment[name] = value
def aws_region(self) -> str:
return self._environment.get(REGION_ENV)
def default_aws_region(self) -> str:
return self._environment.get(DEFAULT_REGION_ENV)
def is_docker(self) -> bool:
return self._environment.get(MODULAR_SERVICE_MODE_ENV) == SERVICE_MODE_DOCKER
def component(self):
return self._environment.get('component_name')
def application(self):
return self._environment.get('application_name')
def queue_url(self) -> Optional[str]:
return self._environment.get('queue_url')
def modular_assume_role_arn(self) -> List[str]:
"""
Returns a list of roles to assume before making requests to
DynamoDB and SSM (currently only DynamoDB and SSM).
They are assumed one by another in order to be able to organize
convenient access to another AWS account. For example:
we have Custodian prod and Modular prod (these are different
AWS accounts). Custodian must query some tables from Modular prod.
To make this work, we create:
- a role on Modular prod which provides access to Modular tables;
- a role on Custodian prod which can assume the role from Modular
prod and can BE assumed by roles of each of our lambdas.
What it gives us? - we don't have to change the trusted
relationships of the role from Modular prod (perceive Modular
prod as something far and external) in case some of our roles
changed their names, or we add new lambdas/roles
:return:
"""
env = self._environment.get(PARAM_ASSUME_ROLE_ARN)
if not env: # None or ''
return []
return env.split(',')
def modular_aws_credentials_expiration(self) -> Optional[str]:
"""
UTC iso
"""
return self._environment.get(MODULAR_AWS_CREDENTIALS_EXPIRATION_ENV)
def modular_aws_access_key_id(self) -> Optional[str]:
return self._environment.get(MODULAR_AWS_ACCESS_KEY_ID_ENV)
def modular_aws_secret_access_key(self) -> Optional[str]:
return self._environment.get(MODULAR_AWS_SECRET_ACCESS_KEY_ENV)
def modular_aws_session_token(self) -> Optional[str]:
return self._environment.get(MODULAR_AWS_SESSION_TOKEN_ENV)
def modular_aws_region(self) -> Optional[str]:
return self._environment.get(MODULAR_REGION_ENV)
def inner_cache_ttl_seconds(self) -> int:
"""
Used for cachetools.TTLCache.
Currently used in the caching wrapper of ssm service
:return:
"""
from_env = str(self._environment.get(ENV_INNER_CACHE_TTL_SECONDS))
if from_env.isdigit():
return int(from_env)
return DEFAULT_INNER_CACHE_TTL_SECONDS
class EnvironmentContext:
"""
Use it with credentials
"""
def __init__(self, envs: Optional[dict] = None,
reset_all: Optional[bool] = True):
self.envs = envs
self._reset_all = reset_all
self._is_set = False
self._old_envs: dict = {}
@property
def envs(self) -> dict:
return self._envs
@envs.setter
def envs(self, value: Optional[dict]):
self._envs = self._adjust_envs(value or {})
@staticmethod
def _adjust_envs(envs: dict) -> dict:
return {
k: str(v) for k, v in envs.items() if v
}
def set(self):
self._old_envs.update(os.environ)
_LOG.info(f'Setting {", ".join(self._envs.keys())} envs')
os.environ.update(self._envs)
self._is_set = True
def clear(self):
_LOG.info(f'Unsetting {", ".join(self._envs.keys())} envs')
if self._reset_all:
os.environ.clear()
os.environ.update(self._old_envs)
else:
[os.environ.pop(key, None) for key in self.envs]
self._old_envs.clear()
self._is_set = False
def __enter__(self):
self.set()
def __exit__(self, exc_type, exc_val, exc_tb):
self.clear()