Skip to content

Commit 590a43d

Browse files
author
Juliya Smith
authored
Bulk Skeletons (#25)
1 parent 4a60406 commit 590a43d

39 files changed

Lines changed: 505 additions & 104 deletions

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Test config file
2-
*config.cfg
1+
*.csv
32

43
.DS_Store
54

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
2121

2222
### Added
2323

24-
- `code42 profile create` command.
2524
- `code42 profile update` command.
25+
- `code42 profile create` command.
26+
- `code42 detection-lists high-risk` commands:
27+
- `bulk` with subcommands:
28+
- `add`: that takes a csv file of users.
29+
- `generate-template`: that creates the csv file template. And parameters:
30+
- `cmd`: with the option `add`.
31+
- `path`
32+
- `add` that takes parameters: `--username`, `--cloud-aliases`, `--risk-factors`, and `--notes`.
2633

2734
### Removed
2835

add_high_risk_employee.csv

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/code42cli/args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ def extend(self, arg_config_dict):
4646

4747

4848
def get_auto_arg_configs(handler):
49-
"""Looks at the parameter names of `handler` and builds an `ArgConfigCollection` containing argparse
50-
parameters based on them."""
49+
"""Looks at the parameter names of `handler` and builds an `ArgConfigCollection` containing
50+
`argparse` parameters based on them."""
5151
arg_configs = ArgConfigCollection()
5252
if callable(handler):
5353
# get the number of positional and keyword args

src/code42cli/bulk.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
import inspect
3+
import csv
4+
5+
from code42cli.compat import open, str
6+
from code42cli.worker import Worker
7+
8+
9+
def generate_template(handler, path=None):
10+
"""Looks at the parameter names of `handler` and creates a csv file with the same column names.
11+
"""
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)
17+
18+
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))
22+
23+
24+
def create_bulk_processor(csv_file_path, row_handler):
25+
"""A factory method to create the bulk processor, useful for testing purposes."""
26+
return BulkProcessor(csv_file_path, row_handler)
27+
28+
29+
class BulkProcessor(object):
30+
"""A class for bulk processing a csv file.
31+
32+
Args:
33+
csv_file_path (str or unicode): The path to the csv file for processing.
34+
row_handler (callable): To be executed on each row given **kwargs representing the column
35+
names mapped to the properties found in the row. For example, if the csv file header
36+
looked like `prop_a,prop_b` and the next row looked like `1,test`, then row handler
37+
would receive args `prop_a: '1', prop_b: 'test'` when processing row 1.
38+
"""
39+
40+
def __init__(self, csv_file_path, row_handler):
41+
self.csv_file_path = csv_file_path
42+
self._row_handler = row_handler
43+
self.__worker = Worker(5)
44+
45+
def run(self):
46+
"""Processes the csv file specified in the ctor, calling `self.row_handler` on each row."""
47+
rows = self._get_rows()
48+
self._process_rows(rows)
49+
self.__worker.wait()
50+
51+
def _get_rows(self):
52+
with open(self.csv_file_path, newline=u"", encoding=u"utf8") as csv_file:
53+
return _create_dict_reader(csv_file)
54+
55+
def _process_rows(self, rows):
56+
for row in rows:
57+
self.__worker.do_async(lambda **kwargs: self._row_handler(**kwargs), **row)
58+
59+
60+
def _create_dict_reader(csv_file):
61+
return csv.DictReader(csv_file)

src/code42cli/cmds/detectionlists/__init__.py

Whitespace-only changes.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from code42cli.cmds.detectionlists.enums import BulkCommandType
2+
from code42cli.commands import Command
3+
4+
5+
def create_usage_prefix(detection_list_name):
6+
return u"code42 detection-list {}".format(detection_list_name)
7+
8+
9+
def create_bulk_usage_prefix(detection_list_name):
10+
return u"{} bulk".format(create_usage_prefix(detection_list_name))
11+
12+
13+
class DetectionListCommandFactory:
14+
def __init__(self, detection_list_name):
15+
self._name = detection_list_name
16+
self._usage_prefix = create_usage_prefix(detection_list_name)
17+
self._bulk_usage_prefix = create_bulk_usage_prefix(detection_list_name)
18+
19+
def create_bulk_command(self, subcommand_loader):
20+
return Command(
21+
u"bulk",
22+
u"Tools for executing bulk {} commands.".format(self._name),
23+
subcommand_loader=subcommand_loader,
24+
)
25+
26+
def create_add_command(self, handler, arg_customizer):
27+
return Command(
28+
u"add",
29+
u"Add a user to the {} detection list.".format(self._name),
30+
u"{} add <username> <optional args>".format(self._usage_prefix),
31+
handler=handler,
32+
arg_customizer=arg_customizer,
33+
)
34+
35+
def create_bulk_generate_template_command(self, handler):
36+
return Command(
37+
u"generate-template",
38+
u"Generate the necessary csv template needed for bulk adding users.",
39+
u"{} gen-template <cmd> <optional args>".format(self._bulk_usage_prefix),
40+
handler=handler,
41+
arg_customizer=DetectionListCommandFactory._load_bulk_generate_template_description,
42+
)
43+
44+
def create_bulk_add_command(self, handler):
45+
return Command(
46+
u"add",
47+
u"Bulk add users to the {} detection list using a csv file.".format(self._name),
48+
u"{} add <csv-file>".format(self._bulk_usage_prefix),
49+
handler=handler,
50+
arg_customizer=self._load_bulk_add_description,
51+
)
52+
53+
@staticmethod
54+
def _load_bulk_generate_template_description(argument_collection):
55+
cmd_type = argument_collection.arg_configs[u"cmd"]
56+
cmd_type.set_help(u"The type of command the template with be used for.")
57+
cmd_type.set_choices(BulkCommandType())
58+
59+
def _load_bulk_add_description(self, argument_collection):
60+
csv_file = argument_collection.arg_configs[u"csv_file"]
61+
csv_file.set_help(
62+
u"The path to the csv file for bulk adding users to the {} detection list.".format(
63+
self._name
64+
)
65+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class DetectionLists(object):
2+
DEPARTING_EMPLOYEE = u"departing-employee"
3+
HIGH_RISK = u"high-risk"
4+
5+
6+
class BulkCommandType(object):
7+
ADD = u"add"
8+
9+
def __iter__(self):
10+
return iter([self.ADD])
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from code42cli.cmds.detectionlists.enums import BulkCommandType, DetectionLists
2+
from code42cli.cmds.detectionlists.commands import DetectionListCommandFactory, create_usage_prefix
3+
from code42cli.bulk import generate_template, create_bulk_processor
4+
5+
6+
_NAME = DetectionLists.HIGH_RISK
7+
_USAGE_PREFIX = create_usage_prefix(_NAME)
8+
9+
10+
def load_subcommands():
11+
factory = DetectionListCommandFactory(_NAME)
12+
bulk = factory.create_bulk_command(lambda: load_bulk_subcommands(factory))
13+
add = factory.create_add_command(add_high_risk_employee, _load_add_description)
14+
return [bulk, add]
15+
16+
17+
def load_bulk_subcommands(factory):
18+
generate_template_cmd = factory.create_bulk_generate_template_command(generate_csv_file)
19+
add = factory.create_bulk_add_command(bulk_add_high_risk_employees)
20+
return [generate_template_cmd, add]
21+
22+
23+
def generate_csv_file(cmd, path=None):
24+
"""Generates a csv template a user would need to fill-in for bulk adding users to the high
25+
risk detection list."""
26+
handler = None
27+
if cmd == BulkCommandType.ADD:
28+
handler = add_high_risk_employee
29+
generate_template(handler, path)
30+
31+
32+
def bulk_add_high_risk_employees(sdk, profile, csv_file):
33+
"""Takes a csv file in the form `username,cloud_aliases,risk_factors,notes` with each row
34+
representing an employee and adds each employee to the high risk detection list in a bulk
35+
fashion.
36+
37+
Args:
38+
sdk (py42.sdk.SDKClient): The py42 sdk.
39+
profile (Code42Profile): The profile under which to execute this command.
40+
csv_file (str): The path to the csv file containing rows of users.
41+
"""
42+
processor = create_bulk_processor(
43+
csv_file, lambda **kwargs: add_high_risk_employee(sdk, profile, **kwargs)
44+
)
45+
processor.run()
46+
47+
48+
def add_high_risk_employee(
49+
sdk, profile, username, cloud_aliases=None, risk_factors=None, notes=None
50+
):
51+
"""Adds the user with the given username to the high risk detection list.
52+
53+
Args:
54+
sdk (py42.sdk.SDKClient): The py42 sdk.
55+
profile (Code42Profile): The profile under which to execute this command.
56+
username (str): The username for the user.
57+
cloud_aliases (iter[str]): A list of cloud aliases associated with the user.
58+
risk_factors (iter[str]): The list of risk factors associated with the user.
59+
notes (str): Notes about the user.
60+
"""
61+
62+
63+
def _load_add_description(argument_collection):
64+
username = argument_collection.arg_configs[u"username"]
65+
risk_factors = argument_collection.arg_configs[u"risk_factors"]
66+
username.set_help(u"A user profile ID for detection lists.")
67+
risk_factors.set_help(u"Risk factors associated with the employee.")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import code42cli.cmds.detectionlists.high_risk as high_risk
2+
from code42cli.commands import Command
3+
4+
5+
def load_subcommands():
6+
return [
7+
Command(
8+
u"high-risk",
9+
u"Add or remove users from the `high risk` detection list.",
10+
subcommand_loader=high_risk.load_subcommands,
11+
)
12+
]

0 commit comments

Comments
 (0)