saltext.bmc 0.0.1__py2.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.
- saltext/bmc/__init__.py +17 -0
- saltext/bmc/modules/__init__.py +0 -0
- saltext/bmc/modules/bmc.py +229 -0
- saltext/bmc/modules/bmc_redfish.py +126 -0
- saltext/bmc/resources/__init__.py +0 -0
- saltext/bmc/resources/bmc/__init__.py +190 -0
- saltext/bmc/resources/bmc/modules/__init__.py +0 -0
- saltext/bmc/resources/bmc/modules/bmc_host.py +160 -0
- saltext/bmc/resources/bmc/states/__init__.py +0 -0
- saltext/bmc/resources/bmc/states/bmc_host.py +166 -0
- saltext/bmc/states/__init__.py +0 -0
- saltext/bmc/states/bmc.py +180 -0
- saltext/bmc/utils/__init__.py +0 -0
- saltext/bmc/utils/backend.py +437 -0
- saltext/bmc/utils/boot.py +47 -0
- saltext/bmc/utils/ipmi.py +473 -0
- saltext/bmc/utils/redfish.py +370 -0
- saltext/bmc/version.py +1 -0
- saltext_bmc-0.0.1.dist-info/METADATA +181 -0
- saltext_bmc-0.0.1.dist-info/RECORD +25 -0
- saltext_bmc-0.0.1.dist-info/WHEEL +6 -0
- saltext_bmc-0.0.1.dist-info/entry_points.txt +2 -0
- saltext_bmc-0.0.1.dist-info/licenses/LICENSE +201 -0
- saltext_bmc-0.0.1.dist-info/licenses/NOTICE +10 -0
- saltext_bmc-0.0.1.dist-info/top_level.txt +1 -0
saltext/bmc/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
PACKAGE_ROOT = pathlib.Path(__file__).resolve().parent
|
|
5
|
+
try:
|
|
6
|
+
from .version import __version__
|
|
7
|
+
except ImportError: # pragma: no cover
|
|
8
|
+
__version__ = "0.0.0.not-installed"
|
|
9
|
+
try:
|
|
10
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
__version__ = version(__name__)
|
|
14
|
+
except PackageNotFoundError:
|
|
15
|
+
pass
|
|
16
|
+
except ImportError:
|
|
17
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execution module for BMC (Baseboard Management Controller) hardware.
|
|
3
|
+
|
|
4
|
+
Supports two protocol backends — Redfish and IPMI (via ``pyghmi``) —
|
|
5
|
+
selected per profile by a ``backend`` key. The default is ``auto``,
|
|
6
|
+
which probes Redfish first and falls back to IPMI. See
|
|
7
|
+
:mod:`saltext.bmc.utils.backend` for backend selection details.
|
|
8
|
+
|
|
9
|
+
Configuration via Pillar::
|
|
10
|
+
|
|
11
|
+
saltext.bmc:
|
|
12
|
+
profiles:
|
|
13
|
+
bmc-host-01:
|
|
14
|
+
host: 10.0.0.5
|
|
15
|
+
username: root
|
|
16
|
+
password: calvin
|
|
17
|
+
verify_ssl: false
|
|
18
|
+
# backend defaults to 'auto'; set explicitly to skip the probe:
|
|
19
|
+
# backend: redfish # or 'ipmi'
|
|
20
|
+
legacy-host:
|
|
21
|
+
host: 10.0.0.7
|
|
22
|
+
username: ADMIN
|
|
23
|
+
password: ADMIN
|
|
24
|
+
backend: ipmi
|
|
25
|
+
port: 623 # IPMI-only
|
|
26
|
+
|
|
27
|
+
CLI examples::
|
|
28
|
+
|
|
29
|
+
salt-call --local bmc.power_status bmc-host-01
|
|
30
|
+
salt-call --local bmc.set_boot_device bmc-host-01 device=http persistent=False
|
|
31
|
+
salt-call --local bmc.power_cycle bmc-host-01
|
|
32
|
+
salt-call --local bmc.get_system_info bmc-host-01
|
|
33
|
+
salt-call --local bmc.get_sensor_data bmc-host-01
|
|
34
|
+
|
|
35
|
+
# Bypass pillar with explicit connection kwargs:
|
|
36
|
+
salt-call --local bmc.power_status host=10.0.0.5 username=root password=calvin verify_ssl=False
|
|
37
|
+
salt-call --local bmc.power_status host=10.0.0.7 username=ADMIN password=ADMIN backend=ipmi
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import logging
|
|
43
|
+
|
|
44
|
+
from saltext.bmc.utils import backend as bk
|
|
45
|
+
from saltext.bmc.utils import boot as boot_canon
|
|
46
|
+
|
|
47
|
+
log = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
__virtualname__ = "bmc"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def __virtual__():
|
|
53
|
+
return __virtualname__
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ----------------------------------------------------------------------
|
|
57
|
+
# Power operations
|
|
58
|
+
# ----------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def power_status(name: str | None = None, **conn) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Return the current power state: ``'on'``, ``'off'``, or ``'unknown'``.
|
|
64
|
+
|
|
65
|
+
:param name: BMC profile name (key under ``saltext.bmc:profiles``).
|
|
66
|
+
Omit to use the top-level ``saltext.bmc`` config.
|
|
67
|
+
|
|
68
|
+
CLI Example:
|
|
69
|
+
|
|
70
|
+
.. code-block:: bash
|
|
71
|
+
|
|
72
|
+
salt-call --local bmc.power_status bmc-host-01
|
|
73
|
+
"""
|
|
74
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
75
|
+
return backend.power_status()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def power_on(name: str | None = None, **conn) -> dict:
|
|
79
|
+
"""
|
|
80
|
+
Power on the host.
|
|
81
|
+
|
|
82
|
+
CLI Example:
|
|
83
|
+
|
|
84
|
+
.. code-block:: bash
|
|
85
|
+
|
|
86
|
+
salt-call --local bmc.power_on bmc-host-01
|
|
87
|
+
"""
|
|
88
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
89
|
+
return backend.do_reset("On")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def power_off(name: str | None = None, force: bool = False, **conn) -> dict:
|
|
93
|
+
"""
|
|
94
|
+
Power off the host.
|
|
95
|
+
|
|
96
|
+
:param force: If True, issue ``ForceOff`` (hard cut). Otherwise
|
|
97
|
+
``GracefulShutdown`` is attempted.
|
|
98
|
+
|
|
99
|
+
CLI Example:
|
|
100
|
+
|
|
101
|
+
.. code-block:: bash
|
|
102
|
+
|
|
103
|
+
salt-call --local bmc.power_off bmc-host-01
|
|
104
|
+
salt-call --local bmc.power_off bmc-host-01 force=True
|
|
105
|
+
"""
|
|
106
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
107
|
+
return backend.do_reset("ForceOff" if force else "GracefulShutdown")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def power_cycle(name: str | None = None, **conn) -> dict:
|
|
111
|
+
"""
|
|
112
|
+
Power-cycle the host (off then on).
|
|
113
|
+
|
|
114
|
+
CLI Example:
|
|
115
|
+
|
|
116
|
+
.. code-block:: bash
|
|
117
|
+
|
|
118
|
+
salt-call --local bmc.power_cycle bmc-host-01
|
|
119
|
+
"""
|
|
120
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
121
|
+
return backend.do_reset("PowerCycle")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def power_reset(name: str | None = None, force: bool = False, **conn) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
Reset the host. Prefers ``GracefulRestart``; pass ``force=True`` for ``ForceRestart``.
|
|
127
|
+
|
|
128
|
+
CLI Example:
|
|
129
|
+
|
|
130
|
+
.. code-block:: bash
|
|
131
|
+
|
|
132
|
+
salt-call --local bmc.power_reset bmc-host-01
|
|
133
|
+
"""
|
|
134
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
135
|
+
return backend.do_reset("ForceRestart" if force else "GracefulRestart")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ----------------------------------------------------------------------
|
|
139
|
+
# Boot-device operations
|
|
140
|
+
# ----------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_boot_device(name: str | None = None, **conn) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
Return the current one-shot/persistent boot override.
|
|
146
|
+
|
|
147
|
+
Returns a dict with keys ``device`` (friendly name), ``redfish_target``,
|
|
148
|
+
``native_target``, and ``enabled`` (``Once``/``Continuous``/``Disabled``).
|
|
149
|
+
On IPMI backends ``redfish_target`` is ``None``.
|
|
150
|
+
|
|
151
|
+
CLI Example:
|
|
152
|
+
|
|
153
|
+
.. code-block:: bash
|
|
154
|
+
|
|
155
|
+
salt-call --local bmc.get_boot_device bmc-host-01
|
|
156
|
+
"""
|
|
157
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
158
|
+
return backend.get_boot()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def set_boot_device(
|
|
162
|
+
name: str | None = None,
|
|
163
|
+
device: str = "none",
|
|
164
|
+
persistent: bool = False,
|
|
165
|
+
**conn,
|
|
166
|
+
) -> dict:
|
|
167
|
+
"""
|
|
168
|
+
Set the boot override.
|
|
169
|
+
|
|
170
|
+
:param str device: One of ``disk``, ``pxe``, ``http``, ``bios``, ``cd``,
|
|
171
|
+
``usb``, ``none``. ``http`` and ``usb`` are not
|
|
172
|
+
supported by the IPMI backend.
|
|
173
|
+
:param bool persistent: If True, override persists across reboots
|
|
174
|
+
(Redfish ``Continuous``). Otherwise it is
|
|
175
|
+
consumed on the next boot (``Once``).
|
|
176
|
+
|
|
177
|
+
CLI Example:
|
|
178
|
+
|
|
179
|
+
.. code-block:: bash
|
|
180
|
+
|
|
181
|
+
salt-call --local bmc.set_boot_device bmc-host-01 device=http
|
|
182
|
+
salt-call --local bmc.set_boot_device bmc-host-01 device=pxe persistent=True
|
|
183
|
+
"""
|
|
184
|
+
# Validate the device name up-front so we fail fast without opening a
|
|
185
|
+
# connection on a typo.
|
|
186
|
+
boot_canon.canonicalize(device)
|
|
187
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
188
|
+
return backend.set_boot(device=device, persistent=persistent)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ----------------------------------------------------------------------
|
|
192
|
+
# Inventory and sensors
|
|
193
|
+
# ----------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_system_info(name: str | None = None, **conn) -> dict:
|
|
197
|
+
"""
|
|
198
|
+
Return a normalised inventory dict.
|
|
199
|
+
|
|
200
|
+
Keys: ``manufacturer``, ``model``, ``serial_number``, ``uuid``, ``sku``,
|
|
201
|
+
``host_name``, ``bios_version``, ``firmware_version``, ``power_state``.
|
|
202
|
+
Unknown fields are returned as ``None``.
|
|
203
|
+
|
|
204
|
+
CLI Example:
|
|
205
|
+
|
|
206
|
+
.. code-block:: bash
|
|
207
|
+
|
|
208
|
+
salt-call --local bmc.get_system_info bmc-host-01
|
|
209
|
+
"""
|
|
210
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
211
|
+
return backend.get_system_info()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_sensor_data(name: str | None = None, **conn) -> dict:
|
|
215
|
+
"""
|
|
216
|
+
Return ``{"temperatures": [...], "fans": [...], "voltages": [...]}``.
|
|
217
|
+
|
|
218
|
+
Each sensor entry has ``name``, ``reading``, ``unit``, ``status``.
|
|
219
|
+
A backend may return an empty list for a sensor class it does not
|
|
220
|
+
expose.
|
|
221
|
+
|
|
222
|
+
CLI Example:
|
|
223
|
+
|
|
224
|
+
.. code-block:: bash
|
|
225
|
+
|
|
226
|
+
salt-call --local bmc.get_sensor_data bmc-host-01
|
|
227
|
+
"""
|
|
228
|
+
with bk.open_backend(__opts__, name=name, **conn) as backend:
|
|
229
|
+
return backend.get_sensors()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Low-level Redfish HTTP passthrough.
|
|
3
|
+
|
|
4
|
+
Bypasses the backend abstraction in :mod:`saltext.bmc.modules.bmc` to let
|
|
5
|
+
operators read or write any Redfish endpoint directly. Useful for
|
|
6
|
+
ad-hoc inspection, vendor-specific OEM extensions, and one-off
|
|
7
|
+
operations that the high-level module does not cover.
|
|
8
|
+
|
|
9
|
+
This module is Redfish-only. If the profile's ``backend`` is set to
|
|
10
|
+
``ipmi``, calls raise :class:`~saltext.bmc.utils.redfish.RedfishError`
|
|
11
|
+
immediately.
|
|
12
|
+
|
|
13
|
+
CLI examples (``path`` is the first positional arg; pass the profile via
|
|
14
|
+
``name=``)::
|
|
15
|
+
|
|
16
|
+
salt-call --local bmc_redfish.get /redfish/v1/ name=bmc-host-01
|
|
17
|
+
salt-call --local bmc_redfish.get /redfish/v1/Systems name=bmc-host-01
|
|
18
|
+
salt-call --local bmc_redfish.patch /redfish/v1/Systems/1 \\
|
|
19
|
+
body='{"AssetTag": "rack-7-slot-3"}' name=bmc-host-01
|
|
20
|
+
salt-call --local bmc_redfish.post \\
|
|
21
|
+
/redfish/v1/Systems/1/Actions/ComputerSystem.Reset \\
|
|
22
|
+
body='{"ResetType": "On"}' name=bmc-host-01
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
|
|
29
|
+
from saltext.bmc.utils import redfish as rf
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
__virtualname__ = "bmc_redfish"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __virtual__():
|
|
37
|
+
return __virtualname__
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _require_redfish(name: str | None, conn: dict) -> None:
|
|
41
|
+
"""Raise if the resolved profile uses a non-Redfish backend."""
|
|
42
|
+
cfg = rf.resolve_conn(__opts__, name=name, **conn)
|
|
43
|
+
backend = (cfg.get("backend") or "redfish").lower()
|
|
44
|
+
if backend == "ipmi":
|
|
45
|
+
raise rf.RedfishError(
|
|
46
|
+
f"Profile {name!r} uses backend={backend!r}; bmc_redfish.* requires Redfish."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _client(name: str | None, conn: dict) -> rf.RedfishClient:
|
|
51
|
+
_require_redfish(name, conn)
|
|
52
|
+
return rf.open_client(__opts__, name=name, **conn)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get(path: str, name: str | None = None, **conn) -> dict:
|
|
56
|
+
"""
|
|
57
|
+
Raw Redfish GET.
|
|
58
|
+
|
|
59
|
+
:param str path: Redfish path (must start with ``/redfish/v1/``).
|
|
60
|
+
:param str name: Profile name; falls back to top-level config.
|
|
61
|
+
|
|
62
|
+
CLI Example:
|
|
63
|
+
|
|
64
|
+
.. code-block:: bash
|
|
65
|
+
|
|
66
|
+
salt-call --local bmc_redfish.get /redfish/v1/Systems name=bmc-host-01
|
|
67
|
+
"""
|
|
68
|
+
with _client(name, conn) as client:
|
|
69
|
+
return client.get(path)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def post(path: str, name: str | None = None, body: dict | None = None, **conn) -> dict | None:
|
|
73
|
+
"""
|
|
74
|
+
Raw Redfish POST.
|
|
75
|
+
|
|
76
|
+
:param str path: Redfish action path.
|
|
77
|
+
:param dict body: Request body (optional for some Redfish actions).
|
|
78
|
+
:param str name: Profile name.
|
|
79
|
+
|
|
80
|
+
CLI Example:
|
|
81
|
+
|
|
82
|
+
.. code-block:: bash
|
|
83
|
+
|
|
84
|
+
salt-call --local bmc_redfish.post \\
|
|
85
|
+
/redfish/v1/Systems/1/Actions/ComputerSystem.Reset \\
|
|
86
|
+
body='{"ResetType": "On"}' name=bmc-host-01
|
|
87
|
+
"""
|
|
88
|
+
with _client(name, conn) as client:
|
|
89
|
+
return client.post(path, body)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def patch(path: str, body: dict, name: str | None = None, **conn) -> dict | None:
|
|
93
|
+
"""
|
|
94
|
+
Raw Redfish PATCH.
|
|
95
|
+
|
|
96
|
+
:param str path: Redfish resource path.
|
|
97
|
+
:param dict body: Request body (required).
|
|
98
|
+
:param str name: Profile name.
|
|
99
|
+
|
|
100
|
+
CLI Example:
|
|
101
|
+
|
|
102
|
+
.. code-block:: bash
|
|
103
|
+
|
|
104
|
+
salt-call --local bmc_redfish.patch /redfish/v1/Systems/1 \\
|
|
105
|
+
body='{"AssetTag": "rack-7-slot-3"}' name=bmc-host-01
|
|
106
|
+
"""
|
|
107
|
+
with _client(name, conn) as client:
|
|
108
|
+
return client.patch(path, body)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def delete(path: str, name: str | None = None, **conn) -> dict | None:
|
|
112
|
+
"""
|
|
113
|
+
Raw Redfish DELETE.
|
|
114
|
+
|
|
115
|
+
:param str path: Redfish resource path.
|
|
116
|
+
:param str name: Profile name.
|
|
117
|
+
|
|
118
|
+
CLI Example:
|
|
119
|
+
|
|
120
|
+
.. code-block:: bash
|
|
121
|
+
|
|
122
|
+
salt-call --local bmc_redfish.delete \\
|
|
123
|
+
/redfish/v1/SessionService/Sessions/3 name=bmc-host-01
|
|
124
|
+
"""
|
|
125
|
+
with _client(name, conn) as client:
|
|
126
|
+
return client.delete(path)
|
|
File without changes
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``bmc`` resource type — per-host BMC management over Redfish or IPMI.
|
|
3
|
+
|
|
4
|
+
Each ``bmc`` resource maps to one physical machine, addressed by its BMC
|
|
5
|
+
host/credentials. The resource ID is the human-friendly name used in
|
|
6
|
+
Pillar and targeting.
|
|
7
|
+
|
|
8
|
+
Configuration (via Pillar)::
|
|
9
|
+
|
|
10
|
+
resources:
|
|
11
|
+
bmc:
|
|
12
|
+
bmc-host-01:
|
|
13
|
+
host: 10.10.10.5
|
|
14
|
+
username: root
|
|
15
|
+
password: calvin
|
|
16
|
+
verify_ssl: false
|
|
17
|
+
# backend defaults to 'auto'; set explicitly to skip the probe:
|
|
18
|
+
# backend: redfish # or 'ipmi'
|
|
19
|
+
legacy-host:
|
|
20
|
+
host: 10.10.10.7
|
|
21
|
+
username: ADMIN
|
|
22
|
+
password: ADMIN
|
|
23
|
+
backend: ipmi
|
|
24
|
+
port: 623
|
|
25
|
+
|
|
26
|
+
Targeting::
|
|
27
|
+
|
|
28
|
+
salt bmc-host-01 bmc_host.power_status # by resource ID alone
|
|
29
|
+
salt -C 'T@bmc:bmc-host-01' bmc_host.power_status # by full SRN
|
|
30
|
+
salt -C 'T@bmc' bmc_host.power_status # all bmc resources
|
|
31
|
+
salt bmc-host-01 state.sls baremetal/boot_http
|
|
32
|
+
|
|
33
|
+
Per-host execution functions live in
|
|
34
|
+
:mod:`saltext.bmc.resources.bmc.modules.bmc_host`; the per-host states
|
|
35
|
+
in :mod:`saltext.bmc.resources.bmc.states.bmc_host`.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import logging
|
|
41
|
+
|
|
42
|
+
import salt.utils.resources # pylint: disable=import-error,no-name-in-module
|
|
43
|
+
|
|
44
|
+
from saltext.bmc.utils import backend as bk
|
|
45
|
+
|
|
46
|
+
log = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
CONTEXT_KEY = "bmc_resource"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def __virtual__():
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Internal helpers
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _resource_id() -> str:
|
|
61
|
+
return __resource__["id"] # pylint: disable=undefined-variable
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _ctx() -> dict:
|
|
65
|
+
return __context__.get(CONTEXT_KEY, {}) # pylint: disable=undefined-variable
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _host_cfg(resource_id: str) -> dict:
|
|
69
|
+
cfg = _ctx().get("hosts", {}).get(resource_id, {})
|
|
70
|
+
if not cfg:
|
|
71
|
+
raise ValueError(f"No bmc resource '{resource_id}' in pillar resources:bmc:")
|
|
72
|
+
return cfg
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _opts_for(resource_id: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
Build a Salt-opts shim that routes through :func:`backend.open_backend`.
|
|
78
|
+
|
|
79
|
+
``open_backend`` reads its connection config from ``opts["pillar"]["saltext.bmc"]``;
|
|
80
|
+
we synthesise that from the per-resource pillar entry so we can reuse the same
|
|
81
|
+
factory.
|
|
82
|
+
"""
|
|
83
|
+
cfg = _host_cfg(resource_id)
|
|
84
|
+
return {
|
|
85
|
+
"pillar": {
|
|
86
|
+
"saltext.bmc": {
|
|
87
|
+
"profiles": {resource_id: cfg},
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _open(resource_id: str | None = None):
|
|
94
|
+
rid = resource_id or _resource_id()
|
|
95
|
+
return bk.open_backend(_opts_for(rid), name=rid)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# Required resource interface
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def init(opts: dict) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Initialise the ``bmc`` resource type.
|
|
106
|
+
|
|
107
|
+
Reads BMC host configs from Pillar and caches them in
|
|
108
|
+
``__context__["bmc_resource"]``.
|
|
109
|
+
"""
|
|
110
|
+
type_cfg = salt.utils.resources.pillar_resources_tree(opts).get("bmc", {}) or {}
|
|
111
|
+
__context__[CONTEXT_KEY] = { # pylint: disable=undefined-variable
|
|
112
|
+
"initialized": True,
|
|
113
|
+
"hosts": type_cfg,
|
|
114
|
+
}
|
|
115
|
+
log.debug("bmc init(), managing: %s", list(type_cfg))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def initialized() -> bool:
|
|
119
|
+
"""Return ``True`` if :func:`init` has been called successfully."""
|
|
120
|
+
return _ctx().get("initialized", False)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def discover(opts: dict) -> list:
|
|
124
|
+
"""
|
|
125
|
+
Return the list of BMC resource IDs declared in Pillar.
|
|
126
|
+
|
|
127
|
+
:param dict opts: The Salt opts dict.
|
|
128
|
+
:rtype: list[str]
|
|
129
|
+
"""
|
|
130
|
+
type_cfg = salt.utils.resources.pillar_resources_tree(opts).get("bmc", {}) or {}
|
|
131
|
+
return list(type_cfg.keys())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Per-resource operations
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def power_status() -> str:
|
|
140
|
+
"""Return ``'on'`` / ``'off'`` / ``'unknown'`` for this host."""
|
|
141
|
+
with _open() as backend:
|
|
142
|
+
return backend.power_status()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def power_on() -> dict:
|
|
146
|
+
"""Power on this host."""
|
|
147
|
+
with _open() as backend:
|
|
148
|
+
return backend.do_reset("On")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def power_off(force: bool = False) -> dict:
|
|
152
|
+
"""Power off this host (``GracefulShutdown`` by default, ``ForceOff`` if force=True)."""
|
|
153
|
+
with _open() as backend:
|
|
154
|
+
return backend.do_reset("ForceOff" if force else "GracefulShutdown")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def power_cycle() -> dict:
|
|
158
|
+
"""Power-cycle this host."""
|
|
159
|
+
with _open() as backend:
|
|
160
|
+
return backend.do_reset("PowerCycle")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def power_reset(force: bool = False) -> dict:
|
|
164
|
+
"""Reset this host (``GracefulRestart`` by default, ``ForceRestart`` if force=True)."""
|
|
165
|
+
with _open() as backend:
|
|
166
|
+
return backend.do_reset("ForceRestart" if force else "GracefulRestart")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_boot_device() -> dict:
|
|
170
|
+
"""Return the current boot override (device/enabled/native_target)."""
|
|
171
|
+
with _open() as backend:
|
|
172
|
+
return backend.get_boot()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def set_boot_device(device: str = "none", persistent: bool = False) -> dict:
|
|
176
|
+
"""Set the boot override on this host."""
|
|
177
|
+
with _open() as backend:
|
|
178
|
+
return backend.set_boot(device=device, persistent=persistent)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_system_info() -> dict:
|
|
182
|
+
"""Return a normalised inventory dict for this host."""
|
|
183
|
+
with _open() as backend:
|
|
184
|
+
return backend.get_system_info()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_sensor_data() -> dict:
|
|
188
|
+
"""Return temperatures/fans/voltages dict for this host."""
|
|
189
|
+
with _open() as backend:
|
|
190
|
+
return backend.get_sensors()
|
|
File without changes
|