Skip to content

Commit df1d3c6

Browse files
committed
add workspace backup functionality
1 parent b00dc0f commit df1d3c6

File tree

49 files changed

+2307
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2307
-103
lines changed

gooddata-pipelines/TODO.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ A list of outstanding tasks, features, or technical debt to be addressed in this
44

55
## Pre-release
66

7-
- [ ] License file
7+
- [ ] License file (FOSSA)
88

99
## Features
1010

11-
- [ ] Workspace backup
1211
- [ ] Workspace restore
13-
- [ ] Handle input from csv files [?]
1412

1513
## Refactoring / Debt
1614

1715
- [ ] Cleanup custom exception
18-
- [ ] Use objects to make `workspace_data_parses.py` more transparent
19-
- [ ] Use objects in API integration. Consider using existing Python SDK objects where possible, otherwise create pydantic models to use instead of `dict[str, Any]` stand-ins for json values
2016
- [ ] Improve test coverage. Write missing unit tests for legacy code (e.g., user data filters)
2117

2218
## Documentation

gooddata-pipelines/gooddata_pipelines/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# (C) 2025 GoodData Corporation
22

33
from ._version import __version__
4+
5+
# -------- Backup and Restore --------
6+
from .backup_and_restore.backup_manager import BackupManager
7+
from .backup_and_restore.models.storage import (
8+
BackupRestoreConfig,
9+
StorageType,
10+
)
11+
from .backup_and_restore.storage.local_storage import LocalStorage
12+
from .backup_and_restore.storage.s3_storage import S3Storage
13+
14+
# -------- Provisioning --------
415
from .provisioning.entities.user_data_filters.models.udf_models import (
516
UserDataFilterFullLoad,
617
)
@@ -26,6 +37,11 @@
2637
from .provisioning.entities.workspaces.workspace import WorkspaceProvisioner
2738

2839
__all__ = [
40+
"BackupManager",
41+
"BackupRestoreConfig",
42+
"StorageType",
43+
"LocalStorage",
44+
"S3Storage",
2945
"WorkspaceFullLoad",
3046
"WorkspaceProvisioner",
3147
"UserIncrementalLoad",

gooddata-pipelines/gooddata_pipelines/api/gooddata_api.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
# or typed dicts.
1212

1313
TIMEOUT = 60
14-
PANTHER_REQUEST_PAGE_SIZE = 250
15-
PANTHER_API_VERSION = "v1"
14+
REQUEST_PAGE_SIZE = 250
15+
API_VERSION = "v1"
1616

1717

1818
class APIMethods:
@@ -43,7 +43,7 @@ def _get_base_url(domain: str) -> str:
4343
if domain.startswith("http://") and not domain.startswith("https://"):
4444
domain = domain.replace("http://", "https://")
4545

46-
return f"{domain}/api/{PANTHER_API_VERSION}"
46+
return f"{domain}/api/{API_VERSION}"
4747

4848
def _get_url(self, endpoint: str) -> str:
4949
"""Returns the full URL for a given API endpoint.
@@ -216,6 +216,18 @@ def post_workspace_data_filter(
216216
endpoint = f"/entities/workspaces/{workspace_id}/workspaceDataFilters"
217217
return self._post(endpoint, data, self.headers)
218218

219+
def get_user_data_filters(self, workspace_id: str) -> requests.Response:
220+
"""Gets the user data filters for a given workspace."""
221+
endpoint = f"/layout/workspaces/{workspace_id}/userDataFilters"
222+
return self._get(endpoint)
223+
224+
def get_automations(self, workspace_id: str) -> requests.Response:
225+
"""Gets the automations for a given workspace."""
226+
endpoint = (
227+
f"/entities/workspaces/{workspace_id}/automations?include=ALL"
228+
)
229+
return self._get(endpoint)
230+
219231
def _get(
220232
self, endpoint: str, headers: dict[str, str] | None = None
221233
) -> requests.Response:

gooddata-pipelines/gooddata_pipelines/api/gooddata_sdk.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
"""Interaction with GoodData Cloud via the Gooddata Python SDK."""
44

5+
from pathlib import Path
6+
57
from gooddata_sdk.catalog.permission.declarative_model.permission import (
68
CatalogDeclarativeWorkspacePermissions,
79
)
@@ -21,21 +23,34 @@
2123
from gooddata_pipelines.api.utils import raise_with_context
2224

2325

26+
def apply_to_all_methods(decorator):
27+
def decorate(cls):
28+
for attr in cls.__dict__:
29+
if callable(getattr(cls, attr)) and not attr.startswith("__"):
30+
setattr(cls, attr, decorator(getattr(cls, attr)))
31+
return cls
32+
33+
return decorate
34+
35+
36+
@apply_to_all_methods(raise_with_context())
2437
class SDKMethods:
2538
"""
2639
Class to intaract with GoodData Cloud via the Gooddata Python SDK.
2740
"""
2841

2942
_sdk: GoodDataSdk
3043

44+
def get_organization_id(self) -> str:
45+
return self._sdk.catalog_organization.organization_id
46+
3147
def check_workspace_exists(self, workspace_id: str) -> bool:
3248
try:
3349
self._sdk.catalog_workspace.get_workspace(workspace_id)
3450
return True
3551
except Exception:
3652
return False
3753

38-
@raise_with_context()
3954
def get_workspace(self, workspace_id: str, **_: str) -> CatalogWorkspace:
4055
"""
4156
Calls GoodData Python SDK to retrieve a workspace by its ID.
@@ -50,7 +65,6 @@ def get_workspace(self, workspace_id: str, **_: str) -> CatalogWorkspace:
5065
"""
5166
return self._sdk.catalog_workspace.get_workspace(workspace_id)
5267

53-
@raise_with_context()
5468
def delete_panther_workspace(self, workspace_id: str) -> None:
5569
"""
5670
Calls GoodData Python SDK to delete a workspace by its ID.
@@ -63,7 +77,6 @@ def delete_panther_workspace(self, workspace_id: str) -> None:
6377
"""
6478
self._sdk.catalog_workspace.delete_workspace(workspace_id)
6579

66-
@raise_with_context()
6780
def create_or_update_panther_workspace(
6881
self,
6982
workspace_id: str,
@@ -116,7 +129,6 @@ def get_panther_children_workspaces(
116129

117130
return children
118131

119-
@raise_with_context()
120132
def list_workspaces(self) -> list[CatalogWorkspace]:
121133
"""Retrieves all workspaces in the GoodData Cloud domain.
122134
@@ -128,7 +140,6 @@ def list_workspaces(self) -> list[CatalogWorkspace]:
128140
"""
129141
return self._sdk.catalog_workspace.list_workspaces()
130142

131-
@raise_with_context()
132143
def get_declarative_permissions(
133144
self, workspace_id: str
134145
) -> CatalogDeclarativeWorkspacePermissions:
@@ -149,7 +160,6 @@ def get_declarative_permissions(
149160
workspace_id
150161
)
151162

152-
@raise_with_context()
153163
def put_declarative_permissions(
154164
self,
155165
workspace_id: str,
@@ -173,7 +183,6 @@ def put_declarative_permissions(
173183
workspace_id, ws_permissions
174184
)
175185

176-
@raise_with_context()
177186
def get_user(self, user_id: str, **_: str) -> CatalogUser:
178187
"""
179188
Calls GoodData Python SDK to retrieve a user by its ID.
@@ -188,7 +197,6 @@ def get_user(self, user_id: str, **_: str) -> CatalogUser:
188197
"""
189198
return self._sdk.catalog_user.get_user(user_id)
190199

191-
@raise_with_context()
192200
def create_or_update_user(self, user: CatalogUser, **_: str) -> None:
193201
"""
194202
Calls GoodData Python SDK to create or update a user.
@@ -203,7 +211,6 @@ def create_or_update_user(self, user: CatalogUser, **_: str) -> None:
203211
"""
204212
return self._sdk.catalog_user.create_or_update_user(user)
205213

206-
@raise_with_context()
207214
def delete_user(self, user_id: str, **_: str) -> None:
208215
"""
209216
Calls GoodData Python SDK to delete a user by its ID.
@@ -218,7 +225,6 @@ def delete_user(self, user_id: str, **_: str) -> None:
218225
"""
219226
return self._sdk.catalog_user.delete_user(user_id)
220227

221-
@raise_with_context()
222228
def get_user_group(self, user_group_id: str, **_: str) -> CatalogUserGroup:
223229
"""
224230
Calls GoodData Python SDK to retrieve a user group by its ID.
@@ -233,7 +239,6 @@ def get_user_group(self, user_group_id: str, **_: str) -> CatalogUserGroup:
233239
"""
234240
return self._sdk.catalog_user.get_user_group(user_group_id)
235241

236-
@raise_with_context()
237242
def list_user_groups(self) -> list[CatalogUserGroup]:
238243
"""
239244
Calls GoodData Python SDK to retrieve all user groups.
@@ -246,7 +251,6 @@ def list_user_groups(self) -> list[CatalogUserGroup]:
246251
"""
247252
return self._sdk.catalog_user.list_user_groups()
248253

249-
@raise_with_context()
250254
def list_users(self) -> list[CatalogUser]:
251255
"""Calls GoodData Python SDK to retrieve all users.
252256
@@ -255,7 +259,6 @@ def list_users(self) -> list[CatalogUser]:
255259
"""
256260
return self._sdk.catalog_user.list_users()
257261

258-
@raise_with_context()
259262
def create_or_update_user_group(
260263
self, catalog_user_group: CatalogUserGroup, **_: str
261264
) -> None:
@@ -273,7 +276,6 @@ def create_or_update_user_group(
273276
catalog_user_group
274277
)
275278

276-
@raise_with_context()
277279
def delete_user_group(self, user_group_id: str) -> None:
278280
"""Calls GoodData Python SDK to delete a user group by its ID.
279281
@@ -287,7 +289,6 @@ def delete_user_group(self, user_group_id: str) -> None:
287289
"""
288290
return self._sdk.catalog_user.delete_user_group(user_group_id)
289291

290-
@raise_with_context()
291292
def get_declarative_workspace_data_filters(
292293
self,
293294
) -> CatalogDeclarativeWorkspaceDataFilters:
@@ -303,7 +304,6 @@ def get_declarative_workspace_data_filters(
303304
self._sdk.catalog_workspace.get_declarative_workspace_data_filters()
304305
)
305306

306-
@raise_with_context()
307307
def list_user_data_filters(
308308
self, workspace_id: str
309309
) -> list[CatalogUserDataFilter]:
@@ -319,7 +319,6 @@ def list_user_data_filters(
319319
"""
320320
return self._sdk.catalog_workspace.list_user_data_filters(workspace_id)
321321

322-
@raise_with_context()
323322
def delete_user_data_filter(
324323
self, workspace_id: str, user_data_filter_id: str
325324
) -> None:
@@ -338,7 +337,6 @@ def delete_user_data_filter(
338337
workspace_id, user_data_filter_id
339338
)
340339

341-
@raise_with_context()
342340
def create_or_update_user_data_filter(
343341
self, workspace_id: str, user_data_filter: CatalogUserDataFilter
344342
) -> None:
@@ -357,3 +355,19 @@ def create_or_update_user_data_filter(
357355
self._sdk.catalog_workspace.create_or_update_user_data_filter(
358356
workspace_id, user_data_filter
359357
)
358+
359+
def store_declarative_workspace(
360+
self, workspace_id: str, export_path: Path
361+
) -> None:
362+
"""Stores the declarative workspace in the specified export path."""
363+
self._sdk.catalog_workspace.store_declarative_workspace(
364+
workspace_id, export_path
365+
)
366+
367+
def store_declarative_filter_views(
368+
self, workspace_id: str, export_path: Path
369+
) -> None:
370+
"""Stores the declarative filter views in the specified export path."""
371+
self._sdk.catalog_workspace.store_declarative_filter_views(
372+
workspace_id, export_path
373+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# (C) 2025 GoodData Corporation

0 commit comments

Comments
 (0)