cartography 0.106.0rc2__py3-none-any.whl → 0.107.0__py3-none-any.whl
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.
Potentially problematic release.
This version of cartography might be problematic. Click here for more details.
- cartography/_version.py +2 -2
- cartography/cli.py +131 -2
- cartography/config.py +42 -0
- cartography/driftdetect/cli.py +3 -2
- cartography/intel/airbyte/__init__.py +105 -0
- cartography/intel/airbyte/connections.py +120 -0
- cartography/intel/airbyte/destinations.py +81 -0
- cartography/intel/airbyte/organizations.py +59 -0
- cartography/intel/airbyte/sources.py +78 -0
- cartography/intel/airbyte/tags.py +64 -0
- cartography/intel/airbyte/users.py +106 -0
- cartography/intel/airbyte/util.py +122 -0
- cartography/intel/airbyte/workspaces.py +63 -0
- cartography/intel/aws/__init__.py +1 -0
- cartography/intel/aws/cloudtrail_management_events.py +364 -0
- cartography/intel/aws/cloudwatch.py +77 -0
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/ec2/subnets.py +1 -1
- cartography/intel/aws/ecs.py +17 -0
- cartography/intel/aws/inspector.py +77 -48
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/sns.py +62 -2
- cartography/intel/entra/users.py +84 -42
- cartography/intel/scaleway/__init__.py +127 -0
- cartography/intel/scaleway/iam/__init__.py +0 -0
- cartography/intel/scaleway/iam/apikeys.py +71 -0
- cartography/intel/scaleway/iam/applications.py +71 -0
- cartography/intel/scaleway/iam/groups.py +71 -0
- cartography/intel/scaleway/iam/users.py +71 -0
- cartography/intel/scaleway/instances/__init__.py +0 -0
- cartography/intel/scaleway/instances/flexibleips.py +86 -0
- cartography/intel/scaleway/instances/instances.py +92 -0
- cartography/intel/scaleway/projects.py +79 -0
- cartography/intel/scaleway/storage/__init__.py +0 -0
- cartography/intel/scaleway/storage/snapshots.py +86 -0
- cartography/intel/scaleway/storage/volumes.py +84 -0
- cartography/intel/scaleway/utils.py +37 -0
- cartography/intel/sentinelone/__init__.py +69 -0
- cartography/intel/sentinelone/account.py +140 -0
- cartography/intel/sentinelone/agent.py +139 -0
- cartography/intel/sentinelone/api.py +113 -0
- cartography/intel/sentinelone/application.py +248 -0
- cartography/intel/sentinelone/utils.py +28 -0
- cartography/models/airbyte/__init__.py +0 -0
- cartography/models/airbyte/connection.py +138 -0
- cartography/models/airbyte/destination.py +75 -0
- cartography/models/airbyte/organization.py +19 -0
- cartography/models/airbyte/source.py +75 -0
- cartography/models/airbyte/stream.py +74 -0
- cartography/models/airbyte/tag.py +69 -0
- cartography/models/airbyte/user.py +111 -0
- cartography/models/airbyte/workspace.py +46 -0
- cartography/models/aws/cloudtrail/management_events.py +64 -0
- cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
- cartography/models/aws/codebuild/__init__.py +0 -0
- cartography/models/aws/codebuild/project.py +49 -0
- cartography/models/aws/ec2/networkinterfaces.py +2 -0
- cartography/models/aws/ec2/subnet_instance.py +2 -0
- cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
- cartography/models/aws/ecs/containers.py +19 -0
- cartography/models/aws/ecs/task_definitions.py +38 -0
- cartography/models/aws/ecs/tasks.py +24 -1
- cartography/models/aws/inspector/findings.py +37 -0
- cartography/models/aws/inspector/packages.py +1 -31
- cartography/models/aws/sns/topic_subscription.py +74 -0
- cartography/models/entra/user.py +17 -51
- cartography/models/scaleway/__init__.py +0 -0
- cartography/models/scaleway/iam/__init__.py +0 -0
- cartography/models/scaleway/iam/apikey.py +96 -0
- cartography/models/scaleway/iam/application.py +52 -0
- cartography/models/scaleway/iam/group.py +95 -0
- cartography/models/scaleway/iam/user.py +60 -0
- cartography/models/scaleway/instance/__init__.py +0 -0
- cartography/models/scaleway/instance/flexibleip.py +52 -0
- cartography/models/scaleway/instance/instance.py +118 -0
- cartography/models/scaleway/organization.py +19 -0
- cartography/models/scaleway/project.py +48 -0
- cartography/models/scaleway/storage/__init__.py +0 -0
- cartography/models/scaleway/storage/snapshot.py +78 -0
- cartography/models/scaleway/storage/volume.py +51 -0
- cartography/models/sentinelone/__init__.py +1 -0
- cartography/models/sentinelone/account.py +40 -0
- cartography/models/sentinelone/agent.py +50 -0
- cartography/models/sentinelone/application.py +44 -0
- cartography/models/sentinelone/application_version.py +96 -0
- cartography/sync.py +11 -4
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/METADATA +20 -16
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/RECORD +92 -28
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/WHEEL +0 -0
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.106.0rc2.dist-info → cartography-0.107.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.intel.airbyte.util import AirbyteClient
|
|
11
|
+
from cartography.intel.airbyte.util import normalize_airbyte_config
|
|
12
|
+
from cartography.models.airbyte.source import AirbyteSourceSchema
|
|
13
|
+
from cartography.util import timeit
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@timeit
|
|
19
|
+
def sync(
|
|
20
|
+
neo4j_session: neo4j.Session,
|
|
21
|
+
api_session: AirbyteClient,
|
|
22
|
+
org_id: str,
|
|
23
|
+
workspace_ids: List[str],
|
|
24
|
+
common_job_parameters: Dict[str, Any],
|
|
25
|
+
) -> None:
|
|
26
|
+
sources = get(api_session, workspace_ids)
|
|
27
|
+
transformed_sources = transform(sources)
|
|
28
|
+
load_sources(
|
|
29
|
+
neo4j_session, transformed_sources, org_id, common_job_parameters["UPDATE_TAG"]
|
|
30
|
+
)
|
|
31
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@timeit
|
|
35
|
+
def get(
|
|
36
|
+
api_session: AirbyteClient,
|
|
37
|
+
workspace_ids: List[str],
|
|
38
|
+
) -> List[Dict[str, Any]]:
|
|
39
|
+
return api_session.get(
|
|
40
|
+
"/sources",
|
|
41
|
+
params={"workspaceIds": ",".join(workspace_ids)} if workspace_ids else None,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def transform(sources: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
46
|
+
transformed_sources = []
|
|
47
|
+
for source in sources:
|
|
48
|
+
source["configuration"] = normalize_airbyte_config(
|
|
49
|
+
source.get("configuration", {})
|
|
50
|
+
)
|
|
51
|
+
transformed_sources.append(source)
|
|
52
|
+
return transformed_sources
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@timeit
|
|
56
|
+
def load_sources(
|
|
57
|
+
neo4j_session: neo4j.Session,
|
|
58
|
+
data: List[Dict[str, Any]],
|
|
59
|
+
org_id: str,
|
|
60
|
+
update_tag: int,
|
|
61
|
+
) -> None:
|
|
62
|
+
logger.info("Loading %d Airbyte Sources into Neo4j.", len(data))
|
|
63
|
+
load(
|
|
64
|
+
neo4j_session,
|
|
65
|
+
AirbyteSourceSchema(),
|
|
66
|
+
data,
|
|
67
|
+
lastupdated=update_tag,
|
|
68
|
+
ORG_ID=org_id,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@timeit
|
|
73
|
+
def cleanup(
|
|
74
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
75
|
+
) -> None:
|
|
76
|
+
GraphJob.from_node_schema(AirbyteSourceSchema(), common_job_parameters).run(
|
|
77
|
+
neo4j_session
|
|
78
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.intel.airbyte.util import AirbyteClient
|
|
11
|
+
from cartography.models.airbyte.tag import AirbyteTagSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@timeit
|
|
18
|
+
def sync(
|
|
19
|
+
neo4j_session: neo4j.Session,
|
|
20
|
+
api_session: AirbyteClient,
|
|
21
|
+
org_id: str,
|
|
22
|
+
workspace_ids: List[str],
|
|
23
|
+
common_job_parameters: Dict[str, Any],
|
|
24
|
+
) -> None:
|
|
25
|
+
tags = get(api_session, workspace_ids)
|
|
26
|
+
load_tags(neo4j_session, tags, org_id, common_job_parameters["UPDATE_TAG"])
|
|
27
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@timeit
|
|
31
|
+
def get(
|
|
32
|
+
api_session: AirbyteClient,
|
|
33
|
+
workspace_ids: List[str],
|
|
34
|
+
) -> List[Dict[str, Any]]:
|
|
35
|
+
return api_session.get(
|
|
36
|
+
"/tags",
|
|
37
|
+
params={"workspaceIds": ",".join(workspace_ids)} if workspace_ids else None,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@timeit
|
|
42
|
+
def load_tags(
|
|
43
|
+
neo4j_session: neo4j.Session,
|
|
44
|
+
data: List[Dict[str, Any]],
|
|
45
|
+
org_id: str,
|
|
46
|
+
update_tag: int,
|
|
47
|
+
) -> None:
|
|
48
|
+
logger.info("Loading %d Airbyte Tags into Neo4j.", len(data))
|
|
49
|
+
load(
|
|
50
|
+
neo4j_session,
|
|
51
|
+
AirbyteTagSchema(),
|
|
52
|
+
data,
|
|
53
|
+
lastupdated=update_tag,
|
|
54
|
+
ORG_ID=org_id,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@timeit
|
|
59
|
+
def cleanup(
|
|
60
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
61
|
+
) -> None:
|
|
62
|
+
GraphJob.from_node_schema(AirbyteTagSchema(), common_job_parameters).run(
|
|
63
|
+
neo4j_session
|
|
64
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.airbyte.util import AirbyteClient
|
|
12
|
+
from cartography.models.airbyte.user import AirbyteUserSchema
|
|
13
|
+
from cartography.util import timeit
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@timeit
|
|
19
|
+
def sync(
|
|
20
|
+
neo4j_session: neo4j.Session,
|
|
21
|
+
api_session: AirbyteClient,
|
|
22
|
+
org_id: str,
|
|
23
|
+
common_job_parameters: Dict[str, Any],
|
|
24
|
+
) -> None:
|
|
25
|
+
users = get(api_session, org_id=org_id)
|
|
26
|
+
for user in users:
|
|
27
|
+
permissions = get_permissions(api_session, user["id"], org_id=org_id)
|
|
28
|
+
org_admin, workspace_admin, workspace_member = transform_permissions(
|
|
29
|
+
permissions
|
|
30
|
+
)
|
|
31
|
+
user["adminOfOrganization"] = org_admin
|
|
32
|
+
user["adminOfWorkspace"] = workspace_admin
|
|
33
|
+
user["memberOfWorkspace"] = workspace_member
|
|
34
|
+
load_users(neo4j_session, users, org_id, common_job_parameters["UPDATE_TAG"])
|
|
35
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@timeit
|
|
39
|
+
def get(
|
|
40
|
+
api_session: AirbyteClient,
|
|
41
|
+
org_id: str,
|
|
42
|
+
) -> List[Dict[str, Any]]:
|
|
43
|
+
return api_session.get("/users", params={"organizationId": org_id})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@timeit
|
|
47
|
+
def get_permissions(
|
|
48
|
+
api_session: AirbyteClient,
|
|
49
|
+
user_id: str,
|
|
50
|
+
org_id: str,
|
|
51
|
+
) -> List[Dict[str, Any]]:
|
|
52
|
+
return api_session.get(
|
|
53
|
+
"/permissions",
|
|
54
|
+
params={"organizationId": org_id, "userId": user_id},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@timeit
|
|
59
|
+
def transform_permissions(
|
|
60
|
+
permissions: List[Dict[str, Any]],
|
|
61
|
+
) -> Tuple[
|
|
62
|
+
List[str], # org_admin
|
|
63
|
+
List[str], # workspace_admin
|
|
64
|
+
List[str], # workspace_member
|
|
65
|
+
]:
|
|
66
|
+
org_admin: list[str] = []
|
|
67
|
+
workspace_admin: list[str] = []
|
|
68
|
+
workspace_member: list[str] = []
|
|
69
|
+
|
|
70
|
+
for permission in permissions:
|
|
71
|
+
# workspace
|
|
72
|
+
if permission["scope"] == "workspace":
|
|
73
|
+
if permission["permissionType"] in ("workspace_owner", "workspace_admin"):
|
|
74
|
+
workspace_admin.append(permission["scopeId"])
|
|
75
|
+
workspace_member.append(permission["scopeId"])
|
|
76
|
+
# organization
|
|
77
|
+
elif permission["scope"] == "organization":
|
|
78
|
+
if permission["permissionType"] in ("organization_admin",):
|
|
79
|
+
org_admin.append(permission["scopeId"])
|
|
80
|
+
return org_admin, workspace_admin, workspace_member
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@timeit
|
|
84
|
+
def load_users(
|
|
85
|
+
neo4j_session: neo4j.Session,
|
|
86
|
+
data: List[Dict[str, Any]],
|
|
87
|
+
org_id: str,
|
|
88
|
+
update_tag: int,
|
|
89
|
+
) -> None:
|
|
90
|
+
logger.info("Loading %d Airbyte Users into Neo4j.", len(data))
|
|
91
|
+
load(
|
|
92
|
+
neo4j_session,
|
|
93
|
+
AirbyteUserSchema(),
|
|
94
|
+
data,
|
|
95
|
+
ORG_ID=org_id,
|
|
96
|
+
lastupdated=update_tag,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@timeit
|
|
101
|
+
def cleanup(
|
|
102
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
103
|
+
) -> None:
|
|
104
|
+
GraphJob.from_node_schema(AirbyteUserSchema(), common_job_parameters).run(
|
|
105
|
+
neo4j_session
|
|
106
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
9
|
+
_TIMEOUT = (60, 60)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AirbyteClient:
|
|
13
|
+
"""A client for interacting with the Airbyte API.
|
|
14
|
+
This client handles authentication and provides methods to make GET requests to the Airbyte API.
|
|
15
|
+
It automatically handles pagination for GET requests that return multiple pages of data.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, base_url: str, client_id: str, client_secret: str) -> None:
|
|
19
|
+
self._client_id = client_id
|
|
20
|
+
self._client_secret = client_secret
|
|
21
|
+
self.base_url = base_url
|
|
22
|
+
self._access_token_expiry: int | None = None
|
|
23
|
+
self._session = requests.Session()
|
|
24
|
+
|
|
25
|
+
def get(self, uri: str, params: dict | None = None, offset: int = 0) -> list[dict]:
|
|
26
|
+
"""Make a GET request to the Airbyte API.
|
|
27
|
+
This method handles authentication and pagination.
|
|
28
|
+
Args:
|
|
29
|
+
uri (str): The URI to make the GET request to.
|
|
30
|
+
params (dict | None): Optional parameters to include in the request.
|
|
31
|
+
offset (int): The offset for pagination, defaults to 0.
|
|
32
|
+
Returns:
|
|
33
|
+
list[dict]: A list of dictionaries containing the data from the response.
|
|
34
|
+
"""
|
|
35
|
+
self.authenticate()
|
|
36
|
+
if params is None:
|
|
37
|
+
params_with_pagination = {}
|
|
38
|
+
else:
|
|
39
|
+
params_with_pagination = params.copy()
|
|
40
|
+
params_with_pagination["offset"] = offset
|
|
41
|
+
response = self._session.get(
|
|
42
|
+
f"{self.base_url}{uri}", params=params_with_pagination, timeout=_TIMEOUT
|
|
43
|
+
)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
data = response.json().get("data")
|
|
46
|
+
if response.json().get("next", "") != "":
|
|
47
|
+
data.extend(
|
|
48
|
+
self.get(
|
|
49
|
+
uri,
|
|
50
|
+
params=params,
|
|
51
|
+
offset=offset + len(data),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
def authenticate(self) -> None:
|
|
57
|
+
"""Authenticate with the Airbyte API using client credentials.
|
|
58
|
+
This method checks if the access token is still valid and renews it if necessary.
|
|
59
|
+
If the access token is expired or not set, it will make a request to obtain a new access token.
|
|
60
|
+
"""
|
|
61
|
+
if self._access_token_expiry and self._access_token_expiry >= time.time():
|
|
62
|
+
return
|
|
63
|
+
self._session.headers.pop("Authorization", None)
|
|
64
|
+
payload = {
|
|
65
|
+
"grant-type": "client_credentials",
|
|
66
|
+
"client_id": self._client_id,
|
|
67
|
+
"client_secret": self._client_secret,
|
|
68
|
+
}
|
|
69
|
+
response = self._session.post(
|
|
70
|
+
f"{self.base_url}/applications/token", json=payload, timeout=_TIMEOUT
|
|
71
|
+
)
|
|
72
|
+
response.raise_for_status()
|
|
73
|
+
data = response.json()
|
|
74
|
+
self._session.headers["Authorization"] = (
|
|
75
|
+
f"Bearer {data.get('access_token', '')}"
|
|
76
|
+
)
|
|
77
|
+
token_expiry = data.get("expires_in", 0)
|
|
78
|
+
self._access_token_expiry = time.time() + token_expiry
|
|
79
|
+
logger.debug("Access token renewed, expires in %s seconds.", token_expiry)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_airbyte_config(config: dict[str, Any]) -> dict[str, Any]:
|
|
83
|
+
"""Normalize the Airbyte configuration dictionary.
|
|
84
|
+
This function takes a configuration dictionary and normalizes it by mapping keys to a standard set of keys.
|
|
85
|
+
This is useful for ensuring consistency across different configurations, and will allow to connect to existing nodes.
|
|
86
|
+
Args:
|
|
87
|
+
config (dict[str, Any]): The configuration dictionary to normalize.
|
|
88
|
+
Returns:
|
|
89
|
+
dict[str, Any]: A normalized configuration dictionary with standardized keys.
|
|
90
|
+
"""
|
|
91
|
+
normalized_config = {}
|
|
92
|
+
for key in config:
|
|
93
|
+
if key in ("host", "port", "name", "region", "endpoint", "account", ""):
|
|
94
|
+
normalized_config[key] = config[key]
|
|
95
|
+
elif key in ("aws_region_name", "region_name", "s3_bucket_region"):
|
|
96
|
+
normalized_config["region"] = config[key]
|
|
97
|
+
elif key in ("queue_url", "url", "s3_endpoint"):
|
|
98
|
+
normalized_config["endpoint"] = config[key]
|
|
99
|
+
elif key in ("azure_blob_storage_account_name", "storage_account_name"):
|
|
100
|
+
normalized_config["account"] = config[key]
|
|
101
|
+
elif key in (
|
|
102
|
+
"azure_blob_storage_container_name",
|
|
103
|
+
"bucket",
|
|
104
|
+
"database",
|
|
105
|
+
"s3_bucket_name",
|
|
106
|
+
):
|
|
107
|
+
normalized_config["name"] = config[key]
|
|
108
|
+
return normalized_config
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def list_to_string(lst: list[str]) -> str | None:
|
|
112
|
+
"""Convert a list of strings to a comma-separated string."""
|
|
113
|
+
if len(lst) == 0:
|
|
114
|
+
return None
|
|
115
|
+
# Sublist
|
|
116
|
+
formated_list: list[str] = []
|
|
117
|
+
for item in lst:
|
|
118
|
+
if isinstance(item, list):
|
|
119
|
+
formated_list.append("|".join(item))
|
|
120
|
+
else:
|
|
121
|
+
formated_list.append(str(item))
|
|
122
|
+
return ",".join(formated_list)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.intel.airbyte.util import AirbyteClient
|
|
11
|
+
from cartography.models.airbyte.workspace import AirbyteWorkspaceSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@timeit
|
|
18
|
+
def sync(
|
|
19
|
+
neo4j_session: neo4j.Session,
|
|
20
|
+
api_session: AirbyteClient,
|
|
21
|
+
org_id: str,
|
|
22
|
+
common_job_parameters: Dict[str, Any],
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
workspaces = get(api_session, org_id)
|
|
25
|
+
load_workspaces(
|
|
26
|
+
neo4j_session, workspaces, org_id, common_job_parameters["UPDATE_TAG"]
|
|
27
|
+
)
|
|
28
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
29
|
+
return workspaces
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@timeit
|
|
33
|
+
def get(
|
|
34
|
+
api_session: AirbyteClient,
|
|
35
|
+
org_id: str,
|
|
36
|
+
) -> List[Dict[str, Any]]:
|
|
37
|
+
return api_session.get("/workspaces", params={"organizationId": org_id})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@timeit
|
|
41
|
+
def load_workspaces(
|
|
42
|
+
neo4j_session: neo4j.Session,
|
|
43
|
+
data: List[Dict[str, Any]],
|
|
44
|
+
org_id: str,
|
|
45
|
+
update_tag: int,
|
|
46
|
+
) -> None:
|
|
47
|
+
logger.info("Loading %d Airbyte Workspaces into Neo4j.", len(data))
|
|
48
|
+
load(
|
|
49
|
+
neo4j_session,
|
|
50
|
+
AirbyteWorkspaceSchema(),
|
|
51
|
+
data,
|
|
52
|
+
lastupdated=update_tag,
|
|
53
|
+
ORG_ID=org_id,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@timeit
|
|
58
|
+
def cleanup(
|
|
59
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
60
|
+
) -> None:
|
|
61
|
+
GraphJob.from_node_schema(AirbyteWorkspaceSchema(), common_job_parameters).run(
|
|
62
|
+
neo4j_session
|
|
63
|
+
)
|
|
@@ -310,6 +310,7 @@ def start_aws_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
310
310
|
common_job_parameters = {
|
|
311
311
|
"UPDATE_TAG": config.update_tag,
|
|
312
312
|
"permission_relationships_file": config.permission_relationships_file,
|
|
313
|
+
"aws_cloudtrail_management_events_lookback_hours": config.aws_cloudtrail_management_events_lookback_hours,
|
|
313
314
|
}
|
|
314
315
|
try:
|
|
315
316
|
boto3_session = boto3.Session()
|