Skip to content

Commit bbb1f1a

Browse files
authored
Merge pull request #721 from hkad98/aac-productization
Integration with @gooddata/code-cli
2 parents fa20827 + 7668ff5 commit bbb1f1a

File tree

13 files changed

+446
-2
lines changed

13 files changed

+446
-2
lines changed

.envrc

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export PYTHONPATH="${PYTHONPATH}:${SCRIPT_DIR}/gooddata-sdk/"
1010
export PYTHONPATH="${PYTHONPATH}:${SCRIPT_DIR}/gooddata-pandas/"
1111
export PYTHONPATH="${PYTHONPATH}:${SCRIPT_DIR}/gooddata-dbt/"
1212

13+
export PATH="${PATH}:${SCRIPT_DIR}/gooddata-sdk/bin"
1314
export PATH="${PATH}:${SCRIPT_DIR}/gooddata-dbt/bin"

gooddata-sdk/bin/gdc

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python3
2+
# (C) 2024 GoodData Corporation
3+
# -*- coding: utf-8 -*-
4+
import re
5+
import sys
6+
7+
from gooddata_sdk.cli.gdc_core import main
8+
9+
if __name__ == "__main__":
10+
sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0])
11+
sys.exit(main(sys.argv[1:]))

gooddata-sdk/gooddata_sdk/catalog/data_source/service.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,10 @@ def put_declarative_data_sources(
193193
"""
194194
if test_data_sources:
195195
self.test_data_sources_connection(declarative_data_sources, credentials_path, config_file)
196-
credentials = self._credentials_from_file(credentials_path) if credentials_path is not None else dict()
197-
self._layout_api.put_data_sources_layout(declarative_data_sources.to_api(credentials, config_file))
196+
credentials = self._credentials_from_file(credentials_path) if credentials_path is not None else None
197+
self._layout_api.put_data_sources_layout(
198+
declarative_data_sources.to_api(credentials=credentials, config_file=config_file)
199+
)
198200

199201
def store_declarative_data_sources(self, layout_root_path: Path = Path.cwd()) -> None:
200202
"""Store data sources layouts in a directory hierarchy.

gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user.py

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ def store_to_disk(self, layout_organization_folder: Path) -> None:
4747
@attr.s(auto_attribs=True, kw_only=True)
4848
class CatalogDeclarativeUser(Base):
4949
id: str
50+
email: Optional[str] = None
51+
firstname: Optional[str] = None
52+
lastname: Optional[str] = None
5053
auth_id: Optional[str] = None
5154
user_groups: List[CatalogDeclarativeUserGroupIdentifier] = attr.field(factory=list)
5255
settings: List[CatalogDeclarativeSetting] = attr.field(factory=list)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# (C) 2024 GoodData Corporation
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# (C) 2024 GoodData Corporation
2+
import argparse
3+
import json
4+
import shutil
5+
import subprocess
6+
from pathlib import Path
7+
8+
from gooddata_sdk import CatalogDeclarativeWorkspaces, GoodDataSdk
9+
from gooddata_sdk.cli.constants import (
10+
BASE_DIR,
11+
CONFIG_FILE,
12+
DATA_SOURCES,
13+
GD_COMMAND,
14+
USER_GROUPS,
15+
USERS,
16+
WORKSPACES,
17+
WORKSPACES_DATA_FILTERS,
18+
)
19+
from gooddata_sdk.cli.utils import (
20+
Bcolors,
21+
measure_clone,
22+
)
23+
24+
25+
def _call_gd_stream_in(workspace_objects: CatalogDeclarativeWorkspaces, path: Path) -> None:
26+
"""
27+
Call 'gd stream-in' command to create workspaces file structure using Node.js CLI.
28+
"""
29+
workspaces = json.dumps({WORKSPACES: workspace_objects.to_dict()[WORKSPACES]})
30+
p = subprocess.Popen(
31+
[GD_COMMAND, "stream-in"],
32+
cwd=path,
33+
stdin=subprocess.PIPE,
34+
stdout=subprocess.PIPE,
35+
stderr=subprocess.PIPE,
36+
)
37+
_, err = p.communicate(input=workspaces.encode())
38+
if err:
39+
print(f"{Bcolors.FAIL}Clone workspaces failed with the following error {err=}.{Bcolors.ENDC}")
40+
41+
42+
@measure_clone(step="workspaces")
43+
def _clone_workspaces(sdk: GoodDataSdk, path: Path) -> None:
44+
assert (path / CONFIG_FILE).exists() and (path / BASE_DIR).exists()
45+
workspace_objects = sdk.catalog_workspace.get_declarative_workspaces()
46+
_call_gd_stream_in(workspace_objects, path)
47+
48+
49+
@measure_clone(step="data sources")
50+
def _clone_data_sources(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
51+
data_sources = sdk.catalog_data_source.get_declarative_data_sources()
52+
data_sources.store_to_disk(analytics_root_dir)
53+
54+
55+
@measure_clone(step="user groups")
56+
def _clone_user_groups(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
57+
user_groups = sdk.catalog_user.get_declarative_user_groups()
58+
user_groups.store_to_disk(analytics_root_dir)
59+
60+
61+
@measure_clone(step="users")
62+
def _clone_users(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
63+
users = sdk.catalog_user.get_declarative_users()
64+
users.store_to_disk(analytics_root_dir)
65+
66+
67+
@measure_clone(step="workspace data filters")
68+
def _clone_workspace_data_filters(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
69+
workspace_data_filters = sdk.catalog_workspace.get_declarative_workspace_data_filters()
70+
workspace_data_filters.store_to_disk(analytics_root_dir)
71+
72+
73+
def clone_all(path: Path) -> None:
74+
init_file = path / CONFIG_FILE
75+
sdk = GoodDataSdk.create_from_profile(profiles_path=init_file)
76+
analytics_root_dir = path / BASE_DIR
77+
78+
# clean the directory
79+
if analytics_root_dir.exists():
80+
shutil.rmtree(analytics_root_dir)
81+
# create directory
82+
analytics_root_dir.mkdir()
83+
84+
print("Cloning the whole organization... ⏲️⏲️️⏲️️")
85+
_clone_data_sources(sdk, analytics_root_dir)
86+
_clone_user_groups(sdk, analytics_root_dir)
87+
_clone_users(sdk, analytics_root_dir)
88+
_clone_workspace_data_filters(sdk, analytics_root_dir)
89+
_clone_workspaces(sdk, path)
90+
print("Cloning finished 🚀🚀🚀")
91+
92+
93+
def clone_granular(path: Path, args: argparse.Namespace) -> None:
94+
init_file = path / CONFIG_FILE
95+
analytics_root_dir = path / "analytics"
96+
config_directory = analytics_root_dir.parent
97+
sdk = GoodDataSdk.create_from_profile(profiles_path=init_file)
98+
selected_entities = set(args.only)
99+
if DATA_SOURCES in selected_entities:
100+
_clone_data_sources(sdk, analytics_root_dir)
101+
if USER_GROUPS in selected_entities:
102+
_clone_user_groups(sdk, analytics_root_dir)
103+
if USERS in selected_entities:
104+
_clone_users(sdk, analytics_root_dir)
105+
if WORKSPACES_DATA_FILTERS in selected_entities:
106+
_clone_workspace_data_filters(sdk, analytics_root_dir)
107+
if WORKSPACES in selected_entities:
108+
_clone_workspaces(sdk, config_directory)
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# (C) 2024 GoodData Corporation
2+
from pathlib import Path
3+
4+
DATA_SOURCES = "data_sources"
5+
USER_GROUPS = "user_groups"
6+
USERS = "users"
7+
WORKSPACES_DATA_FILTERS = "workspaces_data_filters"
8+
WORKSPACES = "workspaces"
9+
WORKSPACE = "workspace"
10+
DATA_SOURCE = "data_source"
11+
12+
CONFIG_FILE = "gooddata.yaml"
13+
BASE_DIR = "analytics"
14+
15+
GD_ROOT = Path.home() / ".gooddata"
16+
GD_COMMAND = GD_ROOT / "node_modules/.bin/gd"
17+
GD_PACKAGE_JSON = GD_ROOT / "node_modules/@gooddata/code-cli/package.json"
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# (C) 2024 GoodData Corporation
2+
import argparse
3+
import json
4+
import subprocess
5+
from pathlib import Path
6+
from typing import Any
7+
8+
from gooddata_sdk import (
9+
CatalogDeclarativeDataSources,
10+
CatalogDeclarativeUserGroups,
11+
CatalogDeclarativeUsers,
12+
CatalogDeclarativeWorkspace,
13+
CatalogDeclarativeWorkspaceDataFilters,
14+
CatalogDeclarativeWorkspaces,
15+
GoodDataSdk,
16+
)
17+
from gooddata_sdk.cli.constants import (
18+
BASE_DIR,
19+
CONFIG_FILE,
20+
DATA_SOURCES,
21+
GD_COMMAND,
22+
USER_GROUPS,
23+
USERS,
24+
WORKSPACES,
25+
WORKSPACES_DATA_FILTERS,
26+
)
27+
from gooddata_sdk.cli.utils import measure_deploy
28+
29+
30+
def _call_gd_stram_out(path: Path) -> dict[str, Any]:
31+
"""
32+
Call 'gd stream-out' command to read workspaces file structure using Node.js CLI.
33+
"""
34+
assert (path / CONFIG_FILE).exists() and (path / BASE_DIR).exists()
35+
p = subprocess.Popen(
36+
[GD_COMMAND, "stream-out", "--no-validate"],
37+
cwd=path,
38+
stdin=subprocess.PIPE,
39+
stdout=subprocess.PIPE,
40+
stderr=subprocess.PIPE,
41+
)
42+
output, err = p.communicate()
43+
if err:
44+
print(f"Deploy workspaces failed with the following error {err=}.")
45+
data = json.loads(output.decode())
46+
if WORKSPACES not in data:
47+
raise ValueError("No workspaces found in the output.")
48+
return data
49+
50+
51+
@measure_deploy(step=WORKSPACES)
52+
def _deploy_workspaces_with_filters(sdk: GoodDataSdk, path: Path) -> None:
53+
analytics_root_dir = path / BASE_DIR
54+
data = _call_gd_stram_out(path)
55+
workspaces = [CatalogDeclarativeWorkspace.from_dict(workspace_dict) for workspace_dict in data[WORKSPACES]]
56+
# fetch this information first, so we do not lose them
57+
workspace_data_filters = CatalogDeclarativeWorkspaceDataFilters.load_from_disk(analytics_root_dir)
58+
workspaces_o = CatalogDeclarativeWorkspaces(
59+
workspaces=workspaces, workspace_data_filters=workspace_data_filters.workspace_data_filters
60+
)
61+
sdk.catalog_workspace.put_declarative_workspaces(workspaces_o)
62+
63+
64+
@measure_deploy(step="data sources")
65+
def _deploy_data_sources(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
66+
data_sources = CatalogDeclarativeDataSources.load_from_disk(analytics_root_dir)
67+
sdk.catalog_data_source.put_declarative_data_sources(
68+
data_sources, config_file=analytics_root_dir.parent / "gooddata.yaml"
69+
)
70+
71+
72+
@measure_deploy(step="user groups")
73+
def _deploy_user_groups(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
74+
user_groups = CatalogDeclarativeUserGroups.load_from_disk(analytics_root_dir)
75+
sdk.catalog_user.put_declarative_user_groups(user_groups)
76+
77+
78+
@measure_deploy(step=USERS)
79+
def _deploy_users(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
80+
users = CatalogDeclarativeUsers.load_from_disk(analytics_root_dir)
81+
sdk.catalog_user.put_declarative_users(users)
82+
83+
84+
@measure_deploy(step="workspace data filters")
85+
def _deploy_workspace_data_filters(sdk: GoodDataSdk, analytics_root_dir: Path) -> None:
86+
workspace_data_filters = CatalogDeclarativeWorkspaceDataFilters.load_from_disk(analytics_root_dir)
87+
sdk.catalog_workspace.put_declarative_workspace_data_filters(workspace_data_filters)
88+
89+
90+
def deploy_all(path: Path) -> None:
91+
init_file = path / CONFIG_FILE
92+
sdk = GoodDataSdk.create_from_profile(profiles_path=init_file)
93+
94+
analytics_root_dir = path / BASE_DIR
95+
96+
print("Deploying the whole organization... ⏲️⏲️⏲️")
97+
_deploy_data_sources(sdk, analytics_root_dir)
98+
_deploy_user_groups(sdk, analytics_root_dir)
99+
_deploy_users(sdk, analytics_root_dir)
100+
_deploy_workspaces_with_filters(sdk, path)
101+
print("Deployed 🚀🚀🚀")
102+
103+
104+
def deploy_granular(path: Path, args: argparse.Namespace) -> None:
105+
init_file = path / CONFIG_FILE
106+
analytics_root_dir = path / "analytics"
107+
selected_entities = set(args.only)
108+
sdk = GoodDataSdk.create_from_profile(profiles_path=init_file)
109+
if DATA_SOURCES in selected_entities:
110+
_deploy_data_sources(sdk, analytics_root_dir)
111+
if USER_GROUPS in selected_entities:
112+
_deploy_user_groups(sdk, analytics_root_dir)
113+
if USERS in selected_entities:
114+
_deploy_users(sdk, analytics_root_dir)
115+
if WORKSPACES_DATA_FILTERS in selected_entities:
116+
_deploy_workspace_data_filters(sdk, analytics_root_dir)
117+
if WORKSPACES in selected_entities:
118+
_deploy_workspaces_with_filters(sdk, analytics_root_dir.parent)

0 commit comments

Comments
 (0)