Skip to content

Commit 91f5289

Browse files
authored
add device rename and bulk rename cmds (#350)
* add device rename and bulk rename cmds * move error checking into helper method so it applies to bulk cmd as well * move error checking into helper method so that it applies to bulk method * make forbidden error more specific
1 parent 1374875 commit 91f5289

File tree

2 files changed

+164
-3
lines changed

2 files changed

+164
-3
lines changed

src/code42cli/cmds/devices.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def devices(state):
3838
"device-guid", type=str, callback=lambda ctx, param, arg: _verify_guid_type(arg),
3939
)
4040

41+
new_device_name_option = click.option(
42+
"-n", "--new-device-name", help="The new name for the device.", required=True
43+
)
44+
4145

4246
def change_device_name_option(help_msg):
4347
return click.option(
@@ -60,6 +64,15 @@ def change_device_name_option(help_msg):
6064
)
6165

6266

67+
@devices.command()
68+
@device_guid_argument
69+
@new_device_name_option
70+
@sdk_options()
71+
def rename(state, device_guid, new_device_name):
72+
"""Rename a device with Code42. Requires the device GUID to rename."""
73+
_change_device_name(state.sdk, device_guid, new_device_name)
74+
75+
6376
@devices.command()
6477
@device_guid_argument
6578
@change_device_name_option(
@@ -144,9 +157,16 @@ def _update_cold_storage_purge_date(sdk, guid, purge_date):
144157

145158

146159
def _change_device_name(sdk, guid, name):
147-
device_settings = sdk.devices.get_settings(guid)
148-
device_settings.name = name
149-
sdk.devices.update_settings(device_settings)
160+
try:
161+
device_settings = sdk.devices.get_settings(guid)
162+
device_settings.name = name
163+
sdk.devices.update_settings(device_settings)
164+
except exceptions.Py42ForbiddenError:
165+
raise Code42CLIError(
166+
f"You don't have the necessary permissions to rename the device with GUID '{guid}'."
167+
)
168+
except exceptions.Py42NotFoundError:
169+
raise Code42CLIError(f"The device with GUID '{guid}' was not found.")
150170

151171

152172
@devices.command()
@@ -544,13 +564,15 @@ def bulk(state):
544564

545565

546566
_bulk_device_activation_headers = ["guid"]
567+
_bulk_device_rename_headers = ["guid", "name"]
547568

548569

549570
devices_generate_template = generate_template_cmd_factory(
550571
group_name="devices",
551572
commands_dict={
552573
"reactivate": _bulk_device_activation_headers,
553574
"deactivate": _bulk_device_activation_headers,
575+
"rename": _bulk_device_rename_headers,
554576
},
555577
help_message="Generate the CSV template needed for bulk device commands.",
556578
)
@@ -632,3 +654,37 @@ def handle_row(**row):
632654
raise_global_error=False,
633655
)
634656
formatter.echo_formatted_list(result_rows)
657+
658+
659+
@bulk.command(name="rename")
660+
@read_csv_arg(headers=_bulk_device_rename_headers)
661+
@format_option
662+
@sdk_options()
663+
def bulk_rename(state, csv_rows, format):
664+
"""Rename all devices from the provided CSV containing a 'guid' and a 'name' column."""
665+
666+
# Initialize the SDK before starting any bulk processes
667+
# to prevent multiple instances and having to enter 2fa multiple times.
668+
sdk = state.sdk
669+
670+
csv_rows[0]["renamed"] = "False"
671+
formatter = OutputFormatter(format, {key: key for key in csv_rows[0].keys()})
672+
stats = create_worker_stats(len(csv_rows))
673+
674+
def handle_row(**row):
675+
try:
676+
_change_device_name(sdk, row["guid"], row["name"])
677+
row["renamed"] = "True"
678+
except Exception as err:
679+
row["renamed"] = f"False: {err}"
680+
stats.increment_total_errors()
681+
return row
682+
683+
result_rows = run_bulk_process(
684+
handle_row,
685+
csv_rows,
686+
progress_label="Renaming devices:",
687+
stats=stats,
688+
raise_global_error=False,
689+
)
690+
formatter.echo_formatted_list(result_rows)

tests/cmds/test_devices.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from code42cli.worker import WorkerStats
2323

2424
_NAMESPACE = "code42cli.cmds.devices"
25+
TEST_NEW_DEVICE_NAME = "test-new-device-name-123"
2526
TEST_DATE_OLDER = "2020-01-01T12:00:00.774Z"
2627
TEST_DATE_NEWER = "2021-01-01T12:00:00.774Z"
2728
TEST_DATE_MIDDLE = "2020-06-01T12:00:00"
@@ -468,6 +469,61 @@ def worker_stats(mocker, worker_stats_factory):
468469
return stats
469470

470471

472+
def test_rename_calls_get_and_update_settings_with_expected_params(runner, cli_state):
473+
cli_state.sdk.devices.get_settings.return_value = mock_device_settings
474+
runner.invoke(
475+
cli,
476+
[
477+
"devices",
478+
"rename",
479+
TEST_DEVICE_GUID,
480+
"--new-device-name",
481+
TEST_NEW_DEVICE_NAME,
482+
],
483+
obj=cli_state,
484+
)
485+
cli_state.sdk.devices.get_settings.assert_called_once_with(TEST_DEVICE_GUID)
486+
cli_state.sdk.devices.update_settings.assert_called_once_with(mock_device_settings)
487+
488+
489+
def test_rename_when_missing_guid_prints_error(runner, cli_state):
490+
result = runner.invoke(
491+
cli, ["devices", "rename", "-n", TEST_NEW_DEVICE_NAME], obj=cli_state
492+
)
493+
assert result.exit_code == 2
494+
assert "Missing argument 'DEVICE_GUID'" in result.output
495+
496+
497+
def test_rename_when_missing_name_prints_error(runner, cli_state):
498+
result = runner.invoke(cli, ["devices", "rename", TEST_DEVICE_GUID], obj=cli_state)
499+
assert result.exit_code == 2
500+
assert "Missing option '-n' / '--new-device-name'" in result.output
501+
502+
503+
def test_rename_when_guid_not_found_py42_raises_exception_prints_error(
504+
runner, cli_state, custom_error
505+
):
506+
cli_state.sdk.devices.get_settings.side_effect = Py42NotFoundError(custom_error)
507+
508+
result = runner.invoke(
509+
cli,
510+
[
511+
"devices",
512+
"rename",
513+
TEST_DEVICE_GUID,
514+
"--new-device-name",
515+
TEST_NEW_DEVICE_NAME,
516+
],
517+
obj=cli_state,
518+
)
519+
cli_state.sdk.devices.get_settings.assert_called_once_with(TEST_DEVICE_GUID)
520+
assert result.exit_code == 1
521+
assert (
522+
f"Error: The device with GUID '{TEST_DEVICE_GUID}' was not found."
523+
in result.output
524+
)
525+
526+
471527
def test_deactivate_deactivates_device(
472528
runner, cli_state, deactivate_device_success, get_device_by_guid_success
473529
):
@@ -983,3 +1039,52 @@ def _get(guid):
9831039
handler(guid="test")
9841040
handler(guid="not test")
9851041
assert worker_stats.increment_total_errors.call_count == 1
1042+
1043+
1044+
def test_bulk_rename_uses_expected_arguments(runner, mocker, cli_state):
1045+
bulk_processor = mocker.patch(f"{_NAMESPACE}.run_bulk_process")
1046+
with runner.isolated_filesystem():
1047+
with open("test_bulk_rename.csv", "w") as csv:
1048+
csv.writelines(["guid,name\n", "test-guid,test-name\n"])
1049+
runner.invoke(
1050+
cli, ["devices", "bulk", "rename", "test_bulk_rename.csv"], obj=cli_state,
1051+
)
1052+
assert bulk_processor.call_args[0][1] == [
1053+
{"guid": "test-guid", "name": "test-name", "renamed": "False"}
1054+
]
1055+
1056+
1057+
def test_bulk_rename_ignores_blank_lines(runner, mocker, cli_state):
1058+
bulk_processor = mocker.patch(f"{_NAMESPACE}.run_bulk_process")
1059+
with runner.isolated_filesystem():
1060+
with open("test_bulk_rename.csv", "w") as csv:
1061+
csv.writelines(["guid,name\n", "\n", "test-guid,test-name\n\n"])
1062+
runner.invoke(
1063+
cli, ["devices", "bulk", "rename", "test_bulk_rename.csv"], obj=cli_state,
1064+
)
1065+
assert bulk_processor.call_args[0][1] == [
1066+
{"guid": "test-guid", "name": "test-name", "renamed": "False"}
1067+
]
1068+
bulk_processor.assert_called_once()
1069+
1070+
1071+
def test_bulk_rename_uses_handler_that_when_encounters_error_increments_total_errors(
1072+
runner, mocker, cli_state, worker_stats
1073+
):
1074+
def _get(guid):
1075+
if guid == "test":
1076+
raise Exception("TEST")
1077+
return create_mock_response(mocker, data=TEST_DEVICE_RESPONSE)
1078+
1079+
cli_state.sdk.devices.get_settings = _get
1080+
bulk_processor = mocker.patch(f"{_NAMESPACE}.run_bulk_process")
1081+
with runner.isolated_filesystem():
1082+
with open("test_bulk_rename.csv", "w") as csv:
1083+
csv.writelines(["guid,name\n", "1,2\n"])
1084+
runner.invoke(
1085+
cli, ["devices", "bulk", "rename", "test_bulk_rename.csv"], obj=cli_state,
1086+
)
1087+
handler = bulk_processor.call_args[0][0]
1088+
handler(guid="test", name="test-name-1")
1089+
handler(guid="not test", name="test-name-2")
1090+
assert worker_stats.increment_total_errors.call_count == 1

0 commit comments

Comments
 (0)