fdc-shared-kernel 0.0.4__tar.gz → 0.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/PKG-INFO +5 -1
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/pyproject.toml +1 -1
- fdc_shared_kernel-0.0.4/src/fdc_shared_kernel.egg-info/requires.txt → fdc_shared_kernel-0.0.5/requirements.txt +4 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/fdc_shared_kernel.egg-info/PKG-INFO +5 -1
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/fdc_shared_kernel.egg-info/SOURCES.txt +5 -1
- fdc_shared_kernel-0.0.4/requirements.txt → fdc_shared_kernel-0.0.5/src/fdc_shared_kernel.egg-info/requires.txt +5 -1
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/config/__init__.py +4 -4
- fdc_shared_kernel-0.0.5/src/shared_kernel/interfaces/__init__.py +2 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/interfaces/keyvault.py +29 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/messaging/nats_databus.py +11 -9
- fdc_shared_kernel-0.0.5/src/shared_kernel/security/__init__.py +1 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/security/key_vault/__init__.py +14 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/security/key_vault/aws_secret_manager.py +98 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/security/key_vault/azure_keyvault.py +67 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/tests/config/test_config.py +35 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/tests/logger/test_logger.py +48 -0
- fdc_shared_kernel-0.0.5/src/shared_kernel/tests/messaging/test_nats_interface.py +40 -0
- fdc_shared_kernel-0.0.4/src/shared_kernel/tests/__init__.py +0 -0
- fdc_shared_kernel-0.0.4/src/shared_kernel/tests/config/test_config.py +0 -35
- fdc_shared_kernel-0.0.4/src/shared_kernel/tests/logger/test_logger.py +0 -48
- fdc_shared_kernel-0.0.4/src/shared_kernel/tests/messaging/test_nats_interface.py +0 -36
- fdc_shared_kernel-0.0.4/src/shared_kernel/tests/utils/utils.py +0 -618
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/README.md +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/README_pypi.md +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/setup.cfg +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/fdc_shared_kernel.egg-info/top_level.txt +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/database/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/configuration_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/custom_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/http_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/operational_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/exceptions/security_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/interfaces/databus.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4/src/shared_kernel/interfaces → fdc_shared_kernel-0.0.5/src/shared_kernel/models}/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4/src/shared_kernel/models → fdc_shared_kernel-0.0.5/src/shared_kernel/tests}/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/tests/utils/test_data_validators.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/tests/utils/test_string_utils.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/utils/data_validators_utils.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/utils/date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/utils/string_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fdc_shared_kernel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Shared library for microservice
|
|
5
5
|
Author-email: Shikhil S <shikhil.s@dbizsolution.com>, Ahammed Akdham N <ahammedakdham.n@dbizsolution.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -26,6 +26,10 @@ Requires-Dist: python-dotenv==1.0.1
|
|
|
26
26
|
Requires-Dist: setuptools==71.0.0
|
|
27
27
|
Requires-Dist: SQLAlchemy==2.0.31
|
|
28
28
|
Requires-Dist: typing_extensions==4.12.2
|
|
29
|
+
Requires-Dist: boto3==1.34.148
|
|
30
|
+
Requires-Dist: boto3==1.34.148
|
|
31
|
+
Requires-Dist: azure-keyvault-secrets==4.8.0
|
|
32
|
+
Requires-Dist: azure-identity==1.17.1
|
|
29
33
|
|
|
30
34
|
# FDC Shared Kernel
|
|
31
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fdc_shared_kernel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Shared library for microservice
|
|
5
5
|
Author-email: Shikhil S <shikhil.s@dbizsolution.com>, Ahammed Akdham N <ahammedakdham.n@dbizsolution.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -26,6 +26,10 @@ Requires-Dist: python-dotenv==1.0.1
|
|
|
26
26
|
Requires-Dist: setuptools==71.0.0
|
|
27
27
|
Requires-Dist: SQLAlchemy==2.0.31
|
|
28
28
|
Requires-Dist: typing_extensions==4.12.2
|
|
29
|
+
Requires-Dist: boto3==1.34.148
|
|
30
|
+
Requires-Dist: boto3==1.34.148
|
|
31
|
+
Requires-Dist: azure-keyvault-secrets==4.8.0
|
|
32
|
+
Requires-Dist: azure-identity==1.17.1
|
|
29
33
|
|
|
30
34
|
# FDC Shared Kernel
|
|
31
35
|
|
{fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/fdc_shared_kernel.egg-info/SOURCES.txt
RENAMED
|
@@ -20,10 +20,15 @@ src/shared_kernel/exceptions/operational_exceptions.py
|
|
|
20
20
|
src/shared_kernel/exceptions/security_exceptions.py
|
|
21
21
|
src/shared_kernel/interfaces/__init__.py
|
|
22
22
|
src/shared_kernel/interfaces/databus.py
|
|
23
|
+
src/shared_kernel/interfaces/keyvault.py
|
|
23
24
|
src/shared_kernel/logger/__init__.py
|
|
24
25
|
src/shared_kernel/messaging/__init__.py
|
|
25
26
|
src/shared_kernel/messaging/nats_databus.py
|
|
26
27
|
src/shared_kernel/models/__init__.py
|
|
28
|
+
src/shared_kernel/security/__init__.py
|
|
29
|
+
src/shared_kernel/security/key_vault/__init__.py
|
|
30
|
+
src/shared_kernel/security/key_vault/aws_secret_manager.py
|
|
31
|
+
src/shared_kernel/security/key_vault/azure_keyvault.py
|
|
27
32
|
src/shared_kernel/tests/__init__.py
|
|
28
33
|
src/shared_kernel/tests/config/test_config.py
|
|
29
34
|
src/shared_kernel/tests/logger/test_logger.py
|
|
@@ -31,7 +36,6 @@ src/shared_kernel/tests/messaging/test_nats_interface.py
|
|
|
31
36
|
src/shared_kernel/tests/utils/test_data_validators.py
|
|
32
37
|
src/shared_kernel/tests/utils/test_date_format_utils.py
|
|
33
38
|
src/shared_kernel/tests/utils/test_string_utils.py
|
|
34
|
-
src/shared_kernel/tests/utils/utils.py
|
|
35
39
|
src/shared_kernel/utils/__init__.py
|
|
36
40
|
src/shared_kernel/utils/data_validators_utils.py
|
|
37
41
|
src/shared_kernel/utils/date_format_utils.py
|
|
@@ -2,9 +2,9 @@ import os
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
from dotenv import load_dotenv, find_dotenv
|
|
4
4
|
from shared_kernel.exceptions import MissingConfiguration, InvalidConfiguration
|
|
5
|
-
import
|
|
5
|
+
from src.shared_kernel.logger import Logger
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
logger = Logger('SHARED_KERNEL')
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Config:
|
|
@@ -26,12 +26,12 @@ class Config:
|
|
|
26
26
|
if env_path is None:
|
|
27
27
|
dotenv_path = find_dotenv()
|
|
28
28
|
if not dotenv_path:
|
|
29
|
-
|
|
29
|
+
logger.error(".env file not found")
|
|
30
30
|
raise InvalidConfiguration(".env file not found")
|
|
31
31
|
else:
|
|
32
32
|
dotenv_path = env_path
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
logger.info(f"Loading environment variables from {dotenv_path}")
|
|
35
35
|
load_dotenv(dotenv_path)
|
|
36
36
|
|
|
37
37
|
@staticmethod
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KeyVaultInterface(ABC):
|
|
5
|
+
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def __init__(self, config: dict):
|
|
8
|
+
"""Initialize the key vault connection with the given configuration dictionary."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def store_secret(self, name: str, secret: str) -> None:
|
|
13
|
+
"""Store a secret in the key vault."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def retrieve_secret(self, name: str) -> str:
|
|
18
|
+
"""Retrieve a secret from the key vault."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def delete_secret(self, name: str) -> None:
|
|
23
|
+
"""Delete a secret from the key vault."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def list_secrets(self) -> list:
|
|
28
|
+
"""List all secrets in the key vault."""
|
|
29
|
+
pass
|
{fdc_shared_kernel-0.0.4 → fdc_shared_kernel-0.0.5}/src/shared_kernel/messaging/nats_databus.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import logging
|
|
3
2
|
from nats.aio.client import Client as NATS
|
|
4
3
|
from nats.js.api import ConsumerConfig, DeliverPolicy, StreamConfig
|
|
5
4
|
from typing import Callable, Any, List, Union
|
|
5
|
+
from src.shared_kernel.interfaces import DataBus
|
|
6
|
+
from src.shared_kernel.logger import Logger
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class NATSDataBus:
|
|
9
|
+
class NATSDataBus(DataBus):
|
|
9
10
|
"""
|
|
10
11
|
A NATS Interface class to handle both standard NATS and JetStream operations.
|
|
11
12
|
"""
|
|
@@ -31,6 +32,7 @@ class NATSDataBus:
|
|
|
31
32
|
self.connected = False
|
|
32
33
|
self.js = None # JetStream context
|
|
33
34
|
self.initialized = True
|
|
35
|
+
self.logger = Logger('SHARED_KERNEL')
|
|
34
36
|
|
|
35
37
|
async def make_connection(self):
|
|
36
38
|
"""
|
|
@@ -88,12 +90,12 @@ class NATSDataBus:
|
|
|
88
90
|
ack = await self.js.publish(
|
|
89
91
|
topic, json.dumps(event_payload).encode("utf-8")
|
|
90
92
|
)
|
|
91
|
-
|
|
93
|
+
self.logger.info(
|
|
92
94
|
f"Published event '{event_payload.get('event_name')}' to topic '{topic}', ack: {ack}"
|
|
93
95
|
)
|
|
94
96
|
return True
|
|
95
97
|
except Exception as e:
|
|
96
|
-
|
|
98
|
+
self.logger.error(
|
|
97
99
|
f"Failed to publish event '{event_payload.get('event_name')}': {str(e)}",
|
|
98
100
|
exc_info=True,
|
|
99
101
|
)
|
|
@@ -119,7 +121,7 @@ class NATSDataBus:
|
|
|
119
121
|
)
|
|
120
122
|
return json.loads(response.data.decode("utf-8"))
|
|
121
123
|
except Exception as e:
|
|
122
|
-
|
|
124
|
+
self.logger.error(f"Failed to request topic '{topic}': {e}", exc_info=True)
|
|
123
125
|
raise e
|
|
124
126
|
|
|
125
127
|
async def subscribe_async_event(
|
|
@@ -142,10 +144,10 @@ class NATSDataBus:
|
|
|
142
144
|
)
|
|
143
145
|
|
|
144
146
|
await self.js.subscribe(topic, cb=callback, config=consumer_config)
|
|
145
|
-
|
|
147
|
+
self.logger.info(f"Subscribed to async event on topic '{topic}'")
|
|
146
148
|
|
|
147
149
|
except Exception as e:
|
|
148
|
-
|
|
150
|
+
self.logger.error(
|
|
149
151
|
f"Failed to subscribe to async event on topic '{topic}': {e}",
|
|
150
152
|
exc_info=True,
|
|
151
153
|
)
|
|
@@ -161,10 +163,10 @@ class NATSDataBus:
|
|
|
161
163
|
"""
|
|
162
164
|
try:
|
|
163
165
|
await self.nc.subscribe(topic, cb=callback)
|
|
164
|
-
|
|
166
|
+
self.logger.info(f"Subscribed to sync event on topic '{topic}'")
|
|
165
167
|
|
|
166
168
|
except Exception as e:
|
|
167
|
-
|
|
169
|
+
self.logger.error(
|
|
168
170
|
f"Failed to subscribe to sync event on topic '{topic}': {e}",
|
|
169
171
|
exc_info=True,
|
|
170
172
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from src.shared_kernel.security.key_vault import *
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from src.shared_kernel.interfaces import KeyVaultInterface
|
|
2
|
+
from src.shared_kernel.security.key_vault.aws_secret_manager import AWSSecretsManager
|
|
3
|
+
from src.shared_kernel.security.key_vault.azure_keyvault import AzureKeyVault
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class KeyVaultFactory:
|
|
7
|
+
@staticmethod
|
|
8
|
+
def create_key_vault(vault_type: str, config: dict) -> KeyVaultInterface:
|
|
9
|
+
if vault_type == 'AZURE':
|
|
10
|
+
return AzureKeyVault(config)
|
|
11
|
+
elif vault_type == 'AWS':
|
|
12
|
+
return AWSSecretsManager(config)
|
|
13
|
+
else:
|
|
14
|
+
raise ValueError(f"Unknown vault type: {vault_type}")
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
from botocore.exceptions import ClientError
|
|
3
|
+
from src.shared_kernel.interfaces import KeyVaultInterface
|
|
4
|
+
from src.shared_kernel.logger import Logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AWSSecretsManager(KeyVaultInterface):
|
|
8
|
+
def __init__(self, config: dict):
|
|
9
|
+
"""
|
|
10
|
+
Initialize the AWS Secrets Manager connection with the given configuration.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (dict): Configuration dictionary containing region_name, aws_access_key_id, _aws_secret_access_key
|
|
14
|
+
"""
|
|
15
|
+
self._region_name = config.get('region_name')
|
|
16
|
+
self._aws_access_key_id = config.get('AWS_SERVER_PUBLIC_KEY')
|
|
17
|
+
self._aws_secret_access_key = config.get('AWS_SERVER_SECRET_KEY')
|
|
18
|
+
self._client = boto3.client('secretsmanager',
|
|
19
|
+
aws_access_key_id=self._aws_access_key_id,
|
|
20
|
+
aws_secret_access_key=self._aws_secret_access_key,
|
|
21
|
+
region_name=self._region_name)
|
|
22
|
+
self.logger = Logger('SHARED_KERNEL')
|
|
23
|
+
self.logger.info(f"Connected to AWS Secrets Manager in region: {self._region_name}")
|
|
24
|
+
|
|
25
|
+
def store_secret(self, name: str, secret: str) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Store a secret in AWS Secrets Manager.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name (str): The name of the secret.
|
|
31
|
+
secret (str): The value of the secret.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ClientError: If storing the secret fails.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
self._client.create_secret(Name=name, SecretString=secret)
|
|
38
|
+
self.logger.info(f"Stored secret '{name}'")
|
|
39
|
+
except ClientError as e:
|
|
40
|
+
if e.response['Error']['Code'] == 'ResourceExistsException':
|
|
41
|
+
self._client.update_secret(SecretId=name, SecretString=secret)
|
|
42
|
+
self.logger.info(f"Updated secret '{name}'")
|
|
43
|
+
else:
|
|
44
|
+
self.logger.error(f"Failed to update secret '{name}' | Error: '{e}'")
|
|
45
|
+
raise e
|
|
46
|
+
|
|
47
|
+
def retrieve_secret(self, name: str) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Retrieve a secret from AWS Secrets Manager.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name (str): The name of the secret.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: The value of the retrieved secret, or None if retrieval fails.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ClientError: If retrieving the secret fails.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
response = self._client.get_secret_value(SecretId=name)
|
|
62
|
+
return response['SecretString']
|
|
63
|
+
except ClientError as e:
|
|
64
|
+
self.logger.error(f"Error retrieving secret '{name}': {e}")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def delete_secret(self, name: str) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Delete a secret from AWS Secrets Manager.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
name (str): The name of the secret.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ClientError: If deleting the secret fails.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
self._client.delete_secret(SecretId=name, ForceDeleteWithoutRecovery=True)
|
|
79
|
+
self.logger.info(f"Deleted secret '{name}'")
|
|
80
|
+
except ClientError as e:
|
|
81
|
+
self.logger.error(f"Error deleting secret '{name}': {e}")
|
|
82
|
+
|
|
83
|
+
def list_secrets(self) -> list:
|
|
84
|
+
"""
|
|
85
|
+
List all secrets in AWS Secrets Manager.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
list: A list of secret names.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ClientError: If listing the secrets fails.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
response = self._client.list_secrets()
|
|
95
|
+
return [secret['Name'] for secret in response['SecretList']]
|
|
96
|
+
except ClientError as e:
|
|
97
|
+
self.logger.error(f"Error listing secrets: {e}")
|
|
98
|
+
return []
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from azure.identity import DefaultAzureCredential, ClientSecretCredential
|
|
2
|
+
from azure.keyvault.secrets import SecretClient
|
|
3
|
+
from src.shared_kernel.interfaces import KeyVaultInterface
|
|
4
|
+
from src.shared_kernel.logger import Logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AzureKeyVault(KeyVaultInterface):
|
|
8
|
+
def __init__(self, config: dict):
|
|
9
|
+
"""
|
|
10
|
+
Initialize the Azure Key Vault connection with the given configuration.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (dict): Configuration dictionary containing 'vault_url'.
|
|
14
|
+
"""
|
|
15
|
+
self._vault_url = config.get('vault_url')
|
|
16
|
+
self._credential = ClientSecretCredential(
|
|
17
|
+
tenant_id=config.get('tenant_id'),
|
|
18
|
+
client_id=config.get('client_id'),
|
|
19
|
+
client_secret=config.get('client_secret')
|
|
20
|
+
)
|
|
21
|
+
self._client = SecretClient(vault_url=self._vault_url, credential=self._credential)
|
|
22
|
+
self.logger = Logger('SHARED_KERNEL')
|
|
23
|
+
self.logger.info(f"Connected to Azure Key Vault at: {self._vault_url}")
|
|
24
|
+
|
|
25
|
+
def store_secret(self, name: str, secret: str) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Store a secret in Azure Key Vault.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name (str): The name of the secret.
|
|
31
|
+
secret (str): The value of the secret.
|
|
32
|
+
"""
|
|
33
|
+
self._client.set_secret(name, secret)
|
|
34
|
+
self.logger.info(f"Stored secret '{name}'")
|
|
35
|
+
|
|
36
|
+
def retrieve_secret(self, name: str) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Retrieve a secret from Azure Key Vault.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name (str): The name of the secret.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: The value of the retrieved secret.
|
|
45
|
+
"""
|
|
46
|
+
retrieved_secret = self._client.get_secret(name)
|
|
47
|
+
return retrieved_secret.value
|
|
48
|
+
|
|
49
|
+
def delete_secret(self, name: str) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Delete a secret from Azure Key Vault.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name (str): The name of the secret.
|
|
55
|
+
"""
|
|
56
|
+
self._client.begin_delete_secret(name)
|
|
57
|
+
self.logger.info(f"Deleted secret '{name}'")
|
|
58
|
+
|
|
59
|
+
def list_secrets(self) -> list:
|
|
60
|
+
"""
|
|
61
|
+
List all secrets in Azure Key Vault.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
list: A list of secret names.
|
|
65
|
+
"""
|
|
66
|
+
secrets = self._client.list_properties_of_secrets()
|
|
67
|
+
return [secret.name for secret in secrets]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import os
|
|
3
|
+
from unittest.mock import patch, mock_open
|
|
4
|
+
from shared_kernel.config import Config
|
|
5
|
+
from shared_kernel.exceptions import InvalidConfiguration, MissingConfiguration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestConfig(unittest.TestCase):
|
|
9
|
+
|
|
10
|
+
@patch('shared_kernel.config.find_dotenv', return_value='.env')
|
|
11
|
+
def test_init_with_env_file_found(self, mock_find_dotenv):
|
|
12
|
+
with patch('builtins.open', mock_open(read_data='KEY=value')):
|
|
13
|
+
config=Config()
|
|
14
|
+
self.assertEqual(config.get('KEY'), 'value')
|
|
15
|
+
|
|
16
|
+
@patch('shared_kernel.config.find_dotenv', return_value='')
|
|
17
|
+
def test_init_with_env_file_not_found(self, mock_find_dotenv):
|
|
18
|
+
with self.assertRaises(InvalidConfiguration):
|
|
19
|
+
Config()
|
|
20
|
+
|
|
21
|
+
@patch('shared_kernel.config.find_dotenv', return_value='.env')
|
|
22
|
+
def test_get_existing_variable(self, mock_find_dotenv):
|
|
23
|
+
with patch.dict(os.environ, {'EXISTING_KEY': 'existing_value'}):
|
|
24
|
+
config=Config()
|
|
25
|
+
self.assertEqual(config.get('EXISTING_KEY'), 'existing_value')
|
|
26
|
+
|
|
27
|
+
@patch('shared_kernel.config.find_dotenv', return_value='/mocked/path/to/.env')
|
|
28
|
+
def test_get_non_existing_variable(self, mock_find_dotenv):
|
|
29
|
+
with self.assertRaises(MissingConfiguration):
|
|
30
|
+
config=Config()
|
|
31
|
+
config.get('NON_EXISTING_KEY')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == '__main__':
|
|
35
|
+
unittest.main()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
import logging
|
|
4
|
+
from shared_kernel.logger import Logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestLogger(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
@patch('logging.getLogger')
|
|
10
|
+
def setUp(self, mock_get_logger):
|
|
11
|
+
# Setup runs before each test method
|
|
12
|
+
self.mock_logger = MagicMock(spec=logging.Logger)
|
|
13
|
+
self.mock_logger.handlers = [] # Initialize handlers attribute
|
|
14
|
+
self.mock_logger.level = logging.NOTSET # Set the level attribute
|
|
15
|
+
mock_get_logger.return_value = self.mock_logger
|
|
16
|
+
self.test_logger = Logger(name='test_logger', log_file='test_log.log')
|
|
17
|
+
|
|
18
|
+
def test_init(self):
|
|
19
|
+
# Test initialization parameters
|
|
20
|
+
self.assertEqual(self.test_logger.name, 'test_logger')
|
|
21
|
+
self.assertEqual(self.test_logger.log_file, 'test_log.log')
|
|
22
|
+
|
|
23
|
+
def test_configure_logger(self):
|
|
24
|
+
# Test logger configuration
|
|
25
|
+
self.test_logger.configure_logger()
|
|
26
|
+
self.mock_logger.addHandler.assert_called()
|
|
27
|
+
|
|
28
|
+
def test_info_logging(self):
|
|
29
|
+
# Test info logging
|
|
30
|
+
message = 'Test info message'
|
|
31
|
+
self.test_logger.info(message)
|
|
32
|
+
self.mock_logger.info.assert_called_with(message)
|
|
33
|
+
|
|
34
|
+
def test_error_logging(self):
|
|
35
|
+
# Test error logging
|
|
36
|
+
message = 'Test error message'
|
|
37
|
+
self.test_logger.error(message)
|
|
38
|
+
self.mock_logger.error.assert_called_with(message)
|
|
39
|
+
|
|
40
|
+
def test_debug_logging(self):
|
|
41
|
+
# Test debug logging
|
|
42
|
+
message = 'Test debug message'
|
|
43
|
+
self.test_logger.debug(message)
|
|
44
|
+
self.mock_logger.debug.assert_called_with(message)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == '__main__':
|
|
48
|
+
unittest.main()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# import pytest
|
|
2
|
+
# from asynctest import CoroutineMock, patch
|
|
3
|
+
# from shared_kernel.messaging import NATSInterface
|
|
4
|
+
#
|
|
5
|
+
# @pytest.fixture
|
|
6
|
+
# async def nats_interface():
|
|
7
|
+
# nats = NATSInterface(servers=None)
|
|
8
|
+
# await nats.connect()
|
|
9
|
+
# yield nats
|
|
10
|
+
# await nats.close()
|
|
11
|
+
#
|
|
12
|
+
# @pytest.mark.asyncio
|
|
13
|
+
# @patch('shared_kernel.messaging.NATSInterface.connect', new_callable=CoroutineMock)
|
|
14
|
+
# @patch('shared_kernel.messaging.NATSInterface.close', new_callable=CoroutineMock)
|
|
15
|
+
# async def test_connect_and_close(mock_connect, mock_close, nats_interface):
|
|
16
|
+
# await nats_interface.connect()
|
|
17
|
+
# mock_connect.assert_called_once()
|
|
18
|
+
#
|
|
19
|
+
# await nats_interface.close()
|
|
20
|
+
# mock_close.assert_called_once()
|
|
21
|
+
#
|
|
22
|
+
# @pytest.mark.asyncio
|
|
23
|
+
# @patch('shared_kernel.messaging.NATSInterface.publish', new_callable=CoroutineMock)
|
|
24
|
+
# async def test_publish(mock_publish, nats_interface):
|
|
25
|
+
# await nats_interface.connect()
|
|
26
|
+
# await nats_interface.publish("test_subject", "Hello, NATS!")
|
|
27
|
+
# mock_publish.assert_called_once_with("test_subject", b"Hello, NATS!")
|
|
28
|
+
# await nats_interface.close()
|
|
29
|
+
#
|
|
30
|
+
# @pytest.mark.asyncio
|
|
31
|
+
# @patch('shared_kernel.messaging.NATSInterface.subscribe', new_callable=CoroutineMock)
|
|
32
|
+
# async def test_subscribe(mock_subscribe, nats_interface):
|
|
33
|
+
# await nats_interface.connect()
|
|
34
|
+
#
|
|
35
|
+
# async def mock_callback(msg):
|
|
36
|
+
# return None
|
|
37
|
+
#
|
|
38
|
+
# await nats_interface.subscribe("test_subject", mock_callback)
|
|
39
|
+
# mock_subscribe.assert_called_once_with("test_subject", cb=mock_callback)
|
|
40
|
+
# await nats_interface.close()
|
|
File without changes
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# import unittest
|
|
2
|
-
# import os
|
|
3
|
-
# from unittest.mock import patch, mock_open
|
|
4
|
-
# from shared_kernel.config import Config
|
|
5
|
-
# from shared_kernel.exceptions import InvalidConfiguration, MissingConfiguration
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# class TestConfig(unittest.TestCase):
|
|
9
|
-
#
|
|
10
|
-
# @patch('shared_kernel.config.find_dotenv', return_value='.env')
|
|
11
|
-
# def test_init_with_env_file_found(self, mock_find_dotenv):
|
|
12
|
-
# with patch('builtins.open', mock_open(read_data='KEY=value')):
|
|
13
|
-
# config=Config()
|
|
14
|
-
# self.assertEqual(config.get('KEY'), 'value')
|
|
15
|
-
#
|
|
16
|
-
# @patch('shared_kernel.config.find_dotenv', return_value='')
|
|
17
|
-
# def test_init_with_env_file_not_found(self, mock_find_dotenv):
|
|
18
|
-
# with self.assertRaises(InvalidConfiguration):
|
|
19
|
-
# Config()
|
|
20
|
-
#
|
|
21
|
-
# @patch('shared_kernel.config.find_dotenv', return_value='.env')
|
|
22
|
-
# def test_get_existing_variable(self, mock_find_dotenv):
|
|
23
|
-
# with patch.dict(os.environ, {'EXISTING_KEY': 'existing_value'}):
|
|
24
|
-
# config=Config()
|
|
25
|
-
# self.assertEqual(config.get('EXISTING_KEY'), 'existing_value')
|
|
26
|
-
#
|
|
27
|
-
# @patch('shared_kernel.config.find_dotenv', return_value='/mocked/path/to/.env')
|
|
28
|
-
# def test_get_non_existing_variable(self, mock_find_dotenv):
|
|
29
|
-
# with self.assertRaises(MissingConfiguration):
|
|
30
|
-
# config=Config()
|
|
31
|
-
# config.get('NON_EXISTING_KEY')
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# if __name__ == '__main__':
|
|
35
|
-
# unittest.main()
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# import unittest
|
|
2
|
-
# from unittest.mock import patch, MagicMock
|
|
3
|
-
# import logging
|
|
4
|
-
# from shared_kernel.logger import Logger
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# class TestLogger(unittest.TestCase):
|
|
8
|
-
#
|
|
9
|
-
# @patch('logging.getLogger')
|
|
10
|
-
# def setUp(self, mock_get_logger):
|
|
11
|
-
# # Setup runs before each test method
|
|
12
|
-
# self.mock_logger = MagicMock(spec=logging.Logger)
|
|
13
|
-
# self.mock_logger.handlers = [] # Initialize handlers attribute
|
|
14
|
-
# self.mock_logger.level = logging.NOTSET # Set the level attribute
|
|
15
|
-
# mock_get_logger.return_value = self.mock_logger
|
|
16
|
-
# self.test_logger = Logger(name='test_logger', log_file='test_log.log')
|
|
17
|
-
#
|
|
18
|
-
# def test_init(self):
|
|
19
|
-
# # Test initialization parameters
|
|
20
|
-
# self.assertEqual(self.test_logger.name, 'test_logger')
|
|
21
|
-
# self.assertEqual(self.test_logger.log_file, 'test_log.log')
|
|
22
|
-
#
|
|
23
|
-
# def test_configure_logger(self):
|
|
24
|
-
# # Test logger configuration
|
|
25
|
-
# self.test_logger.configure_logger()
|
|
26
|
-
# self.mock_logger.addHandler.assert_called()
|
|
27
|
-
#
|
|
28
|
-
# def test_info_logging(self):
|
|
29
|
-
# # Test info logging
|
|
30
|
-
# message = 'Test info message'
|
|
31
|
-
# self.test_logger.info(message)
|
|
32
|
-
# self.mock_logger.info.assert_called_with(message)
|
|
33
|
-
#
|
|
34
|
-
# def test_error_logging(self):
|
|
35
|
-
# # Test error logging
|
|
36
|
-
# message = 'Test error message'
|
|
37
|
-
# self.test_logger.error(message)
|
|
38
|
-
# self.mock_logger.error.assert_called_with(message)
|
|
39
|
-
#
|
|
40
|
-
# def test_debug_logging(self):
|
|
41
|
-
# # Test debug logging
|
|
42
|
-
# message = 'Test debug message'
|
|
43
|
-
# self.test_logger.debug(message)
|
|
44
|
-
# self.mock_logger.debug.assert_called_with(message)
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# if __name__ == '__main__':
|
|
48
|
-
# unittest.main()
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from unittest.mock import patch
|
|
3
|
-
from nats.aio.client import Client as NATS
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@pytest.mark.asyncio
|
|
8
|
-
async def test_nats_subscriber():
|
|
9
|
-
# Start the patch
|
|
10
|
-
mock_client = patch('nats.aio.client.Client').start()
|
|
11
|
-
|
|
12
|
-
# Get the mock instance
|
|
13
|
-
instance = mock_client.return_value
|
|
14
|
-
|
|
15
|
-
# Configure the mock to return True for is_connected and None for publish
|
|
16
|
-
instance.is_connected.return_value = True
|
|
17
|
-
instance.publish.return_value = None
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
# Initialize the NATS client
|
|
21
|
-
nc = NATS()
|
|
22
|
-
|
|
23
|
-
# Connect to the mock NATS server
|
|
24
|
-
await nc.connect('nats://localhost:4222')
|
|
25
|
-
|
|
26
|
-
# Subscribe to a topic asynchronously
|
|
27
|
-
await nc.subscribe('foo', cb=lambda m: print(f'Received: {m.data}'))
|
|
28
|
-
|
|
29
|
-
# Publish a message to the subscribed topic
|
|
30
|
-
await nc.publish('foo', b'bar')
|
|
31
|
-
|
|
32
|
-
# Assert that the callback was called with the published message
|
|
33
|
-
instance.publish.assert_called_once_with('foo', b'bar')
|
|
34
|
-
finally:
|
|
35
|
-
# Stop the patch after the test
|
|
36
|
-
mock_client.stop()
|