cartography 0.103.0__py3-none-any.whl → 0.104.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 (33) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +21 -3
  3. cartography/config.py +4 -0
  4. cartography/intel/anthropic/__init__.py +62 -0
  5. cartography/intel/anthropic/apikeys.py +72 -0
  6. cartography/intel/anthropic/users.py +75 -0
  7. cartography/intel/anthropic/util.py +51 -0
  8. cartography/intel/anthropic/workspaces.py +95 -0
  9. cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
  10. cartography/intel/aws/secretsmanager.py +136 -3
  11. cartography/intel/aws/ssm.py +71 -0
  12. cartography/intel/openai/adminapikeys.py +1 -2
  13. cartography/intel/openai/apikeys.py +1 -1
  14. cartography/intel/openai/projects.py +4 -1
  15. cartography/intel/openai/serviceaccounts.py +1 -1
  16. cartography/intel/openai/users.py +0 -3
  17. cartography/intel/openai/util.py +17 -1
  18. cartography/models/anthropic/__init__.py +0 -0
  19. cartography/models/anthropic/apikey.py +90 -0
  20. cartography/models/anthropic/organization.py +19 -0
  21. cartography/models/anthropic/user.py +48 -0
  22. cartography/models/anthropic/workspace.py +90 -0
  23. cartography/models/aws/secretsmanager/__init__.py +0 -0
  24. cartography/models/aws/secretsmanager/secret_version.py +116 -0
  25. cartography/models/aws/ssm/parameters.py +84 -0
  26. cartography/models/openai/project.py +20 -1
  27. cartography/sync.py +2 -0
  28. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/METADATA +4 -4
  29. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/RECORD +33 -20
  30. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/WHEEL +1 -1
  31. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/entry_points.txt +0 -0
  32. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/licenses/LICENSE +0 -0
  33. {cartography-0.103.0.dist-info → cartography-0.104.0rc1.dist-info}/top_level.txt +0 -0
cartography/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.103.0'
21
- __version_tuple__ = version_tuple = (0, 103, 0)
20
+ __version__ = version = '0.104.0rc1'
21
+ __version_tuple__ = version_tuple = (0, 104, 0, 'rc1')
cartography/cli.py CHANGED
@@ -561,7 +561,7 @@ class CLI:
561
561
  type=str,
562
562
  default=None,
563
563
  help=(
564
- "Your SnipeIT base URI"
564
+ "Your SnipeIT base URI. "
565
565
  "Required if you are using the SnipeIT intel module. Ignored otherwise."
566
566
  ),
567
567
  )
@@ -588,7 +588,7 @@ class CLI:
588
588
  type=str,
589
589
  default=None,
590
590
  help=(
591
- "The name of an environment variable containing a Tailscale API token."
591
+ "The name of an environment variable containing a Tailscale API token. "
592
592
  "Required if you are using the Tailscale intel module. Ignored otherwise."
593
593
  ),
594
594
  )
@@ -615,7 +615,7 @@ class CLI:
615
615
  type=str,
616
616
  default=None,
617
617
  help=(
618
- "The name of an environment variable containing a OpenAI API Key."
618
+ "The name of an environment variable containing a OpenAI API Key. "
619
619
  "Required if you are using the OpenAI intel module. Ignored otherwise."
620
620
  ),
621
621
  )
@@ -628,6 +628,15 @@ class CLI:
628
628
  "Required if you are using the OpenAI intel module. Ignored otherwise."
629
629
  ),
630
630
  )
631
+ parser.add_argument(
632
+ "--anthropic-apikey-env-var",
633
+ type=str,
634
+ default=None,
635
+ help=(
636
+ "The name of an environment variable containing an Anthropic API Key. "
637
+ "Required if you are using the Anthropic intel module. Ignored otherwise."
638
+ ),
639
+ )
631
640
 
632
641
  return parser
633
642
 
@@ -937,6 +946,15 @@ class CLI:
937
946
  else:
938
947
  config.openai_apikey = None
939
948
 
949
+ # Anthropic config
950
+ if config.anthropic_apikey_env_var:
951
+ logger.debug(
952
+ f"Reading Anthropic API key from environment variable {config.anthropic_apikey_env_var}",
953
+ )
954
+ config.anthropic_apikey = os.environ.get(config.anthropic_apikey_env_var)
955
+ else:
956
+ config.anthropic_apikey = None
957
+
940
958
  # Run cartography
941
959
  try:
942
960
  return cartography.sync.run_with_config(self.sync, config)
cartography/config.py CHANGED
@@ -135,6 +135,8 @@ class Config:
135
135
  :param openai_apikey: OpenAI API key. Optional.
136
136
  :type openai_org_id: string
137
137
  :param openai_org_id: OpenAI organization id. Optional.
138
+ :type anthropic_apikey: string
139
+ :param anthropic_apikey: Anthropic API key. Optional.
138
140
  """
139
141
 
140
142
  def __init__(
@@ -206,6 +208,7 @@ class Config:
206
208
  cloudflare_token=None,
207
209
  openai_apikey=None,
208
210
  openai_org_id=None,
211
+ anthropic_apikey=None,
209
212
  ):
210
213
  self.neo4j_uri = neo4j_uri
211
214
  self.neo4j_user = neo4j_user
@@ -274,3 +277,4 @@ class Config:
274
277
  self.cloudflare_token = cloudflare_token
275
278
  self.openai_apikey = openai_apikey
276
279
  self.openai_org_id = openai_org_id
280
+ self.anthropic_apikey = anthropic_apikey
@@ -0,0 +1,62 @@
1
+ import logging
2
+
3
+ import neo4j
4
+ import requests
5
+
6
+ import cartography.intel.anthropic.apikeys
7
+ import cartography.intel.anthropic.users
8
+ import cartography.intel.anthropic.workspaces
9
+ from cartography.config import Config
10
+ from cartography.util import timeit
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @timeit
16
+ def start_anthropic_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
17
+ """
18
+ If this module is configured, perform ingestion of Anthropic data. Otherwise warn and exit
19
+ :param neo4j_session: Neo4J session for database interface
20
+ :param config: A cartography.config object
21
+ :return: None
22
+ """
23
+
24
+ if not config.anthropic_apikey:
25
+ logger.info(
26
+ "Anthropic import is not configured - skipping this module. "
27
+ "See docs to configure.",
28
+ )
29
+ return
30
+
31
+ # Create requests sessions
32
+ api_session = requests.session()
33
+ api_session.headers.update(
34
+ {
35
+ "X-Api-Key": config.anthropic_apikey,
36
+ "anthropic-version": "2023-06-01",
37
+ }
38
+ )
39
+
40
+ common_job_parameters = {
41
+ "UPDATE_TAG": config.update_tag,
42
+ "BASE_URL": "https://api.anthropic.com/v1",
43
+ }
44
+
45
+ # Organization node is created during the users sync
46
+ cartography.intel.anthropic.users.sync(
47
+ neo4j_session,
48
+ api_session,
49
+ common_job_parameters,
50
+ )
51
+
52
+ cartography.intel.anthropic.workspaces.sync(
53
+ neo4j_session,
54
+ api_session,
55
+ common_job_parameters,
56
+ )
57
+
58
+ cartography.intel.anthropic.apikeys.sync(
59
+ neo4j_session,
60
+ api_session,
61
+ common_job_parameters,
62
+ )
@@ -0,0 +1,72 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Tuple
4
+
5
+ import neo4j
6
+ import requests
7
+
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.intel.anthropic.util import paginated_get
11
+ from cartography.models.anthropic.apikey import AnthropicApiKeySchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
16
+ _TIMEOUT = (60, 60)
17
+
18
+
19
+ @timeit
20
+ def sync(
21
+ neo4j_session: neo4j.Session,
22
+ api_session: requests.Session,
23
+ common_job_parameters: dict[str, Any],
24
+ ) -> None:
25
+ org_id, apikeys = get(
26
+ api_session,
27
+ common_job_parameters["BASE_URL"],
28
+ )
29
+ common_job_parameters["ORG_ID"] = org_id
30
+ load_apikeys(
31
+ neo4j_session,
32
+ apikeys,
33
+ org_id,
34
+ common_job_parameters["UPDATE_TAG"],
35
+ )
36
+ cleanup(neo4j_session, common_job_parameters)
37
+
38
+
39
+ @timeit
40
+ def get(
41
+ api_session: requests.Session,
42
+ base_url: str,
43
+ ) -> Tuple[str, list[dict[str, Any]]]:
44
+ return paginated_get(
45
+ api_session, f"{base_url}/organizations/api_keys", timeout=_TIMEOUT
46
+ )
47
+
48
+
49
+ @timeit
50
+ def load_apikeys(
51
+ neo4j_session: neo4j.Session,
52
+ data: list[dict[str, Any]],
53
+ ORG_ID: str,
54
+ update_tag: int,
55
+ ) -> None:
56
+ logger.info("Loading %d Anthropic ApiKey into Neo4j.", len(data))
57
+ load(
58
+ neo4j_session,
59
+ AnthropicApiKeySchema(),
60
+ data,
61
+ lastupdated=update_tag,
62
+ ORG_ID=ORG_ID,
63
+ )
64
+
65
+
66
+ @timeit
67
+ def cleanup(
68
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
69
+ ) -> None:
70
+ GraphJob.from_node_schema(AnthropicApiKeySchema(), common_job_parameters).run(
71
+ neo4j_session
72
+ )
@@ -0,0 +1,75 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Tuple
4
+
5
+ import neo4j
6
+ import requests
7
+
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.intel.anthropic.util import paginated_get
11
+ from cartography.models.anthropic.organization import AnthropicOrganizationSchema
12
+ from cartography.models.anthropic.user import AnthropicUserSchema
13
+ from cartography.util import timeit
14
+
15
+ logger = logging.getLogger(__name__)
16
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
17
+ _TIMEOUT = (60, 60)
18
+
19
+
20
+ @timeit
21
+ def sync(
22
+ neo4j_session: neo4j.Session,
23
+ api_session: requests.Session,
24
+ common_job_parameters: dict[str, Any],
25
+ ) -> str:
26
+ org_id, users = get(
27
+ api_session,
28
+ common_job_parameters["BASE_URL"],
29
+ )
30
+ common_job_parameters["ORG_ID"] = org_id
31
+ load_users(neo4j_session, users, org_id, common_job_parameters["UPDATE_TAG"])
32
+ cleanup(neo4j_session, common_job_parameters)
33
+ return org_id
34
+
35
+
36
+ @timeit
37
+ def get(
38
+ api_session: requests.Session,
39
+ base_url: str,
40
+ ) -> Tuple[str, list[dict[str, Any]]]:
41
+ return paginated_get(
42
+ api_session, f"{base_url}/organizations/users", timeout=_TIMEOUT
43
+ )
44
+
45
+
46
+ @timeit
47
+ def load_users(
48
+ neo4j_session: neo4j.Session,
49
+ data: list[dict[str, Any]],
50
+ ORG_ID: str,
51
+ update_tag: int,
52
+ ) -> None:
53
+ load(
54
+ neo4j_session,
55
+ AnthropicOrganizationSchema(),
56
+ [{"id": ORG_ID}],
57
+ lastupdated=update_tag,
58
+ )
59
+ logger.info("Loading %d Anthropic User into Neo4j.", len(data))
60
+ load(
61
+ neo4j_session,
62
+ AnthropicUserSchema(),
63
+ data,
64
+ lastupdated=update_tag,
65
+ ORG_ID=ORG_ID,
66
+ )
67
+
68
+
69
+ @timeit
70
+ def cleanup(
71
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
72
+ ) -> None:
73
+ GraphJob.from_node_schema(AnthropicUserSchema(), common_job_parameters).run(
74
+ neo4j_session
75
+ )
@@ -0,0 +1,51 @@
1
+ from typing import Any
2
+
3
+ import requests
4
+
5
+
6
+ def paginated_get(
7
+ api_session: requests.Session,
8
+ url: str,
9
+ timeout: tuple[int, int],
10
+ after: str | None = None,
11
+ ) -> tuple[str, list[dict[str, Any]]]:
12
+ """Helper function to get paginated data from the Anthropic API.
13
+
14
+ This function handles the pagination of the API requests and returns
15
+ the results in a list. It also retrieves the organization ID from the
16
+ response headers. The function will continue to make requests until
17
+ all pages of data have been retrieved. The results are returned as a
18
+ list of dictionaries, where each dictionary represents a single
19
+ entity.
20
+
21
+ Args:
22
+ api_session (requests.Session): The requests session to use for making API calls.
23
+ url (str): The URL to make the API call to.
24
+ timeout (tuple[int, int]): The timeout for the API call.
25
+ after (str | None): The ID of the last item retrieved in the previous request.
26
+ If None, the first page of results will be retrieved.
27
+ Returns:
28
+ tuple[str, list[dict[str, Any]]]: A tuple containing the organization ID and a list of
29
+ dictionaries representing the results.
30
+ """
31
+ results: list[dict[str, Any]] = []
32
+ params = {"after_id": after} if after else {}
33
+ req = api_session.get(
34
+ url,
35
+ params=params,
36
+ timeout=timeout,
37
+ )
38
+ req.raise_for_status()
39
+ # Get organization_id from the headers
40
+ organization_id = req.headers.get("anthropic-organization-id", "")
41
+ result = req.json()
42
+ results.extend(result.get("data", []))
43
+ if result.get("has_more"):
44
+ _, next_results = paginated_get(
45
+ api_session,
46
+ url,
47
+ timeout=timeout,
48
+ after=result.get("last_id"),
49
+ )
50
+ results.extend(next_results)
51
+ return organization_id, results
@@ -0,0 +1,95 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Tuple
4
+
5
+ import neo4j
6
+ import requests
7
+
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.intel.anthropic.util import paginated_get
11
+ from cartography.models.anthropic.workspace import AnthropicWorkspaceSchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
16
+ _TIMEOUT = (60, 60)
17
+
18
+
19
+ @timeit
20
+ def sync(
21
+ neo4j_session: neo4j.Session,
22
+ api_session: requests.Session,
23
+ common_job_parameters: dict[str, Any],
24
+ ) -> list[dict]:
25
+ org_id, workspaces = get(
26
+ api_session,
27
+ common_job_parameters["BASE_URL"],
28
+ )
29
+ common_job_parameters["ORG_ID"] = org_id
30
+ for workspace in workspaces:
31
+ workspace["users"] = []
32
+ workspace["admins"] = []
33
+ for user in get_workspace_users(
34
+ api_session,
35
+ common_job_parameters["BASE_URL"],
36
+ workspace["id"],
37
+ ):
38
+ workspace["users"].append(user["user_id"])
39
+ if user["workspace_role"] == "workspace_admin":
40
+ workspace["admins"].append(user["user_id"])
41
+ load_workspaces(
42
+ neo4j_session, workspaces, org_id, common_job_parameters["UPDATE_TAG"]
43
+ )
44
+ cleanup(neo4j_session, common_job_parameters)
45
+ return workspaces
46
+
47
+
48
+ @timeit
49
+ def get(
50
+ api_session: requests.Session,
51
+ base_url: str,
52
+ ) -> Tuple[str, list[dict[str, Any]]]:
53
+ return paginated_get(
54
+ api_session, f"{base_url}/organizations/workspaces", timeout=_TIMEOUT
55
+ )
56
+
57
+
58
+ @timeit
59
+ def get_workspace_users(
60
+ api_session: requests.Session,
61
+ base_url: str,
62
+ workspace_id: str,
63
+ ) -> list[dict[str, Any]]:
64
+ _, result = paginated_get(
65
+ api_session,
66
+ f"{base_url}/organizations/workspaces/{workspace_id}/members",
67
+ timeout=_TIMEOUT,
68
+ )
69
+ return result
70
+
71
+
72
+ @timeit
73
+ def load_workspaces(
74
+ neo4j_session: neo4j.Session,
75
+ data: list[dict[str, Any]],
76
+ ORG_ID: str,
77
+ update_tag: int,
78
+ ) -> None:
79
+ logger.info("Loading %d Anthropic workspaces into Neo4j.", len(data))
80
+ load(
81
+ neo4j_session,
82
+ AnthropicWorkspaceSchema(),
83
+ data,
84
+ lastupdated=update_tag,
85
+ ORG_ID=ORG_ID,
86
+ )
87
+
88
+
89
+ @timeit
90
+ def cleanup(
91
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
92
+ ) -> None:
93
+ GraphJob.from_node_schema(AnthropicWorkspaceSchema(), common_job_parameters).run(
94
+ neo4j_session
95
+ )
@@ -99,7 +99,10 @@ def load_load_balancer_v2s(
99
99
  SET r.lastupdated = $update_tag
100
100
  """
101
101
  for lb in data:
102
- load_balancer_id = lb["DNSName"]
102
+ load_balancer_id = lb.get("DNSName")
103
+ if not load_balancer_id:
104
+ logger.warning("Skipping load balancer entry with missing DNSName: %r", lb)
105
+ continue
103
106
 
104
107
  neo4j_session.run(
105
108
  ingest_load_balancer_v2,
@@ -5,12 +5,20 @@ from typing import List
5
5
  import boto3
6
6
  import neo4j
7
7
 
8
+ from cartography.client.core.tx import load
9
+ from cartography.graph.job import GraphJob
10
+ from cartography.models.aws.secretsmanager.secret_version import (
11
+ SecretsManagerSecretVersionSchema,
12
+ )
13
+ from cartography.stats import get_stats_client
8
14
  from cartography.util import aws_handle_regions
9
15
  from cartography.util import dict_date_to_epoch
16
+ from cartography.util import merge_module_sync_metadata
10
17
  from cartography.util import run_cleanup_job
11
18
  from cartography.util import timeit
12
19
 
13
20
  logger = logging.getLogger(__name__)
21
+ stat_handler = get_stats_client(__name__)
14
22
 
15
23
 
16
24
  @timeit
@@ -76,6 +84,93 @@ def cleanup_secrets(neo4j_session: neo4j.Session, common_job_parameters: Dict) -
76
84
  )
77
85
 
78
86
 
87
+ @timeit
88
+ @aws_handle_regions
89
+ def get_secret_versions(
90
+ boto3_session: boto3.session.Session, region: str, secret_arn: str
91
+ ) -> List[Dict]:
92
+ """
93
+ Get all versions of a secret from AWS Secrets Manager.
94
+ """
95
+ client = boto3_session.client("secretsmanager", region_name=region)
96
+ paginator = client.get_paginator("list_secret_version_ids")
97
+ versions = []
98
+
99
+ for page in paginator.paginate(SecretId=secret_arn, IncludeDeprecated=True):
100
+ for version in page["Versions"]:
101
+ version["SecretId"] = secret_arn
102
+ version["ARN"] = f"{secret_arn}:version:{version['VersionId']}"
103
+ versions.extend(page["Versions"])
104
+
105
+ return versions
106
+
107
+
108
+ def transform_secret_versions(
109
+ versions: List[Dict],
110
+ region: str,
111
+ aws_account_id: str,
112
+ ) -> List[Dict]:
113
+ """
114
+ Transform AWS Secrets Manager Secret Versions to match the data model.
115
+ """
116
+ transformed_data = []
117
+ for version in versions:
118
+ transformed = {
119
+ "ARN": version["ARN"],
120
+ "SecretId": version["SecretId"],
121
+ "VersionId": version["VersionId"],
122
+ "VersionStages": version["VersionStages"],
123
+ "CreatedDate": dict_date_to_epoch(version, "CreatedDate"),
124
+ }
125
+
126
+ if "KmsKeyId" in version and version["KmsKeyId"]:
127
+ transformed["KmsKeyId"] = version["KmsKeyId"]
128
+
129
+ if "Tags" in version and version["Tags"]:
130
+ transformed["Tags"] = version["Tags"]
131
+
132
+ transformed_data.append(transformed)
133
+
134
+ return transformed_data
135
+
136
+
137
+ @timeit
138
+ def load_secret_versions(
139
+ neo4j_session: neo4j.Session,
140
+ data: List[Dict],
141
+ region: str,
142
+ aws_account_id: str,
143
+ update_tag: int,
144
+ ) -> None:
145
+ """
146
+ Load secret versions into Neo4j using the data model.
147
+ """
148
+ logger.info(f"Loading {len(data)} Secret Versions for region {region} into graph.")
149
+
150
+ load(
151
+ neo4j_session,
152
+ SecretsManagerSecretVersionSchema(),
153
+ data,
154
+ lastupdated=update_tag,
155
+ Region=region,
156
+ AWS_ID=aws_account_id,
157
+ )
158
+
159
+
160
+ @timeit
161
+ def cleanup_secret_versions(
162
+ neo4j_session: neo4j.Session, common_job_parameters: Dict
163
+ ) -> None:
164
+ """
165
+ Run Secret Versions cleanup job.
166
+ """
167
+ logger.debug("Running Secret Versions cleanup job.")
168
+ cleanup_job = GraphJob.from_node_schema(
169
+ SecretsManagerSecretVersionSchema(), common_job_parameters
170
+ )
171
+ cleanup_job.run(neo4j_session)
172
+
173
+
79
174
  @timeit
80
175
  def sync(
81
176
  neo4j_session: neo4j.Session,
@@ -85,12 +180,50 @@ def sync(
85
180
  update_tag: int,
86
181
  common_job_parameters: Dict,
87
182
  ) -> None:
183
+ """
184
+ Sync AWS Secrets Manager resources.
185
+ """
88
186
  for region in regions:
89
187
  logger.info(
90
- "Syncing Secrets Manager for region '%s' in account '%s'.",
91
- region,
92
- current_aws_account_id,
188
+ f"Syncing Secrets Manager for region '{region}' in account '{current_aws_account_id}'."
93
189
  )
94
190
  secrets = get_secret_list(boto3_session, region)
191
+
95
192
  load_secrets(neo4j_session, secrets, region, current_aws_account_id, update_tag)
193
+
194
+ all_versions = []
195
+ for secret in secrets:
196
+ logger.info(
197
+ f"Getting versions for secret {secret.get('Name', 'unnamed')} ({secret['ARN']})"
198
+ )
199
+ versions = get_secret_versions(boto3_session, region, secret["ARN"])
200
+ logger.info(
201
+ f"Found {len(versions)} versions for secret {secret.get('Name', 'unnamed')}"
202
+ )
203
+ all_versions.extend(versions)
204
+
205
+ transformed_data = transform_secret_versions(
206
+ all_versions,
207
+ region,
208
+ current_aws_account_id,
209
+ )
210
+
211
+ load_secret_versions(
212
+ neo4j_session,
213
+ transformed_data,
214
+ region,
215
+ current_aws_account_id,
216
+ update_tag,
217
+ )
218
+
96
219
  cleanup_secrets(neo4j_session, common_job_parameters)
220
+ cleanup_secret_versions(neo4j_session, common_job_parameters)
221
+
222
+ merge_module_sync_metadata(
223
+ neo4j_session,
224
+ group_type="AWSAccount",
225
+ group_id=current_aws_account_id,
226
+ synced_type="SecretsManagerSecretVersion",
227
+ update_tag=update_tag,
228
+ stat_handler=stat_handler,
229
+ )