Skip to content

Commit 9a402e9

Browse files
authored
Add support for import/export project (#338)
1 parent e834841 commit 9a402e9

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,17 +1196,29 @@ res = descope_client.mgmt.authz.get_modified()
11961196

11971197
### Manage Project
11981198

1199-
You can change the project name, as well as to clone the current project to a new one.
1199+
You can change the project name, as well as clone the current project to
1200+
create a new one.
12001201

12011202
```python
1202-
12031203
# Change the project name
1204-
descope.client.mgmt.project.change_name("new-project-name")
1204+
descope_client.mgmt.project.change_name("new-project-name")
12051205

12061206
# Clone the current project, including its settings and configurations.
12071207
# Note that this action is supported only with a pro license or above.
12081208
# Users, tenants and access keys are not cloned.
1209-
clone_resp = descope.client.mgmt.project.clone("new-project-name")
1209+
clone_resp = descope_client.mgmt.project.clone("new-project-name")
1210+
```
1211+
1212+
You can manage your project's settings and configurations by exporting your
1213+
project's environment. You can also import previously exported data into
1214+
the same project or a different one.
1215+
1216+
```python
1217+
# Exports the current state of the project
1218+
export = descope_client.mgmt.project.export_project()
1219+
1220+
# Import the previously exported data into the current project
1221+
descope_client.mgmt.project.import_project(export)
12101222
```
12111223

12121224
### Manage SSO Applications

descope/management/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ class MgmtV1:
124124
# Project
125125
project_update_name = "/v1/mgmt/project/update/name"
126126
project_clone = "/v1/mgmt/project/clone"
127+
project_export = "/v1/mgmt/project/export"
128+
project_import = "/v1/mgmt/project/import"
127129

128130

129131
class AssociatedTenant:

descope/management/project.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ def clone(
3737
3838
Args:
3939
name (str): The new name for the project.
40-
tag(str): Optional tag for the project. Currently, only the "production" tag is supported.
40+
tag (str): Optional tag for the project. Currently, only the "production" tag is supported.
4141
42-
Return value (dict):
42+
Return value (dict):
4343
Return dict Containing the new project details (name, id, and tag).
4444
4545
Raise:
@@ -54,3 +54,52 @@ def clone(
5454
pswd=self._auth.management_key,
5555
)
5656
return response.json()
57+
58+
def export_project(
59+
self,
60+
):
61+
"""
62+
Exports all settings and configurations for a project and returns the
63+
raw JSON files response as a dictionary.
64+
- This action is supported only with a pro license or above.
65+
- Users, tenants and access keys are not cloned.
66+
- Secrets, keys and tokens are not stripped from the exported data.
67+
68+
Return value (dict):
69+
Return dict Containing the exported JSON files payload.
70+
71+
Raise:
72+
AuthException: raised if export operation fails
73+
"""
74+
response = self._auth.do_post(
75+
MgmtV1.project_export,
76+
{},
77+
pswd=self._auth.management_key,
78+
)
79+
return response.json()["files"]
80+
81+
def import_project(
82+
self,
83+
files: dict,
84+
):
85+
"""
86+
Imports all settings and configurations for a project overriding any current
87+
configuration.
88+
- This action is supported only with a pro license or above.
89+
- Secrets, keys and tokens are not overwritten unless overwritten in the input.
90+
91+
Args:
92+
files (dict): The raw JSON dictionary of files, in the same format as the one
93+
returned by calls to export.
94+
95+
Raise:
96+
AuthException: raised if import operation fails
97+
"""
98+
response = self._auth.do_post(
99+
MgmtV1.project_import,
100+
{
101+
"files": files,
102+
},
103+
pswd=self._auth.management_key,
104+
)
105+
return

tests/management/test_project.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,92 @@ def test_clone(self):
110110
verify=True,
111111
timeout=DEFAULT_TIMEOUT_SECONDS,
112112
)
113+
114+
def test_export_project(self):
115+
client = DescopeClient(
116+
self.dummy_project_id,
117+
self.public_key_dict,
118+
False,
119+
self.dummy_management_key,
120+
)
121+
122+
# Test failed flows
123+
with patch("requests.post") as mock_post:
124+
mock_post.return_value.ok = False
125+
self.assertRaises(
126+
AuthException,
127+
client.mgmt.project.export_project,
128+
)
129+
130+
# Test success flow
131+
with patch("requests.post") as mock_post:
132+
network_resp = mock.Mock()
133+
network_resp.ok = True
134+
network_resp.json.return_value = json.loads(
135+
"""
136+
{
137+
"files":{"foo":"bar"}
138+
}
139+
"""
140+
)
141+
mock_post.return_value = network_resp
142+
resp = client.mgmt.project.export_project()
143+
self.assertIsNotNone(resp)
144+
self.assertEqual(resp["foo"], "bar")
145+
mock_post.assert_called_with(
146+
f"{common.DEFAULT_BASE_URL}{MgmtV1.project_export}",
147+
headers={
148+
**common.default_headers,
149+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
150+
},
151+
params=None,
152+
json={},
153+
allow_redirects=False,
154+
verify=True,
155+
timeout=DEFAULT_TIMEOUT_SECONDS,
156+
)
157+
158+
def test_import_project(self):
159+
client = DescopeClient(
160+
self.dummy_project_id,
161+
self.public_key_dict,
162+
False,
163+
self.dummy_management_key,
164+
)
165+
166+
# Test failed flows
167+
with patch("requests.post") as mock_post:
168+
mock_post.return_value.ok = False
169+
self.assertRaises(
170+
AuthException,
171+
client.mgmt.project.import_project,
172+
{
173+
"foo": "bar",
174+
},
175+
)
176+
177+
# Test success flow
178+
with patch("requests.post") as mock_post:
179+
network_resp = mock.Mock()
180+
network_resp.ok = True
181+
mock_post.return_value = network_resp
182+
files = {
183+
"foo": "bar",
184+
}
185+
client.mgmt.project.import_project(files)
186+
mock_post.assert_called_with(
187+
f"{common.DEFAULT_BASE_URL}{MgmtV1.project_import}",
188+
headers={
189+
**common.default_headers,
190+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
191+
},
192+
params=None,
193+
json={
194+
"files": {
195+
"foo": "bar",
196+
},
197+
},
198+
allow_redirects=False,
199+
verify=True,
200+
timeout=DEFAULT_TIMEOUT_SECONDS,
201+
)

0 commit comments

Comments
 (0)