dominus-sdk-python 8.0.0__tar.gz → 9.0.0__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.
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/PKG-INFO +1 -1
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/__init__.py +10 -2
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/__init__.py +9 -1
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/secrets.py +109 -3
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/SOURCES.txt +1 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/pyproject.toml +1 -1
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_namespace_barrel_exports.py +21 -0
- dominus_sdk_python-9.0.0/tests/test_secrets_presence_contract.py +143 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/README.md +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/config/endpoints.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/errors.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/cache.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/core.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/helpers/trace.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/ai.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/artifacts.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/authority.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/browser.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/coder.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/courier.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/deployer.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/logs.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/platform.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/publisher.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/recipes.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/redis.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/stash.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/warden.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/namespaces/workflow.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/py.typed +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus/start.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/setup.cfg +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_auth.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_authority_public_vocabulary.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_browser_namespace.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_control_plane_namespaces.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_errors.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_flat_commands.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_health.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_logs.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_platform_coder_namespaces.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_provisioning_parity.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_public_exports.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_publisher_namespace.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_recipes_namespace.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_recipes_stash_routing.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_stash_artifact_facade.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_stash_tables_namespace.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_transport_compat.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_workflow_lifecycle.py +0 -0
- {dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_workflow_refs.py +0 -0
|
@@ -82,7 +82,12 @@ from .namespaces.files import FilesNamespace
|
|
|
82
82
|
from .namespaces.portal import PortalNamespace
|
|
83
83
|
from .namespaces.admin import AdminNamespace
|
|
84
84
|
from .namespaces.secure import SecureNamespace, SecureAccessContext
|
|
85
|
-
from .namespaces.secrets import
|
|
85
|
+
from .namespaces.secrets import (
|
|
86
|
+
SecretsNamespace,
|
|
87
|
+
SecretEnvironment,
|
|
88
|
+
SecretPresenceCell,
|
|
89
|
+
SecretPresenceGrid,
|
|
90
|
+
)
|
|
86
91
|
from .namespaces.redis import RedisNamespace
|
|
87
92
|
from .namespaces.logs import LogsNamespace
|
|
88
93
|
from .namespaces.courier import CourierNamespace
|
|
@@ -173,7 +178,7 @@ from .errors import (
|
|
|
173
178
|
TimeoutError as DominusTimeoutError,
|
|
174
179
|
)
|
|
175
180
|
|
|
176
|
-
__version__ = "
|
|
181
|
+
__version__ = "9.0.0"
|
|
177
182
|
__all__ = [
|
|
178
183
|
# Main SDK instance
|
|
179
184
|
"dominus",
|
|
@@ -197,6 +202,9 @@ __all__ = [
|
|
|
197
202
|
"SecureNamespace",
|
|
198
203
|
"SecureAccessContext",
|
|
199
204
|
"SecretsNamespace",
|
|
205
|
+
"SecretEnvironment",
|
|
206
|
+
"SecretPresenceCell",
|
|
207
|
+
"SecretPresenceGrid",
|
|
200
208
|
"RedisNamespace",
|
|
201
209
|
"LogsNamespace",
|
|
202
210
|
"CourierNamespace",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"""Dominus SDK namespace exports."""
|
|
2
|
-
from .secrets import
|
|
2
|
+
from .secrets import (
|
|
3
|
+
SecretsNamespace,
|
|
4
|
+
SecretEnvironment,
|
|
5
|
+
SecretPresenceCell,
|
|
6
|
+
SecretPresenceGrid,
|
|
7
|
+
)
|
|
3
8
|
from .db import DbNamespace
|
|
4
9
|
from .redis import RedisNamespace
|
|
5
10
|
from .files import FilesNamespace
|
|
@@ -33,6 +38,9 @@ from .ai import (
|
|
|
33
38
|
|
|
34
39
|
__all__ = [
|
|
35
40
|
"SecretsNamespace",
|
|
41
|
+
"SecretEnvironment",
|
|
42
|
+
"SecretPresenceCell",
|
|
43
|
+
"SecretPresenceGrid",
|
|
36
44
|
"DbNamespace",
|
|
37
45
|
"RedisNamespace",
|
|
38
46
|
"FilesNamespace",
|
|
@@ -3,12 +3,115 @@ Secrets Namespace - Warden secrets management.
|
|
|
3
3
|
|
|
4
4
|
Provides CRUD operations for secrets stored in the Warden service.
|
|
5
5
|
"""
|
|
6
|
-
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
6
|
+
from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, TypedDict
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from ..start import Dominus
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
SecretEnvironment = Literal["development", "staging", "production"]
|
|
13
|
+
_SECRET_ENVIRONMENTS = ("development", "staging", "production")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SecretPresenceCell(TypedDict):
|
|
17
|
+
present: bool
|
|
18
|
+
preview: Optional[str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SecretPresenceGrid(TypedDict):
|
|
22
|
+
secrets: Dict[str, Dict[SecretEnvironment, SecretPresenceCell]]
|
|
23
|
+
keys: List[str]
|
|
24
|
+
count: int
|
|
25
|
+
environment_counts: Dict[SecretEnvironment, int]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _invalid_presence_response() -> None:
|
|
29
|
+
raise ValueError("Invalid secret presence response")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _safe_preview(value: str) -> str:
|
|
33
|
+
if value == "":
|
|
34
|
+
return ""
|
|
35
|
+
if len(value) <= 8:
|
|
36
|
+
return "<redacted>"
|
|
37
|
+
return f"{value[:4]}...{value[-4:]}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_canonical_preview(value: str) -> bool:
|
|
41
|
+
return value in ("", "<redacted>") or (len(value) == 11 and value[4:7] == "...")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _normalize_presence_grid(value: Any) -> SecretPresenceGrid:
|
|
45
|
+
if not isinstance(value, dict) or not isinstance(value.get("secrets"), dict):
|
|
46
|
+
_invalid_presence_response()
|
|
47
|
+
keys = value.get("keys")
|
|
48
|
+
count = value.get("count")
|
|
49
|
+
if not isinstance(keys, list) or not all(isinstance(key, str) for key in keys):
|
|
50
|
+
_invalid_presence_response()
|
|
51
|
+
if type(count) is not int or count < 0:
|
|
52
|
+
_invalid_presence_response()
|
|
53
|
+
|
|
54
|
+
unique_sorted = sorted(set(keys))
|
|
55
|
+
if keys != unique_sorted or sorted(value["secrets"].keys()) != keys or count != len(keys):
|
|
56
|
+
_invalid_presence_response()
|
|
57
|
+
|
|
58
|
+
canonical_mode = "environment_counts" in value
|
|
59
|
+
counts: Dict[SecretEnvironment, int] = {
|
|
60
|
+
"development": 0,
|
|
61
|
+
"staging": 0,
|
|
62
|
+
"production": 0,
|
|
63
|
+
}
|
|
64
|
+
secrets: Dict[str, Dict[SecretEnvironment, SecretPresenceCell]] = {}
|
|
65
|
+
|
|
66
|
+
for key in keys:
|
|
67
|
+
raw_row = value["secrets"][key]
|
|
68
|
+
if not isinstance(raw_row, dict) or not set(raw_row).issubset(_SECRET_ENVIRONMENTS):
|
|
69
|
+
_invalid_presence_response()
|
|
70
|
+
row: Dict[SecretEnvironment, SecretPresenceCell] = {}
|
|
71
|
+
for environment in _SECRET_ENVIRONMENTS:
|
|
72
|
+
has_cell = environment in raw_row
|
|
73
|
+
raw_cell = raw_row.get(environment)
|
|
74
|
+
if canonical_mode:
|
|
75
|
+
if not has_cell or not isinstance(raw_cell, dict) or set(raw_cell) != {"present", "preview"}:
|
|
76
|
+
_invalid_presence_response()
|
|
77
|
+
if type(raw_cell["present"]) is not bool:
|
|
78
|
+
_invalid_presence_response()
|
|
79
|
+
if raw_cell["present"]:
|
|
80
|
+
if not isinstance(raw_cell["preview"], str) or not _is_canonical_preview(raw_cell["preview"]):
|
|
81
|
+
_invalid_presence_response()
|
|
82
|
+
elif raw_cell["preview"] is not None:
|
|
83
|
+
_invalid_presence_response()
|
|
84
|
+
cell: SecretPresenceCell = {
|
|
85
|
+
"present": raw_cell["present"],
|
|
86
|
+
"preview": raw_cell["preview"],
|
|
87
|
+
}
|
|
88
|
+
elif not has_cell or raw_cell is None:
|
|
89
|
+
cell = {"present": False, "preview": None}
|
|
90
|
+
elif isinstance(raw_cell, str):
|
|
91
|
+
cell = {"present": True, "preview": _safe_preview(raw_cell)}
|
|
92
|
+
else:
|
|
93
|
+
_invalid_presence_response()
|
|
94
|
+
row[environment] = cell
|
|
95
|
+
if cell["present"]:
|
|
96
|
+
counts[environment] += 1
|
|
97
|
+
secrets[key] = row
|
|
98
|
+
|
|
99
|
+
if canonical_mode:
|
|
100
|
+
response_counts = value["environment_counts"]
|
|
101
|
+
if not isinstance(response_counts, dict) or set(response_counts) != set(_SECRET_ENVIRONMENTS):
|
|
102
|
+
_invalid_presence_response()
|
|
103
|
+
if any(type(response_counts[environment]) is not int or response_counts[environment] != counts[environment]
|
|
104
|
+
for environment in _SECRET_ENVIRONMENTS):
|
|
105
|
+
_invalid_presence_response()
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
"secrets": secrets,
|
|
109
|
+
"keys": list(keys),
|
|
110
|
+
"count": len(keys),
|
|
111
|
+
"environment_counts": counts,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
12
115
|
class SecretsNamespace:
|
|
13
116
|
"""
|
|
14
117
|
Secrets management namespace.
|
|
@@ -193,9 +296,11 @@ class SecretsNamespace:
|
|
|
193
296
|
|
|
194
297
|
async def list_secrets_all_environments(
|
|
195
298
|
self, infisical_project_id: str
|
|
196
|
-
) ->
|
|
299
|
+
) -> SecretPresenceGrid:
|
|
197
300
|
"""Warden grid: secrets across environments for an Infisical project id."""
|
|
198
|
-
|
|
301
|
+
if not infisical_project_id.strip():
|
|
302
|
+
raise ValueError("Infisical project id is required")
|
|
303
|
+
response = await self._client._request(
|
|
199
304
|
endpoint="/api/warden/secrets",
|
|
200
305
|
body={
|
|
201
306
|
"action": "list-all-environments",
|
|
@@ -203,3 +308,4 @@ class SecretsNamespace:
|
|
|
203
308
|
},
|
|
204
309
|
use_gateway=True,
|
|
205
310
|
)
|
|
311
|
+
return _normalize_presence_grid(response)
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/SOURCES.txt
RENAMED
|
@@ -64,6 +64,7 @@ tests/test_public_exports.py
|
|
|
64
64
|
tests/test_publisher_namespace.py
|
|
65
65
|
tests/test_recipes_namespace.py
|
|
66
66
|
tests/test_recipes_stash_routing.py
|
|
67
|
+
tests/test_secrets_presence_contract.py
|
|
67
68
|
tests/test_stash_artifact_facade.py
|
|
68
69
|
tests/test_stash_tables_namespace.py
|
|
69
70
|
tests/test_transport_compat.py
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_namespace_barrel_exports.py
RENAMED
|
@@ -39,3 +39,24 @@ def test_stash_tables_namespace_is_exported_from_both_barrels():
|
|
|
39
39
|
|
|
40
40
|
def test_package_includes_declared_pep561_marker():
|
|
41
41
|
assert Path(dominus.__file__).with_name("py.typed").is_file()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_secret_presence_types_are_identical_across_both_public_barrels():
|
|
45
|
+
from dominus import SecretEnvironment, SecretPresenceCell, SecretPresenceGrid
|
|
46
|
+
from dominus.namespaces import (
|
|
47
|
+
SecretEnvironment as NamespaceEnvironment,
|
|
48
|
+
SecretPresenceCell as NamespaceCell,
|
|
49
|
+
SecretPresenceGrid as NamespaceGrid,
|
|
50
|
+
)
|
|
51
|
+
from dominus.namespaces.secrets import (
|
|
52
|
+
SecretEnvironment as DefinedEnvironment,
|
|
53
|
+
SecretPresenceCell as DefinedCell,
|
|
54
|
+
SecretPresenceGrid as DefinedGrid,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert SecretEnvironment is NamespaceEnvironment is DefinedEnvironment
|
|
58
|
+
assert SecretPresenceCell is NamespaceCell is DefinedCell
|
|
59
|
+
assert SecretPresenceGrid is NamespaceGrid is DefinedGrid
|
|
60
|
+
for name in ("SecretEnvironment", "SecretPresenceCell", "SecretPresenceGrid"):
|
|
61
|
+
assert name in dominus.__all__
|
|
62
|
+
assert name in dominus.namespaces.__all__
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Literal, get_args, get_type_hints
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from dominus.namespaces.secrets import SecretsNamespace
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
RAW_SENTINEL = "SENTINEL-DO-NOT-LEAK"
|
|
10
|
+
LEGACY = {
|
|
11
|
+
"secrets": {
|
|
12
|
+
"EMPTY": {"development": "", "staging": None, "production": None},
|
|
13
|
+
"LONG": {"development": RAW_SENTINEL, "staging": None, "production": "tiny"},
|
|
14
|
+
"STAGE": {"development": None, "staging": "12345678", "production": None},
|
|
15
|
+
},
|
|
16
|
+
"keys": ["EMPTY", "LONG", "STAGE"],
|
|
17
|
+
"count": 3,
|
|
18
|
+
}
|
|
19
|
+
OMITTED = {
|
|
20
|
+
"secrets": {
|
|
21
|
+
"EMPTY": {"development": ""},
|
|
22
|
+
"LONG": {"development": RAW_SENTINEL, "production": "tiny"},
|
|
23
|
+
"STAGE": {"staging": "12345678"},
|
|
24
|
+
},
|
|
25
|
+
"keys": ["EMPTY", "LONG", "STAGE"],
|
|
26
|
+
"count": 3,
|
|
27
|
+
}
|
|
28
|
+
EXPECTED = {
|
|
29
|
+
"secrets": {
|
|
30
|
+
"EMPTY": {
|
|
31
|
+
"development": {"present": True, "preview": ""},
|
|
32
|
+
"staging": {"present": False, "preview": None},
|
|
33
|
+
"production": {"present": False, "preview": None},
|
|
34
|
+
},
|
|
35
|
+
"LONG": {
|
|
36
|
+
"development": {"present": True, "preview": "SENT...LEAK"},
|
|
37
|
+
"staging": {"present": False, "preview": None},
|
|
38
|
+
"production": {"present": True, "preview": "<redacted>"},
|
|
39
|
+
},
|
|
40
|
+
"STAGE": {
|
|
41
|
+
"development": {"present": False, "preview": None},
|
|
42
|
+
"staging": {"present": True, "preview": "<redacted>"},
|
|
43
|
+
"production": {"present": False, "preview": None},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"keys": ["EMPTY", "LONG", "STAGE"],
|
|
47
|
+
"count": 3,
|
|
48
|
+
"environment_counts": {"development": 2, "staging": 1, "production": 1},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Client:
|
|
53
|
+
def __init__(self, response=None, error=None):
|
|
54
|
+
self.response = response
|
|
55
|
+
self.error = error
|
|
56
|
+
self.calls = []
|
|
57
|
+
|
|
58
|
+
async def _request(self, **kwargs):
|
|
59
|
+
self.calls.append(kwargs)
|
|
60
|
+
if self.error is not None:
|
|
61
|
+
raise self.error
|
|
62
|
+
return self.response
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_physical_id_wire_and_legacy_safe_rollback_normalization():
|
|
67
|
+
for response in (LEGACY, EXPECTED, LEGACY):
|
|
68
|
+
client = Client(response)
|
|
69
|
+
result = await SecretsNamespace(client).list_secrets_all_environments("physical-123")
|
|
70
|
+
assert client.calls == [{
|
|
71
|
+
"endpoint": "/api/warden/secrets",
|
|
72
|
+
"body": {"action": "list-all-environments", "infisical_project_id": "physical-123"},
|
|
73
|
+
"use_gateway": True,
|
|
74
|
+
}]
|
|
75
|
+
assert result == EXPECTED
|
|
76
|
+
assert RAW_SENTINEL not in json.dumps(result)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_omitted_legacy_properties_equal_explicit_nulls():
|
|
81
|
+
assert await SecretsNamespace(Client(OMITTED)).list_secrets_all_environments("physical-123") == EXPECTED
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_blank_physical_id_is_rejected_locally():
|
|
86
|
+
client = Client(LEGACY)
|
|
87
|
+
with pytest.raises(ValueError, match="Infisical project id is required"):
|
|
88
|
+
await SecretsNamespace(client).list_secrets_all_environments(" ")
|
|
89
|
+
assert client.calls == []
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_upstream_failure_propagates_unchanged():
|
|
94
|
+
failure = RuntimeError("upstream unavailable")
|
|
95
|
+
with pytest.raises(RuntimeError) as caught:
|
|
96
|
+
await SecretsNamespace(Client(error=failure)).list_secrets_all_environments("physical-123")
|
|
97
|
+
assert caught.value is failure
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.asyncio
|
|
101
|
+
async def test_malformed_responses_fail_without_raw_cell_in_error_or_logs(caplog):
|
|
102
|
+
malformed = [
|
|
103
|
+
{"secrets": {"BAD": {"development": value, "staging": None, "production": None}}, "keys": ["BAD"], "count": 1}
|
|
104
|
+
for value in (1, True, [], {})
|
|
105
|
+
] + [
|
|
106
|
+
{**EXPECTED, "secrets": {"BAD": {"development": {"present": False, "preview": "bad"}, "staging": {"present": False, "preview": None}, "production": {"present": False, "preview": None}}}, "keys": ["BAD"], "count": 1, "environment_counts": {"development": 0, "staging": 0, "production": 0}},
|
|
107
|
+
{**EXPECTED, "secrets": {"BAD": {"development": {"present": True, "preview": None}, "staging": {"present": False, "preview": None}, "production": {"present": False, "preview": None}}}, "keys": ["BAD"], "count": 1, "environment_counts": {"development": 1, "staging": 0, "production": 0}},
|
|
108
|
+
{"secrets": {"BAD": {"development": RAW_SENTINEL, "staging": None, "production": None, "preview": "unknown"}}, "keys": ["BAD"], "count": 1},
|
|
109
|
+
{**LEGACY, "keys": ["LONG", "EMPTY", "STAGE"]},
|
|
110
|
+
{**LEGACY, "keys": ["EMPTY", "EMPTY", "STAGE"]},
|
|
111
|
+
{**LEGACY, "count": 2},
|
|
112
|
+
{**EXPECTED, "environment_counts": {"development": 99, "staging": 1, "production": 1}},
|
|
113
|
+
{"secrets": LEGACY["secrets"], "keys": LEGACY["keys"]},
|
|
114
|
+
{"success": False, "error": RAW_SENTINEL},
|
|
115
|
+
]
|
|
116
|
+
for response in malformed:
|
|
117
|
+
with pytest.raises(ValueError) as caught:
|
|
118
|
+
await SecretsNamespace(Client(response)).list_secrets_all_environments("physical-123")
|
|
119
|
+
assert RAW_SENTINEL not in str(caught.value)
|
|
120
|
+
assert RAW_SENTINEL not in caplog.text
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_public_types_and_return_annotation_are_exact():
|
|
124
|
+
from dominus import SecretEnvironment, SecretPresenceCell, SecretPresenceGrid
|
|
125
|
+
from dominus.namespaces import (
|
|
126
|
+
SecretEnvironment as NamespaceEnvironment,
|
|
127
|
+
SecretPresenceCell as NamespaceCell,
|
|
128
|
+
SecretPresenceGrid as NamespaceGrid,
|
|
129
|
+
)
|
|
130
|
+
from dominus.namespaces.secrets import (
|
|
131
|
+
SecretEnvironment as DefinedEnvironment,
|
|
132
|
+
SecretPresenceCell as DefinedCell,
|
|
133
|
+
SecretPresenceGrid as DefinedGrid,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert SecretEnvironment is NamespaceEnvironment is DefinedEnvironment
|
|
137
|
+
assert SecretPresenceCell is NamespaceCell is DefinedCell
|
|
138
|
+
assert SecretPresenceGrid is NamespaceGrid is DefinedGrid
|
|
139
|
+
assert get_args(SecretEnvironment) == ("development", "staging", "production")
|
|
140
|
+
assert SecretEnvironment == Literal["development", "staging", "production"]
|
|
141
|
+
assert SecretPresenceCell.__required_keys__ == frozenset({"present", "preview"})
|
|
142
|
+
assert SecretPresenceGrid.__required_keys__ == frozenset({"secrets", "keys", "count", "environment_counts"})
|
|
143
|
+
assert get_type_hints(SecretsNamespace.list_secrets_all_environments)["return"] is SecretPresenceGrid
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/dominus_sdk_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_authority_public_vocabulary.py
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_control_plane_namespaces.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-8.0.0 → dominus_sdk_python-9.0.0}/tests/test_platform_coder_namespaces.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|