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.
Files changed (179) hide show
  1. idrac_ctl/__init__.py +18 -0
  2. redfish_ctl/__init__.py +146 -0
  3. redfish_ctl/__main__.py +3 -0
  4. redfish_ctl/accounts/__init__.py +0 -0
  5. redfish_ctl/accounts/cmd_account_manage.py +198 -0
  6. redfish_ctl/accounts/cmd_account_sshkey.py +121 -0
  7. redfish_ctl/accounts/cmd_account_svc.py +121 -0
  8. redfish_ctl/accounts/cmd_accounts.py +84 -0
  9. redfish_ctl/accounts/cmd_privilage_registry.py +57 -0
  10. redfish_ctl/accounts/cmd_query_account.py +80 -0
  11. redfish_ctl/actions/__init__.py +0 -0
  12. redfish_ctl/actions/action_policy.py +91 -0
  13. redfish_ctl/actions/cmd_action_list.py +130 -0
  14. redfish_ctl/attribute/__init__.py +0 -0
  15. redfish_ctl/attribute/cmd_attribute.py +116 -0
  16. redfish_ctl/attribute/cmd_attribute_clear_pending.py +91 -0
  17. redfish_ctl/attribute/cmd_attribute_update.py +99 -0
  18. redfish_ctl/bios/__init__.py +0 -0
  19. redfish_ctl/bios/bios_registry.py +214 -0
  20. redfish_ctl/bios/cmd_bios.py +214 -0
  21. redfish_ctl/bios/cmd_bios_clear_pending.py +89 -0
  22. redfish_ctl/bios/cmd_bios_pending.py +89 -0
  23. redfish_ctl/bios/cmd_bios_reset_default.py +0 -0
  24. redfish_ctl/bios/cmd_bios_snapshot.py +91 -0
  25. redfish_ctl/bios/cmd_change_bios.py +380 -0
  26. redfish_ctl/bios/cmd_change_boot_order.py +202 -0
  27. redfish_ctl/boot_options/__init__.py +0 -0
  28. redfish_ctl/boot_options/cmd_boot_option_list.py +107 -0
  29. redfish_ctl/boot_options/cmd_boot_options_query.py +55 -0
  30. redfish_ctl/boot_source/__init__.py +0 -0
  31. redfish_ctl/boot_source/cmd_boot_one_shot.py +192 -0
  32. redfish_ctl/boot_source/cmd_boot_settings.py +92 -0
  33. redfish_ctl/boot_source/cmd_boot_source_get.py +110 -0
  34. redfish_ctl/boot_source/cmd_boot_source_registry.py +66 -0
  35. redfish_ctl/boot_source/cmd_boot_state.py +118 -0
  36. redfish_ctl/boot_source/cmd_clear_pending.py +83 -0
  37. redfish_ctl/boot_source/cmd_enable.py +136 -0
  38. redfish_ctl/boot_source/cmd_pending.py +95 -0
  39. redfish_ctl/boot_source/cmd_update.py +204 -0
  40. redfish_ctl/chassis/__init__.py +0 -0
  41. redfish_ctl/chassis/cmd_chasis_reset.py +125 -0
  42. redfish_ctl/chassis/cmd_chassis_query.py +93 -0
  43. redfish_ctl/chassis/cmd_update_chassis.py +137 -0
  44. redfish_ctl/cmd_boot.py +105 -0
  45. redfish_ctl/cmd_current_boot.py +84 -0
  46. redfish_ctl/cmd_exceptions.py +91 -0
  47. redfish_ctl/cmd_query.py +64 -0
  48. redfish_ctl/cmd_utils.py +136 -0
  49. redfish_ctl/cmd_wait.py +148 -0
  50. redfish_ctl/component_integrity/__init__.py +0 -0
  51. redfish_ctl/component_integrity/cmd_component_integrity.py +98 -0
  52. redfish_ctl/compute/__init__.py +0 -0
  53. redfish_ctl/compute/cmd_compute_setting.py +70 -0
  54. redfish_ctl/compute/cmd_power_state.py +194 -0
  55. redfish_ctl/compute/cmd_system_reset.py +75 -0
  56. redfish_ctl/compute/cmd_update.py +85 -0
  57. redfish_ctl/custom_argparser/__init__.py +0 -0
  58. redfish_ctl/custom_argparser/customer_argdefault.py +201 -0
  59. redfish_ctl/dell_lc/__init__.py +0 -0
  60. redfish_ctl/dell_lc/cmd_dell_lc_api.py +60 -0
  61. redfish_ctl/dell_lc/cmd_dell_lc_rs.py +59 -0
  62. redfish_ctl/dell_lc/cmd_dell_lc_services.py +67 -0
  63. redfish_ctl/delloem/__init__.py +0 -0
  64. redfish_ctl/delloem/delloem_actions.py +61 -0
  65. redfish_ctl/delloem/delloem_attach.py +102 -0
  66. redfish_ctl/delloem/delloem_attach_status.py +80 -0
  67. redfish_ctl/delloem/delloem_boot_netios.py +108 -0
  68. redfish_ctl/delloem/delloem_detach.py +67 -0
  69. redfish_ctl/delloem/delloem_disconnect.py +66 -0
  70. redfish_ctl/delloem/delloem_get_networkios.py +90 -0
  71. redfish_ctl/delloem/delloem_os_deployment.py +62 -0
  72. redfish_ctl/discover/__init__.py +28 -0
  73. redfish_ctl/discover/classifier.py +132 -0
  74. redfish_ctl/discover/cli.py +240 -0
  75. redfish_ctl/discover/scanner.py +134 -0
  76. redfish_ctl/discovery/__init__.py +0 -0
  77. redfish_ctl/discovery/cmd_bmc_scan.py +78 -0
  78. redfish_ctl/discovery/cmd_discovery.py +353 -0
  79. redfish_ctl/discovery/net_scan.py +110 -0
  80. redfish_ctl/events/__init__.py +0 -0
  81. redfish_ctl/events/cmd_event_submit_test.py +86 -0
  82. redfish_ctl/firmware/__init__.py +0 -0
  83. redfish_ctl/firmware/cmd_firmware.py +96 -0
  84. redfish_ctl/firmware/cmd_firmware_inv.py +75 -0
  85. redfish_ctl/firmware/cmd_firmware_update.py +92 -0
  86. redfish_ctl/idrac_manager.py +2272 -0
  87. redfish_ctl/idrac_shared.py +703 -0
  88. redfish_ctl/jobs/__init__.py +0 -0
  89. redfish_ctl/jobs/cmd_job_apply.py +150 -0
  90. redfish_ctl/jobs/cmd_job_del.py +72 -0
  91. redfish_ctl/jobs/cmd_job_delete_all.py +87 -0
  92. redfish_ctl/jobs/cmd_job_dell_services.py +60 -0
  93. redfish_ctl/jobs/cmd_job_get.py +76 -0
  94. redfish_ctl/jobs/cmd_job_service_update.py +36 -0
  95. redfish_ctl/jobs/cmd_job_services.py +74 -0
  96. redfish_ctl/jobs/cmd_job_watch.py +69 -0
  97. redfish_ctl/jobs/cmd_jobs.py +243 -0
  98. redfish_ctl/logs/__init__.py +0 -0
  99. redfish_ctl/logs/cmd_logs.py +117 -0
  100. redfish_ctl/manager/__init__.py +0 -0
  101. redfish_ctl/manager/cmd_console_info.py +73 -0
  102. redfish_ctl/manager/cmd_manager.py +62 -0
  103. redfish_ctl/manager/cmd_manager_reset.py +119 -0
  104. redfish_ctl/manager/cmd_manager_time.py +118 -0
  105. redfish_ctl/network/__init__.py +0 -0
  106. redfish_ctl/network/cmd_ethernet_interfaces.py +108 -0
  107. redfish_ctl/network/cmd_network_adapters.py +108 -0
  108. redfish_ctl/network/cmd_network_ports.py +95 -0
  109. redfish_ctl/oem/__init__.py +0 -0
  110. redfish_ctl/oem/cmd_oem_info.py +89 -0
  111. redfish_ctl/pci/__init__.py +0 -0
  112. redfish_ctl/pci/cmd_pci.py +148 -0
  113. redfish_ctl/ports/__init__.py +0 -0
  114. redfish_ctl/ports/cmd_nvlink_ports.py +120 -0
  115. redfish_ctl/raid/__init__.py +0 -0
  116. redfish_ctl/raid/cmd_pd_state.py +6 -0
  117. redfish_ctl/raid/cmd_raid_service.py +95 -0
  118. redfish_ctl/redfish_exceptions.py +52 -0
  119. redfish_ctl/redfish_main.py +577 -0
  120. redfish_ctl/redfish_manager.py +682 -0
  121. redfish_ctl/redfish_query.py +107 -0
  122. redfish_ctl/redfish_respond.py +214 -0
  123. redfish_ctl/redfish_respond_error.py +190 -0
  124. redfish_ctl/redfish_shared.py +111 -0
  125. redfish_ctl/redfish_task_state.py +36 -0
  126. redfish_ctl/security/__init__.py +0 -0
  127. redfish_ctl/security/cmd_secure_boot.py +104 -0
  128. redfish_ctl/sensors/__init__.py +0 -0
  129. redfish_ctl/sensors/cmd_sensors.py +105 -0
  130. redfish_ctl/serial_console/__init__.py +0 -0
  131. redfish_ctl/serial_console/cmd_serial_console.py +177 -0
  132. redfish_ctl/storage/__init__.py +0 -0
  133. redfish_ctl/storage/cmd_convert_none_raid.py +117 -0
  134. redfish_ctl/storage/cmd_convert_to_raid.py +120 -0
  135. redfish_ctl/storage/cmd_drives.py +98 -0
  136. redfish_ctl/storage/cmd_storage_controllers.py +138 -0
  137. redfish_ctl/storage/cmd_storage_get.py +115 -0
  138. redfish_ctl/storage/cmd_storage_list.py +65 -0
  139. redfish_ctl/system/__init__.py +0 -0
  140. redfish_ctl/system/cmd_system.py +129 -0
  141. redfish_ctl/system/cmd_system_config.py +142 -0
  142. redfish_ctl/system/cmd_system_import.py +169 -0
  143. redfish_ctl/system/cmd_system_one_time_boot.py +151 -0
  144. redfish_ctl/system/util.py +0 -0
  145. redfish_ctl/tasks/__init__.py +0 -0
  146. redfish_ctl/tasks/cmd_task_svc.py +59 -0
  147. redfish_ctl/tasks/cmd_task_watch.py +93 -0
  148. redfish_ctl/tasks/cmd_tasks_get.py +72 -0
  149. redfish_ctl/tasks/cmd_tasks_list.py +67 -0
  150. redfish_ctl/telemetry/__init__.py +0 -0
  151. redfish_ctl/telemetry/cmd_exporter.py +216 -0
  152. redfish_ctl/telemetry/cmd_metric_definitions.py +86 -0
  153. redfish_ctl/telemetry/cmd_metric_reports.py +97 -0
  154. redfish_ctl/telemetry/cmd_telemetry_triggers.py +75 -0
  155. redfish_ctl/telemetry/exporter.py +570 -0
  156. redfish_ctl/vendors/__init__.py +21 -0
  157. redfish_ctl/vendors/base.py +63 -0
  158. redfish_ctl/vendors/dell/__init__.py +0 -0
  159. redfish_ctl/vendors/dell/capabilities.py +40 -0
  160. redfish_ctl/vendors/hpe/__init__.py +0 -0
  161. redfish_ctl/vendors/hpe/capabilities.py +18 -0
  162. redfish_ctl/vendors/supermicro/__init__.py +0 -0
  163. redfish_ctl/vendors/supermicro/capabilities.py +30 -0
  164. redfish_ctl/version.py +1 -0
  165. redfish_ctl/virtual_media/__init__.py +0 -0
  166. redfish_ctl/virtual_media/cmd_smc_virtual_media.py +147 -0
  167. redfish_ctl/virtual_media/cmd_virtual_media_eject.py +122 -0
  168. redfish_ctl/virtual_media/cmd_virtual_media_get.py +133 -0
  169. redfish_ctl/virtual_media/cmd_virtual_media_insert.py +236 -0
  170. redfish_ctl/volumes/__init__.py +0 -0
  171. redfish_ctl/volumes/cmd_initilize.py +79 -0
  172. redfish_ctl/volumes/cmd_virtual_disk.py +118 -0
  173. redfish_ctl/volumes/cmd_volumes.py +65 -0
  174. redfish_ctl-1.1.0.dist-info/LICENSE +21 -0
  175. redfish_ctl-1.1.0.dist-info/METADATA +171 -0
  176. redfish_ctl-1.1.0.dist-info/RECORD +179 -0
  177. redfish_ctl-1.1.0.dist-info/WHEEL +5 -0
  178. redfish_ctl-1.1.0.dist-info/entry_points.txt +4 -0
  179. 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
@@ -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 *
@@ -0,0 +1,3 @@
1
+ from redfish_ctl.redfish_main import redfish_main_ctl
2
+ if __name__ == "__main__":
3
+ redfish_main_ctl()
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