redfish-ctl 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idrac_ctl/__init__.py +18 -0
- redfish_ctl/__init__.py +146 -0
- redfish_ctl/__main__.py +3 -0
- redfish_ctl/accounts/__init__.py +0 -0
- redfish_ctl/accounts/cmd_account_manage.py +198 -0
- redfish_ctl/accounts/cmd_account_sshkey.py +121 -0
- redfish_ctl/accounts/cmd_account_svc.py +121 -0
- redfish_ctl/accounts/cmd_accounts.py +84 -0
- redfish_ctl/accounts/cmd_privilage_registry.py +57 -0
- redfish_ctl/accounts/cmd_query_account.py +80 -0
- redfish_ctl/actions/__init__.py +0 -0
- redfish_ctl/actions/action_policy.py +91 -0
- redfish_ctl/actions/cmd_action_list.py +130 -0
- redfish_ctl/attribute/__init__.py +0 -0
- redfish_ctl/attribute/cmd_attribute.py +116 -0
- redfish_ctl/attribute/cmd_attribute_clear_pending.py +91 -0
- redfish_ctl/attribute/cmd_attribute_update.py +99 -0
- redfish_ctl/bios/__init__.py +0 -0
- redfish_ctl/bios/bios_registry.py +214 -0
- redfish_ctl/bios/cmd_bios.py +214 -0
- redfish_ctl/bios/cmd_bios_clear_pending.py +89 -0
- redfish_ctl/bios/cmd_bios_pending.py +89 -0
- redfish_ctl/bios/cmd_bios_reset_default.py +0 -0
- redfish_ctl/bios/cmd_bios_snapshot.py +91 -0
- redfish_ctl/bios/cmd_change_bios.py +380 -0
- redfish_ctl/bios/cmd_change_boot_order.py +202 -0
- redfish_ctl/boot_options/__init__.py +0 -0
- redfish_ctl/boot_options/cmd_boot_option_list.py +107 -0
- redfish_ctl/boot_options/cmd_boot_options_query.py +55 -0
- redfish_ctl/boot_source/__init__.py +0 -0
- redfish_ctl/boot_source/cmd_boot_one_shot.py +192 -0
- redfish_ctl/boot_source/cmd_boot_settings.py +92 -0
- redfish_ctl/boot_source/cmd_boot_source_get.py +110 -0
- redfish_ctl/boot_source/cmd_boot_source_registry.py +66 -0
- redfish_ctl/boot_source/cmd_boot_state.py +118 -0
- redfish_ctl/boot_source/cmd_clear_pending.py +83 -0
- redfish_ctl/boot_source/cmd_enable.py +136 -0
- redfish_ctl/boot_source/cmd_pending.py +95 -0
- redfish_ctl/boot_source/cmd_update.py +204 -0
- redfish_ctl/chassis/__init__.py +0 -0
- redfish_ctl/chassis/cmd_chasis_reset.py +125 -0
- redfish_ctl/chassis/cmd_chassis_query.py +93 -0
- redfish_ctl/chassis/cmd_update_chassis.py +137 -0
- redfish_ctl/cmd_boot.py +105 -0
- redfish_ctl/cmd_current_boot.py +84 -0
- redfish_ctl/cmd_exceptions.py +91 -0
- redfish_ctl/cmd_query.py +64 -0
- redfish_ctl/cmd_utils.py +136 -0
- redfish_ctl/cmd_wait.py +148 -0
- redfish_ctl/component_integrity/__init__.py +0 -0
- redfish_ctl/component_integrity/cmd_component_integrity.py +98 -0
- redfish_ctl/compute/__init__.py +0 -0
- redfish_ctl/compute/cmd_compute_setting.py +70 -0
- redfish_ctl/compute/cmd_power_state.py +194 -0
- redfish_ctl/compute/cmd_system_reset.py +75 -0
- redfish_ctl/compute/cmd_update.py +85 -0
- redfish_ctl/custom_argparser/__init__.py +0 -0
- redfish_ctl/custom_argparser/customer_argdefault.py +201 -0
- redfish_ctl/dell_lc/__init__.py +0 -0
- redfish_ctl/dell_lc/cmd_dell_lc_api.py +60 -0
- redfish_ctl/dell_lc/cmd_dell_lc_rs.py +59 -0
- redfish_ctl/dell_lc/cmd_dell_lc_services.py +67 -0
- redfish_ctl/delloem/__init__.py +0 -0
- redfish_ctl/delloem/delloem_actions.py +61 -0
- redfish_ctl/delloem/delloem_attach.py +102 -0
- redfish_ctl/delloem/delloem_attach_status.py +80 -0
- redfish_ctl/delloem/delloem_boot_netios.py +108 -0
- redfish_ctl/delloem/delloem_detach.py +67 -0
- redfish_ctl/delloem/delloem_disconnect.py +66 -0
- redfish_ctl/delloem/delloem_get_networkios.py +90 -0
- redfish_ctl/delloem/delloem_os_deployment.py +62 -0
- redfish_ctl/discover/__init__.py +28 -0
- redfish_ctl/discover/classifier.py +132 -0
- redfish_ctl/discover/cli.py +240 -0
- redfish_ctl/discover/scanner.py +134 -0
- redfish_ctl/discovery/__init__.py +0 -0
- redfish_ctl/discovery/cmd_bmc_scan.py +78 -0
- redfish_ctl/discovery/cmd_discovery.py +353 -0
- redfish_ctl/discovery/net_scan.py +110 -0
- redfish_ctl/events/__init__.py +0 -0
- redfish_ctl/events/cmd_event_submit_test.py +86 -0
- redfish_ctl/firmware/__init__.py +0 -0
- redfish_ctl/firmware/cmd_firmware.py +96 -0
- redfish_ctl/firmware/cmd_firmware_inv.py +75 -0
- redfish_ctl/firmware/cmd_firmware_update.py +92 -0
- redfish_ctl/idrac_manager.py +2272 -0
- redfish_ctl/idrac_shared.py +703 -0
- redfish_ctl/jobs/__init__.py +0 -0
- redfish_ctl/jobs/cmd_job_apply.py +150 -0
- redfish_ctl/jobs/cmd_job_del.py +72 -0
- redfish_ctl/jobs/cmd_job_delete_all.py +87 -0
- redfish_ctl/jobs/cmd_job_dell_services.py +60 -0
- redfish_ctl/jobs/cmd_job_get.py +76 -0
- redfish_ctl/jobs/cmd_job_service_update.py +36 -0
- redfish_ctl/jobs/cmd_job_services.py +74 -0
- redfish_ctl/jobs/cmd_job_watch.py +69 -0
- redfish_ctl/jobs/cmd_jobs.py +243 -0
- redfish_ctl/logs/__init__.py +0 -0
- redfish_ctl/logs/cmd_logs.py +117 -0
- redfish_ctl/manager/__init__.py +0 -0
- redfish_ctl/manager/cmd_console_info.py +73 -0
- redfish_ctl/manager/cmd_manager.py +62 -0
- redfish_ctl/manager/cmd_manager_reset.py +119 -0
- redfish_ctl/manager/cmd_manager_time.py +118 -0
- redfish_ctl/network/__init__.py +0 -0
- redfish_ctl/network/cmd_ethernet_interfaces.py +108 -0
- redfish_ctl/network/cmd_network_adapters.py +108 -0
- redfish_ctl/network/cmd_network_ports.py +95 -0
- redfish_ctl/oem/__init__.py +0 -0
- redfish_ctl/oem/cmd_oem_info.py +89 -0
- redfish_ctl/pci/__init__.py +0 -0
- redfish_ctl/pci/cmd_pci.py +148 -0
- redfish_ctl/ports/__init__.py +0 -0
- redfish_ctl/ports/cmd_nvlink_ports.py +120 -0
- redfish_ctl/raid/__init__.py +0 -0
- redfish_ctl/raid/cmd_pd_state.py +6 -0
- redfish_ctl/raid/cmd_raid_service.py +95 -0
- redfish_ctl/redfish_exceptions.py +52 -0
- redfish_ctl/redfish_main.py +577 -0
- redfish_ctl/redfish_manager.py +682 -0
- redfish_ctl/redfish_query.py +107 -0
- redfish_ctl/redfish_respond.py +214 -0
- redfish_ctl/redfish_respond_error.py +190 -0
- redfish_ctl/redfish_shared.py +111 -0
- redfish_ctl/redfish_task_state.py +36 -0
- redfish_ctl/security/__init__.py +0 -0
- redfish_ctl/security/cmd_secure_boot.py +104 -0
- redfish_ctl/sensors/__init__.py +0 -0
- redfish_ctl/sensors/cmd_sensors.py +105 -0
- redfish_ctl/serial_console/__init__.py +0 -0
- redfish_ctl/serial_console/cmd_serial_console.py +177 -0
- redfish_ctl/storage/__init__.py +0 -0
- redfish_ctl/storage/cmd_convert_none_raid.py +117 -0
- redfish_ctl/storage/cmd_convert_to_raid.py +120 -0
- redfish_ctl/storage/cmd_drives.py +98 -0
- redfish_ctl/storage/cmd_storage_controllers.py +138 -0
- redfish_ctl/storage/cmd_storage_get.py +115 -0
- redfish_ctl/storage/cmd_storage_list.py +65 -0
- redfish_ctl/system/__init__.py +0 -0
- redfish_ctl/system/cmd_system.py +129 -0
- redfish_ctl/system/cmd_system_config.py +142 -0
- redfish_ctl/system/cmd_system_import.py +169 -0
- redfish_ctl/system/cmd_system_one_time_boot.py +151 -0
- redfish_ctl/system/util.py +0 -0
- redfish_ctl/tasks/__init__.py +0 -0
- redfish_ctl/tasks/cmd_task_svc.py +59 -0
- redfish_ctl/tasks/cmd_task_watch.py +93 -0
- redfish_ctl/tasks/cmd_tasks_get.py +72 -0
- redfish_ctl/tasks/cmd_tasks_list.py +67 -0
- redfish_ctl/telemetry/__init__.py +0 -0
- redfish_ctl/telemetry/cmd_exporter.py +216 -0
- redfish_ctl/telemetry/cmd_metric_definitions.py +86 -0
- redfish_ctl/telemetry/cmd_metric_reports.py +97 -0
- redfish_ctl/telemetry/cmd_telemetry_triggers.py +75 -0
- redfish_ctl/telemetry/exporter.py +570 -0
- redfish_ctl/vendors/__init__.py +21 -0
- redfish_ctl/vendors/base.py +63 -0
- redfish_ctl/vendors/dell/__init__.py +0 -0
- redfish_ctl/vendors/dell/capabilities.py +40 -0
- redfish_ctl/vendors/hpe/__init__.py +0 -0
- redfish_ctl/vendors/hpe/capabilities.py +18 -0
- redfish_ctl/vendors/supermicro/__init__.py +0 -0
- redfish_ctl/vendors/supermicro/capabilities.py +30 -0
- redfish_ctl/version.py +1 -0
- redfish_ctl/virtual_media/__init__.py +0 -0
- redfish_ctl/virtual_media/cmd_smc_virtual_media.py +147 -0
- redfish_ctl/virtual_media/cmd_virtual_media_eject.py +122 -0
- redfish_ctl/virtual_media/cmd_virtual_media_get.py +133 -0
- redfish_ctl/virtual_media/cmd_virtual_media_insert.py +236 -0
- redfish_ctl/volumes/__init__.py +0 -0
- redfish_ctl/volumes/cmd_initilize.py +79 -0
- redfish_ctl/volumes/cmd_virtual_disk.py +118 -0
- redfish_ctl/volumes/cmd_volumes.py +65 -0
- redfish_ctl-1.1.0.dist-info/LICENSE +21 -0
- redfish_ctl-1.1.0.dist-info/METADATA +171 -0
- redfish_ctl-1.1.0.dist-info/RECORD +179 -0
- redfish_ctl-1.1.0.dist-info/WHEEL +5 -0
- redfish_ctl-1.1.0.dist-info/entry_points.txt +4 -0
- redfish_ctl-1.1.0.dist-info/top_level.txt +2 -0
idrac_ctl/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""idrac_ctl — backward-compatible alias for the redfish_ctl package.
|
|
2
|
+
|
|
3
|
+
The tool was renamed ``idrac_ctl`` -> ``redfish_ctl``. This shim keeps
|
|
4
|
+
``import idrac_ctl`` and ``from idrac_ctl.<submodule> import ...`` working (resolving
|
|
5
|
+
to the same objects as ``redfish_ctl``) for existing callers. New code should import
|
|
6
|
+
``redfish_ctl``. See .internal/REDFISH_CTL_RENAME_PLAN.md.
|
|
7
|
+
"""
|
|
8
|
+
import importlib
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
# Import the real package (registers the whole command tree via __init_subclass__),
|
|
12
|
+
# then alias it and every submodule under the legacy idrac_ctl name using the SAME
|
|
13
|
+
# module objects, so identities hold across both names.
|
|
14
|
+
_real = importlib.import_module("redfish_ctl")
|
|
15
|
+
|
|
16
|
+
for _name, _mod in list(sys.modules.items()):
|
|
17
|
+
if _name == "redfish_ctl" or _name.startswith("redfish_ctl."):
|
|
18
|
+
sys.modules["idrac_ctl" + _name[len("redfish_ctl"):]] = _mod
|
redfish_ctl/__init__.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ruff: noqa: F403, I001
|
|
2
|
+
# from bios import BiosQuery
|
|
3
|
+
# from hardware import HardwareInventorQuery
|
|
4
|
+
from .redfish_shared import *
|
|
5
|
+
|
|
6
|
+
from .system.cmd_system import *
|
|
7
|
+
from .system.cmd_system_config import *
|
|
8
|
+
from .system.cmd_system_import import *
|
|
9
|
+
#
|
|
10
|
+
from .cmd_boot import *
|
|
11
|
+
from .dell_lc.cmd_dell_lc_api import *
|
|
12
|
+
from .dell_lc.cmd_dell_lc_rs import *
|
|
13
|
+
from .dell_lc.cmd_dell_lc_services import *
|
|
14
|
+
#
|
|
15
|
+
# compute
|
|
16
|
+
from .compute.cmd_power_state import *
|
|
17
|
+
from .compute.cmd_compute_setting import *
|
|
18
|
+
|
|
19
|
+
from .idrac_shared import *
|
|
20
|
+
from .raid.cmd_raid_service import *
|
|
21
|
+
#
|
|
22
|
+
# bios commands
|
|
23
|
+
from .bios.cmd_bios import *
|
|
24
|
+
from .bios.cmd_bios_clear_pending import *
|
|
25
|
+
from .bios.cmd_bios_pending import *
|
|
26
|
+
from .bios.cmd_change_boot_order import *
|
|
27
|
+
from .bios.bios_registry import *
|
|
28
|
+
from .bios.cmd_change_bios import *
|
|
29
|
+
from .bios.cmd_bios_snapshot import *
|
|
30
|
+
from .bios.cmd_bios_reset_default import *
|
|
31
|
+
#
|
|
32
|
+
from .attribute.cmd_attribute import *
|
|
33
|
+
from .attribute.cmd_attribute_clear_pending import *
|
|
34
|
+
from .attribute.cmd_attribute_update import *
|
|
35
|
+
#
|
|
36
|
+
#
|
|
37
|
+
# # jobs command
|
|
38
|
+
from .jobs.cmd_jobs import *
|
|
39
|
+
from .jobs.cmd_job_get import *
|
|
40
|
+
from .jobs.cmd_job_services import *
|
|
41
|
+
from .jobs.cmd_job_watch import *
|
|
42
|
+
from .jobs.cmd_job_del import *
|
|
43
|
+
from .jobs.cmd_job_dell_services import *
|
|
44
|
+
from .jobs.cmd_job_delete_all import *
|
|
45
|
+
from .jobs.cmd_job_apply import *
|
|
46
|
+
#
|
|
47
|
+
# # firmwares cmds
|
|
48
|
+
from .firmware.cmd_firmware import *
|
|
49
|
+
from .firmware.cmd_firmware_inv import *
|
|
50
|
+
|
|
51
|
+
from .pci.cmd_pci import *
|
|
52
|
+
|
|
53
|
+
# manager cmds
|
|
54
|
+
from .manager.cmd_manager import *
|
|
55
|
+
from .manager.cmd_manager_reset import *
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# virtual medial cmds
|
|
59
|
+
from .virtual_media.cmd_virtual_media_get import *
|
|
60
|
+
from .virtual_media.cmd_virtual_media_insert import *
|
|
61
|
+
from .virtual_media.cmd_virtual_media_eject import *
|
|
62
|
+
from .virtual_media.cmd_smc_virtual_media import *
|
|
63
|
+
from .cmd_current_boot import *
|
|
64
|
+
|
|
65
|
+
from .cmd_query import *
|
|
66
|
+
from .cmd_wait import *
|
|
67
|
+
|
|
68
|
+
# storage
|
|
69
|
+
from .storage.cmd_storage_controllers import *
|
|
70
|
+
from .storage.cmd_storage_list import *
|
|
71
|
+
from .storage.cmd_storage_get import *
|
|
72
|
+
from .storage.cmd_drives import *
|
|
73
|
+
from .storage.cmd_convert_none_raid import *
|
|
74
|
+
from .storage.cmd_convert_to_raid import *
|
|
75
|
+
|
|
76
|
+
# chassis cmd
|
|
77
|
+
from .chassis.cmd_chassis_query import *
|
|
78
|
+
from .chassis.cmd_chasis_reset import *
|
|
79
|
+
from .sensors.cmd_sensors import *
|
|
80
|
+
from .telemetry.cmd_metric_reports import *
|
|
81
|
+
from .telemetry.cmd_metric_definitions import *
|
|
82
|
+
from .telemetry.cmd_exporter import *
|
|
83
|
+
from .component_integrity.cmd_component_integrity import *
|
|
84
|
+
from .network.cmd_network_adapters import *
|
|
85
|
+
from .ports.cmd_nvlink_ports import *
|
|
86
|
+
from .actions.cmd_action_list import *
|
|
87
|
+
from .events.cmd_event_submit_test import *
|
|
88
|
+
from .compute.cmd_system_reset import *
|
|
89
|
+
from .logs.cmd_logs import *
|
|
90
|
+
from .network.cmd_ethernet_interfaces import *
|
|
91
|
+
from .security.cmd_secure_boot import *
|
|
92
|
+
from .firmware.cmd_firmware_update import *
|
|
93
|
+
from .telemetry.cmd_telemetry_triggers import *
|
|
94
|
+
from .network.cmd_network_ports import *
|
|
95
|
+
from .oem.cmd_oem_info import *
|
|
96
|
+
from .manager.cmd_console_info import *
|
|
97
|
+
from .serial_console.cmd_serial_console import *
|
|
98
|
+
from .manager.cmd_manager_time import *
|
|
99
|
+
from .chassis.cmd_chassis_query import *
|
|
100
|
+
|
|
101
|
+
# dell oem attach
|
|
102
|
+
from .delloem.delloem_attach_status import *
|
|
103
|
+
from .delloem.delloem_actions import *
|
|
104
|
+
from .delloem.delloem_attach import *
|
|
105
|
+
from .delloem.delloem_detach import *
|
|
106
|
+
from .delloem.delloem_disconnect import *
|
|
107
|
+
from .delloem.delloem_get_networkios import *
|
|
108
|
+
from .delloem.delloem_boot_netios import *
|
|
109
|
+
from .delloem.delloem_os_deployment import *
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# tasks
|
|
113
|
+
from .tasks.cmd_tasks_list import *
|
|
114
|
+
from .tasks.cmd_tasks_get import *
|
|
115
|
+
from .tasks.cmd_task_watch import *
|
|
116
|
+
|
|
117
|
+
from .volumes.cmd_initilize import *
|
|
118
|
+
from .volumes.cmd_volumes import *
|
|
119
|
+
from .volumes.cmd_virtual_disk import *
|
|
120
|
+
|
|
121
|
+
# boot options
|
|
122
|
+
from .boot_options.cmd_boot_option_list import *
|
|
123
|
+
from .boot_options.cmd_boot_options_query import *
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# boot sources
|
|
127
|
+
from .boot_source.cmd_boot_one_shot import *
|
|
128
|
+
from .boot_source.cmd_boot_settings import *
|
|
129
|
+
from .boot_source.cmd_boot_source_get import *
|
|
130
|
+
from .boot_source.cmd_clear_pending import *
|
|
131
|
+
from .boot_source.cmd_pending import *
|
|
132
|
+
from .boot_source.cmd_enable import *
|
|
133
|
+
from .boot_source.cmd_update import *
|
|
134
|
+
from .boot_source.cmd_boot_source_registry import *
|
|
135
|
+
from .boot_source.cmd_boot_state import *
|
|
136
|
+
|
|
137
|
+
# account
|
|
138
|
+
from .accounts.cmd_accounts import *
|
|
139
|
+
from .accounts.cmd_account_manage import *
|
|
140
|
+
from .accounts.cmd_account_sshkey import *
|
|
141
|
+
from .accounts.cmd_query_account import *
|
|
142
|
+
from .accounts.cmd_account_svc import *
|
|
143
|
+
from .accounts.cmd_privilage_registry import *
|
|
144
|
+
|
|
145
|
+
from .discovery.cmd_discovery import *
|
|
146
|
+
from .discovery.cmd_bmc_scan import *
|
redfish_ctl/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Create, update, and delete Redfish accounts (ManagerAccount).
|
|
2
|
+
|
|
3
|
+
redfish_ctl account-create --username test --password '***' --role ReadOnly --confirm
|
|
4
|
+
redfish_ctl account-update --username test --role Operator --confirm
|
|
5
|
+
redfish_ctl account-delete --username test --confirm
|
|
6
|
+
|
|
7
|
+
Vendor-neutral: create POSTs to the AccountService ``Accounts`` collection,
|
|
8
|
+
update PATCHes ``Accounts/<id>``, delete DELETEs it — all standard Redfish that
|
|
9
|
+
works on Dell/HPE/Supermicro/generic. Every command is **dry-run by default**
|
|
10
|
+
(it prints the intended change, password masked, and writes nothing) and only
|
|
11
|
+
mutates with ``--confirm``. Delete additionally refuses to remove the account
|
|
12
|
+
you are logged in as, so a misfire can never lock you out.
|
|
13
|
+
|
|
14
|
+
Author Mus spyroot@gmail.com
|
|
15
|
+
"""
|
|
16
|
+
from abc import abstractmethod
|
|
17
|
+
from typing import Optional, Tuple
|
|
18
|
+
|
|
19
|
+
from ..idrac_manager import IDracManager
|
|
20
|
+
from ..idrac_shared import IDRAC_API, ApiRequestType, Singleton
|
|
21
|
+
from ..redfish_manager import CommandResult
|
|
22
|
+
|
|
23
|
+
# RoleId is the standard Redfish privilege model (present on Dell/HPE/Supermicro).
|
|
24
|
+
DEFAULT_ROLE = "ReadOnly"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_create_payload(username: str, password: str, role: str) -> dict:
|
|
28
|
+
"""Build the POST body that creates a ManagerAccount."""
|
|
29
|
+
return {"UserName": username, "Password": password, "RoleId": role or DEFAULT_ROLE}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_update_payload(password: Optional[str],
|
|
33
|
+
role: Optional[str],
|
|
34
|
+
enabled: Optional[bool]) -> dict:
|
|
35
|
+
"""Build a PATCH body from only the fields the caller actually set."""
|
|
36
|
+
payload: dict = {}
|
|
37
|
+
if password is not None:
|
|
38
|
+
payload["Password"] = password
|
|
39
|
+
if role is not None:
|
|
40
|
+
payload["RoleId"] = role
|
|
41
|
+
if enabled is not None:
|
|
42
|
+
payload["Enabled"] = enabled
|
|
43
|
+
return payload
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _mask(payload: dict) -> dict:
|
|
47
|
+
"""Return a copy of a payload with the Password value masked for display."""
|
|
48
|
+
return {k: ("***" if k == "Password" else v) for k, v in payload.items()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _AccountBase(IDracManager):
|
|
52
|
+
"""Shared account resolution for the write commands."""
|
|
53
|
+
|
|
54
|
+
def _resolve_account(self, username: Optional[str],
|
|
55
|
+
account_id: Optional[str]) -> Tuple[Optional[str], dict]:
|
|
56
|
+
"""Find an account by UserName or Id; return (uri, data) or (None, {}).
|
|
57
|
+
|
|
58
|
+
Reads the Accounts collection and each member (vendor-neutral — it never
|
|
59
|
+
assumes an id scheme), so it matches on the real ``UserName``/``Id``. The
|
|
60
|
+
collection is fetched WITHOUT ``$expand`` because some services (HPE iLO 5)
|
|
61
|
+
reject an expanded Accounts query with HTTP 400; each member is fetched
|
|
62
|
+
individually instead.
|
|
63
|
+
"""
|
|
64
|
+
coll = (self.base_query(IDRAC_API.Accounts).data or {})
|
|
65
|
+
for m in coll.get("Members", []):
|
|
66
|
+
data = m if "UserName" in m else (self.base_query(m.get("@odata.id", "")).data or {})
|
|
67
|
+
uri = data.get("@odata.id") or m.get("@odata.id")
|
|
68
|
+
if account_id and str(data.get("Id")) == str(account_id):
|
|
69
|
+
return uri, data
|
|
70
|
+
if username and data.get("UserName") == username:
|
|
71
|
+
return uri, data
|
|
72
|
+
return None, {}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AccountCreate(_AccountBase,
|
|
76
|
+
scm_type=ApiRequestType.AccountCreate,
|
|
77
|
+
name='account-create',
|
|
78
|
+
metaclass=Singleton):
|
|
79
|
+
"""Create a new Redfish account (dry-run unless --confirm)."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, *args, **kwargs):
|
|
82
|
+
super(AccountCreate, self).__init__(*args, **kwargs)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def register_subcommand(cls):
|
|
87
|
+
cmd_parser = cls.base_parser()
|
|
88
|
+
cmd_parser.add_argument('--username', required=True, type=str, dest='acct_user',
|
|
89
|
+
help="new account UserName")
|
|
90
|
+
cmd_parser.add_argument('--password', required=True, type=str, dest='acct_password',
|
|
91
|
+
help="new account password (not logged)")
|
|
92
|
+
cmd_parser.add_argument('--role', required=False, type=str, dest='acct_role',
|
|
93
|
+
default=DEFAULT_ROLE, help="RoleId (default ReadOnly)")
|
|
94
|
+
cmd_parser.add_argument('--confirm', action='store_true', required=False,
|
|
95
|
+
dest='acct_confirm', default=False,
|
|
96
|
+
help="actually create (otherwise dry-run)")
|
|
97
|
+
return cmd_parser, "account-create", "create a Redfish account (guarded)"
|
|
98
|
+
|
|
99
|
+
def execute(self, acct_user=None, acct_password=None, acct_role=DEFAULT_ROLE,
|
|
100
|
+
acct_confirm=False, **kwargs) -> CommandResult:
|
|
101
|
+
payload = build_create_payload(acct_user, acct_password, acct_role)
|
|
102
|
+
if not acct_confirm:
|
|
103
|
+
return CommandResult(
|
|
104
|
+
{"dry_run": True, "action": "create", "payload": _mask(payload),
|
|
105
|
+
"hint": "re-run with --confirm to create"}, None, None, None)
|
|
106
|
+
res, status = self.base_post(IDRAC_API.Accounts, payload=payload, expected_status=201)
|
|
107
|
+
return CommandResult(
|
|
108
|
+
{"action": "create", "username": acct_user, "role": acct_role,
|
|
109
|
+
"status": str(status), "error": res.error}, None, None, res.error)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class AccountUpdate(_AccountBase,
|
|
113
|
+
scm_type=ApiRequestType.AccountUpdate,
|
|
114
|
+
name='account-update',
|
|
115
|
+
metaclass=Singleton):
|
|
116
|
+
"""Update a Redfish account's password/role/enabled (dry-run unless --confirm)."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, *args, **kwargs):
|
|
119
|
+
super(AccountUpdate, self).__init__(*args, **kwargs)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def register_subcommand(cls):
|
|
124
|
+
cmd_parser = cls.base_parser()
|
|
125
|
+
cmd_parser.add_argument('--username', required=False, type=str, dest='acct_user', default=None)
|
|
126
|
+
cmd_parser.add_argument('--id', required=False, type=str, dest='acct_id', default=None,
|
|
127
|
+
help="target by account Id instead of username")
|
|
128
|
+
cmd_parser.add_argument('--role', required=False, type=str, dest='acct_role', default=None)
|
|
129
|
+
cmd_parser.add_argument('--password', required=False, type=str, dest='acct_password', default=None)
|
|
130
|
+
cmd_parser.add_argument('--enabled', required=False, type=str, dest='acct_enabled', default=None,
|
|
131
|
+
help="true/false to enable or disable the account")
|
|
132
|
+
cmd_parser.add_argument('--confirm', action='store_true', required=False,
|
|
133
|
+
dest='acct_confirm', default=False)
|
|
134
|
+
return cmd_parser, "account-update", "update a Redfish account (guarded)"
|
|
135
|
+
|
|
136
|
+
def execute(self, acct_user=None, acct_id=None, acct_role=None, acct_password=None,
|
|
137
|
+
acct_enabled=None, acct_confirm=False, **kwargs) -> CommandResult:
|
|
138
|
+
if not acct_user and not acct_id:
|
|
139
|
+
return CommandResult(None, None, None, "provide --username or --id")
|
|
140
|
+
enabled = None
|
|
141
|
+
if acct_enabled is not None:
|
|
142
|
+
enabled = str(acct_enabled).strip().lower() in ("1", "true", "yes", "on")
|
|
143
|
+
payload = build_update_payload(acct_password, acct_role, enabled)
|
|
144
|
+
if not payload:
|
|
145
|
+
return CommandResult(None, None, None, "nothing to update (set --role/--password/--enabled)")
|
|
146
|
+
uri, data = self._resolve_account(acct_user, acct_id)
|
|
147
|
+
if not uri:
|
|
148
|
+
return CommandResult(None, None, None, f"account not found: {acct_user or acct_id}")
|
|
149
|
+
if not acct_confirm:
|
|
150
|
+
return CommandResult(
|
|
151
|
+
{"dry_run": True, "action": "update", "target": data.get("UserName"),
|
|
152
|
+
"uri": uri, "payload": _mask(payload)}, None, None, None)
|
|
153
|
+
res, status = self.base_patch(uri, payload=payload)
|
|
154
|
+
return CommandResult(
|
|
155
|
+
{"action": "update", "target": data.get("UserName"), "status": str(status),
|
|
156
|
+
"error": res.error}, None, None, res.error)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AccountDelete(_AccountBase,
|
|
160
|
+
scm_type=ApiRequestType.AccountDelete,
|
|
161
|
+
name='account-delete',
|
|
162
|
+
metaclass=Singleton):
|
|
163
|
+
"""Delete a Redfish account (refuses without --confirm; never self-deletes)."""
|
|
164
|
+
|
|
165
|
+
def __init__(self, *args, **kwargs):
|
|
166
|
+
super(AccountDelete, self).__init__(*args, **kwargs)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def register_subcommand(cls):
|
|
171
|
+
cmd_parser = cls.base_parser()
|
|
172
|
+
cmd_parser.add_argument('--username', required=False, type=str, dest='acct_user', default=None)
|
|
173
|
+
cmd_parser.add_argument('--id', required=False, type=str, dest='acct_id', default=None)
|
|
174
|
+
cmd_parser.add_argument('--confirm', action='store_true', required=False,
|
|
175
|
+
dest='acct_confirm', default=False,
|
|
176
|
+
help="required to actually delete (irreversible)")
|
|
177
|
+
return cmd_parser, "account-delete", "delete a Redfish account (guarded, irreversible)"
|
|
178
|
+
|
|
179
|
+
def execute(self, acct_user=None, acct_id=None, acct_confirm=False, **kwargs) -> CommandResult:
|
|
180
|
+
if not acct_user and not acct_id:
|
|
181
|
+
return CommandResult(None, None, None, "provide --username or --id")
|
|
182
|
+
uri, data = self._resolve_account(acct_user, acct_id)
|
|
183
|
+
if not uri:
|
|
184
|
+
return CommandResult(None, None, None, f"account not found: {acct_user or acct_id}")
|
|
185
|
+
target = data.get("UserName")
|
|
186
|
+
# Never delete the account we authenticated as — that could lock the box out.
|
|
187
|
+
if target and target == self._username:
|
|
188
|
+
return CommandResult(
|
|
189
|
+
None, None, None,
|
|
190
|
+
f"refusing to delete the logged-in account '{target}' (self-delete guard)")
|
|
191
|
+
if not acct_confirm:
|
|
192
|
+
return CommandResult(
|
|
193
|
+
{"dry_run": True, "action": "delete", "target": target, "uri": uri,
|
|
194
|
+
"hint": "re-run with --confirm to delete (irreversible)"}, None, None, None)
|
|
195
|
+
res, status = self.base_delete(uri)
|
|
196
|
+
return CommandResult(
|
|
197
|
+
{"action": "delete", "target": target, "status": str(status),
|
|
198
|
+
"error": res.error}, None, None, res.error)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Import or remove an authorized SSH public key for an account (HPE iLO OEM).
|
|
2
|
+
|
|
3
|
+
redfish_ctl account-import-sshkey --username test --key-file ~/.ssh/id_rsa.pub --confirm
|
|
4
|
+
redfish_ctl account-import-sshkey --username test --remove --confirm
|
|
5
|
+
|
|
6
|
+
HPE iLO 5 stores per-account authorized SSH keys under ``Oem.Hpe.SSHKeys`` (a list
|
|
7
|
+
of OpenSSH key strings), set by PATCHing the account resource. This is HPE-specific
|
|
8
|
+
OEM — other vendors expose SSH keys differently (Dell/Supermicro not mapped yet),
|
|
9
|
+
so the command refuses on a non-HPE account rather than sending a payload that
|
|
10
|
+
would not apply.
|
|
11
|
+
|
|
12
|
+
HPE constraints enforced here: the public key must be RSA (DSA is blocked on iLO
|
|
13
|
+
firmware >= 2.10 in Production mode) and at most 1366 bytes. Deleting the account
|
|
14
|
+
auto-purges its keys, so a leftover test account never leaves a dangling key.
|
|
15
|
+
|
|
16
|
+
NOTE — acceptance is firmware-dependent. This sends HPE's *documented* body, but a
|
|
17
|
+
live iLO 5 on fw 2.96 rejects it with ``PropertyNotWritableOrUnknown`` (its Web UI
|
|
18
|
+
uses a different, undocumented path there). The offline test asserts the request
|
|
19
|
+
shape against the stateful mock; confirm acceptance against your own firmware.
|
|
20
|
+
|
|
21
|
+
Author Mus spyroot@gmail.com
|
|
22
|
+
"""
|
|
23
|
+
import os
|
|
24
|
+
from abc import abstractmethod
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from ..idrac_shared import ApiRequestType, Singleton
|
|
28
|
+
from ..redfish_manager import CommandResult
|
|
29
|
+
from .cmd_account_manage import _AccountBase
|
|
30
|
+
|
|
31
|
+
# HPE iLO's documented authorized-SSH-key size limit.
|
|
32
|
+
MAX_SSH_KEY_BYTES = 1366
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate_ssh_key(key: str) -> Optional[str]:
|
|
36
|
+
"""Return an error string if ``key`` is not an iLO-acceptable SSH pubkey, else None."""
|
|
37
|
+
key = (key or "").strip()
|
|
38
|
+
if not key:
|
|
39
|
+
return "empty SSH key"
|
|
40
|
+
kind = key.split(None, 1)[0]
|
|
41
|
+
if not (kind.startswith("ssh-") or kind.startswith("ecdsa-")):
|
|
42
|
+
return "not an OpenSSH public key (expected 'ssh-rsa AAAA...')"
|
|
43
|
+
if kind == "ssh-dss":
|
|
44
|
+
return "DSA keys are blocked by iLO (fw >= 2.10, Production mode) — use an RSA key"
|
|
45
|
+
n = len(key.encode("utf-8"))
|
|
46
|
+
if n > MAX_SSH_KEY_BYTES:
|
|
47
|
+
return f"key is {n} bytes, exceeds the iLO limit of {MAX_SSH_KEY_BYTES}"
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AccountImportSSHKey(_AccountBase,
|
|
52
|
+
scm_type=ApiRequestType.AccountImportSSHKey,
|
|
53
|
+
name='account-import-sshkey',
|
|
54
|
+
metaclass=Singleton):
|
|
55
|
+
"""PATCH an account's Oem.Hpe.SSHKeys to authorize (or clear) an SSH key."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, *args, **kwargs):
|
|
58
|
+
super(AccountImportSSHKey, self).__init__(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def register_subcommand(cls):
|
|
63
|
+
cmd_parser = cls.base_parser()
|
|
64
|
+
cmd_parser.add_argument('--username', required=False, type=str, dest='acct_user', default=None)
|
|
65
|
+
cmd_parser.add_argument('--id', required=False, type=str, dest='acct_id', default=None)
|
|
66
|
+
cmd_parser.add_argument('--key', required=False, type=str, dest='ssh_key', default=None,
|
|
67
|
+
help="the SSH public key string (ssh-rsa AAAA...)")
|
|
68
|
+
cmd_parser.add_argument('--key-file', required=False, type=str, dest='ssh_key_file', default=None,
|
|
69
|
+
help="path to a .pub file to read the key from (e.g. ~/.ssh/id_rsa.pub)")
|
|
70
|
+
cmd_parser.add_argument('--remove', action='store_true', required=False, dest='ssh_remove',
|
|
71
|
+
default=False, help="clear the account's authorized SSH keys")
|
|
72
|
+
cmd_parser.add_argument('--confirm', action='store_true', required=False,
|
|
73
|
+
dest='acct_confirm', default=False,
|
|
74
|
+
help="actually write (otherwise dry-run)")
|
|
75
|
+
return cmd_parser, "account-import-sshkey", "authorize/clear an account SSH key (HPE OEM, guarded)"
|
|
76
|
+
|
|
77
|
+
def execute(self, acct_user=None, acct_id=None, ssh_key=None, ssh_key_file=None,
|
|
78
|
+
ssh_remove=False, acct_confirm=False, **kwargs) -> CommandResult:
|
|
79
|
+
if not acct_user and not acct_id:
|
|
80
|
+
return CommandResult(None, None, None, "provide --username or --id")
|
|
81
|
+
|
|
82
|
+
if ssh_remove:
|
|
83
|
+
keys = []
|
|
84
|
+
key = None
|
|
85
|
+
else:
|
|
86
|
+
key = ssh_key
|
|
87
|
+
if key is None and ssh_key_file:
|
|
88
|
+
path = os.path.expanduser(ssh_key_file)
|
|
89
|
+
try:
|
|
90
|
+
with open(path, "r") as fh:
|
|
91
|
+
key = fh.read()
|
|
92
|
+
except OSError as e:
|
|
93
|
+
return CommandResult(None, None, None, f"cannot read key file {path}: {e}")
|
|
94
|
+
if not key:
|
|
95
|
+
return CommandResult(None, None, None, "provide --key, --key-file, or --remove")
|
|
96
|
+
err = validate_ssh_key(key)
|
|
97
|
+
if err:
|
|
98
|
+
return CommandResult(None, None, None, f"invalid SSH key: {err}")
|
|
99
|
+
keys = [key.strip()]
|
|
100
|
+
|
|
101
|
+
uri, data = self._resolve_account(acct_user, acct_id)
|
|
102
|
+
if not uri:
|
|
103
|
+
return CommandResult(None, None, None, f"account not found: {acct_user or acct_id}")
|
|
104
|
+
if "Hpe" not in (data.get("Oem") or {}):
|
|
105
|
+
vendor = list((data.get("Oem") or {}).keys()) or ["unknown"]
|
|
106
|
+
return CommandResult(
|
|
107
|
+
None, None, None,
|
|
108
|
+
f"SSH key import is HPE-only for now (Oem.Hpe.SSHKeys); this account's OEM is {vendor}")
|
|
109
|
+
|
|
110
|
+
payload = {"Oem": {"Hpe": {"SSHKeys": keys}}}
|
|
111
|
+
action = "remove-ssh-key" if ssh_remove else "import-ssh-key"
|
|
112
|
+
if not acct_confirm:
|
|
113
|
+
preview = None if ssh_remove else (keys[0][:40] + "...")
|
|
114
|
+
return CommandResult(
|
|
115
|
+
{"dry_run": True, "action": action, "target": data.get("UserName"),
|
|
116
|
+
"uri": uri, "key_preview": preview,
|
|
117
|
+
"hint": "re-run with --confirm to write"}, None, None, None)
|
|
118
|
+
res, status = self.base_patch(uri, payload=payload)
|
|
119
|
+
return CommandResult(
|
|
120
|
+
{"action": action, "target": data.get("UserName"), "status": str(status),
|
|
121
|
+
"error": res.error}, None, None, res.error)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""iDRAC query account services
|
|
2
|
+
|
|
3
|
+
Command query account services.
|
|
4
|
+
|
|
5
|
+
Author Mus spyroot@gmail.com
|
|
6
|
+
"""
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from ..idrac_manager import IDracManager
|
|
11
|
+
from ..idrac_shared import IDRAC_API
|
|
12
|
+
from ..idrac_shared import Singleton, ApiRequestType
|
|
13
|
+
from ..redfish_manager import CommandResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class QueryAccountService(IDracManager,
|
|
17
|
+
scm_type=ApiRequestType.QueryAccountService,
|
|
18
|
+
name='query_account_svc',
|
|
19
|
+
metaclass=Singleton):
|
|
20
|
+
"""A command query iDRAC account services based on a resource path.
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, *args, **kwargs):
|
|
23
|
+
super(QueryAccountService, self).__init__(*args, **kwargs)
|
|
24
|
+
# maps from cli choice to a key in respond
|
|
25
|
+
self._json_filter_dict = {
|
|
26
|
+
'account-type': 'SupportedAccountTypes',
|
|
27
|
+
'oem_account-type': 'SupportedOEMAccountTypes',
|
|
28
|
+
'accounts': 'Accounts',
|
|
29
|
+
'ldap': 'LDAP',
|
|
30
|
+
'ad': 'ActiveDirectory',
|
|
31
|
+
'local': 'LocalAccountAuth',
|
|
32
|
+
'lockout-reset-after': 'AccountLockoutCounterResetAfter',
|
|
33
|
+
'lockout-duration': 'AccountLockoutDuration',
|
|
34
|
+
'lockout-threshold': 'AccountLockoutThreshold',
|
|
35
|
+
'status': 'Status',
|
|
36
|
+
'roles': 'Roles'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def register_subcommand(cls):
|
|
42
|
+
"""Register command and all optional flags.
|
|
43
|
+
:param cls:
|
|
44
|
+
:return:
|
|
45
|
+
"""
|
|
46
|
+
cmd_parser = cls.base_parser()
|
|
47
|
+
help_text = "command query account service."
|
|
48
|
+
cmd_parser.add_argument('--filter', choices=['account-type',
|
|
49
|
+
'oem_account-type',
|
|
50
|
+
'accounts',
|
|
51
|
+
'ldap',
|
|
52
|
+
'ad',
|
|
53
|
+
'local',
|
|
54
|
+
'lockout-reset-after',
|
|
55
|
+
'lockout-duration',
|
|
56
|
+
'lockout-threshold',
|
|
57
|
+
'status',
|
|
58
|
+
'roles'],
|
|
59
|
+
|
|
60
|
+
default=None, required=False, dest="schema_filter",
|
|
61
|
+
help="filter show account types, ldap, roles etc")
|
|
62
|
+
|
|
63
|
+
cmd_parser.add_argument(
|
|
64
|
+
'--account_types', action='store_true',
|
|
65
|
+
required=False, dest="account_types",
|
|
66
|
+
default=False, help="return account types.")
|
|
67
|
+
|
|
68
|
+
cmd_parser.add_argument(
|
|
69
|
+
'--oem_account_types', action='store_true',
|
|
70
|
+
required=False, dest="oem_account_types",
|
|
71
|
+
default=False, help="return oem account types.")
|
|
72
|
+
|
|
73
|
+
return cmd_parser, "account-svc", help_text
|
|
74
|
+
|
|
75
|
+
def execute(self,
|
|
76
|
+
schema_filter: Optional[str] = None,
|
|
77
|
+
filename: Optional[str] = None,
|
|
78
|
+
data_type: Optional[str] = "json",
|
|
79
|
+
verbose: Optional[bool] = False,
|
|
80
|
+
do_async: Optional[bool] = False,
|
|
81
|
+
do_expanded: Optional[bool] = False,
|
|
82
|
+
account_types: Optional[bool] = False,
|
|
83
|
+
oem_account_types: Optional[bool] = False,
|
|
84
|
+
**kwargs) -> CommandResult:
|
|
85
|
+
"""Executes query command
|
|
86
|
+
python redfish_ctl.py query
|
|
87
|
+
|
|
88
|
+
:param oem_account_types: return list of oem account types
|
|
89
|
+
:param account_types: return list account types
|
|
90
|
+
:param schema_filter: filter account services based on schema filter key.
|
|
91
|
+
:param do_async: note async will subscribe to an event loop.
|
|
92
|
+
:param do_expanded: will do expand query
|
|
93
|
+
:param filename: if filename indicate call will save a bios setting to a file.
|
|
94
|
+
:param verbose: enables verbose output
|
|
95
|
+
:param data_type: json or xml
|
|
96
|
+
:return: CommandResult and if filename provide will save to a file.
|
|
97
|
+
"""
|
|
98
|
+
json_filter = ""
|
|
99
|
+
is_expanded = False
|
|
100
|
+
|
|
101
|
+
if schema_filter is not None and len(schema_filter) > 0 or do_expanded:
|
|
102
|
+
is_expanded = True
|
|
103
|
+
if schema_filter in self._json_filter_dict:
|
|
104
|
+
json_filter = self._json_filter_dict[schema_filter]
|
|
105
|
+
|
|
106
|
+
cmd_result = self.base_query(IDRAC_API.AccountServiceQuery,
|
|
107
|
+
filename=filename,
|
|
108
|
+
do_async=do_async,
|
|
109
|
+
do_expanded=is_expanded)
|
|
110
|
+
|
|
111
|
+
if account_types and 'SupportedAccountTypes' in cmd_result.data:
|
|
112
|
+
return CommandResult(cmd_result.data['SupportedAccountTypes'], None, None, None)
|
|
113
|
+
|
|
114
|
+
if oem_account_types and 'SupportedOEMAccountTypes' in cmd_result.data:
|
|
115
|
+
return CommandResult(cmd_result.data['SupportedOEMAccountTypes'], None, None, None)
|
|
116
|
+
|
|
117
|
+
if cmd_result.error is None:
|
|
118
|
+
if len(json_filter) > 0 and json_filter in cmd_result.data:
|
|
119
|
+
cmd_result = CommandResult(cmd_result.data[json_filter], None, None, None)
|
|
120
|
+
|
|
121
|
+
return cmd_result
|