envilder 0.1.0__py3-none-any.whl
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.
- envilder/__init__.py +29 -0
- envilder/application/__init__.py +7 -0
- envilder/application/envilder_client.py +28 -0
- envilder/application/map_file_parser.py +60 -0
- envilder/domain/__init__.py +13 -0
- envilder/domain/envilder_options.py +12 -0
- envilder/domain/map_file_config.py +12 -0
- envilder/domain/parsed_map_file.py +11 -0
- envilder/domain/ports/__init__.py +5 -0
- envilder/domain/ports/secret_provider.py +7 -0
- envilder/domain/secret_provider_type.py +6 -0
- envilder/infrastructure/__init__.py +15 -0
- envilder/infrastructure/aws/__init__.py +5 -0
- envilder/infrastructure/aws/aws_ssm_secret_provider.py +30 -0
- envilder/infrastructure/azure/__init__.py +5 -0
- envilder/infrastructure/azure/azure_key_vault_secret_provider.py +24 -0
- envilder/infrastructure/secret_provider_factory.py +96 -0
- envilder/py.typed +0 -0
- envilder-0.1.0.dist-info/METADATA +166 -0
- envilder-0.1.0.dist-info/RECORD +21 -0
- envilder-0.1.0.dist-info/WHEEL +4 -0
envilder/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from envilder.application.envilder_client import EnvilderClient
|
|
2
|
+
from envilder.application.map_file_parser import MapFileParser
|
|
3
|
+
from envilder.domain.envilder_options import EnvilderOptions
|
|
4
|
+
from envilder.domain.map_file_config import MapFileConfig
|
|
5
|
+
from envilder.domain.parsed_map_file import ParsedMapFile
|
|
6
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
7
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
8
|
+
from envilder.infrastructure.aws.aws_ssm_secret_provider import (
|
|
9
|
+
AwsSsmSecretProvider,
|
|
10
|
+
)
|
|
11
|
+
from envilder.infrastructure.azure.azure_key_vault_secret_provider import (
|
|
12
|
+
AzureKeyVaultSecretProvider,
|
|
13
|
+
)
|
|
14
|
+
from envilder.infrastructure.secret_provider_factory import (
|
|
15
|
+
SecretProviderFactory,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AwsSsmSecretProvider",
|
|
20
|
+
"AzureKeyVaultSecretProvider",
|
|
21
|
+
"EnvilderClient",
|
|
22
|
+
"EnvilderOptions",
|
|
23
|
+
"ISecretProvider",
|
|
24
|
+
"MapFileConfig",
|
|
25
|
+
"MapFileParser",
|
|
26
|
+
"ParsedMapFile",
|
|
27
|
+
"SecretProviderFactory",
|
|
28
|
+
"SecretProviderType",
|
|
29
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from envilder.domain.parsed_map_file import ParsedMapFile
|
|
6
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EnvilderClient:
|
|
10
|
+
def __init__(self, secret_provider: ISecretProvider) -> None:
|
|
11
|
+
if secret_provider is None:
|
|
12
|
+
raise ValueError("secret_provider cannot be None")
|
|
13
|
+
self._secret_provider = secret_provider
|
|
14
|
+
|
|
15
|
+
def resolve_secrets(self, map_file: ParsedMapFile) -> dict[str, str]:
|
|
16
|
+
result: dict[str, str] = {}
|
|
17
|
+
|
|
18
|
+
for env_var_name, secret_path in map_file.mappings.items():
|
|
19
|
+
secret_value = self._secret_provider.get_secret(secret_path)
|
|
20
|
+
if secret_value is not None:
|
|
21
|
+
result[env_var_name] = secret_value
|
|
22
|
+
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def inject_into_environment(secrets: dict[str, str]) -> None:
|
|
27
|
+
for key, value in secrets.items():
|
|
28
|
+
os.environ[key] = value
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from envilder.domain.map_file_config import MapFileConfig
|
|
6
|
+
from envilder.domain.parsed_map_file import ParsedMapFile
|
|
7
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
8
|
+
|
|
9
|
+
_CONFIG_KEY = "$config"
|
|
10
|
+
|
|
11
|
+
_PROVIDER_MAP = {
|
|
12
|
+
"aws": SecretProviderType.AWS,
|
|
13
|
+
"azure": SecretProviderType.AZURE,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MapFileParser:
|
|
18
|
+
def parse(self, json_content: str) -> ParsedMapFile:
|
|
19
|
+
document = json.loads(json_content)
|
|
20
|
+
|
|
21
|
+
if not isinstance(document, dict):
|
|
22
|
+
raise ValueError("Map file must be a JSON object.")
|
|
23
|
+
|
|
24
|
+
mappings: dict[str, str] = {}
|
|
25
|
+
config = MapFileConfig()
|
|
26
|
+
|
|
27
|
+
for key, value in document.items():
|
|
28
|
+
if key == _CONFIG_KEY:
|
|
29
|
+
if isinstance(value, dict):
|
|
30
|
+
config = _deserialize_config(value)
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if isinstance(value, str):
|
|
34
|
+
mappings[key] = value
|
|
35
|
+
|
|
36
|
+
return ParsedMapFile(config=config, mappings=mappings)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _deserialize_config(raw: dict[str, object]) -> MapFileConfig:
|
|
40
|
+
provider_raw = raw.get("provider")
|
|
41
|
+
provider = None
|
|
42
|
+
if provider_raw is not None:
|
|
43
|
+
if not isinstance(provider_raw, str):
|
|
44
|
+
raise ValueError("Provider must be a string.")
|
|
45
|
+
normalized = provider_raw.strip().lower()
|
|
46
|
+
provider = _PROVIDER_MAP.get(normalized)
|
|
47
|
+
if provider is None:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Unsupported provider '{provider_raw}'."
|
|
50
|
+
" Expected one of: aws, azure."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
vault_url = raw.get("vaultUrl")
|
|
54
|
+
profile = raw.get("profile")
|
|
55
|
+
|
|
56
|
+
return MapFileConfig(
|
|
57
|
+
provider=provider,
|
|
58
|
+
vault_url=vault_url if isinstance(vault_url, str) else None,
|
|
59
|
+
profile=profile if isinstance(profile, str) else None,
|
|
60
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from envilder.domain.envilder_options import EnvilderOptions
|
|
2
|
+
from envilder.domain.map_file_config import MapFileConfig
|
|
3
|
+
from envilder.domain.parsed_map_file import ParsedMapFile
|
|
4
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
5
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"EnvilderOptions",
|
|
9
|
+
"ISecretProvider",
|
|
10
|
+
"MapFileConfig",
|
|
11
|
+
"ParsedMapFile",
|
|
12
|
+
"SecretProviderType",
|
|
13
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class EnvilderOptions:
|
|
10
|
+
provider: SecretProviderType | None = field(default=None)
|
|
11
|
+
vault_url: str | None = field(default=None)
|
|
12
|
+
profile: str | None = field(default=None)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class MapFileConfig:
|
|
10
|
+
provider: SecretProviderType | None = field(default=None)
|
|
11
|
+
vault_url: str | None = field(default=None)
|
|
12
|
+
profile: str | None = field(default=None)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from envilder.infrastructure.aws.aws_ssm_secret_provider import (
|
|
2
|
+
AwsSsmSecretProvider,
|
|
3
|
+
)
|
|
4
|
+
from envilder.infrastructure.azure.azure_key_vault_secret_provider import (
|
|
5
|
+
AzureKeyVaultSecretProvider,
|
|
6
|
+
)
|
|
7
|
+
from envilder.infrastructure.secret_provider_factory import (
|
|
8
|
+
SecretProviderFactory,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AwsSsmSecretProvider",
|
|
13
|
+
"AzureKeyVaultSecretProvider",
|
|
14
|
+
"SecretProviderFactory",
|
|
15
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
from mypy_boto3_ssm import SSMClient
|
|
5
|
+
|
|
6
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AwsSsmSecretProvider(ISecretProvider):
|
|
10
|
+
def __init__(self, ssm_client: SSMClient) -> None:
|
|
11
|
+
if ssm_client is None:
|
|
12
|
+
raise ValueError("ssm_client cannot be None")
|
|
13
|
+
self._ssm_client = ssm_client
|
|
14
|
+
|
|
15
|
+
def get_secret(self, name: str) -> str | None:
|
|
16
|
+
if not name or not name.strip():
|
|
17
|
+
raise ValueError("Secret name cannot be null or whitespace.")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
response = self._ssm_client.get_parameter(
|
|
21
|
+
Name=name, WithDecryption=True
|
|
22
|
+
)
|
|
23
|
+
param = response.get("Parameter")
|
|
24
|
+
value = param.get("Value") if param else None
|
|
25
|
+
return str(value) if value is not None else None
|
|
26
|
+
except ClientError as e:
|
|
27
|
+
error = e.response.get("Error", {})
|
|
28
|
+
if error.get("Code") == "ParameterNotFound":
|
|
29
|
+
return None
|
|
30
|
+
raise
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from azure.core.exceptions import ResourceNotFoundError
|
|
4
|
+
from azure.keyvault.secrets import SecretClient
|
|
5
|
+
|
|
6
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AzureKeyVaultSecretProvider(ISecretProvider):
|
|
10
|
+
def __init__(self, secret_client: SecretClient) -> None:
|
|
11
|
+
if secret_client is None:
|
|
12
|
+
raise ValueError("secret_client cannot be None")
|
|
13
|
+
self._secret_client = secret_client
|
|
14
|
+
|
|
15
|
+
def get_secret(self, name: str) -> str | None:
|
|
16
|
+
if not name or not name.strip():
|
|
17
|
+
raise ValueError("Secret name cannot be null or empty.")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
response = self._secret_client.get_secret(name)
|
|
21
|
+
value: str | None = response.value
|
|
22
|
+
return value
|
|
23
|
+
except ResourceNotFoundError:
|
|
24
|
+
return None
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
from azure.identity import DefaultAzureCredential
|
|
7
|
+
from azure.keyvault.secrets import SecretClient
|
|
8
|
+
|
|
9
|
+
from envilder.domain.envilder_options import EnvilderOptions
|
|
10
|
+
from envilder.domain.map_file_config import MapFileConfig
|
|
11
|
+
from envilder.domain.ports.secret_provider import ISecretProvider
|
|
12
|
+
from envilder.domain.secret_provider_type import SecretProviderType
|
|
13
|
+
from envilder.infrastructure.aws.aws_ssm_secret_provider import (
|
|
14
|
+
AwsSsmSecretProvider,
|
|
15
|
+
)
|
|
16
|
+
from envilder.infrastructure.azure.azure_key_vault_secret_provider import (
|
|
17
|
+
AzureKeyVaultSecretProvider,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SecretProviderFactory:
|
|
22
|
+
@staticmethod
|
|
23
|
+
def create(
|
|
24
|
+
config: MapFileConfig,
|
|
25
|
+
options: EnvilderOptions | None = None,
|
|
26
|
+
) -> ISecretProvider:
|
|
27
|
+
if config is None:
|
|
28
|
+
raise ValueError("config cannot be None")
|
|
29
|
+
|
|
30
|
+
provider = (
|
|
31
|
+
options.provider
|
|
32
|
+
if options and options.provider
|
|
33
|
+
else config.provider
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
match provider:
|
|
37
|
+
case SecretProviderType.AZURE:
|
|
38
|
+
return _create_azure_provider(config, options)
|
|
39
|
+
case SecretProviderType.AWS | None:
|
|
40
|
+
return _create_aws_provider(config, options)
|
|
41
|
+
case _:
|
|
42
|
+
raise ValueError(f"Unsupported secret provider: {provider!r}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _create_azure_provider(
|
|
46
|
+
config: MapFileConfig, options: EnvilderOptions | None
|
|
47
|
+
) -> AzureKeyVaultSecretProvider:
|
|
48
|
+
vault_url = (
|
|
49
|
+
options.vault_url
|
|
50
|
+
if options and options.vault_url
|
|
51
|
+
else config.vault_url
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not vault_url or not vault_url.strip():
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"Vault URL must be provided for Azure Key Vault" " provider."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
credential = DefaultAzureCredential()
|
|
60
|
+
secret_client = SecretClient(vault_url=vault_url, credential=credential)
|
|
61
|
+
return AzureKeyVaultSecretProvider(secret_client)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _create_aws_provider(
|
|
65
|
+
config: MapFileConfig, options: EnvilderOptions | None
|
|
66
|
+
) -> AwsSsmSecretProvider:
|
|
67
|
+
profile = (
|
|
68
|
+
options.profile if options and options.profile else config.profile
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if profile:
|
|
72
|
+
try:
|
|
73
|
+
region = _resolve_region_from_env()
|
|
74
|
+
session = (
|
|
75
|
+
boto3.Session(profile_name=profile, region_name=region)
|
|
76
|
+
if region
|
|
77
|
+
else boto3.Session(profile_name=profile)
|
|
78
|
+
)
|
|
79
|
+
ssm_client = session.client("ssm")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Failed to create AWS session with profile"
|
|
83
|
+
f" '{profile}': {e}"
|
|
84
|
+
) from e
|
|
85
|
+
else:
|
|
86
|
+
region = _resolve_region_from_env()
|
|
87
|
+
session = (
|
|
88
|
+
boto3.Session(region_name=region) if region else boto3.Session()
|
|
89
|
+
)
|
|
90
|
+
ssm_client = session.client("ssm")
|
|
91
|
+
|
|
92
|
+
return AwsSsmSecretProvider(ssm_client)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _resolve_region_from_env() -> str | None:
|
|
96
|
+
return os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
|
envilder/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envilder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Securely load environment variables from AWS SSM Parameter Store or Azure Key Vault.
|
|
5
|
+
Project-URL: Homepage, https://github.com/macalbert/envilder
|
|
6
|
+
Project-URL: Repository, https://github.com/macalbert/envilder
|
|
7
|
+
Project-URL: Documentation, https://github.com/macalbert/envilder/tree/main/src/sdks/python
|
|
8
|
+
Project-URL: Issues, https://github.com/macalbert/envilder/issues
|
|
9
|
+
Author: macalbert
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: aws,azure,envilder,environment,keyvault,secrets,ssm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: azure-identity>=1.14
|
|
23
|
+
Requires-Dist: azure-keyvault-secrets>=4.7
|
|
24
|
+
Requires-Dist: boto3>=1.26
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: boto3-stubs[ssm]>=1.26; extra == 'dev'
|
|
28
|
+
Requires-Dist: isort>=5.13; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: requests>=2.31; extra == 'dev'
|
|
32
|
+
Requires-Dist: testcontainers[localstack]>=4.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Envilder Python SDK
|
|
36
|
+
|
|
37
|
+
Securely load environment variables from **AWS SSM Parameter Store** or **Azure Key Vault** directly into your Python application.
|
|
38
|
+
Zero vendor lock-in — secrets stay in your cloud.
|
|
39
|
+
|
|
40
|
+
Part of the [Envilder](https://github.com/macalbert/envilder) project.
|
|
41
|
+
|
|
42
|
+
## Prerequisites
|
|
43
|
+
|
|
44
|
+
- Python 3.10+
|
|
45
|
+
- **AWS provider**: AWS credentials configured (CLI, environment variables, or IAM role)
|
|
46
|
+
- **Azure provider**: Azure credentials via `az login`, managed identity, or environment variables
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install envilder
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
### Direct usage
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from envilder import (
|
|
60
|
+
EnvilderClient,
|
|
61
|
+
MapFileParser,
|
|
62
|
+
SecretProviderFactory,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
json_content = open('secrets-map.json').read()
|
|
66
|
+
map_file = MapFileParser().parse(json_content)
|
|
67
|
+
provider = SecretProviderFactory.create(map_file.config)
|
|
68
|
+
client = EnvilderClient(provider)
|
|
69
|
+
secrets = client.resolve_secrets(map_file)
|
|
70
|
+
|
|
71
|
+
print(secrets['DB_PASSWORD'])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Inject into environment
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
EnvilderClient.inject_into_environment(secrets)
|
|
78
|
+
|
|
79
|
+
import os
|
|
80
|
+
print(os.environ['DB_PASSWORD'])
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### With runtime overrides (EnvilderOptions)
|
|
84
|
+
|
|
85
|
+
Override the map file's `$config` at runtime — useful for switching providers per environment:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from envilder import EnvilderOptions, SecretProviderType
|
|
89
|
+
|
|
90
|
+
options = EnvilderOptions(
|
|
91
|
+
provider=SecretProviderType.AZURE,
|
|
92
|
+
vault_url='https://my-vault.vault.azure.net',
|
|
93
|
+
)
|
|
94
|
+
provider = SecretProviderFactory.create(map_file.config, options)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Map File Format
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"$config": {
|
|
102
|
+
"provider": "aws",
|
|
103
|
+
"profile": "my-profile"
|
|
104
|
+
},
|
|
105
|
+
"DB_PASSWORD": "/app/prod/db-password",
|
|
106
|
+
"API_KEY": "/app/prod/api-key"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Supported providers: `aws` (default), `azure`.
|
|
111
|
+
|
|
112
|
+
For Azure, add `vaultUrl`:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"$config": {
|
|
117
|
+
"provider": "azure",
|
|
118
|
+
"vaultUrl": "https://my-vault.vault.azure.net"
|
|
119
|
+
},
|
|
120
|
+
"DB_PASSWORD": "db-password",
|
|
121
|
+
"API_KEY": "api-key"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
### Setup
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# From the repo root
|
|
135
|
+
make install-sdk-python
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Quality checks
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
make check-sdk-python # black + isort + mypy (no changes)
|
|
142
|
+
make format-sdk-python # auto-format
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Running tests
|
|
146
|
+
|
|
147
|
+
Unit tests run without any external dependencies:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cd tests/sdks/python
|
|
151
|
+
python -m pytest -v -m "not acceptance"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Acceptance tests require **Docker** and a **LocalStack auth token**:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
export LOCALSTACK_AUTH_TOKEN=<your-token>
|
|
158
|
+
cd tests/sdks/python
|
|
159
|
+
python -m pytest -v -m acceptance
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
All tests:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
make test-sdk-python
|
|
166
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
envilder/__init__.py,sha256=FYL3mKC-Eo--xTdtscDz969_2K-31RS8IjTa6USTEXc,998
|
|
2
|
+
envilder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
envilder/application/__init__.py,sha256=9qHF5WPYPHkVNI3aI0_P00IhGRgq8SFy_A_VDVip6wA,185
|
|
4
|
+
envilder/application/envilder_client.py,sha256=SuCtF6ffoFWYl-YpmQs7giD6e5QzVFZlZCYCBdxmnSk,941
|
|
5
|
+
envilder/application/map_file_parser.py,sha256=dhFGrlP8ftB5D3c9blwGCkRKLjsI0YSgoJ7NEHYwqd4,1809
|
|
6
|
+
envilder/domain/__init__.py,sha256=CDNs7nHin6FbYlm5nYcbdP3PuZOoKWMjUF4WtSEXLjM,440
|
|
7
|
+
envilder/domain/envilder_options.py,sha256=Glul_NZlyooOu9RFQWgleHWQajBLy5z-bZ4kKKUDzPw,338
|
|
8
|
+
envilder/domain/map_file_config.py,sha256=rBUGyUZgPLI3HF-NP2k6ZMhfGAfQ46WctPHHfEjoPWU,336
|
|
9
|
+
envilder/domain/parsed_map_file.py,sha256=bKcbTUOPZk1ptZY_e5VL3C-_kkuT2aQLjae7P3bvDA8,218
|
|
10
|
+
envilder/domain/secret_provider_type.py,sha256=CjCD36Cv0RazWbbP8I3mBrKcX5TlmSHWwEeGnpA1eyM,97
|
|
11
|
+
envilder/domain/ports/__init__.py,sha256=fbMM9haUZu49JBBBTYkRmu4eRO8xsjOiPOxqVbO1SBU,104
|
|
12
|
+
envilder/domain/ports/secret_provider.py,sha256=wCoSVZOp7tSD4KEoUOMxEXkE3z9gZ5anynVaKWbXREw,154
|
|
13
|
+
envilder/infrastructure/__init__.py,sha256=v9lKtr0qRrdmSPAKa0edcLq_M1ObZZr5aH5fJDqY4BA,403
|
|
14
|
+
envilder/infrastructure/secret_provider_factory.py,sha256=_FbXa2X-JcdipL8gnweQuJq6IoxgVBJHS2SUNjCjxks,3007
|
|
15
|
+
envilder/infrastructure/aws/__init__.py,sha256=P8aZbG2x5d-qqcUKBFFoXFiQNAD3DNtWhRkrjVS67WQ,130
|
|
16
|
+
envilder/infrastructure/aws/aws_ssm_secret_provider.py,sha256=T1Zmpet3Y2ulnaqPHe11t-MtcHPA0aJgBe4TBddoLgM,1059
|
|
17
|
+
envilder/infrastructure/azure/__init__.py,sha256=GDtqMa_NYMbA9TESaAnq-HfPpr5ggLZoYC7oxiBhv4g,154
|
|
18
|
+
envilder/infrastructure/azure/azure_key_vault_secret_provider.py,sha256=xZcY6raehOIeQVRt7_5b28PTJFT4EnZ6nyBeDvtFo2E,831
|
|
19
|
+
envilder-0.1.0.dist-info/METADATA,sha256=mFZkgvJMizFt74rPlWJUBTH6rwS9PQBZontciWmJ-Qk,3980
|
|
20
|
+
envilder-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
21
|
+
envilder-0.1.0.dist-info/RECORD,,
|