Skip to content

Commit 19485a3

Browse files
authored
Feature/INTEG_978 handle keyboard interrupts (#60)
* update ArgConfig to allow changing metavar setting * move verify_timestamp_order to date_helper module * - add AlertCursorStore class - refactor shared methods onto the base class - set default db name to "checkpoints.db" so we don't create separate dbs for each cursor store * - moved enums to shared module - added Alert enums * pull out shared extraction functions * move logger_factory to shared module * update securitydata extraction and main * add alerts extraction and main * add main alerts command * update setup.py * update some tests * update conftest * bump dependency versions * fix bugs in advanced_arg handling * extract shared search arguments into shared module * complete the warning change * fixed existing tests * fixed global error state flagging * rename shared func * more test fixes * Remove CEF option for alerts output * hard-code cursor_store db to `file_event_checkpoints.db` * update profile deletion to handle alert cursors * fix log method docstrings * fix import * logging adjustments * add new logging to alert extraction * update shared extraction logging * improve error handling * fix file event tests with new logging changes * add alert tests and reorganize a bit * update arg options * fix --exclude-actor-contains nargs * more test fixes/updates * fix alertstate choices in help * remove "Accepts multiple args." from help messages * enumerate rule types in help * rename shared folder to search_shared * rename shared folder to search_shared * YYYY-MM-DD -> yyyy-MM-dd, along with clarity on partial time, and remove duplication of arg format message. * move shared table init logic to base class * adjust DateArgumentException error message to use max_days_back var instead of hardcoded # * range compatibility * get alert details instead of summaries in extraction * update py42 minimum req * update changelog * implement clean interrupt handling * remove test code * docstring and changelog * remove a test thing * change decorator behavior to exit after function completes on first ctrl-c
1 parent f92947f commit 19485a3

File tree

4 files changed

+63
-0
lines changed

4 files changed

+63
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
4747
- A custom error in the error log when you try adding unknown risk tags to user.
4848

4949
- A custom error in the error log when you try adding a user to a detection list who is already added.
50+
- Graceful handling of keyboard interrupts (ctrl-c) so stack traces aren't printed to console.
51+
- Warning message printed when ctrl-c is encountered in the middle of an operation that could cause incorrect checkpoint
52+
state, a second ctrl-c is required to quit while that operation is ongoing.
5053

5154
### Fixed
5255

src/code42cli/cmds/search_shared/extraction.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from code42cli.date_helper import parse_min_timestamp, parse_max_timestamp, verify_timestamp_order
88
from code42cli.logger import get_main_cli_logger
99
from code42cli.cmds.alerts.util import get_alert_details
10+
from code42cli.util import warn_interrupt
1011

1112
logger = get_main_cli_logger()
1213

@@ -50,6 +51,9 @@ def handle_error(exception):
5051
handlers.record_cursor_position = cursor_store.replace_stored_cursor_timestamp
5152
handlers.get_cursor_position = cursor_store.get_stored_cursor_timestamp
5253

54+
@warn_interrupt(
55+
warning=u"Cancelling operation cleanly to keep checkpoint data accurate. One moment..."
56+
)
5357
def handle_response(response):
5458
response_dict = json.loads(response.text)
5559
events = response_dict.get(event_key)

src/code42cli/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import platform
2+
import signal
23
import sys
34

45
from py42.settings import set_user_agent_suffix
@@ -15,6 +16,14 @@
1516
from code42cli.cmds.alerts.rules.commands import AlertRulesCommands
1617

1718

19+
# Handle KeyboardInterrupts by just exiting instead of printing out a stack
20+
def exit_on_interrupt(signal, frame):
21+
sys.exit(1)
22+
23+
24+
signal.signal(signal.SIGINT, exit_on_interrupt)
25+
26+
1827
# If on Windows, configure console session to handle ANSI escape sequences correctly
1928
# source: https://bugs.python.org/issue29059
2029
if platform.system().lower() == u"windows":

src/code42cli/util.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import print_function
22
import sys
3+
from functools import wraps
34
from os import makedirs, path
5+
from signal import signal, getsignal, SIGINT
46

57
from code42cli.compat import open, str
68

@@ -88,3 +90,48 @@ def format_to_table(rows, column_size):
8890
for key in row.keys():
8991
print(str(row[key]).ljust(column_size[key] + _PADDING_SIZE), end=u" ")
9092
print(u"")
93+
94+
95+
class warn_interrupt(object):
96+
"""A context decorator class used to wrap functions where a keyboard interrupt could potentially
97+
leave things in a bad state. Warns the user with provided message and exits when wrapped
98+
function is complete. Requires user to ctrl-c a second time to force exit.
99+
100+
Usage:
101+
102+
@warn_interrupt(warning="example message")
103+
def my_important_func():
104+
pass
105+
"""
106+
107+
def __init__(self, warning="Cancelling operation cleanly, one moment... "):
108+
self.warning = warning
109+
self.old_handler = None
110+
self.interrupted = False
111+
self.exit_instructions = "Hit CTRL-C again to force quit."
112+
113+
def __enter__(self):
114+
self.old_handler = getsignal(SIGINT)
115+
signal(SIGINT, self._handle_interrupts)
116+
return self
117+
118+
def __exit__(self, exc_type, exc_val, exc_tb):
119+
if self.interrupted:
120+
exit(1)
121+
signal(SIGINT, self.old_handler)
122+
return False
123+
124+
def _handle_interrupts(self, sig, frame):
125+
if not self.interrupted:
126+
self.interrupted = True
127+
print("\n{}\n{}".format(self.warning, self.exit_instructions), file=sys.stderr)
128+
else:
129+
exit()
130+
131+
def __call__(self, func):
132+
@wraps(func)
133+
def inner(*args, **kwds):
134+
with self:
135+
return func(*args, **kwds)
136+
137+
return inner

0 commit comments

Comments
 (0)