Skip to content

Commit 7ca176e

Browse files
authored
Merge pull request #245 from GeekMasher/errors-as-code
feat: Update Octokit errors
2 parents b406a80 + 6893464 commit 7ca176e

File tree

9 files changed

+158
-57
lines changed

9 files changed

+158
-57
lines changed

examples/secrets.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Secret Scanning API example."""
2+
23
import os
34
from ghastoolkit import GitHub, SecretScanning
45

@@ -11,8 +12,6 @@
1112
print("Secret Scanning is disabled :(")
1213
exit(1)
1314

14-
print(f"Push Protection :: {secret_scanning.isPushProtectionEnabled()}")
15-
1615
try:
1716
alerts = secret_scanning.getAlerts("open")
1817
except:

src/ghastoolkit/octokit/advisories.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""GitHub Security Advisories API."""
22

33
from typing import Dict, Optional
4+
from ghastoolkit.errors import GHASToolkitError, GHASToolkitTypeError
45
from ghastoolkit.octokit.github import GitHub, Repository
56
from ghastoolkit.octokit.octokit import RestRequest
67
from ghastoolkit.supplychain.advisories import Advisories, Advisory, AdvisoryAffect
@@ -20,7 +21,10 @@ def __init__(self, repository: Optional[Repository] = None) -> None:
2021
self.rest = RestRequest(self.repository)
2122

2223
def getAdvisories(self) -> Advisories:
23-
"""Get list of security advisories from a repository."""
24+
"""Get list of security advisories from a repository.
25+
26+
https://docs.github.com/en/rest/security-advisories/repository-advisories#list-repository-security-advisories
27+
"""
2428
results = self.rest.get(
2529
"/repos/{owner}/{repo}/security-advisories", authenticated=True
2630
)
@@ -29,24 +33,38 @@ def getAdvisories(self) -> Advisories:
2933
for advisory in results:
3034
advisories.append(self.loadAdvisoryData(advisory))
3135
return advisories
32-
raise Exception(f"Error getting advisories from repository")
36+
37+
raise GHASToolkitTypeError(
38+
f"Error getting advisories from repository",
39+
docs="https://docs.github.com/en/rest/security-advisories/repository-advisories#list-repository-security-advisories",
40+
)
3341

3442
def getAdvisory(self, ghsa_id: str) -> Advisory:
35-
"""Get advisory by ghsa id."""
43+
"""Get advisory by ghsa id.
44+
45+
https://docs.github.com/en/rest/security-advisories/repository-advisories#get-a-repository-security-advisory
46+
"""
3647
result = self.rest.get(
3748
"/repos/{owner}/{repo}/security-advisories/{ghsa_id}",
3849
{"ghsa_id": ghsa_id},
3950
authenticated=True,
4051
)
4152
if isinstance(result, dict):
4253
return self.loadAdvisoryData(result)
43-
raise Exception(f"Error getting advisory by id")
54+
55+
raise GHASToolkitTypeError(
56+
f"Error getting advisory by id",
57+
docs="https://docs.github.com/en/rest/security-advisories/repository-advisories#get-a-repository-security-advisory",
58+
)
4459

4560
def createAdvisory(
4661
self, advisory: Advisory, repository: Optional[Repository] = None
4762
):
48-
"""Create a GitHub Security Advisories for a repository."""
49-
raise Exception("Unsupported feature")
63+
"""Create a GitHub Security Advisories for a repository.
64+
65+
https://docs.github.com/en/rest/security-advisories/repository-advisories#create-a-repository-security-advisory
66+
"""
67+
raise GHASToolkitError("Unsupported feature")
5068

5169
def createPrivateAdvisory(
5270
self, advisory: Advisory, repository: Optional[Repository] = None
@@ -57,8 +75,11 @@ def createPrivateAdvisory(
5775
def updateAdvisory(
5876
self, advisory: Advisory, repository: Optional[Repository] = None
5977
):
60-
"""Update GitHub Security Advisory."""
61-
raise Exception("Unsupported feature")
78+
"""Update GitHub Security Advisory.
79+
80+
https://docs.github.com/en/rest/security-advisories/repository-advisories#update-a-repository-security-advisory
81+
"""
82+
raise GHASToolkitError("Unsupported feature")
6283

6384
def loadAdvisoryData(self, data: Dict) -> Advisory:
6485
"""Load Advisory from API data."""

src/ghastoolkit/octokit/clearlydefined.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any, Optional
33
from requests import Session
44

5+
from ghastoolkit.errors import GHASToolkitError
56
from ghastoolkit.supplychain.dependencies import Dependency
67

78

@@ -41,7 +42,7 @@ def createCurationUrl(self, dependency: Dependency) -> Optional[str]:
4142

4243
def getCurations(self, dependency: Dependency) -> dict[str, Any]:
4344
if not dependency.manager:
44-
raise Exception(f"Dependency manager / type must be set")
45+
raise GHASToolkitError(f"Dependency manager / type must be set")
4546

4647
url = self.createCurationUrl(dependency)
4748
if not url:

src/ghastoolkit/octokit/dependabot.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
from typing import Optional
55

6+
from ghastoolkit.errors import GHASToolkitError, GHASToolkitTypeError
67
from ghastoolkit.octokit.github import GitHub, Repository
78
from ghastoolkit.octokit.octokit import GraphQLRequest, RestRequest
89
from ghastoolkit.supplychain.advisories import Advisory
@@ -39,10 +40,17 @@ def isEnabled(self) -> bool:
3940
return False
4041

4142
def isSecurityUpdatesEnabled(self) -> bool:
42-
"""Is Security Updates for Dependabot enabled."""
43+
"""Is Security Updates for Dependabot enabled.
44+
45+
https://docs.github.com/en/rest/reference/repos#get-a-repository
46+
"""
4347
result = self.rest.get("get/repos/{owner}/{repo}")
4448
if not isinstance(result, dict):
45-
raise Exception(f"Unable to get repository info")
49+
raise GHASToolkitTypeError(
50+
"Unable to get repository info",
51+
permissions=["Repository Administration (read)"],
52+
docs="https://docs.github.com/en/rest/reference/repos#get-a-repository",
53+
)
4654
saa = result.get("source", {}).get("security_and_analysis", {})
4755
status = saa.get("dependabot_security_updates", {}).get("status", "disabled")
4856
return status == "enabled"
@@ -58,10 +66,13 @@ def getAlerts(
5866
) -> list[DependencyAlert]:
5967
"""Get All Dependabot alerts from REST API.
6068
61-
https://docs.github.com/en/enterprise-cloud@latest/rest/dependabot/alerts?apiVersion=2022-11-28
69+
https://docs.github.com/en/rest/dependabot/alerts
6270
"""
6371
if state not in ["auto_dismissed", "dismissed", "fixed", "open"]:
64-
raise Exception(f"Invalid state provided: {state}")
72+
raise GHASToolkitError(
73+
f"Invalid state provided: {state}",
74+
docs="https://docs.github.com/en/rest/reference/repos#get-a-repository",
75+
)
6576

6677
logger.debug(f"Getting Dependabot alerts with state: {state}")
6778

@@ -100,7 +111,10 @@ def getAlerts(
100111
)
101112

102113
return retval
103-
raise Exception(f"Error getting Dependabot alerts")
114+
raise GHASToolkitTypeError(
115+
f"Error getting Dependabot alerts",
116+
docs="https://docs.github.com/en/rest/dependabot/alerts",
117+
)
104118

105119
def getAlertsGraphQL(self) -> list[DependencyAlert]:
106120
"""Get All Dependabot alerts from GraphQL API using the `GetDependencyAlerts` query."""
@@ -117,7 +131,8 @@ def getAlertsGraphQL(self) -> list[DependencyAlert]:
117131
logger.error(
118132
"This could be due to a lack of permissions or access token"
119133
)
120-
raise Exception(f"Failed to get GraphQL repository alerts")
134+
raise GHASToolkitError(f"Failed to get GraphQL repository alerts")
135+
121136
alerts = repo.get("vulnerabilityAlerts", {})
122137

123138
for alert in alerts.get("edges", []):

src/ghastoolkit/octokit/dependencygraph.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from semantic_version import Version
88

9+
from ghastoolkit.errors import GHASToolkitError, GHASToolkitTypeError
910
from ghastoolkit.octokit.github import GitHub, Repository
1011
from ghastoolkit.supplychain.advisories import Advisory
1112
from ghastoolkit.supplychain.dependencyalert import DependencyAlert
@@ -70,7 +71,7 @@ def getDependencies(self) -> Dependencies:
7071
logger.warning("Using GraphQL API to resolve dependencies (GHES 3.6+)")
7172
deps = self.getDependenciesGraphQL()
7273
else:
73-
raise Exception("Enterprise Server version must be >= 3.6.0")
74+
raise GHASToolkitError("Enterprise Server version must be >= 3.6.0")
7475
else:
7576
# cloud: download SBOM
7677
deps = self.getDependenciesSbom()
@@ -179,7 +180,7 @@ def getDependenciesInPR(self, base: str, head: str) -> Dependencies:
179180
"""Get all the dependencies from a Pull Request."""
180181

181182
if GitHub.isEnterpriseServer() and GitHub.server_version < Version("3.6.0"):
182-
raise Exception("Enterprise Server version must be >= 3.6")
183+
raise GHASToolkitError("Enterprise Server version must be >= 3.6")
183184

184185
dependencies = Dependencies()
185186
base = urllib.parse.quote(base, safe="")
@@ -234,8 +235,18 @@ def getDependenciesInPR(self, base: str, head: str) -> Dependencies:
234235
return dependencies
235236

236237
def exportBOM(self) -> Dependencies:
237-
"""Download / Export DependencyGraph SBOM."""
238-
return self.rest.get("/repos/{owner}/{repo}/dependency-graph/sbom")
238+
"""Download / Export DependencyGraph SBOM.
239+
240+
https://docs.github.com/en/rest/dependency-graph/sboms#export-a-software-bill-of-materials-sbom-for-a-repository
241+
"""
242+
result = self.rest.get("/repos/{owner}/{repo}/dependency-graph/sbom")
243+
if result:
244+
return result
245+
246+
raise GHASToolkitTypeError(
247+
"Failed to download SBOM",
248+
docs="https://docs.github.com/en/rest/dependency-graph/sboms#export-a-software-bill-of-materials-sbom-for-a-repository",
249+
)
239250

240251
def submitDependencies(
241252
self,

src/ghastoolkit/octokit/enterprise.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from semantic_version import Version
55

6+
from ghastoolkit.errors import GHASToolkitError
67
from ghastoolkit.octokit.github import GitHub
78
from ghastoolkit.octokit.octokit import Octokit, RestRequest
89
from ghastoolkit.octokit.repository import Repository
@@ -23,12 +24,19 @@ def __init__(
2324
self.rest = RestRequest(GitHub.repository)
2425

2526
def getRepositories(self) -> List[Repository]:
26-
"""Get Repositories."""
27+
"""Get Repositories.
28+
29+
https://docs.github.com/en/rest/repos/repos#list-organization-repositories
30+
"""
2731
repositories = []
2832
result = self.rest.get(f"/orgs/{self.name}/repos")
2933
if not isinstance(result, list):
3034
logger.error("Error getting repositories")
31-
return []
35+
raise GHASToolkitError(
36+
"Error getting repositories",
37+
permissions=["Metadata repository permissions (read)"],
38+
docs="https://docs.github.com/en/rest/repos/repos#list-organization-repositories",
39+
)
3240

3341
for repository in result:
3442
repositories.append(Repository.parseRepository(repository.get("full_name")))
@@ -81,6 +89,10 @@ def enableDefaultSetup(self) -> bool:
8189
logger.error(
8290
"Enterprise Server 3.8 or lower does not support default setup"
8391
)
92+
raise GHASToolkitError(
93+
"Enterprise Server 3.8 or lower does not support default setup"
94+
)
95+
8496
elif GitHub.server_version and GitHub.server_version < Version("3.11.0"):
8597
from ghastoolkit.octokit.codescanning import CodeScanning
8698

@@ -136,13 +148,21 @@ def getOrganizations(self, include_github: bool = False) -> List[Organization]:
136148

137149
if response.status_code != 200:
138150
logger.error("Error getting organizations")
139-
return []
151+
raise GHASToolkitError(
152+
"Error getting organizations",
153+
permissions=["Metadata repository permissions (read)"],
154+
docs="https://docs.github.com/en/rest/orgs/orgs#list-organizations",
155+
)
140156

141157
result = response.json()
142158

143159
if not isinstance(result, list):
144160
logger.error("Error getting organizations")
145-
return []
161+
raise GHASToolkitError(
162+
"Error getting organizations",
163+
permissions=["Metadata repository permissions (read)"],
164+
docs="https://docs.github.com/en/rest/orgs/orgs#list-organizations",
165+
)
146166

147167
for org in result:
148168
if not include_github and org.get("login") in github_orgs:

src/ghastoolkit/octokit/octokit.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def patchJson(
288288
) -> dict:
289289
repo = self.repository or GitHub.repository
290290
if not repo:
291-
raise Exception("Repository needs to be set")
291+
raise GHASToolkitError("Repository needs to be set")
292292

293293
url = Octokit.route(path, repo, rtype="rest", **parameters)
294294
logger.debug(f"Patching content from URL :: {url}")
@@ -303,8 +303,8 @@ def patchJson(
303303
logger.error(f"{response.content}")
304304
known_error = __OCTOKIT_ERRORS__.get(response.status_code)
305305
if known_error:
306-
raise Exception(known_error)
307-
raise Exception("Failed to patch data")
306+
raise known_error
307+
raise GHASToolkitError("Failed to patch data")
308308

309309
return response.json()
310310

@@ -333,7 +333,10 @@ def query(self, name: str, options: dict[str, Any] = {}) -> dict:
333333
query_content = self.queries.get(name)
334334

335335
if not query_content:
336-
raise Exception(f"Failed to load GraphQL query :: {name}")
336+
raise GHASToolkitError(
337+
f"Failed to load GraphQL query :: {name}",
338+
docs="https://docs.github.com/en/enterprise-cloud@latest/graphql/overview/about-the-graphql-api",
339+
)
337340

338341
cursor = f'after: "{self.cursor}"' if self.cursor != "" else ""
339342

@@ -345,7 +348,10 @@ def query(self, name: str, options: dict[str, Any] = {}) -> dict:
345348
if response.status_code != 200:
346349
logger.error(f"GraphQL API Status :: {response.status_code}")
347350
logger.error(f"GraphQL Content :: {response.content}")
348-
raise Exception(f"Failed to get data from GraphQL API")
351+
raise GHASToolkitError(
352+
f"Failed to get data from GraphQL API",
353+
docs="https://docs.github.com/en/enterprise-cloud@latest/graphql/overview/about-the-graphql-api",
354+
)
349355

350356
rjson = response.json()
351357
if rjson.get("errors"):

src/ghastoolkit/octokit/repository.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from typing import Optional, Union
99
from urllib.parse import urlparse
1010

11+
from ghastoolkit.errors import GHASToolkitError
12+
1113

1214
logger = logging.getLogger("ghastoolkit.octokit.repository")
1315

@@ -29,6 +31,9 @@ class Repository:
2931
path: Optional[str] = None
3032
"""Path inside the repository"""
3133

34+
pr_number: Optional[int] = None
35+
"""Pull Request Number (if in a PR)"""
36+
3237
__prinfo__: Optional[dict] = None
3338

3439
sha: Optional[str] = None
@@ -89,16 +94,17 @@ def getPullRequestInfo(self) -> dict:
8994
if not self.__prinfo__:
9095
from ghastoolkit.octokit.octokit import RestRequest
9196

92-
pull_number = self.getPullRequestNumber()
97+
self.pr_number = self.getPullRequestNumber()
9398
self.__prinfo__ = RestRequest().get(
9499
"/repos/{owner}/{repo}/pulls/{pull_number}",
95-
{"pull_number": pull_number},
100+
{"pull_number": self.pr_number},
96101
)
97102
return self.__prinfo__
98103

99104
def getPullRequestCommits(self) -> list[str]:
100105
"""Get list of Pull Request commits."""
101106
result = []
107+
102108
if self.isInPullRequest():
103109
from ghastoolkit.octokit.octokit import RestRequest
104110

@@ -224,7 +230,7 @@ def gitsha(self) -> str:
224230
def getFile(self, path: str) -> str:
225231
"""Get a path relative from the base of the cloned repository."""
226232
if not self.clone_path:
227-
raise Exception(f"Unknown clone path")
233+
raise GHASToolkitError(f"Unknown clone path")
228234
return os.path.join(self.clone_path, path)
229235

230236
def display(self) -> str:

0 commit comments

Comments
 (0)