Skip to content

Commit 51b3e19

Browse files
author
Juliya Smith
authored
Feature/rm hr (#36)
1 parent 3f138cc commit 51b3e19

23 files changed

Lines changed: 635 additions & 194 deletions

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1717
- `--c42username` flag renamed to `--c42-username`.
1818
- `--filename` flag renamed to `--file-name`.
1919
- `--filepath` flag renamed to `--file-path`.
20-
- `--processOwner` flag renamed to `--process-owner`
20+
- `--processOwner` flag renamed to `--process-owner`.
2121
- `-b|--begin` and `-e|--end` arguments now accept shorthand date-range strings for days, hours, and minute intervals going back from the current time (e.g. `30d`, `24h`, `15m`).
2222
- Default profile validation logic added to prevent confusing error states.
2323

@@ -31,9 +31,11 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
3131
- `bulk` with subcommands:
3232
- `add`: that takes a csv file of users.
3333
- `generate-template`: that creates the csv file template. And parameters:
34-
- `cmd`: with the option `add`.
34+
- `cmd`: with options `add` and `remove`.
3535
- `path`
36-
- `add` that takes parameters: `--username`, `--cloud-aliases`, `--risk-factors`, and `--notes`.
36+
- `remove`: that takes a list of users in a file.
37+
- `add` that takes parameters: `--username`, `--cloud-alias`, `--risk-factor`, and `--notes`.
38+
- `remove` that takes a username.
3739

3840
### Removed
3941

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# The Code42 CLI
22

33
Use the `code42` command to interact with your Code42 environment.
4-
`code42 security-data` is a CLI tool for extracting AED events.
5-
Additionally, you can choose to only get events that Code42 previously did not observe since you last recorded a checkpoint
6-
(provided you do not change your query).
4+
5+
* `code42 security-data` is a CLI tool for extracting AED events.
6+
Additionally, you can choose to only get events that Code42 previously did not observe since you last recorded a
7+
checkpoint (provided you do not change your query).
8+
* `code42 high-risk-employee` is a collection of tools for managing the high risk employee detection list.
79

810
## Requirements
911

@@ -53,6 +55,8 @@ To see all your profiles, do:
5355
code42 profile list
5456
```
5557

58+
## Security Data
59+
5660
Using the CLI, you can query for events and send them to three possible destination types:
5761
* stdout
5862
* A file
@@ -150,9 +154,37 @@ Each destination-type subcommand shares query parameters
150154
You cannot use other query parameters if you use `--advanced-query`.
151155
To learn more about acceptable arguments, add the `-h` flag to `code42` or any of the destination-type subcommands.
152156

157+
## Detection Lists
158+
159+
You can both add and remove employees from detection lists using the CLI. This example uses `high-risk-employee`.
160+
161+
```bash
162+
code42 high-risk-employee add user@example.com --notes "These are notes"
163+
code42 high-risk-employee remove user@example.com
164+
```
165+
166+
Detection lists include a `bulk` command. To add employees to a list, you can pass in a csv file. First, generate the
167+
csv file for the desired command by executing the `generate-template` command:
168+
169+
```bash
170+
code42 high-risk-employee bulk generate-template add
171+
```
172+
173+
Notice that `generate-template` takes a `cmd` parameter for determining what type of template to generate. In the
174+
example above, we give it the value `add` to generate a file for bulk adding users to the high risk employee list.
175+
176+
Next, fill out the csv file with all the users and then pass it in as a parameter to `bulk add`:
177+
178+
```bash
179+
code42 high-risk-employee bulk add users_to_add.csv
180+
```
181+
182+
Note that for `bulk remove`, the file only has to be an end-line delimited list of users with one line per user.
183+
153184
## Known Issues
154185

155-
Only the first 10,000 of each set of events containing the exact same insertion timestamp is reported.
186+
In `security-data`, only the first 10,000 of each set of events containing the exact same insertion timestamp is
187+
reported.
156188

157189
## Troubleshooting
158190

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
package_dir={"": "src"},
2222
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4",
2323
install_requires=[
24-
"c42eventextractor==0.2.5",
24+
"c42eventextractor",
2525
"keyring==18.0.1",
2626
"keyrings.alt==3.2.0",
27-
"py42==0.9.0",
27+
"py42",
2828
],
2929
license="MIT",
3030
include_package_data=True,

src/code42cli/args.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from collections import OrderedDict
22
import inspect
33

4+
45
PROFILE_HELP = u"The name of the Code42 profile use when executing this command."
6+
SDK_ARG_NAME = u"sdk"
7+
PROFILE_ARG_NAME = u"profile"
58

69

710
class ArgConfig(object):
@@ -61,14 +64,14 @@ def get_auto_arg_configs(handler):
6164

6265
for arg_position, key in enumerate(argspec.args):
6366
# do not create cli parameters for arguments named "sdk", "args", or "kwargs"
64-
if not key in [u"sdk", u"args", u"kwargs", u"self"]:
67+
if not key in [SDK_ARG_NAME, u"args", u"kwargs", u"self"]:
6568
arg_config = _create_auto_args_config(
6669
arg_position, key, argspec, num_args, num_kw_args
6770
)
6871
_set_smart_defaults(arg_config)
6972
arg_configs.append(key, arg_config)
7073

71-
if u"sdk" in argspec.args:
74+
if SDK_ARG_NAME in argspec.args:
7275
_build_sdk_arg_configs(arg_configs)
7376

7477
return arg_configs
@@ -108,5 +111,5 @@ def _build_sdk_arg_configs(arg_config_collection):
108111
"""Add extra cli parameters that will always be relevant when a handler needs the sdk."""
109112
profile = ArgConfig(u"--profile", help=PROFILE_HELP)
110113
debug = ArgConfig(u"-d", u"--debug", action=u"store_true", help=u"Turn on Debug logging.")
111-
extras = {u"profile": profile, u"debug": debug}
114+
extras = {PROFILE_ARG_NAME: profile, u"debug": debug}
112115
arg_config_collection.extend(extras)

src/code42cli/bulk.py

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,121 @@
44

55
from code42cli.compat import open, str
66
from code42cli.worker import Worker
7+
from code42cli.errors import print_errors_occurred
8+
from code42cli.args import SDK_ARG_NAME, PROFILE_ARG_NAME
79

810

911
def generate_template(handler, path=None):
10-
"""Looks at the parameter names of `handler` and creates a csv file with the same column names.
12+
"""Looks at the parameter names of `handler` and creates a file with the same column names. If
13+
`handler` only has one parameter that is not `sdk` or `profile`, it will create a blank file.
14+
This is useful for commands such as `remove` which only require a list of users.
1115
"""
12-
if callable(handler):
13-
argspec = inspect.getargspec(handler)
14-
columns = [str(arg) for arg in argspec.args if arg not in [u"sdk", u"profile"]]
15-
path = path or u"{0}/{1}.csv".format(os.getcwd(), str(handler.__name__))
16-
_write_template_file(path, columns)
16+
path = path or u"{0}/{1}.csv".format(os.getcwd(), str(handler.__name__))
17+
args = [
18+
arg
19+
for arg in inspect.getargspec(handler).args
20+
if arg != SDK_ARG_NAME and arg != PROFILE_ARG_NAME
21+
]
1722

23+
if len(args) <= 1:
24+
print(
25+
u"A blank file was generated because there are no csv headers needed for this command. "
26+
u"Simply enter one {} per line.".format(args[0])
27+
)
28+
# Set args to None so that we don't make a header out of the single arg.
29+
args = None
1830

19-
def _write_template_file(path, columns):
20-
with open(path, u"w", encoding=u"utf8") as new_csv:
21-
new_csv.write(u",".join(columns))
31+
_write_template_file(path, args)
2232

2333

24-
def run_bulk_process(csv_file_path, row_handler):
25-
processor = _create_bulk_processor(csv_file_path, row_handler)
34+
def _write_template_file(path, columns=None):
35+
with open(path, u"w", encoding=u"utf8") as new_file:
36+
if columns:
37+
new_file.write(u",".join(columns))
38+
39+
40+
def run_bulk_process(file_path, row_handler, reader=None):
41+
"""Runs a bulk process.
42+
43+
Args:
44+
file_path (str or unicode): The path to the file feeding the data for the bulk process.
45+
row_handler (callable): A callable that you define to process values from the row as
46+
either *args or **kwargs.
47+
reader: (CSVReader or FlatFileReader, optional): A generator that reads rows and yields data into
48+
`row_handler`. If None, it will use a CSVReader. Defaults to None.
49+
"""
50+
reader = reader or CSVReader()
51+
processor = _create_bulk_processor(file_path, row_handler, reader)
2652
processor.run()
2753

2854

29-
def _create_bulk_processor(csv_file_path, row_handler):
55+
def _create_bulk_processor(file_path, row_handler, reader):
3056
"""A factory method to create the bulk processor, useful for testing purposes."""
31-
return BulkProcessor(csv_file_path, row_handler)
57+
return BulkProcessor(file_path, row_handler, reader)
3258

3359

3460
class BulkProcessor(object):
35-
"""A class for bulk processing a csv file.
61+
"""A class for bulk processing a file.
3662
3763
Args:
38-
csv_file_path (str or unicode): The path to the csv file for processing.
39-
row_handler (callable): To be executed on each row given **kwargs representing the column
40-
names mapped to the properties found in the row. For example, if the csv file header
41-
looked like `prop_a,prop_b` and the next row looked like `1,test`, then row handler
42-
would receive args `prop_a: '1', prop_b: 'test'` when processing row 1.
64+
file_path (str or unicode): The path to the file for processing.
65+
row_handler (callable): A callable that you define to process values from the row as
66+
either *args or **kwargs. For example, if it's a csv file with header `prop_a,prop_b`
67+
and first row `1,test`, then `row_handler` should receive kwargs
68+
`prop_a: '1', prop_b: 'test'` when processing the first row. If it's a flat file, then
69+
`row_handler` only needs to take an extra arg.
70+
reader (CSVReader or FlatFileReader): A generator that reads rows and yields data into `row_handler`.
4371
"""
4472

45-
def __init__(self, csv_file_path, row_handler):
46-
self.csv_file_path = csv_file_path
73+
def __init__(self, file_path, row_handler, reader):
74+
self.file_path = file_path
4775
self._row_handler = row_handler
76+
self._reader = reader
4877
self.__worker = Worker(5)
4978

5079
def run(self):
5180
"""Processes the csv file specified in the ctor, calling `self.row_handler` on each row."""
52-
with open(self.csv_file_path, newline=u"", encoding=u"utf8") as csv_file:
53-
rows = _create_dict_reader(csv_file)
54-
self._process_rows(rows)
81+
with open(self.file_path, newline=u"", encoding=u"utf8") as bulk_file:
82+
for row in self._reader(bulk_file=bulk_file):
83+
self._process_row(row)
5584
self.__worker.wait()
85+
self._print_result()
86+
87+
def _process_row(self, row):
88+
if type(row) is dict:
89+
self._process_csv_row(row)
90+
elif row:
91+
self._process_flat_file_row(row.strip())
92+
93+
def _process_csv_row(self, row):
94+
# Removes problems from including extra comments. Error messages from out of order args
95+
# are more indicative this way too.
96+
row.pop(None, None)
97+
self.__worker.do_async(lambda *args, **kwargs: self._row_handler(*args, **kwargs), **row)
98+
99+
def _process_flat_file_row(self, row):
100+
if row:
101+
self.__worker.do_async(lambda *args, **kwargs: self._row_handler(*args, **kwargs), row)
102+
103+
def _print_result(self):
104+
stats = self.__worker.stats
105+
successes = stats.total - stats.total_errors
106+
print(u"{} processed successfully out of {}.".format(successes, stats.total))
107+
if stats.total_errors:
108+
print_errors_occurred()
109+
110+
111+
class CSVReader(object):
112+
"""A generator that yields header keys mapped to row values from a csv file."""
113+
114+
def __call__(self, *args, **kwargs):
115+
for row in csv.DictReader(kwargs.get(u"bulk_file")):
116+
yield row
56117

57-
def _process_rows(self, rows):
58-
for row in rows:
59-
self.__worker.do_async(lambda **kwargs: self._row_handler(**kwargs), **row)
60118

119+
class FlatFileReader(object):
120+
"""A generator that yields a single-value per row from a file."""
61121

62-
def _create_dict_reader(csv_file):
63-
return csv.DictReader(csv_file)
122+
def __call__(self, *args, **kwargs):
123+
for row in kwargs[u"bulk_file"]:
124+
yield row

0 commit comments

Comments
 (0)