codecraftai-restclient-vsphere 1.2.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 (77) hide show
  1. ccai/restclient/vsphere/__init__.py +3 -0
  2. ccai/restclient/vsphere/apigroups/__init__.py +7 -0
  3. ccai/restclient/vsphere/apigroups/basegroup.py +139 -0
  4. ccai/restclient/vsphere/apigroups/certificatemanagementgroup.py +29 -0
  5. ccai/restclient/vsphere/apigroups/clustergroup.py +29 -0
  6. ccai/restclient/vsphere/apigroups/computegroup.py +29 -0
  7. ccai/restclient/vsphere/apigroups/contentgroup.py +270 -0
  8. ccai/restclient/vsphere/apigroups/cryptomanagergroup.py +29 -0
  9. ccai/restclient/vsphere/apigroups/customizationrenderers.py +261 -0
  10. ccai/restclient/vsphere/apigroups/datacentergroup.py +124 -0
  11. ccai/restclient/vsphere/apigroups/datastoregroup.py +82 -0
  12. ccai/restclient/vsphere/apigroups/deploymentgroup.py +29 -0
  13. ccai/restclient/vsphere/apigroups/eventsgroup.py +123 -0
  14. ccai/restclient/vsphere/apigroups/eventstream.py +213 -0
  15. ccai/restclient/vsphere/apigroups/foldergroup.py +136 -0
  16. ccai/restclient/vsphere/apigroups/guestgroup.py +461 -0
  17. ccai/restclient/vsphere/apigroups/hostgroup.py +121 -0
  18. ccai/restclient/vsphere/apigroups/hvcgroup.py +29 -0
  19. ccai/restclient/vsphere/apigroups/identitygroup.py +29 -0
  20. ccai/restclient/vsphere/apigroups/inventoryerrors.py +60 -0
  21. ccai/restclient/vsphere/apigroups/inventorygroup.py +359 -0
  22. ccai/restclient/vsphere/apigroups/isogroup.py +82 -0
  23. ccai/restclient/vsphere/apigroups/lcmgroup.py +29 -0
  24. ccai/restclient/vsphere/apigroups/namespacemanagementgroup.py +29 -0
  25. ccai/restclient/vsphere/apigroups/namespacesgroup.py +29 -0
  26. ccai/restclient/vsphere/apigroups/networkgroup.py +302 -0
  27. ccai/restclient/vsphere/apigroups/ovfgroup.py +278 -0
  28. ccai/restclient/vsphere/apigroups/resourcepoolgroup.py +29 -0
  29. ccai/restclient/vsphere/apigroups/servicesgroup.py +29 -0
  30. ccai/restclient/vsphere/apigroups/storagegroup.py +29 -0
  31. ccai/restclient/vsphere/apigroups/systemconfiggroup.py +29 -0
  32. ccai/restclient/vsphere/apigroups/tagginggroup.py +256 -0
  33. ccai/restclient/vsphere/apigroups/tokenservicegroup.py +29 -0
  34. ccai/restclient/vsphere/apigroups/topologygroup.py +29 -0
  35. ccai/restclient/vsphere/apigroups/trustedinfrastructuregroup.py +29 -0
  36. ccai/restclient/vsphere/apigroups/vmgroup.py +610 -0
  37. ccai/restclient/vsphere/apigroups/waiters.py +63 -0
  38. ccai/restclient/vsphere/aspectsvsphere.py +171 -0
  39. ccai/restclient/vsphere/enums/__init__.py +3 -0
  40. ccai/restclient/vsphere/enums/guestos.py +221 -0
  41. ccai/restclient/vsphere/enums/hardwareversion.py +39 -0
  42. ccai/restclient/vsphere/enums/powerstate.py +23 -0
  43. ccai/restclient/vsphere/enums/vmpoweraction.py +27 -0
  44. ccai/restclient/vsphere/linkedmode.py +174 -0
  45. ccai/restclient/vsphere/model/__init__.py +3 -0
  46. ccai/restclient/vsphere/model/datastore.py +40 -0
  47. ccai/restclient/vsphere/model/event.py +28 -0
  48. ccai/restclient/vsphere/model/library.py +28 -0
  49. ccai/restclient/vsphere/model/linkedvcenter.py +26 -0
  50. ccai/restclient/vsphere/model/network.py +34 -0
  51. ccai/restclient/vsphere/model/ovf.py +36 -0
  52. ccai/restclient/vsphere/model/summary.py +56 -0
  53. ccai/restclient/vsphere/model/task.py +18 -0
  54. ccai/restclient/vsphere/model/vm.py +58 -0
  55. ccai/restclient/vsphere/py.typed +0 -0
  56. ccai/restclient/vsphere/specs/__init__.py +3 -0
  57. ccai/restclient/vsphere/specs/contentdeploy.py +28 -0
  58. ccai/restclient/vsphere/specs/eventfilter.py +30 -0
  59. ccai/restclient/vsphere/specs/networkconfig.py +40 -0
  60. ccai/restclient/vsphere/specs/ovfdeploy.py +30 -0
  61. ccai/restclient/vsphere/specs/vmclone.py +26 -0
  62. ccai/restclient/vsphere/specs/vmcreate.py +27 -0
  63. ccai/restclient/vsphere/specs/vmguestcustomization.py +28 -0
  64. ccai/restclient/vsphere/specs/vmhardware.py +25 -0
  65. ccai/restclient/vsphere/specs/vmplacement.py +27 -0
  66. ccai/restclient/vsphere/specs/vmpoweraction.py +22 -0
  67. ccai/restclient/vsphere/specs/vmsnapshot.py +25 -0
  68. ccai/restclient/vsphere/specs/vmstorage.py +32 -0
  69. ccai/restclient/vsphere/vsphereclient.py +565 -0
  70. ccai/restclient/vsphere/vspherefilterstate.py +102 -0
  71. ccai/restclient/vsphere/vspherehealth.py +32 -0
  72. ccai/restclient/vsphere/vspheresession.py +95 -0
  73. ccai/restclient/vsphere/vspheretaskhandle.py +471 -0
  74. codecraftai_restclient_vsphere-1.2.0.dist-info/METADATA +94 -0
  75. codecraftai_restclient_vsphere-1.2.0.dist-info/RECORD +77 -0
  76. codecraftai_restclient_vsphere-1.2.0.dist-info/WHEEL +4 -0
  77. codecraftai_restclient_vsphere-1.2.0.dist-info/licenses/LICENSE.md +204 -0
@@ -0,0 +1,3 @@
1
+ """
2
+ Package marker for ``ccai.restclient.vsphere``.
3
+ """
@@ -0,0 +1,7 @@
1
+ """
2
+ Package marker for ``ccai.restclient.vsphere.apigroups``.
3
+
4
+ Each ``*Group`` module owns its own class. This package init does
5
+ not eagerly import the groups; importing happens lazily through
6
+ :meth:`VSphereClient.__getattr__` so the package is cheap to load.
7
+ """
@@ -0,0 +1,139 @@
1
+ """
2
+ .. module:: basegroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Shared base class for every vSphere ``*Group`` API
5
+ surface. Adds filter-state-aware URL composition on top of the
6
+ generic :class:`RestApiGroup`.
7
+
8
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
9
+ """
10
+
11
+ __author__ = "Myron Walker"
12
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
13
+ __credits__ = []
14
+
15
+
16
+ import os
17
+
18
+ from typing import TYPE_CHECKING
19
+
20
+ from ccai.errors.exceptions import SemanticError
21
+ from ccai.restclient.base.restapigroup import RestApiGroup
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from ccai.restclient.vsphere.vsphereclient import VSphereClient
26
+
27
+
28
+ _API_PREFIX = "/api/"
29
+ _REST_PREFIX = "/rest/"
30
+
31
+
32
+ class BaseGroup(RestApiGroup):
33
+ """
34
+ Common ancestor for every vSphere API group.
35
+
36
+ Subclasses MUST set :data:`BASE_LEAF` to a value that begins
37
+ with either ``/api/`` or ``/rest/`` so the client knows which
38
+ root to compose against. The prefix is validated at subclass
39
+ registration time (``__init_subclass__``) so misconfiguration
40
+ fails fast.
41
+ """
42
+
43
+ BASE_LEAF: str = ""
44
+
45
+ def __init_subclass__(cls, **kwargs) -> None:
46
+ """
47
+ Validate ``BASE_LEAF`` of subclasses at class-definition
48
+ time.
49
+
50
+ :returns: ``None``
51
+
52
+ :raises: SemanticError
53
+ """
54
+ super().__init_subclass__(**kwargs)
55
+ # An empty BASE_LEAF is permitted only on the base class itself
56
+ # which is the class statically declared in this module.
57
+ if cls is BaseGroup:
58
+ return None
59
+
60
+ base = cls.BASE_LEAF
61
+ if base is None or len(base) == 0:
62
+ err_msg_lines = [
63
+ "API group must declare a non-empty BASE_LEAF.",
64
+ "CLASS: {}".format(cls.__name__),
65
+ ]
66
+ err_msg = os.linesep.join(err_msg_lines)
67
+ raise SemanticError(err_msg)
68
+
69
+ if not (base.startswith(_API_PREFIX) or base.startswith(_REST_PREFIX)):
70
+ err_msg_lines = [
71
+ "API group BASE_LEAF must begin with '/api/' or '/rest/'.",
72
+ "CLASS: {}".format(cls.__name__),
73
+ "GOT: {!r}".format(base),
74
+ ]
75
+ err_msg = os.linesep.join(err_msg_lines)
76
+ raise SemanticError(err_msg)
77
+
78
+ return None
79
+
80
+ def __init__(self, client: "VSphereClient") -> None:
81
+ """
82
+ Initialise the group with a back-reference to the client.
83
+
84
+ :param client: Owning :class:`VSphereClient` instance.
85
+
86
+ :returns: ``None``
87
+ """
88
+ super().__init__(client)
89
+ return
90
+
91
+ # ------------------------------------------------------------------
92
+ # URL composition wrapped over RestApiGroup.build_leaf
93
+ # ------------------------------------------------------------------
94
+
95
+ def compose_url(self, *, parts=None, params=None) -> str:
96
+ """
97
+ Compose an absolute URL by joining BASE_LEAF with any
98
+ additional path parts.
99
+
100
+ Query parameters are not embedded into the returned URL;
101
+ they are emitted via the `params=` argument to requests in
102
+ sorted-key order. The sort key ordering invariant is
103
+ enforced at the call site (e.g. the ``params`` dict passed
104
+ to ``session.get``) so the property-test suite need not
105
+ inspect raw URLs.
106
+
107
+ :param parts: Optional list of additional path segments
108
+ to append after BASE_LEAF.
109
+ :param params: Optional dict of query parameters (forwarded
110
+ to callers; not embedded into the URL string here).
111
+
112
+ :returns: The absolute URL composed against the client.
113
+ """
114
+ _ = params
115
+ client = self.client
116
+ if parts is None or len(parts) == 0:
117
+ leaf = self.BASE_LEAF
118
+ else:
119
+ leaf = self.build_leaf(parts=parts)
120
+ if not leaf.startswith("/"):
121
+ leaf = "/" + leaf
122
+
123
+ rtnval = client.build_url(leaf=leaf)
124
+ return rtnval
125
+
126
+ def sort_params(self, *, params) -> dict:
127
+ """
128
+ Return the params dict re-keyed alphabetically.
129
+
130
+ :param params: Raw params dict (may be ``None``).
131
+
132
+ :returns: A new dict with keys in sorted order.
133
+ """
134
+ rtnval = {}
135
+ if params is None:
136
+ return rtnval
137
+ for key in sorted(params.keys()):
138
+ rtnval[key] = params[key]
139
+ return rtnval
@@ -0,0 +1,29 @@
1
+ """
2
+ .. module:: certificatemanagementgroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Certificate management API group for :class:`VSphereClient`.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ from ccai.restclient.vsphere.apigroups.basegroup import BaseGroup
15
+
16
+
17
+ class CertificateManagementGroup(BaseGroup):
18
+ """
19
+ Certificate management operations.
20
+
21
+ The legacy ``*Ext`` class in ``codecraftai-landscaping`` did not
22
+ expose any public methods on this group; the structural
23
+ migration is complete and feature methods will be added by
24
+ downstream specs as needed (see ``vsphere-content-library-deploy``,
25
+ ``vsphere-events-stream``, ``vsphere-linked-mode``,
26
+ ``vsphere-inventory-helpers`` for the planned additions).
27
+ """
28
+
29
+ BASE_LEAF = "/api/vcenter/certificate-management"
@@ -0,0 +1,29 @@
1
+ """
2
+ .. module:: clustergroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Cluster API group for :class:`VSphereClient`.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ from ccai.restclient.vsphere.apigroups.basegroup import BaseGroup
15
+
16
+
17
+ class ClusterGroup(BaseGroup):
18
+ """
19
+ Cluster operations.
20
+
21
+ The legacy ``*Ext`` class in ``codecraftai-landscaping`` did not
22
+ expose any public methods on this group; the structural
23
+ migration is complete and feature methods will be added by
24
+ downstream specs as needed (see ``vsphere-content-library-deploy``,
25
+ ``vsphere-events-stream``, ``vsphere-linked-mode``,
26
+ ``vsphere-inventory-helpers`` for the planned additions).
27
+ """
28
+
29
+ BASE_LEAF = "/api/vcenter/cluster"
@@ -0,0 +1,29 @@
1
+ """
2
+ .. module:: computegroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Compute resources API group for :class:`VSphereClient`.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ from ccai.restclient.vsphere.apigroups.basegroup import BaseGroup
15
+
16
+
17
+ class ComputeGroup(BaseGroup):
18
+ """
19
+ Compute resources operations.
20
+
21
+ The legacy ``*Ext`` class in ``codecraftai-landscaping`` did not
22
+ expose any public methods on this group; the structural
23
+ migration is complete and feature methods will be added by
24
+ downstream specs as needed (see ``vsphere-content-library-deploy``,
25
+ ``vsphere-events-stream``, ``vsphere-linked-mode``,
26
+ ``vsphere-inventory-helpers`` for the planned additions).
27
+ """
28
+
29
+ BASE_LEAF = "/api/vcenter/compute"
@@ -0,0 +1,270 @@
1
+ """
2
+ .. module:: contentgroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Content library API group for :class:`VSphereClient`.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ import os
15
+
16
+ from http import HTTPStatus
17
+
18
+ from typing import List, Optional
19
+
20
+ from ccai.errors.raising import raise_for_http_status
21
+
22
+ from ccai.restclient.vsphere.apigroups.basegroup import BaseGroup
23
+ from ccai.restclient.vsphere.apigroups.inventoryerrors import (
24
+ VSphereAmbiguousResolutionError,
25
+ VSphereResourceNotFoundError,
26
+ )
27
+ from ccai.restclient.vsphere.aspectsvsphere import (
28
+ AspectsVSphere,
29
+ DEFAULT_VSPHERE_ASPECTS,
30
+ )
31
+ from ccai.restclient.vsphere.model.library import LibraryItemSummary
32
+ from ccai.restclient.vsphere.specs.contentdeploy import ContentDeploySpec
33
+ from ccai.restclient.vsphere.specs.vmplacement import VmPlacementSpec
34
+ from ccai.restclient.vsphere.vspheretaskhandle import VSphereTaskHandle
35
+
36
+
37
+ class ContentGroup(BaseGroup):
38
+ """
39
+ Content library operations (``/api/content/...``).
40
+
41
+ The legacy ``ContentExt`` did not expose public methods; this
42
+ class adds ``list_items``, ``find_item``, and ``deploy_ovf_item``
43
+ per the ``vsphere-content-library-deploy`` spec.
44
+ """
45
+
46
+ BASE_LEAF = "/api/content"
47
+
48
+ LIBRARY_LEAF = "/api/content/library"
49
+ LIBRARY_ITEM_LEAF = "/api/content/library/item"
50
+ LIBRARY_ITEM_DEPLOY_LEAF = "/api/vcenter/ovf/library-item"
51
+
52
+ def list_items(
53
+ self,
54
+ *,
55
+ library_id: str,
56
+ tag: Optional[str] = None,
57
+ aspects: AspectsVSphere = DEFAULT_VSPHERE_ASPECTS,
58
+ ) -> List[LibraryItemSummary]:
59
+ """
60
+ Enumerate items in a content library.
61
+
62
+ Tag filtering is performed client-side against the ``tags``
63
+ field on the returned summaries so the helper is uniform
64
+ regardless of whether the underlying endpoint supports
65
+ server-side tag filtering.
66
+
67
+ :param library_id: Content-library identifier.
68
+ :param tag: Optional tag filter.
69
+ :param aspects: Behaviour controls.
70
+
71
+ :returns: List of :class:`LibraryItemSummary`.
72
+
73
+ :raises: VSphereResourceNotFoundError
74
+ """
75
+ client = self.client
76
+ url = client.build_url(leaf=self.LIBRARY_ITEM_LEAF)
77
+ session = client.ensure_session(aspects=aspects)
78
+ params = {"library_id": library_id}
79
+ resp = session.get(url, params=params, timeout=aspects.completion_timeout)
80
+
81
+ if resp.status_code == HTTPStatus.NOT_FOUND:
82
+ err_msg_lines = [
83
+ "Content library not found.",
84
+ "LIBRARY_ID: {}".format(library_id),
85
+ ]
86
+ err_msg = os.linesep.join(err_msg_lines)
87
+ raise VSphereResourceNotFoundError(err_msg)
88
+ if resp.status_code != HTTPStatus.OK:
89
+ raise_for_http_status(
90
+ resp.status_code, resp.content, url, context="ContentGroup.list_items",
91
+ )
92
+
93
+ body = resp.json()
94
+ parsed: List[LibraryItemSummary] = []
95
+ for raw in body:
96
+ if isinstance(raw, str):
97
+ # Endpoint variant returns bare item-ids; promote each
98
+ # to a minimal summary so the surface stays uniform.
99
+ parsed.append(LibraryItemSummary(
100
+ item_id=raw, library_id=library_id, name=raw,
101
+ ))
102
+ continue
103
+ normalised = dict(raw)
104
+ if "item_id" not in normalised:
105
+ if "id" in normalised:
106
+ normalised["item_id"] = normalised.pop("id")
107
+ if "library_id" not in normalised:
108
+ normalised["library_id"] = library_id
109
+ parsed.append(LibraryItemSummary.from_dict(normalised))
110
+
111
+ if tag is None:
112
+ rtnval = parsed
113
+ return rtnval
114
+
115
+ filtered: List[LibraryItemSummary] = []
116
+ for item in parsed:
117
+ tags = getattr(item, "tags", None)
118
+ if tags is None:
119
+ continue
120
+ if tag in tags:
121
+ filtered.append(item)
122
+ rtnval = filtered
123
+ return rtnval
124
+
125
+ def find_item(
126
+ self,
127
+ *,
128
+ name: str,
129
+ tag: Optional[str] = None,
130
+ library_id: Optional[str] = None,
131
+ all_library_ids: Optional[List[str]] = None,
132
+ aspects: AspectsVSphere = DEFAULT_VSPHERE_ASPECTS,
133
+ ) -> LibraryItemSummary:
134
+ """
135
+ Resolve a friendly name (and optional tag) to a single
136
+ :class:`LibraryItemSummary`.
137
+
138
+ When ``library_id`` is omitted, ``all_library_ids`` is used
139
+ as the search scope. The latter is named-only and explicit
140
+ so callers cannot accidentally trigger a full-tenancy enum
141
+ without acknowledging it.
142
+
143
+ :param name: Item display name.
144
+ :param tag: Optional tag the item must also carry.
145
+ :param library_id: Optional single-library scope.
146
+ :param all_library_ids: Optional multi-library scope.
147
+ :param aspects: Behaviour controls.
148
+
149
+ :returns: A single :class:`LibraryItemSummary`.
150
+
151
+ :raises: VSphereResourceNotFoundError,
152
+ VSphereAmbiguousResolutionError
153
+ """
154
+ scope: List[str] = []
155
+ if library_id is not None:
156
+ scope.append(library_id)
157
+ elif all_library_ids is not None:
158
+ scope.extend(all_library_ids)
159
+ else:
160
+ err_msg_lines = [
161
+ "ContentGroup.find_item requires either library_id or",
162
+ "all_library_ids to define the search scope.",
163
+ ]
164
+ err_msg = os.linesep.join(err_msg_lines)
165
+ raise VSphereResourceNotFoundError(err_msg)
166
+
167
+ candidates: List[LibraryItemSummary] = []
168
+ for lib_id in scope:
169
+ for item in self.list_items(library_id=lib_id, tag=tag, aspects=aspects):
170
+ if getattr(item, "name", None) == name:
171
+ candidates.append(item)
172
+
173
+ if len(candidates) == 0:
174
+ err_msg_lines = [
175
+ "No content-library item matched the requested name.",
176
+ "NAME: {}".format(name),
177
+ "TAG: {}".format(tag),
178
+ "SCOPE: {}".format(scope),
179
+ ]
180
+ err_msg = os.linesep.join(err_msg_lines)
181
+ raise VSphereResourceNotFoundError(err_msg)
182
+
183
+ if len(candidates) > 1:
184
+ err_msg_lines = [
185
+ "Multiple content-library items matched the requested name.",
186
+ "NAME: {}".format(name),
187
+ "COUNT: {}".format(len(candidates)),
188
+ ]
189
+ for cand in candidates:
190
+ err_msg_lines.append(
191
+ " {}: {}".format(cand.item_id, cand.library_id),
192
+ )
193
+ err_msg = os.linesep.join(err_msg_lines)
194
+ raise VSphereAmbiguousResolutionError(err_msg, candidates=candidates)
195
+
196
+ rtnval = candidates[0]
197
+ return rtnval
198
+
199
+ def deploy_ovf_item(
200
+ self,
201
+ *,
202
+ item_id: str,
203
+ target: VmPlacementSpec,
204
+ deploy_spec: ContentDeploySpec,
205
+ aspects: AspectsVSphere = DEFAULT_VSPHERE_ASPECTS,
206
+ ) -> VSphereTaskHandle:
207
+ """
208
+ Deploy an OVF library item into the requested placement.
209
+
210
+ :param item_id: Library item identifier.
211
+ :param target: :class:`VmPlacementSpec`.
212
+ :param deploy_spec: :class:`ContentDeploySpec`.
213
+ :param aspects: Behaviour controls.
214
+
215
+ :returns: A :class:`VSphereTaskHandle` for the deployment.
216
+ """
217
+ client = self.client
218
+ leaf = "{}/{}?action=deploy".format(self.LIBRARY_ITEM_DEPLOY_LEAF, item_id)
219
+ url = client.build_url(leaf=leaf)
220
+
221
+ deployment_target = target.to_dict()
222
+ deployment_spec = deploy_spec.to_dict()
223
+ body = {
224
+ "target": deployment_target,
225
+ "deployment_spec": deployment_spec,
226
+ }
227
+
228
+ session = client.ensure_session(aspects=aspects)
229
+ resp = session.post(url, json=body, timeout=aspects.completion_timeout)
230
+ if resp.status_code not in (HTTPStatus.OK, HTTPStatus.ACCEPTED, HTTPStatus.CREATED):
231
+ raise_for_http_status(
232
+ resp.status_code, resp.content, url,
233
+ context="ContentGroup.deploy_ovf_item",
234
+ )
235
+ try:
236
+ response_body = resp.json()
237
+ except ValueError:
238
+ response_body = {}
239
+ task_id = _extract_task_id(payload=response_body)
240
+ rtnval = VSphereTaskHandle(task_id=task_id, client=client)
241
+ return rtnval
242
+
243
+
244
+ def _extract_task_id(*, payload) -> str:
245
+ """
246
+ Extract a task id from a vCenter response payload.
247
+
248
+ :param payload: Decoded JSON body (str or dict).
249
+
250
+ :returns: The task identifier string.
251
+ """
252
+ if isinstance(payload, str):
253
+ rtnval = payload
254
+ return rtnval
255
+ if isinstance(payload, dict):
256
+ if "task" in payload:
257
+ candidate = payload["task"]
258
+ if isinstance(candidate, str):
259
+ rtnval = candidate
260
+ return rtnval
261
+ if "value" in payload:
262
+ candidate = payload["value"]
263
+ if isinstance(candidate, str):
264
+ rtnval = candidate
265
+ return rtnval
266
+ if "succeeded" in payload and isinstance(payload.get("resource_id"), str):
267
+ rtnval = payload["resource_id"]
268
+ return rtnval
269
+ rtnval = "" if payload is None else str(payload)
270
+ return rtnval
@@ -0,0 +1,29 @@
1
+ """
2
+ .. module:: cryptomanagergroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Crypto manager API group for :class:`VSphereClient`.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ from ccai.restclient.vsphere.apigroups.basegroup import BaseGroup
15
+
16
+
17
+ class CryptoManagerGroup(BaseGroup):
18
+ """
19
+ Crypto manager operations.
20
+
21
+ The legacy ``*Ext`` class in ``codecraftai-landscaping`` did not
22
+ expose any public methods on this group; the structural
23
+ migration is complete and feature methods will be added by
24
+ downstream specs as needed (see ``vsphere-content-library-deploy``,
25
+ ``vsphere-events-stream``, ``vsphere-linked-mode``,
26
+ ``vsphere-inventory-helpers`` for the planned additions).
27
+ """
28
+
29
+ BASE_LEAF = "/api/vcenter/crypto-manager"