cartography 0.106.0rc2__py3-none-any.whl → 0.107.0rc1__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.

Files changed (78) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +131 -2
  3. cartography/config.py +42 -0
  4. cartography/driftdetect/cli.py +3 -2
  5. cartography/intel/airbyte/__init__.py +105 -0
  6. cartography/intel/airbyte/connections.py +120 -0
  7. cartography/intel/airbyte/destinations.py +81 -0
  8. cartography/intel/airbyte/organizations.py +59 -0
  9. cartography/intel/airbyte/sources.py +78 -0
  10. cartography/intel/airbyte/tags.py +64 -0
  11. cartography/intel/airbyte/users.py +106 -0
  12. cartography/intel/airbyte/util.py +122 -0
  13. cartography/intel/airbyte/workspaces.py +63 -0
  14. cartography/intel/aws/__init__.py +1 -0
  15. cartography/intel/aws/cloudtrail_management_events.py +364 -0
  16. cartography/intel/aws/codebuild.py +132 -0
  17. cartography/intel/aws/resources.py +4 -0
  18. cartography/intel/aws/sns.py +62 -2
  19. cartography/intel/entra/users.py +84 -42
  20. cartography/intel/scaleway/__init__.py +127 -0
  21. cartography/intel/scaleway/iam/__init__.py +0 -0
  22. cartography/intel/scaleway/iam/apikeys.py +71 -0
  23. cartography/intel/scaleway/iam/applications.py +71 -0
  24. cartography/intel/scaleway/iam/groups.py +71 -0
  25. cartography/intel/scaleway/iam/users.py +71 -0
  26. cartography/intel/scaleway/instances/__init__.py +0 -0
  27. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  28. cartography/intel/scaleway/instances/instances.py +92 -0
  29. cartography/intel/scaleway/projects.py +79 -0
  30. cartography/intel/scaleway/storage/__init__.py +0 -0
  31. cartography/intel/scaleway/storage/snapshots.py +86 -0
  32. cartography/intel/scaleway/storage/volumes.py +84 -0
  33. cartography/intel/scaleway/utils.py +37 -0
  34. cartography/intel/sentinelone/__init__.py +63 -0
  35. cartography/intel/sentinelone/account.py +140 -0
  36. cartography/intel/sentinelone/agent.py +139 -0
  37. cartography/intel/sentinelone/api.py +113 -0
  38. cartography/intel/sentinelone/utils.py +9 -0
  39. cartography/models/airbyte/__init__.py +0 -0
  40. cartography/models/airbyte/connection.py +138 -0
  41. cartography/models/airbyte/destination.py +75 -0
  42. cartography/models/airbyte/organization.py +19 -0
  43. cartography/models/airbyte/source.py +75 -0
  44. cartography/models/airbyte/stream.py +74 -0
  45. cartography/models/airbyte/tag.py +69 -0
  46. cartography/models/airbyte/user.py +111 -0
  47. cartography/models/airbyte/workspace.py +46 -0
  48. cartography/models/aws/cloudtrail/management_events.py +64 -0
  49. cartography/models/aws/codebuild/__init__.py +0 -0
  50. cartography/models/aws/codebuild/project.py +49 -0
  51. cartography/models/aws/ecs/containers.py +19 -0
  52. cartography/models/aws/ecs/task_definitions.py +38 -0
  53. cartography/models/aws/sns/topic_subscription.py +74 -0
  54. cartography/models/entra/user.py +17 -51
  55. cartography/models/scaleway/__init__.py +0 -0
  56. cartography/models/scaleway/iam/__init__.py +0 -0
  57. cartography/models/scaleway/iam/apikey.py +96 -0
  58. cartography/models/scaleway/iam/application.py +52 -0
  59. cartography/models/scaleway/iam/group.py +95 -0
  60. cartography/models/scaleway/iam/user.py +60 -0
  61. cartography/models/scaleway/instance/__init__.py +0 -0
  62. cartography/models/scaleway/instance/flexibleip.py +52 -0
  63. cartography/models/scaleway/instance/instance.py +118 -0
  64. cartography/models/scaleway/organization.py +19 -0
  65. cartography/models/scaleway/project.py +48 -0
  66. cartography/models/scaleway/storage/__init__.py +0 -0
  67. cartography/models/scaleway/storage/snapshot.py +78 -0
  68. cartography/models/scaleway/storage/volume.py +51 -0
  69. cartography/models/sentinelone/__init__.py +1 -0
  70. cartography/models/sentinelone/account.py +40 -0
  71. cartography/models/sentinelone/agent.py +50 -0
  72. cartography/sync.py +11 -4
  73. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/METADATA +20 -16
  74. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/RECORD +78 -18
  75. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/WHEEL +0 -0
  76. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/entry_points.txt +0 -0
  77. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/licenses/LICENSE +0 -0
  78. {cartography-0.106.0rc2.dist-info → cartography-0.107.0rc1.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,51 @@ from cartography.util import timeit
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
+ # NOTE:
19
+ # Microsoft Graph imposes limits on the length of the $select clause as well as
20
+ # the number of properties that can be selected in a single request. In
21
+ # practice we have seen 400 Bad Request responses that bubble up as
22
+ # `Microsoft.SharePoint.Client.InvalidClientQueryException` once that limit is
23
+ # breached (Graph internally rewrites the next-link using a SharePoint style
24
+ # `id in (…)` filter which is then rejected).
25
+ #
26
+ # To avoid tripping this bug we only request a *core* subset of user attributes
27
+ # that are most commonly used in downstream analysis. The transform() function
28
+ # tolerates missing attributes (the generated MS Graph SDK simply returns
29
+ # `None` for properties that are not present in the payload), so fetching fewer
30
+ # fields is safe – we merely get more `null` values in the graph.
31
+ #
32
+ # If you need additional attributes in the future, append them here but keep the
33
+ # total character count of the comma-separated list comfortably below 500 and
34
+ # stay within the official v1.0 contract (beta-only fields cause similar
35
+ # failures). 20–25 fields is a good rule-of-thumb.
36
+ #
37
+ # References:
38
+ # • https://learn.microsoft.com/graph/query-parameters#select-parameter
39
+ # • https://learn.microsoft.com/graph/api/user-list?view=graph-rest-1.0
40
+ #
41
+ USER_SELECT_FIELDS = [
42
+ "id",
43
+ "userPrincipalName",
44
+ "displayName",
45
+ "givenName",
46
+ "surname",
47
+ "mail",
48
+ "mobilePhone",
49
+ "businessPhones",
50
+ "jobTitle",
51
+ "department",
52
+ "officeLocation",
53
+ "city",
54
+ "country",
55
+ "companyName",
56
+ "preferredLanguage",
57
+ "employeeId",
58
+ "employeeType",
59
+ "accountEnabled",
60
+ "ageGroup",
61
+ ]
62
+
18
63
 
19
64
  @timeit
20
65
  async def get_tenant(client: GraphServiceClient) -> Organization:
@@ -27,14 +72,20 @@ async def get_tenant(client: GraphServiceClient) -> Organization:
27
72
 
28
73
  @timeit
29
74
  async def get_users(client: GraphServiceClient) -> list[User]:
75
+ """Fetch all users with their manager reference in as few requests as possible.
76
+
77
+ We leverage `$expand=manager($select=id)` so the manager's *id* is hydrated
78
+ alongside every user record. This avoids making a second round-trip per
79
+ user – vastly reducing latency and eliminating the noisy 404s that occur
80
+ when a user has no manager assigned.
30
81
  """
31
- Get all users from Microsoft Graph API with pagination support
32
- """
82
+
33
83
  all_users: list[User] = []
34
84
  request_configuration = client.users.UsersRequestBuilderGetRequestConfiguration(
35
85
  query_parameters=client.users.UsersRequestBuilderGetQueryParameters(
36
- # Request more items per page to reduce number of API calls
37
86
  top=999,
87
+ select=USER_SELECT_FIELDS,
88
+ expand=["manager($select=id)"],
38
89
  ),
39
90
  )
40
91
 
@@ -43,18 +94,32 @@ async def get_users(client: GraphServiceClient) -> list[User]:
43
94
  all_users.extend(page.value)
44
95
  if not page.odata_next_link:
45
96
  break
46
- page = await client.users.with_url(page.odata_next_link).get()
97
+
98
+ try:
99
+ page = await client.users.with_url(page.odata_next_link).get()
100
+ except Exception as e:
101
+ logger.error(
102
+ "Failed to fetch next page of Entra ID users – stopping pagination early: %s",
103
+ e,
104
+ )
105
+ break
47
106
 
48
107
  return all_users
49
108
 
50
109
 
51
110
  @timeit
111
+ # The manager reference is now embedded in the user objects courtesy of the
112
+ # `$expand` we added above, so we no longer need a separate `manager_map`.
52
113
  def transform_users(users: list[User]) -> list[dict[str, Any]]:
53
- """
54
- Transform the API response into the format expected by our schema
55
- """
114
+ """Convert MS Graph SDK `User` models into dicts matching our schema."""
115
+
56
116
  result: list[dict[str, Any]] = []
57
117
  for user in users:
118
+ manager_id: str | None = None
119
+ if getattr(user, "manager", None) is not None:
120
+ # The SDK materialises `manager` as a DirectoryObject (or subclass)
121
+ manager_id = getattr(user.manager, "id", None)
122
+
58
123
  transformed_user = {
59
124
  "id": user.id,
60
125
  "user_principal_name": user.user_principal_name,
@@ -62,47 +127,24 @@ def transform_users(users: list[User]) -> list[dict[str, Any]]:
62
127
  "given_name": user.given_name,
63
128
  "surname": user.surname,
64
129
  "mail": user.mail,
65
- "other_mails": user.other_mails,
66
- "preferred_language": user.preferred_language,
67
- "preferred_name": user.preferred_name,
68
- "state": user.state,
69
- "usage_location": user.usage_location,
70
- "user_type": user.user_type,
71
- "show_in_address_list": user.show_in_address_list,
72
- "sign_in_sessions_valid_from_date_time": user.sign_in_sessions_valid_from_date_time,
73
- "security_identifier": user.on_premises_security_identifier,
74
- "account_enabled": user.account_enabled,
75
- "age_group": user.age_group,
130
+ "mobile_phone": user.mobile_phone,
76
131
  "business_phones": user.business_phones,
132
+ "job_title": user.job_title,
133
+ "department": user.department,
134
+ "office_location": user.office_location,
77
135
  "city": user.city,
78
- "company_name": user.company_name,
79
- "consent_provided_for_minor": user.consent_provided_for_minor,
136
+ "state": user.state,
80
137
  "country": user.country,
81
- "created_date_time": user.created_date_time,
82
- "creation_type": user.creation_type,
83
- "deleted_date_time": user.deleted_date_time,
84
- "department": user.department,
138
+ "company_name": user.company_name,
139
+ "preferred_language": user.preferred_language,
85
140
  "employee_id": user.employee_id,
86
141
  "employee_type": user.employee_type,
87
- "external_user_state": user.external_user_state,
88
- "external_user_state_change_date_time": user.external_user_state_change_date_time,
89
- "hire_date": user.hire_date,
90
- "is_management_restricted": user.is_management_restricted,
91
- "is_resource_account": user.is_resource_account,
92
- "job_title": user.job_title,
93
- "last_password_change_date_time": user.last_password_change_date_time,
94
- "mail_nickname": user.mail_nickname,
95
- "office_location": user.office_location,
96
- "on_premises_distinguished_name": user.on_premises_distinguished_name,
97
- "on_premises_domain_name": user.on_premises_domain_name,
98
- "on_premises_immutable_id": user.on_premises_immutable_id,
99
- "on_premises_last_sync_date_time": user.on_premises_last_sync_date_time,
100
- "on_premises_sam_account_name": user.on_premises_sam_account_name,
101
- "on_premises_security_identifier": user.on_premises_security_identifier,
102
- "on_premises_sync_enabled": user.on_premises_sync_enabled,
103
- "on_premises_user_principal_name": user.on_premises_user_principal_name,
142
+ "account_enabled": user.account_enabled,
143
+ "age_group": user.age_group,
144
+ "manager_id": manager_id,
104
145
  }
105
146
  result.append(transformed_user)
147
+
106
148
  return result
107
149
 
108
150
 
@@ -198,7 +240,7 @@ async def sync_entra_users(
198
240
  credential, scopes=["https://graph.microsoft.com/.default"]
199
241
  )
200
242
 
201
- # Get tenant information
243
+ # Fetch tenant and users (with manager reference already populated by `$expand`)
202
244
  tenant = await get_tenant(client)
203
245
  users = await get_users(client)
204
246
 
@@ -0,0 +1,127 @@
1
+ import logging
2
+
3
+ import neo4j
4
+ import scaleway
5
+
6
+ import cartography.intel.scaleway.iam.apikeys
7
+ import cartography.intel.scaleway.iam.applications
8
+ import cartography.intel.scaleway.iam.groups
9
+ import cartography.intel.scaleway.iam.users
10
+ import cartography.intel.scaleway.instances.flexibleips
11
+ import cartography.intel.scaleway.instances.instances
12
+ import cartography.intel.scaleway.projects
13
+ import cartography.intel.scaleway.storage.snapshots
14
+ import cartography.intel.scaleway.storage.volumes
15
+ from cartography.config import Config
16
+ from cartography.util import timeit
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @timeit
22
+ def start_scaleway_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
23
+ """
24
+ If this module is configured, perform ingestion of Scaleway data. Otherwise warn and exit
25
+ :param neo4j_session: Neo4J session for database interface
26
+ :param config: A cartography.config object
27
+ :return: None
28
+ """
29
+
30
+ if (
31
+ not config.scaleway_access_key
32
+ or not config.scaleway_secret_key
33
+ or not config.scaleway_org
34
+ ):
35
+ logger.info(
36
+ "Tailscale import is not configured - skipping this module. "
37
+ "See docs to configure.",
38
+ )
39
+ return
40
+
41
+ # Create client
42
+ client = scaleway.Client(
43
+ access_key=config.scaleway_access_key,
44
+ secret_key=config.scaleway_secret_key,
45
+ )
46
+
47
+ common_job_parameters = {
48
+ "UPDATE_TAG": config.update_tag,
49
+ "ORG_ID": config.scaleway_org,
50
+ }
51
+
52
+ # Organization level
53
+ projects = cartography.intel.scaleway.projects.sync(
54
+ neo4j_session,
55
+ client,
56
+ common_job_parameters,
57
+ org_id=config.scaleway_org,
58
+ update_tag=config.update_tag,
59
+ )
60
+ projects_id = [project["id"] for project in projects]
61
+ cartography.intel.scaleway.iam.users.sync(
62
+ neo4j_session,
63
+ client,
64
+ common_job_parameters,
65
+ org_id=config.scaleway_org,
66
+ update_tag=config.update_tag,
67
+ )
68
+ cartography.intel.scaleway.iam.applications.sync(
69
+ neo4j_session,
70
+ client,
71
+ common_job_parameters,
72
+ org_id=config.scaleway_org,
73
+ update_tag=config.update_tag,
74
+ )
75
+ cartography.intel.scaleway.iam.groups.sync(
76
+ neo4j_session,
77
+ client,
78
+ common_job_parameters,
79
+ org_id=config.scaleway_org,
80
+ update_tag=config.update_tag,
81
+ )
82
+ cartography.intel.scaleway.iam.apikeys.sync(
83
+ neo4j_session,
84
+ client,
85
+ common_job_parameters,
86
+ org_id=config.scaleway_org,
87
+ update_tag=config.update_tag,
88
+ )
89
+
90
+ # Storage
91
+ cartography.intel.scaleway.storage.volumes.sync(
92
+ neo4j_session,
93
+ client,
94
+ common_job_parameters,
95
+ org_id=config.scaleway_org,
96
+ projects_id=projects_id,
97
+ update_tag=config.update_tag,
98
+ )
99
+ cartography.intel.scaleway.storage.snapshots.sync(
100
+ neo4j_session,
101
+ client,
102
+ common_job_parameters,
103
+ org_id=config.scaleway_org,
104
+ projects_id=projects_id,
105
+ update_tag=config.update_tag,
106
+ )
107
+
108
+ # Instances
109
+ # DISABLED due to https://github.com/scaleway/scaleway-sdk-python/issues/1040
110
+ """
111
+ cartography.intel.scaleway.instances.flexibleips.sync(
112
+ neo4j_session,
113
+ client,
114
+ common_job_parameters,
115
+ org_id=config.scaleway_org,
116
+ projects_id=projects_id,
117
+ update_tag=config.update_tag,
118
+ )
119
+ """
120
+ cartography.intel.scaleway.instances.instances.sync(
121
+ neo4j_session,
122
+ client,
123
+ common_job_parameters,
124
+ org_id=config.scaleway_org,
125
+ projects_id=projects_id,
126
+ update_tag=config.update_tag,
127
+ )
File without changes
@@ -0,0 +1,71 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import scaleway
6
+ from scaleway.iam.v1alpha1 import APIKey
7
+ from scaleway.iam.v1alpha1 import IamV1Alpha1API
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.scaleway.utils import scaleway_obj_to_dict
12
+ from cartography.models.scaleway.iam.apikey import ScalewayApiKeySchema
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
+ client: scaleway.Client,
22
+ common_job_parameters: dict[str, Any],
23
+ org_id: str,
24
+ update_tag: int,
25
+ ) -> None:
26
+ apikeys = get(client, org_id)
27
+ formatted_apikeys = transform_apikeys(apikeys)
28
+ load_apikeys(neo4j_session, formatted_apikeys, org_id, update_tag)
29
+ cleanup(neo4j_session, common_job_parameters)
30
+
31
+
32
+ @timeit
33
+ def get(
34
+ client: scaleway.Client,
35
+ org_id: str,
36
+ ) -> list[APIKey]:
37
+ api = IamV1Alpha1API(client)
38
+ return api.list_api_keys_all(organization_id=org_id)
39
+
40
+
41
+ def transform_apikeys(apikeys: list[APIKey]) -> list[dict[str, Any]]:
42
+ formatted_apikeys = []
43
+ for apikey in apikeys:
44
+ formatted_apikeys.append(scaleway_obj_to_dict(apikey))
45
+ return formatted_apikeys
46
+
47
+
48
+ @timeit
49
+ def load_apikeys(
50
+ neo4j_session: neo4j.Session,
51
+ data: list[dict[str, Any]],
52
+ org_id: str,
53
+ update_tag: int,
54
+ ) -> None:
55
+ logger.info("Loading %d Scaleway ApiKeys into Neo4j.", len(data))
56
+ load(
57
+ neo4j_session,
58
+ ScalewayApiKeySchema(),
59
+ data,
60
+ lastupdated=update_tag,
61
+ ORG_ID=org_id,
62
+ )
63
+
64
+
65
+ @timeit
66
+ def cleanup(
67
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
68
+ ) -> None:
69
+ GraphJob.from_node_schema(ScalewayApiKeySchema(), common_job_parameters).run(
70
+ neo4j_session
71
+ )
@@ -0,0 +1,71 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import scaleway
6
+ from scaleway.iam.v1alpha1 import Application
7
+ from scaleway.iam.v1alpha1 import IamV1Alpha1API
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.scaleway.utils import scaleway_obj_to_dict
12
+ from cartography.models.scaleway.iam.application import ScalewayApplicationSchema
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
+ client: scaleway.Client,
22
+ common_job_parameters: dict[str, Any],
23
+ org_id: str,
24
+ update_tag: int,
25
+ ) -> None:
26
+ applications = get(client, org_id)
27
+ formatted_applications = transform_applications(applications)
28
+ load_applications(neo4j_session, formatted_applications, org_id, update_tag)
29
+ cleanup(neo4j_session, common_job_parameters)
30
+
31
+
32
+ @timeit
33
+ def get(
34
+ client: scaleway.Client,
35
+ org_id: str,
36
+ ) -> list[Application]:
37
+ api = IamV1Alpha1API(client)
38
+ return api.list_applications_all(organization_id=org_id)
39
+
40
+
41
+ def transform_applications(applications: list[Application]) -> list[dict[str, Any]]:
42
+ formatted_applications = []
43
+ for application in applications:
44
+ formatted_applications.append(scaleway_obj_to_dict(application))
45
+ return formatted_applications
46
+
47
+
48
+ @timeit
49
+ def load_applications(
50
+ neo4j_session: neo4j.Session,
51
+ data: list[dict[str, Any]],
52
+ org_id: str,
53
+ update_tag: int,
54
+ ) -> None:
55
+ logger.info("Loading %d Scaleway Applications into Neo4j.", len(data))
56
+ load(
57
+ neo4j_session,
58
+ ScalewayApplicationSchema(),
59
+ data,
60
+ lastupdated=update_tag,
61
+ ORG_ID=org_id,
62
+ )
63
+
64
+
65
+ @timeit
66
+ def cleanup(
67
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
68
+ ) -> None:
69
+ GraphJob.from_node_schema(ScalewayApplicationSchema(), common_job_parameters).run(
70
+ neo4j_session
71
+ )
@@ -0,0 +1,71 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import scaleway
6
+ from scaleway.iam.v1alpha1 import Group
7
+ from scaleway.iam.v1alpha1 import IamV1Alpha1API
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.scaleway.utils import scaleway_obj_to_dict
12
+ from cartography.models.scaleway.iam.group import ScalewayGroupSchema
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
+ client: scaleway.Client,
22
+ common_job_parameters: dict[str, Any],
23
+ org_id: str,
24
+ update_tag: int,
25
+ ) -> None:
26
+ groups = get(client, org_id)
27
+ formatted_groups = transform_groups(groups)
28
+ load_groups(neo4j_session, formatted_groups, org_id, update_tag)
29
+ cleanup(neo4j_session, common_job_parameters)
30
+
31
+
32
+ @timeit
33
+ def get(
34
+ client: scaleway.Client,
35
+ org_id: str,
36
+ ) -> list[Group]:
37
+ api = IamV1Alpha1API(client)
38
+ return api.list_groups_all(organization_id=org_id)
39
+
40
+
41
+ def transform_groups(groups: list[Group]) -> list[dict[str, Any]]:
42
+ formatted_groups = []
43
+ for group in groups:
44
+ formatted_groups.append(scaleway_obj_to_dict(group))
45
+ return formatted_groups
46
+
47
+
48
+ @timeit
49
+ def load_groups(
50
+ neo4j_session: neo4j.Session,
51
+ data: list[dict[str, Any]],
52
+ org_id: str,
53
+ update_tag: int,
54
+ ) -> None:
55
+ logger.info("Loading %d Scaleway Groups into Neo4j.", len(data))
56
+ load(
57
+ neo4j_session,
58
+ ScalewayGroupSchema(),
59
+ data,
60
+ lastupdated=update_tag,
61
+ ORG_ID=org_id,
62
+ )
63
+
64
+
65
+ @timeit
66
+ def cleanup(
67
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
68
+ ) -> None:
69
+ GraphJob.from_node_schema(ScalewayGroupSchema(), common_job_parameters).run(
70
+ neo4j_session
71
+ )
@@ -0,0 +1,71 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import scaleway
6
+ from scaleway.iam.v1alpha1 import IamV1Alpha1API
7
+ from scaleway.iam.v1alpha1 import User
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.scaleway.utils import scaleway_obj_to_dict
12
+ from cartography.models.scaleway.iam.user import ScalewayUserSchema
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
+ client: scaleway.Client,
22
+ common_job_parameters: dict[str, Any],
23
+ org_id: str,
24
+ update_tag: int,
25
+ ) -> None:
26
+ users = get(client, org_id)
27
+ formatted_users = transform_users(users)
28
+ load_users(neo4j_session, formatted_users, org_id, update_tag)
29
+ cleanup(neo4j_session, common_job_parameters)
30
+
31
+
32
+ @timeit
33
+ def get(
34
+ client: scaleway.Client,
35
+ org_id: str,
36
+ ) -> list[User]:
37
+ api = IamV1Alpha1API(client)
38
+ return api.list_users_all(organization_id=org_id)
39
+
40
+
41
+ def transform_users(users: list[User]) -> list[dict[str, Any]]:
42
+ formatted_users = []
43
+ for user in users:
44
+ formatted_users.append(scaleway_obj_to_dict(user))
45
+ return formatted_users
46
+
47
+
48
+ @timeit
49
+ def load_users(
50
+ neo4j_session: neo4j.Session,
51
+ data: list[dict[str, Any]],
52
+ org_id: str,
53
+ update_tag: int,
54
+ ) -> None:
55
+ logger.info("Loading %d Scaleway Users into Neo4j.", len(data))
56
+ load(
57
+ neo4j_session,
58
+ ScalewayUserSchema(),
59
+ data,
60
+ lastupdated=update_tag,
61
+ ORG_ID=org_id,
62
+ )
63
+
64
+
65
+ @timeit
66
+ def cleanup(
67
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
68
+ ) -> None:
69
+ GraphJob.from_node_schema(ScalewayUserSchema(), common_job_parameters).run(
70
+ neo4j_session
71
+ )
File without changes
@@ -0,0 +1,86 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ import scaleway
6
+ from scaleway.instance.v1 import InstanceV1API
7
+ from scaleway.instance.v1 import Ip
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.scaleway.utils import DEFAULT_ZONE
12
+ from cartography.intel.scaleway.utils import scaleway_obj_to_dict
13
+ from cartography.models.scaleway.instance.flexibleip import ScalewayFlexibleIpSchema
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @timeit
20
+ def sync(
21
+ neo4j_session: neo4j.Session,
22
+ client: scaleway.Client,
23
+ common_job_parameters: dict[str, Any],
24
+ org_id: str,
25
+ projects_id: list[str],
26
+ update_tag: int,
27
+ ) -> None:
28
+ flexibleips = get(client, org_id)
29
+ flexibleips_by_project = transform_flexibleips(flexibleips)
30
+ load_flexibleips(neo4j_session, flexibleips_by_project, update_tag)
31
+ cleanup(neo4j_session, projects_id, common_job_parameters)
32
+
33
+
34
+ @timeit
35
+ def get(
36
+ client: scaleway.Client,
37
+ org_id: str,
38
+ ) -> list[Ip]:
39
+ api = InstanceV1API(client)
40
+ return api.list_ips_all(organization=org_id, zone=DEFAULT_ZONE)
41
+
42
+
43
+ def transform_flexibleips(
44
+ flexibleips: list[Ip],
45
+ ) -> dict[str, list[dict[str, Any]]]:
46
+ result: dict[str, list[dict[str, Any]]] = {}
47
+ for flexibleip in flexibleips:
48
+ project_id = flexibleip.project
49
+ formatted_flexibleip = scaleway_obj_to_dict(flexibleip)
50
+ result.setdefault(project_id, []).append(formatted_flexibleip)
51
+ return result
52
+
53
+
54
+ @timeit
55
+ def load_flexibleips(
56
+ neo4j_session: neo4j.Session,
57
+ data: dict[str, list[dict[str, Any]]],
58
+ update_tag: int,
59
+ ) -> None:
60
+ for project_id, flexibleips in data.items():
61
+ logger.info(
62
+ "Loading %d Scaleway Flexible IPs in project '%s' into Neo4j.",
63
+ len(flexibleips),
64
+ project_id,
65
+ )
66
+ load(
67
+ neo4j_session,
68
+ ScalewayFlexibleIpSchema(),
69
+ flexibleips,
70
+ lastupdated=update_tag,
71
+ PROJECT_ID=project_id,
72
+ )
73
+
74
+
75
+ @timeit
76
+ def cleanup(
77
+ neo4j_session: neo4j.Session,
78
+ projects_id: list[str],
79
+ common_job_parameters: dict[str, Any],
80
+ ) -> None:
81
+ for project_id in projects_id:
82
+ scopped_job_parameters = common_job_parameters.copy()
83
+ scopped_job_parameters["PROJECT_ID"] = project_id
84
+ GraphJob.from_node_schema(
85
+ ScalewayFlexibleIpSchema(), scopped_job_parameters
86
+ ).run(neo4j_session)