Skip to content

Commit b3a0c6f

Browse files
committed
Merge branch 'main' of github.com:singlestore-labs/singlestore-python
2 parents 7b104bd + 2f68c7e commit b3a0c6f

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

singlestoredb/management/organization.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env python
22
"""SingleStoreDB Cloud Organization."""
3+
import datetime
34
from typing import Dict
45
from typing import List
56
from typing import Optional
67
from typing import Union
78

9+
from ..exceptions import ManagementError
810
from .manager import Manager
911
from .utils import vars_to_str
1012

@@ -21,6 +23,91 @@ def stringify(x: Union[str, List[str]]) -> str:
2123
return x
2224

2325

26+
class Secret(object):
27+
"""
28+
SingleStoreDB secrets definition.
29+
30+
This object is not directly instantiated. It is used in results
31+
of API calls on the :class:`Organization`. See :meth:`Organization.get_secret`.
32+
"""
33+
34+
def __init__(
35+
self,
36+
id: str,
37+
name: str,
38+
value: str,
39+
created_by: str,
40+
created_at: Union[str, datetime.datetime],
41+
last_updated_by: str,
42+
last_updated_at: Union[str, datetime.datetime],
43+
deleted_by: Optional[str] = None,
44+
deleted_at: Optional[Union[str, datetime.datetime]] = None,
45+
):
46+
# UUID of the secret
47+
self.id = id
48+
49+
# Name of the secret
50+
self.name = name
51+
52+
# Value of the secret
53+
self.value = value
54+
55+
# User who created the secret
56+
self.created_by = created_by
57+
58+
# Time when the secret was created
59+
self.created_at = created_at
60+
61+
# UUID of the user who last updated the secret
62+
self.last_updated_by = last_updated_by
63+
64+
# Time when the secret was last updated
65+
self.last_updated_at = last_updated_at
66+
67+
# UUID of the user who deleted the secret
68+
self.deleted_by = deleted_by
69+
70+
# Time when the secret was deleted
71+
self.deleted_at = deleted_at
72+
73+
@classmethod
74+
def from_dict(cls, obj: Dict[str, str]) -> 'Secret':
75+
"""
76+
Construct a Secret from a dictionary of values.
77+
78+
Parameters
79+
----------
80+
obj : dict
81+
Dictionary of values
82+
83+
Returns
84+
-------
85+
:class:`Secret`
86+
87+
"""
88+
out = cls(
89+
id=obj['secretID'],
90+
name=obj['name'],
91+
value=obj['value'],
92+
created_by=obj['createdBy'],
93+
created_at=obj['createdAt'],
94+
last_updated_by=obj['lastUpdatedBy'],
95+
last_updated_at=obj['lastUpdatedAt'],
96+
deleted_by=obj.get('deletedBy'),
97+
deleted_at=obj.get('deletedAt'),
98+
)
99+
100+
return out
101+
102+
def __str__(self) -> str:
103+
"""Return string representation."""
104+
return vars_to_str(self)
105+
106+
def __repr__(self) -> str:
107+
"""Return string representation."""
108+
return str(self)
109+
110+
24111
class Organization(object):
25112
"""
26113
Organization in SingleStoreDB Cloud portal.
@@ -55,6 +142,22 @@ def __repr__(self) -> str:
55142
"""Return string representation."""
56143
return str(self)
57144

145+
def get_secret(self, name: str) -> Secret:
146+
if self._manager is None:
147+
raise ManagementError(msg='Organization not initialized')
148+
149+
res = self._manager._get('secrets', params=dict(name=name))
150+
151+
secrets = [Secret.from_dict(item) for item in res.json()['secrets']]
152+
153+
if len(secrets) == 0:
154+
raise ManagementError(msg=f'Secret {name} not found')
155+
156+
if len(secrets) > 1:
157+
raise ManagementError(msg=f'Multiple secrets found for {name}')
158+
159+
return secrets[0]
160+
58161
@classmethod
59162
def from_dict(
60163
cls,

singlestoredb/tests/test_management.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,3 +793,60 @@ def test_stage_object(self):
793793
assert not st.exists('obj_test.sql')
794794
assert st.exists('obj_test_2.sql')
795795
assert f1.abspath() == 'obj_test_2.sql'
796+
797+
798+
@pytest.mark.management
799+
class TestSecrets(unittest.TestCase):
800+
801+
manager = None
802+
wg = None
803+
password = None
804+
805+
@classmethod
806+
def setUpClass(cls):
807+
cls.manager = s2.manage_workspaces()
808+
809+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
810+
cls.password = secrets.token_urlsafe(20)
811+
812+
name = clean_name(secrets.token_urlsafe(20)[:20])
813+
814+
cls.wg = cls.manager.create_workspace_group(
815+
f'wg-test-{name}',
816+
region=random.choice(us_regions).id,
817+
admin_password=cls.password,
818+
firewall_ranges=['0.0.0.0/0'],
819+
)
820+
821+
@classmethod
822+
def tearDownClass(cls):
823+
if cls.wg is not None:
824+
cls.wg.terminate(force=True)
825+
cls.wg = None
826+
cls.manager = None
827+
cls.password = None
828+
829+
def test_get_secret(self):
830+
# manually create secret and then get secret
831+
# try to delete the secret if it exists
832+
try:
833+
secret = self.manager.organizations.current.get_secret('secret_name')
834+
835+
secret_id = secret.id
836+
837+
self.manager._delete(f'secrets/{secret_id}')
838+
except s2.ManagementError:
839+
pass
840+
841+
self.manager._post(
842+
'secrets',
843+
json=dict(
844+
name='secret_name',
845+
value='secret_value',
846+
),
847+
)
848+
849+
secret = self.manager.organizations.current.get_secret('secret_name')
850+
851+
assert secret.name == 'secret_name'
852+
assert secret.value == 'secret_value'

0 commit comments

Comments
 (0)