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.
- ccai/restclient/vsphere/__init__.py +3 -0
- ccai/restclient/vsphere/apigroups/__init__.py +7 -0
- ccai/restclient/vsphere/apigroups/basegroup.py +139 -0
- ccai/restclient/vsphere/apigroups/certificatemanagementgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/clustergroup.py +29 -0
- ccai/restclient/vsphere/apigroups/computegroup.py +29 -0
- ccai/restclient/vsphere/apigroups/contentgroup.py +270 -0
- ccai/restclient/vsphere/apigroups/cryptomanagergroup.py +29 -0
- ccai/restclient/vsphere/apigroups/customizationrenderers.py +261 -0
- ccai/restclient/vsphere/apigroups/datacentergroup.py +124 -0
- ccai/restclient/vsphere/apigroups/datastoregroup.py +82 -0
- ccai/restclient/vsphere/apigroups/deploymentgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/eventsgroup.py +123 -0
- ccai/restclient/vsphere/apigroups/eventstream.py +213 -0
- ccai/restclient/vsphere/apigroups/foldergroup.py +136 -0
- ccai/restclient/vsphere/apigroups/guestgroup.py +461 -0
- ccai/restclient/vsphere/apigroups/hostgroup.py +121 -0
- ccai/restclient/vsphere/apigroups/hvcgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/identitygroup.py +29 -0
- ccai/restclient/vsphere/apigroups/inventoryerrors.py +60 -0
- ccai/restclient/vsphere/apigroups/inventorygroup.py +359 -0
- ccai/restclient/vsphere/apigroups/isogroup.py +82 -0
- ccai/restclient/vsphere/apigroups/lcmgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/namespacemanagementgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/namespacesgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/networkgroup.py +302 -0
- ccai/restclient/vsphere/apigroups/ovfgroup.py +278 -0
- ccai/restclient/vsphere/apigroups/resourcepoolgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/servicesgroup.py +29 -0
- ccai/restclient/vsphere/apigroups/storagegroup.py +29 -0
- ccai/restclient/vsphere/apigroups/systemconfiggroup.py +29 -0
- ccai/restclient/vsphere/apigroups/tagginggroup.py +256 -0
- ccai/restclient/vsphere/apigroups/tokenservicegroup.py +29 -0
- ccai/restclient/vsphere/apigroups/topologygroup.py +29 -0
- ccai/restclient/vsphere/apigroups/trustedinfrastructuregroup.py +29 -0
- ccai/restclient/vsphere/apigroups/vmgroup.py +610 -0
- ccai/restclient/vsphere/apigroups/waiters.py +63 -0
- ccai/restclient/vsphere/aspectsvsphere.py +171 -0
- ccai/restclient/vsphere/enums/__init__.py +3 -0
- ccai/restclient/vsphere/enums/guestos.py +221 -0
- ccai/restclient/vsphere/enums/hardwareversion.py +39 -0
- ccai/restclient/vsphere/enums/powerstate.py +23 -0
- ccai/restclient/vsphere/enums/vmpoweraction.py +27 -0
- ccai/restclient/vsphere/linkedmode.py +174 -0
- ccai/restclient/vsphere/model/__init__.py +3 -0
- ccai/restclient/vsphere/model/datastore.py +40 -0
- ccai/restclient/vsphere/model/event.py +28 -0
- ccai/restclient/vsphere/model/library.py +28 -0
- ccai/restclient/vsphere/model/linkedvcenter.py +26 -0
- ccai/restclient/vsphere/model/network.py +34 -0
- ccai/restclient/vsphere/model/ovf.py +36 -0
- ccai/restclient/vsphere/model/summary.py +56 -0
- ccai/restclient/vsphere/model/task.py +18 -0
- ccai/restclient/vsphere/model/vm.py +58 -0
- ccai/restclient/vsphere/py.typed +0 -0
- ccai/restclient/vsphere/specs/__init__.py +3 -0
- ccai/restclient/vsphere/specs/contentdeploy.py +28 -0
- ccai/restclient/vsphere/specs/eventfilter.py +30 -0
- ccai/restclient/vsphere/specs/networkconfig.py +40 -0
- ccai/restclient/vsphere/specs/ovfdeploy.py +30 -0
- ccai/restclient/vsphere/specs/vmclone.py +26 -0
- ccai/restclient/vsphere/specs/vmcreate.py +27 -0
- ccai/restclient/vsphere/specs/vmguestcustomization.py +28 -0
- ccai/restclient/vsphere/specs/vmhardware.py +25 -0
- ccai/restclient/vsphere/specs/vmplacement.py +27 -0
- ccai/restclient/vsphere/specs/vmpoweraction.py +22 -0
- ccai/restclient/vsphere/specs/vmsnapshot.py +25 -0
- ccai/restclient/vsphere/specs/vmstorage.py +32 -0
- ccai/restclient/vsphere/vsphereclient.py +565 -0
- ccai/restclient/vsphere/vspherefilterstate.py +102 -0
- ccai/restclient/vsphere/vspherehealth.py +32 -0
- ccai/restclient/vsphere/vspheresession.py +95 -0
- ccai/restclient/vsphere/vspheretaskhandle.py +471 -0
- codecraftai_restclient_vsphere-1.2.0.dist-info/METADATA +94 -0
- codecraftai_restclient_vsphere-1.2.0.dist-info/RECORD +77 -0
- codecraftai_restclient_vsphere-1.2.0.dist-info/WHEEL +4 -0
- codecraftai_restclient_vsphere-1.2.0.dist-info/licenses/LICENSE.md +204 -0
|
@@ -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"
|