Skip to content

Commit

Permalink
Merge pull request #88 from epam/develop
Browse files Browse the repository at this point in the history
Release 6.2.1
  • Loading branch information
bohdan-onsha authored Oct 22, 2024
2 parents 09ea174 + 60d1599 commit 79bd856
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 140 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ 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).

## [6.2.1] - 2024-10-18
- Update `Modular` and `MaestroHTTPTransport` classes
- Add `MaestroHTTPConfig` class
- Improved logging

## [6.2.0] - 2024-10-04
- added `MaestroHTTPTransport` to allow to interact with maestro using https protocol

Expand Down
5 changes: 5 additions & 0 deletions modular_sdk/commons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer

from modular_sdk.commons.exception import ModularException
from modular_sdk.commons.log_helper import get_logger


_LOG = get_logger(__name__)


RESPONSE_BAD_REQUEST_CODE = 400
RESPONSE_UNAUTHORIZED = 401
Expand Down
7 changes: 1 addition & 6 deletions modular_sdk/commons/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,9 @@ def set(self, val: str | None):
INNER_CACHE_TTL_SECONDS = 'MODULAR_SDK_INNER_CACHE_TTL_SECONDS', '300'

# these below are used

# proxies, impacts only our boto3 clients. Boto inside Pynamodb is NOT
# routed via these proxies
HTTP_PROXY = 'MODULAR_SDK_HTTP_PROXY'
HTTPS_PROXY = 'MODULAR_SDK_HTTPS_PROXY'

AWS_REGION = 'AWS_REGION'
AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION'
LOG_LEVEL = 'MODULAR_SDK_LOG_LEVEL', 'INFO'


REGION_ENV = Env.AWS_REGION.value
Expand Down
100 changes: 25 additions & 75 deletions modular_sdk/commons/log_helper.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,30 @@
import logging
import os
import re
from functools import cached_property
from sys import stdout
from typing import Dict
import logging.config

_name_to_level = {
'CRITICAL': logging.CRITICAL,
'FATAL': logging.FATAL,
'ERROR': logging.ERROR,
'WARNING': logging.WARNING,
'INFO': logging.INFO,
'DEBUG': logging.DEBUG
}
from modular_sdk.commons.constants import Env

LOG_FORMAT = '%(asctime)s - %(levelname)s - %(name)s - %(message)s'


class SensitiveFormatter(logging.Formatter):
"""Formatter that removes sensitive information."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._param_to_regex: Dict[str, re.Pattern] = {}

@cached_property
def secured_params(self) -> set:
return {
'refresh_token', 'id_token', 'password', 'authorization', 'secret',
'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret',
'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET',
'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id',
'Authorization', 'Authentication'
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {},
'handlers': {
'null_handler': {
'class': 'logging.NullHandler'
}

@staticmethod
def _compile_param_regex(param: str) -> re.Pattern:
"""
It searches for values in JSON objects where key is $param:
If param is "password" the string '{"password": "blabla"}' will be
printed as '{"password": "****"}'
[\'"] - single or double quote; [ ]* - zero or more spaces
"""
return re.compile(f'[\'"]{param}[\'"]:[ ]*[\'"](.*?)[\'"]')

def get_param_regex(self, param: str) -> re.Pattern:
if param not in self._param_to_regex:
self._param_to_regex[param] = self._compile_param_regex(param)
return self._param_to_regex[param]

def _filter(self, string):
# Hoping that this regex substitutions do not hit performance...
for param in self.secured_params:
string = re.sub(self.get_param_regex(param),
f'\'{param}\': \'****\'', string)
return string

def format(self, record):
original = logging.Formatter.format(self, record)
return self._filter(original)


logger = logging.getLogger(__name__)
logger.propagate = False
console_handler = logging.StreamHandler(stream=stdout)
console_handler.setFormatter(SensitiveFormatter(LOG_FORMAT))
logger.addHandler(console_handler)

log_level = _name_to_level.get(os.environ.get('log_level'))
if not log_level:
log_level = logging.INFO
logging.captureWarnings(True)


def get_logger(log_name, level=log_level):
module_logger = logger.getChild(log_name)
},
'loggers': {
'modular_sdk': {
'level': Env.LOG_LEVEL.get(),
'handlers': ['null_handler'],
'propagate': False,
},
}
})


def get_logger(name: str, level: str | None = None, /):
log = logging.getLogger(name)
if level:
module_logger.setLevel(level)
return module_logger
log.setLevel(level)
return log

17 changes: 17 additions & 0 deletions modular_sdk/modular.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
ASSUMES_ROLE_SESSION_NAME, MODULAR_AWS_ACCESS_KEY_ID_ENV, \
MODULAR_AWS_SECRET_ACCESS_KEY_ENV, MODULAR_AWS_SESSION_TOKEN_ENV, \
MODULAR_AWS_CREDENTIALS_EXPIRATION_ENV
from modular_sdk.services.impl.maestro_http_transport_service import \
MaestroHTTPConfig


class Modular(metaclass=SingletonMeta):
Expand All @@ -26,6 +28,7 @@ class Modular(metaclass=SingletonMeta):
__lambda_service = None
__events_service = None
__rabbit_transport_service = None
__http_transport_service = None
__settings_service = None
__instantiated_setting_group = []
__credentials_service = None
Expand Down Expand Up @@ -209,6 +212,20 @@ def rabbit_transport_service(self, connection_url, config,
)
return self.__rabbit_transport_service

def http_transport_service(
self,
api_link: str,
config: MaestroHTTPConfig,
timeout: int | None = None,
):
if not self.__http_transport_service:
from modular_sdk.services.impl.maestro_http_transport_service import \
MaestroHTTPTransport
self.__http_transport_service = MaestroHTTPTransport(
config=config, api_link=api_link, timeout=timeout,
)
return self.__http_transport_service

def settings_service(self, group_name):
if not self.__settings_service or \
group_name not in self.__instantiated_setting_group:
Expand Down
17 changes: 0 additions & 17 deletions modular_sdk/services/aws_creds_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,6 @@ def __init__(self, service_name: str, aws_region: str,
self._aws_secret_access_key = aws_secret_access_key
self._aws_session_token = aws_session_token

@staticmethod
def build_default_config() -> Optional[Config]:
"""
Default config for boto3 clients that respects modular proxy env
variables
"""
proxy = {}
if url := Env.HTTP_PROXY.get():
proxy['http'] = url
if url := Env.HTTPS_PROXY.get():
proxy['https'] = url
if proxy:
return Config(proxies=proxy)
return

@cached_property
def client(self) -> BaseClient:
_LOG.info(f'Initializing {self._service_name} boto3 client')
Expand All @@ -55,7 +40,6 @@ def client(self) -> BaseClient:
aws_access_key_id=self._aws_access_key_id,
aws_secret_access_key=self._aws_secret_access_key,
aws_session_token=self._aws_session_token,
config=self.build_default_config()
)


Expand Down Expand Up @@ -132,6 +116,5 @@ def __get__(self, instance, owner) -> BaseClient:
self._client = self.get_session().client(
service_name=self._service_name,
region_name=r,
config=AWSCredentialsProvider.build_default_config()
)
return self._client
46 changes: 19 additions & 27 deletions modular_sdk/services/impl/maestro_http_transport_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,30 @@
_LOG = get_logger(__name__)


class MaestroHTTPTransport(AbstractTransport):
class MaestroHTTPConfig:
def __init__(
self,
sdk_access_key: str,
sdk_secret_key: str,
maestro_user: str,
):
self.sdk_access_key = sdk_access_key
self.sdk_secret_key = sdk_secret_key
self.maestro_user = maestro_user


class MaestroHTTPTransport(AbstractTransport):
def __init__(
self,
config: MaestroHTTPConfig,
api_link: str,
timeout: int = HTTP_DEFAULT_RESPONSE_TIMEOUT,
timeout: int | None = HTTP_DEFAULT_RESPONSE_TIMEOUT,
):
self.access_key = sdk_access_key
self.secret_key = sdk_secret_key
self.user = maestro_user
self.access_key = config.sdk_access_key
self.secret_key = config.sdk_secret_key
self.user = config.maestro_user
self.api_link = api_link
self.timeout = timeout
self.timeout = timeout or HTTP_DEFAULT_RESPONSE_TIMEOUT

def pre_process_request(self, command_name: str, parameters: list[dict] | dict,
secure_parameters: list | None = None,
Expand Down Expand Up @@ -96,30 +106,12 @@ def post_process_request(self, response: bytes) -> tuple[int, str, Any]:
_LOG.error('Response cannot be decoded - invalid JSON string')
raise ModularException(code=502, content="Response can't be decoded")
status = response_json.get('status')
status_code = response_json.get('statusCode')
warnings = response_json.get('warnings')
code = response_json.get('statusCode')
if status == SUCCESS_STATUS:
data = response_json.get('data')
else:
data = response_json.get('error') or response_json.get('readableError')
try:
data = json.loads(data)
except json.decoder.JSONDecodeError:
data = data
response = {'status': status,'status_code': status_code}
if isinstance(data, str):
response.update({'message': data})
if isinstance(data, dict):
data = [data]
if isinstance(data, list):
response.update({'items': data})
if items := response_json.get('items'):
response.update({'items': items})
if table_title := response_json.get('tableTitle'):
response.update({'table_title': table_title})
if warnings:
response.update({'warnings': warnings})
return status_code, status, response
data = response_json.get('readableError') or response_json.get('error')
return code, status, data

def send_sync(self, command_name: str, parameters: list[dict] | dict,
secure_parameters: list | None = None,
Expand Down
13 changes: 4 additions & 9 deletions modular_sdk/services/impl/maestro_rabbit_transport_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ def pre_process_request(self, command_name, parameters, secure_parameters,
request_id=request_id,
is_flat_request=is_flat_request
)
_LOG.debug('Prepared command: {0}\nCommand format: {1}'
.format(command_name, secure_message))

signer = MaestroSignatureBuilder(
access_key=config.sdk_access_key if config and config.sdk_access_key else self.access_key,
Expand Down Expand Up @@ -101,18 +99,15 @@ def post_process_request(self, response: bytes) -> tuple[int, str, Any]:
except binascii.Error:
response_item = response.decode('utf-8')
try:
_LOG.debug(f'Raw decrypted message from server: {response_item}')
_LOG.debug('Received and decrypted message from server')
response_json = json.loads(response_item).get('results')[0]
except json.decoder.JSONDecodeError:
_LOG.error('Response can not be decoded - invalid Json string')
raise ModularException(
code=502, content='Response can not be decoded'
)
raise ModularException(code=502, content="Response can't be decoded")
status = response_json.get('status')
code = response_json.get('statusCode')
if status == SUCCESS_STATUS:
data = response_json.get('data')
return code, status, data
else:
data = response_json.get('readableError')
return code, status, data
data = response_json.get('readableError') or response_json.get('error')
return code, status, data
4 changes: 2 additions & 2 deletions modular_sdk/services/region_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
VIRT_PROFILE_ERROR_PATTERN = 'There is no virt profiles in region {0}'
SHAPE_MAPPING_ERROR_PATTERN = 'There is no shape mapping in region {0}'

_LOG = get_logger('modular_sdk-region-service')
_LOG = get_logger(__name__)


def _extract_region_fields(region_item):
_LOG = get_logger('_extract_region_fields')
_log = _LOG.getChild('_extract_region_fields')
region_fields = region_item.fields
if not region_fields:
_LOG.error('There are no fields in region item')
Expand Down
2 changes: 1 addition & 1 deletion modular_sdk/services/sqs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from modular_sdk.services.environment_service import EnvironmentService


_LOG = get_logger('modular_sdk-sqs-service')
_LOG = get_logger(__name__)


class SQSService(AWSCredentialsProvider):
Expand Down
2 changes: 1 addition & 1 deletion modular_sdk/services/thread_local_storage_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def storage(self):
return self.__storage.value

def set(self, key: str, value):
_LOG.debug(f'Setting {key}:{value} to storage')
_LOG.debug(f'Setting {key} to storage')
self.storage[key] = value

def get(self, key):
Expand Down
2 changes: 1 addition & 1 deletion modular_sdk/utils/job_tracer/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
ModularOperationModeManagerService
from modular_sdk.utils.job_tracer.abstract import AbstractJobTracer

_LOG = get_logger('modular_sdk-job-tracer')
_LOG = get_logger(__name__)


class ModularJobTracer(AbstractJobTracer):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "modular_sdk"
version = "6.2.0"
version = "6.2.1"
authors = [
{name = "EPAM Systems", email = "support@syndicate.team"}
]
Expand Down

0 comments on commit 79bd856

Please sign in to comment.