-
Notifications
You must be signed in to change notification settings - Fork 134
/
errors.py
171 lines (127 loc) · 4.65 KB
/
errors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""
This module centralizes GGShield error handling. For more details, have a look
at doc/dev/error-handling.md.
"""
import platform
import traceback
from enum import IntEnum
from typing import Any, Dict
import click
from marshmallow import ValidationError
from ggshield.core.text_utils import display_error
from ggshield.utils.git_shell import GitError, InvalidGitRefError
class ExitCode(IntEnum):
"""
Define constant exit codes based on their type
"""
# Everything went well
SUCCESS = 0
# Scan was successful, and found problems (leaked secrets, IAC security issues...)
SCAN_FOUND_PROBLEMS = 1
# Error on the command-line, like a missing parameter
USAGE_ERROR = 2
# auth subcommand failed
AUTHENTICATION_ERROR = 3
# Add new exit codes here.
# If you add a new exit code, make sure you also add it to the documentation.
# Catch all for other failures
UNEXPECTED_ERROR = 128
class _ExitError(click.ClickException):
"""
Base class for exceptions which must exit with an exit code as defined in ExitCode.
This class is internal, inherit from it to create public exception classes.
"""
def __init__(self, exit_code: ExitCode, message: str) -> None:
super().__init__(message)
self.exit_code = exit_code
class UnexpectedError(_ExitError):
def __init__(self, message: str) -> None:
super().__init__(ExitCode.UNEXPECTED_ERROR, message)
class ParseError(_ExitError):
"""
Failed to load file
"""
def __init__(self, message: str):
super().__init__(ExitCode.UNEXPECTED_ERROR, message)
class AuthError(_ExitError):
"""
Base exception for Auth-related configuration error
"""
def __init__(self, instance: str, message: str):
super().__init__(ExitCode.AUTHENTICATION_ERROR, message)
self.instance = instance
class QuotaLimitReachedError(_ExitError):
def __init__(self):
super().__init__(
ExitCode.UNEXPECTED_ERROR,
"Could not perform the requested action: no more API calls available.",
)
class UnknownInstanceError(AuthError):
"""
Raised when the requested instance does not exist
"""
def __init__(self, instance: str):
super().__init__(instance, f"Unknown instance: '{instance}'")
class AuthExpiredError(AuthError):
"""
Raised when authentication has expired for the given instance
"""
def __init__(self, instance: str):
super().__init__(
instance,
f"Instance '{instance}' authentication expired, please authenticate again.",
)
class MissingTokenError(AuthError):
def __init__(self, instance: str):
super().__init__(instance, f"No token is saved for this instance: '{instance}'")
class APIKeyCheckError(AuthError):
"""
Raised when checking the API key fails
"""
def __init__(self, instance: str, message: str):
super().__init__(instance, message)
def format_validation_error(exc: ValidationError) -> str:
"""
Take a Marshmallow ValidationError and turn it into a more user-friendly message
"""
message_dct = exc.normalized_messages()
lines = []
def format_items(dct: Dict[str, Any], indent: int) -> None:
for key, value in dct.items():
message = " " * indent + f"{key}: "
if isinstance(value, dict):
lines.append(message)
format_items(value, indent + 2)
else:
message += str(value)
lines.append(message)
format_items(message_dct, 0)
return "\n".join(lines)
def handle_exception(exc: Exception, verbose: bool) -> int:
"""
Take an exception, print information about it and return the exit code to use
"""
if isinstance(exc, click.exceptions.Abort):
return ExitCode.SUCCESS
# Get exit code
if isinstance(exc, _ExitError):
exit_code = exc.exit_code
elif isinstance(exc, (InvalidGitRefError, click.UsageError)):
exit_code = ExitCode.USAGE_ERROR
else:
exit_code = ExitCode.UNEXPECTED_ERROR
click.echo()
display_error(f"Error: {exc}")
if isinstance(exc, UnicodeEncodeError) and platform.system() == "Windows":
display_error(
"\n"
"ggshield failed to print a message because of an Unicode encoding issue."
" To workaround that, try setting the PYTHONUTF8 environment variable to 1."
)
if not isinstance(exc, (click.ClickException, GitError)):
click.echo()
if verbose:
traceback.print_exc()
else:
display_error("Re-run the command with --verbose to get a stack trace.")
return exit_code