cartography 0.96.1__py3-none-any.whl → 0.96.2__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.

@@ -85,30 +85,32 @@ def _call_cves_api(url: str, api_key: str | None, params: Dict[str, Any]) -> Dic
85
85
  )
86
86
  results: Dict[Any, Any] = dict()
87
87
 
88
- while params["resultsPerPage"] > 0 or params["startIndex"] < totalResults:
89
- try:
90
- res = requests.get(
91
- url, params=params, headers=headers, timeout=CONNECT_AND_READ_TIMEOUT,
92
- )
93
- res.raise_for_status()
94
- except requests.exceptions.HTTPError:
95
- logger.error(
96
- f"Failed to get CVE data from NIST NVD API {res.status_code} : {res.text}",
97
- )
98
- retries += 1
99
- if retries >= MAX_RETRIES:
100
- raise
101
- # Exponential backoff
102
- sleep_time *= 2
88
+ with requests.Session() as session:
89
+ while params["resultsPerPage"] > 0 or params["startIndex"] < totalResults:
90
+ logger.info(f"Calling NIST NVD API at {url} with params {params}")
91
+ try:
92
+ res = session.get(
93
+ url, params=params, headers=headers, timeout=CONNECT_AND_READ_TIMEOUT,
94
+ )
95
+ res.raise_for_status()
96
+ data = res.json()
97
+ except requests.exceptions.HTTPError:
98
+ logger.error(
99
+ f"Failed to get CVE data from NIST NVD API {res.status_code} : {res.text}",
100
+ )
101
+ retries += 1
102
+ if retries >= MAX_RETRIES:
103
+ raise
104
+ # Exponential backoff
105
+ sleep_time *= 2
106
+ time.sleep(sleep_time)
107
+ continue
108
+ _map_cve_dict(results, data)
109
+ totalResults = data["totalResults"]
110
+ params["resultsPerPage"] = data["resultsPerPage"]
111
+ params["startIndex"] += data["resultsPerPage"]
112
+ retries = 0
103
113
  time.sleep(sleep_time)
104
- continue
105
- data = res.json()
106
- _map_cve_dict(results, data)
107
- totalResults = data["totalResults"]
108
- params["resultsPerPage"] = data["resultsPerPage"]
109
- params["startIndex"] += data["resultsPerPage"]
110
- retries = 0
111
- time.sleep(sleep_time)
112
114
  return results
113
115
 
114
116
 
@@ -140,22 +140,40 @@ def _get_repo_collaborators_inner_func(
140
140
  org: str,
141
141
  api_url: str,
142
142
  token: str,
143
- repo_name: str,
143
+ repo_raw_data: list[dict[str, Any]],
144
144
  affiliation: str,
145
145
  collab_users: list[dict[str, Any]],
146
146
  collab_permission: list[str],
147
- ) -> None:
148
- logger.info(f"Loading {affiliation} collaborators for repo {repo_name}.")
149
- collaborators = _get_repo_collaborators(token, api_url, org, repo_name, affiliation)
147
+ ) -> dict[str, list[UserAffiliationAndRepoPermission]]:
148
+ result: dict[str, list[UserAffiliationAndRepoPermission]] = {}
149
+
150
+ for repo in repo_raw_data:
151
+ repo_name = repo['name']
152
+ repo_url = repo['url']
153
+
154
+ if ((affiliation == 'OUTSIDE' and repo['outsideCollaborators']['totalCount'] == 0) or
155
+ (affiliation == 'DIRECT' and repo['directCollaborators']['totalCount'] == 0)):
156
+ # repo has no collabs of the affiliation type we're looking for, so don't waste time making an API call
157
+ result[repo_url] = []
158
+ continue
159
+
160
+ logger.info(f"Loading {affiliation} collaborators for repo {repo_name}.")
161
+ collaborators = _get_repo_collaborators(token, api_url, org, repo_name, affiliation)
150
162
 
151
- # nodes and edges are expected to always be present given that we only call for them if totalCount is > 0
152
- # however sometimes GitHub returns None, as in issue 1334 and 1404.
153
- for collab in collaborators.nodes or []:
154
- collab_users.append(collab)
163
+ # nodes and edges are expected to always be present given that we only call for them if totalCount is > 0
164
+ # however sometimes GitHub returns None, as in issue 1334 and 1404.
165
+ for collab in collaborators.nodes or []:
166
+ collab_users.append(collab)
155
167
 
156
- # The `or []` is because `.edges` can be None.
157
- for perm in collaborators.edges or []:
158
- collab_permission.append(perm['permission'])
168
+ # The `or []` is because `.edges` can be None.
169
+ for perm in collaborators.edges or []:
170
+ collab_permission.append(perm['permission'])
171
+
172
+ result[repo_url] = [
173
+ UserAffiliationAndRepoPermission(user, permission, affiliation)
174
+ for user, permission in zip(collab_users, collab_permission)
175
+ ]
176
+ return result
159
177
 
160
178
 
161
179
  def _get_repo_collaborators_for_multiple_repos(
@@ -175,39 +193,24 @@ def _get_repo_collaborators_for_multiple_repos(
175
193
  :param token: The Github API token as string.
176
194
  :return: A dictionary of repo URL to list of UserAffiliationAndRepoPermission
177
195
  """
178
- result: dict[str, list[UserAffiliationAndRepoPermission]] = {}
179
- for repo in repo_raw_data:
180
- repo_name = repo['name']
181
- repo_url = repo['url']
182
-
183
- if ((affiliation == 'OUTSIDE' and repo['outsideCollaborators']['totalCount'] == 0) or
184
- (affiliation == 'DIRECT' and repo['directCollaborators']['totalCount'] == 0)):
185
- # repo has no collabs of the affiliation type we're looking for, so don't waste time making an API call
186
- result[repo_url] = []
187
- continue
188
-
189
- collab_users: List[dict[str, Any]] = []
190
- collab_permission: List[str] = []
191
-
192
- retries_with_backoff(
193
- _get_repo_collaborators_inner_func,
194
- TypeError,
195
- 5,
196
- backoff_handler,
197
- )(
198
- org=org,
199
- api_url=api_url,
200
- token=token,
201
- repo_name=repo_name,
202
- affiliation=affiliation,
203
- collab_users=collab_users,
204
- collab_permission=collab_permission,
205
- )
206
-
207
- result[repo_url] = [
208
- UserAffiliationAndRepoPermission(user, permission, affiliation)
209
- for user, permission in zip(collab_users, collab_permission)
210
- ]
196
+ logger.info(f'Retrieving repo collaborators for affiliation "{affiliation}" on org "{org}".')
197
+ collab_users: List[dict[str, Any]] = []
198
+ collab_permission: List[str] = []
199
+
200
+ result: dict[str, list[UserAffiliationAndRepoPermission]] = retries_with_backoff(
201
+ _get_repo_collaborators_inner_func,
202
+ TypeError,
203
+ 5,
204
+ backoff_handler,
205
+ )(
206
+ org=org,
207
+ api_url=api_url,
208
+ token=token,
209
+ repo_raw_data=repo_raw_data,
210
+ affiliation=affiliation,
211
+ collab_users=collab_users,
212
+ collab_permission=collab_permission,
213
+ )
211
214
  return result
212
215
 
213
216
 
@@ -260,8 +263,9 @@ def get(token: str, api_url: str, organization: str) -> List[Dict]:
260
263
 
261
264
 
262
265
  def transform(
263
- repos_json: List[Dict], direct_collaborators: dict[str, List[UserAffiliationAndRepoPermission]],
264
- outside_collaborators: dict[str, List[UserAffiliationAndRepoPermission]],
266
+ repos_json: List[Dict],
267
+ direct_collaborators: dict[str, List[UserAffiliationAndRepoPermission]],
268
+ outside_collaborators: dict[str, List[UserAffiliationAndRepoPermission]],
265
269
  ) -> Dict:
266
270
  """
267
271
  Parses the JSON returned from GitHub API to create data for graph ingestion
@@ -289,16 +293,24 @@ def transform(
289
293
  _transform_repo_languages(repo_object['url'], repo_object, transformed_repo_languages)
290
294
  _transform_repo_objects(repo_object, transformed_repo_list)
291
295
  _transform_repo_owners(repo_object['owner']['url'], repo_object, transformed_repo_owners)
292
- _transform_collaborators(
293
- repo_object['url'], outside_collaborators[repo_object['url']],
294
- transformed_outside_collaborators,
295
- )
296
- _transform_collaborators(
297
- repo_object['url'], direct_collaborators[repo_object['url']],
298
- transformed_direct_collaborators,
299
- )
300
- _transform_requirements_txt(repo_object['requirements'], repo_object['url'], transformed_requirements_files)
301
- _transform_setup_cfg_requirements(repo_object['setupCfg'], repo_object['url'], transformed_requirements_files)
296
+
297
+ # Allow sync to continue if we didn't have permissions to list collaborators
298
+ repo_url = repo_object['url']
299
+ if repo_url in outside_collaborators:
300
+ _transform_collaborators(
301
+ repo_object['url'],
302
+ outside_collaborators[repo_object['url']],
303
+ transformed_outside_collaborators,
304
+ )
305
+ if repo_url in direct_collaborators:
306
+ _transform_collaborators(
307
+ repo_object['url'],
308
+ direct_collaborators[repo_object['url']],
309
+ transformed_direct_collaborators,
310
+ )
311
+
312
+ _transform_requirements_txt(repo_object['requirements'], repo_url, transformed_requirements_files)
313
+ _transform_setup_cfg_requirements(repo_object['setupCfg'], repo_url, transformed_requirements_files)
302
314
  results = {
303
315
  'repos': transformed_repo_list,
304
316
  'repo_languages': transformed_repo_languages,
@@ -737,12 +749,18 @@ def sync(
737
749
  """
738
750
  logger.info("Syncing GitHub repos")
739
751
  repos_json = get(github_api_key, github_url, organization)
740
- direct_collabs = _get_repo_collaborators_for_multiple_repos(
741
- repos_json, "DIRECT", organization, github_url, github_api_key,
742
- )
743
- outside_collabs = _get_repo_collaborators_for_multiple_repos(
744
- repos_json, "OUTSIDE", organization, github_url, github_api_key,
745
- )
752
+ direct_collabs: dict[str, list[UserAffiliationAndRepoPermission]] = {}
753
+ outside_collabs: dict[str, list[UserAffiliationAndRepoPermission]] = {}
754
+ try:
755
+ direct_collabs = _get_repo_collaborators_for_multiple_repos(
756
+ repos_json, "DIRECT", organization, github_url, github_api_key,
757
+ )
758
+ outside_collabs = _get_repo_collaborators_for_multiple_repos(
759
+ repos_json, "OUTSIDE", organization, github_url, github_api_key,
760
+ )
761
+ except TypeError:
762
+ # due to permission errors or transient network error or some other nonsense
763
+ logger.warning('Unable to list repo collaborators due to permission errors; continuing on.', exc_info=True)
746
764
  repo_data = transform(repos_json, direct_collabs, outside_collabs)
747
765
  load(neo4j_session, common_job_parameters, repo_data)
748
766
  run_cleanup_job('github_repos_cleanup.json', neo4j_session, common_job_parameters)
@@ -90,6 +90,7 @@ def get_users(token: str, api_url: str, organization: str) -> Tuple[List[Dict],
90
90
  2. data on the owning GitHub organization
91
91
  see tests.data.github.users.GITHUB_USER_DATA for shape of both
92
92
  """
93
+ logger.info(f"Retrieving users from GitHub organization {organization}")
93
94
  users, org = fetch_all(
94
95
  token,
95
96
  api_url,
@@ -112,6 +113,7 @@ def get_enterprise_owners(token: str, api_url: str, organization: str) -> Tuple[
112
113
  3. data on the owning GitHub organization
113
114
  see tests.data.github.users.GITHUB_ENTERPRISE_OWNER_DATA for shape
114
115
  """
116
+ logger.info(f"Retrieving enterprise owners from GitHub organization {organization}")
115
117
  owners, org = fetch_all(
116
118
  token,
117
119
  api_url,
@@ -163,7 +163,8 @@ def fetch_all(
163
163
 
164
164
  if retry >= retries:
165
165
  logger.error(
166
- f"GitHub: Could not retrieve page of resource `{resource_type}` due to HTTP error.",
166
+ f"GitHub: Could not retrieve page of resource `{resource_type}` due to HTTP error "
167
+ f"after {retry} retries. Raising exception.",
167
168
  exc_info=True,
168
169
  )
169
170
  raise exc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cartography
3
- Version: 0.96.1
3
+ Version: 0.96.2
4
4
  Summary: Explore assets and their relationships across your technical infrastructure.
5
5
  Home-page: https://www.github.com/cartography-cncf/cartography
6
6
  Maintainer: Cartography Contributors
@@ -209,7 +209,7 @@ cartography/intel/crowdstrike/endpoints.py,sha256=tdqokMDW3p4fK3dHKKb2T1DTogvOJB
209
209
  cartography/intel/crowdstrike/spotlight.py,sha256=yNhj44-RYF6ubck-hHMKhKiNU0fCfhQf4Oagopc31EM,4754
210
210
  cartography/intel/crowdstrike/util.py,sha256=gfJ6Ptr6YdbBS9Qj9a_-Jc-IJroADDRcXqjh5TW0qXE,277
211
211
  cartography/intel/cve/__init__.py,sha256=3PLAhQ36g-aq40IHvba789WANsA-TY9B-Oe9mpQweQ8,2516
212
- cartography/intel/cve/feed.py,sha256=bKzetBVGYz8e2GKupUh2BilBCZuIwSfZdN23ArxYLZU,10147
212
+ cartography/intel/cve/feed.py,sha256=1q9ZxbA4Hp2V84h7kadTImyCk7w0dmV7ROsZQ-kxKwE,10365
213
213
  cartography/intel/digitalocean/__init__.py,sha256=SMYB7LGIQOj_EgGSGVjWZk7SJNbP43hQuOfgOu6xYm4,1526
214
214
  cartography/intel/digitalocean/compute.py,sha256=9XctwMjq9h5dExFgExvawoqyiEwSoocNgaMm3Fgl5GM,4911
215
215
  cartography/intel/digitalocean/management.py,sha256=YWRnBLLL_bAP1vefIAQgm_-QzefGH0sZKmyU_EokHfA,3764
@@ -229,10 +229,10 @@ cartography/intel/gcp/dns.py,sha256=y2pvbmV04cnrMyuu_nbW3oc7uwHX6yEzn1n7veCsjmk,
229
229
  cartography/intel/gcp/gke.py,sha256=qaTwsVaxkwNhW5_Mw4bedOk7fgJK8y0LwwcYlUABXDg,7966
230
230
  cartography/intel/gcp/storage.py,sha256=oO_ayEhkXlj2Gn7T5MU41ZXiqwRwe6Ud4wzqyRTsyf4,9075
231
231
  cartography/intel/github/__init__.py,sha256=y876JJGTDJZEOFCDiNCJfcLNxN24pVj4s2N0YmuuoaE,1914
232
- cartography/intel/github/repos.py,sha256=FaNJK3Hs2MywuQaEyx_TTjHTtSXxfqs0jiV79aU_p7g,28959
232
+ cartography/intel/github/repos.py,sha256=MmpxZASDJFQxDeSMxX3pZcpxCHFPos4_uYC_cX9KjOg,29865
233
233
  cartography/intel/github/teams.py,sha256=uQTiOfUBCk4qk0uw7jEPevAq-b2fvynRJ3t-62RZW2c,10659
234
- cartography/intel/github/users.py,sha256=zkMLVNfEMZIYYtPfqTsMXs2BNFzK8SfFRO7qnnlHy_s,8399
235
- cartography/intel/github/util.py,sha256=K6hbxypy4luKhIE1Uh5VWZc9OyjMK2OuO00vBAQfloA,8049
234
+ cartography/intel/github/users.py,sha256=kkxk0TqPp8HKQAd3RvJs-N_ySFIxHqLsvkvJf0WoGyc,8565
235
+ cartography/intel/github/util.py,sha256=K0cXOPuhnGvN-aqcSUBO3vTuKQLjufVal9kn2HwOpbo,8110
236
236
  cartography/intel/gsuite/__init__.py,sha256=AGIUskGlLCVGHbnQicNpNWi9AvmV7_7hUKTt-hsB2J8,4306
237
237
  cartography/intel/gsuite/api.py,sha256=J0dkNdfBVMrEv8vvStQu7YKVxXSyV45WueFhUS4aOG4,10310
238
238
  cartography/intel/jamf/__init__.py,sha256=Nof-LrUeevoieo6oP2GyfTwx8k5TUIgreW6hSj53YjQ,419
@@ -353,9 +353,9 @@ cartography/models/snipeit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
353
353
  cartography/models/snipeit/asset.py,sha256=FyRAaeXuZjMy0eUQcSDFcgEAF5lbLMlvqp1Tv9d3Lv4,3238
354
354
  cartography/models/snipeit/tenant.py,sha256=p4rFnpNNuF1W5ilGBbexDaETWTwavfb38RcQGoImkQI,679
355
355
  cartography/models/snipeit/user.py,sha256=MsB4MiCVNTH6JpESime7cOkB89autZOXQpL6Z0l7L6o,2113
356
- cartography-0.96.1.dist-info/LICENSE,sha256=kvLEBRYaQ1RvUni6y7Ti9uHeooqnjPoo6n_-0JO1ETc,11351
357
- cartography-0.96.1.dist-info/METADATA,sha256=6IcNRa-SP4o3DT8IBROqO5fdVzRMOlub-1VKVaEeWR0,1938
358
- cartography-0.96.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
359
- cartography-0.96.1.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
360
- cartography-0.96.1.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
361
- cartography-0.96.1.dist-info/RECORD,,
356
+ cartography-0.96.2.dist-info/LICENSE,sha256=kvLEBRYaQ1RvUni6y7Ti9uHeooqnjPoo6n_-0JO1ETc,11351
357
+ cartography-0.96.2.dist-info/METADATA,sha256=JPfNQ2Z_bulMqksx5unRej-WGueUKhi1Q9wFWqaOHk8,1938
358
+ cartography-0.96.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
359
+ cartography-0.96.2.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
360
+ cartography-0.96.2.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
361
+ cartography-0.96.2.dist-info/RECORD,,