codecraftai-restclient-nutanix 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 (37) hide show
  1. ccai/restclient/nutanix/__init__.py +12 -0
  2. ccai/restclient/nutanix/apigroups/__init__.py +0 -0
  3. ccai/restclient/nutanix/apigroups/clustersgroup.py +125 -0
  4. ccai/restclient/nutanix/apigroups/groupsupport.py +161 -0
  5. ccai/restclient/nutanix/apigroups/imagesgroup.py +117 -0
  6. ccai/restclient/nutanix/apigroups/networkgroups.py +307 -0
  7. ccai/restclient/nutanix/apigroups/projectsgroup.py +160 -0
  8. ccai/restclient/nutanix/apigroups/snapshotsgroup.py +128 -0
  9. ccai/restclient/nutanix/apigroups/tasksgroup.py +63 -0
  10. ccai/restclient/nutanix/apigroups/vmsgroup.py +445 -0
  11. ccai/restclient/nutanix/apigroups/volumegroupsgroup.py +121 -0
  12. ccai/restclient/nutanix/aspectsnutanix.py +106 -0
  13. ccai/restclient/nutanix/exceptions/__init__.py +0 -0
  14. ccai/restclient/nutanix/exceptions/nutanixexceptions.py +26 -0
  15. ccai/restclient/nutanix/model/__init__.py +0 -0
  16. ccai/restclient/nutanix/model/nutanixcategory.py +66 -0
  17. ccai/restclient/nutanix/model/nutanixcluster.py +65 -0
  18. ccai/restclient/nutanix/model/nutanixhost.py +68 -0
  19. ccai/restclient/nutanix/model/nutaniximage.py +80 -0
  20. ccai/restclient/nutanix/model/nutanixproject.py +68 -0
  21. ccai/restclient/nutanix/model/nutanixsecuritypolicy.py +68 -0
  22. ccai/restclient/nutanix/model/nutanixsnapshot.py +80 -0
  23. ccai/restclient/nutanix/model/nutanixstoragecontainer.py +76 -0
  24. ccai/restclient/nutanix/model/nutanixsubnet.py +94 -0
  25. ccai/restclient/nutanix/model/nutanixtaskhandle.py +198 -0
  26. ccai/restclient/nutanix/model/nutanixtaskstatus.py +136 -0
  27. ccai/restclient/nutanix/model/nutanixvm.py +120 -0
  28. ccai/restclient/nutanix/model/nutanixvolumegroup.py +71 -0
  29. ccai/restclient/nutanix/model/nutanixvpc.py +68 -0
  30. ccai/restclient/nutanix/nutanixclient.py +612 -0
  31. ccai/restclient/nutanix/py.typed +0 -0
  32. ccai/restclient/nutanix/specs/__init__.py +0 -0
  33. ccai/restclient/nutanix/specs/nutanixspecs.py +350 -0
  34. codecraftai_restclient_nutanix-1.2.0.dist-info/METADATA +84 -0
  35. codecraftai_restclient_nutanix-1.2.0.dist-info/RECORD +37 -0
  36. codecraftai_restclient_nutanix-1.2.0.dist-info/WHEEL +4 -0
  37. codecraftai_restclient_nutanix-1.2.0.dist-info/licenses/LICENSE.md +204 -0
@@ -0,0 +1,12 @@
1
+ """
2
+ .. module:: nutanix
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: The ``ccai.restclient.nutanix`` package — the typed,
5
+ aspect-driven Nutanix Prism REST client.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2026, Code Craft AI LLC"
12
+ __credits__ = []
File without changes
@@ -0,0 +1,125 @@
1
+ """
2
+ .. module:: clustersgroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Module that contains the :class:`ClustersGroup` and
5
+ :class:`HostsGroup` api groups exposing the Prism Central
6
+ cluster and host enumeration endpoints.
7
+
8
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
9
+ """
10
+
11
+ __author__ = "Myron Walker"
12
+ __copyright__ = "Copyright 2026, Code Craft AI LLC"
13
+ __credits__ = []
14
+
15
+
16
+ from typing import Any, Dict, List
17
+
18
+ from ccai.restclient.base.restapigroup import RestApiGroup
19
+
20
+ from ccai.restclient.nutanix.aspectsnutanix import (
21
+ AspectsNutanix, DEFAULT_NUTANIX_ASPECTS
22
+ )
23
+ from ccai.restclient.nutanix.apigroups.groupsupport import iter_list_entities
24
+ from ccai.restclient.nutanix.model.nutanixcluster import NutanixCluster
25
+ from ccai.restclient.nutanix.model.nutanixhost import NutanixHost
26
+ from ccai.restclient.nutanix.model.nutanixstoragecontainer import (
27
+ NutanixStorageContainer
28
+ )
29
+
30
+
31
+ class ClustersGroup(RestApiGroup):
32
+ """
33
+ :class:`ClustersGroup` exposes the Prism Central cluster and
34
+ storage-container enumeration endpoints.
35
+ """
36
+
37
+ def list(self, *,
38
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
39
+ ) -> List[NutanixCluster]:
40
+ """
41
+ Enumerate every cluster managed by Prism Central.
42
+
43
+ :param aspects: Behaviour control for the call.
44
+
45
+ :returns: A list of :class:`NutanixCluster` records.
46
+ """
47
+ client = self.client
48
+
49
+ def _fetch_page(offset: int, length: int) -> Dict[str, Any]:
50
+ page = client.post_list(entity="clusters", offset=offset,
51
+ length=length, action="list clusters",
52
+ aspects=aspects)
53
+ return page
54
+
55
+ entities = iter_list_entities(fetch_page=_fetch_page, aspects=aspects)
56
+ rtnval: List[NutanixCluster] = []
57
+ for entity in entities:
58
+ rtnval.append(NutanixCluster.from_dict(entity))
59
+ return rtnval
60
+
61
+ def list_storage_containers(self, *,
62
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
63
+ ) -> List[NutanixStorageContainer]:
64
+ """
65
+ Enumerate every storage container known to Prism Central.
66
+
67
+ :param aspects: Behaviour control for the call.
68
+
69
+ :returns: A list of :class:`NutanixStorageContainer` records.
70
+ """
71
+ client = self.client
72
+
73
+ def _fetch_page(offset: int, length: int) -> Dict[str, Any]:
74
+ page = client.post_list(entity="storage_containers",
75
+ offset=offset, length=length,
76
+ action="list storage containers",
77
+ aspects=aspects)
78
+ return page
79
+
80
+ entities = iter_list_entities(fetch_page=_fetch_page, aspects=aspects)
81
+ rtnval: List[NutanixStorageContainer] = []
82
+ for entity in entities:
83
+ rtnval.append(NutanixStorageContainer.from_dict(entity))
84
+ return rtnval
85
+
86
+
87
+ class HostsGroup(RestApiGroup):
88
+ """
89
+ :class:`HostsGroup` exposes the Prism Central host enumeration
90
+ endpoint.
91
+ """
92
+
93
+ def list(self, *, cluster_uuid: str,
94
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
95
+ ) -> List[NutanixHost]:
96
+ """
97
+ Enumerate the hosts belonging to a cluster.
98
+
99
+ :param cluster_uuid: The cluster uuid to filter hosts by.
100
+ :param aspects: Behaviour control for the call.
101
+
102
+ :returns: A list of :class:`NutanixHost` records.
103
+ """
104
+ client = self.client
105
+
106
+ def _fetch_page(offset: int, length: int) -> Dict[str, Any]:
107
+ page = client.post_list(entity="hosts", offset=offset,
108
+ length=length, action="list hosts",
109
+ aspects=aspects)
110
+ return page
111
+
112
+ entities = iter_list_entities(fetch_page=_fetch_page, aspects=aspects)
113
+ rtnval: List[NutanixHost] = []
114
+ for entity in entities:
115
+ host = NutanixHost.from_dict(entity)
116
+ status = entity.get("status", {})
117
+ cluster_ref = status.get("cluster_reference", {})
118
+ host_cluster = cluster_ref.get("uuid", None)
119
+ if cluster_uuid is None or len(cluster_uuid) == 0:
120
+ rtnval.append(host)
121
+ elif host_cluster == cluster_uuid:
122
+ rtnval.append(host)
123
+ elif host_cluster is None:
124
+ rtnval.append(host)
125
+ return rtnval
@@ -0,0 +1,161 @@
1
+ """
2
+ .. module:: groupsupport
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Module that contains shared helpers used by the Nutanix
5
+ Prism Central api-group classes.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2026, Code Craft AI LLC"
12
+ __credits__ = []
13
+
14
+
15
+ import os
16
+
17
+ from typing import Any, Callable, Dict, List
18
+ from urllib.parse import quote
19
+
20
+ from requests import Response
21
+
22
+ from ccai.errors.exceptions import SemanticError
23
+
24
+ from ccai.restclient.nutanix.aspectsnutanix import AspectsNutanix
25
+ from ccai.restclient.nutanix.exceptions.nutanixexceptions import (
26
+ NutanixAuthenticationError
27
+ )
28
+
29
+
30
+ def encode_segment(*, segment: str) -> str:
31
+ """
32
+ Percent-encode a single URL path segment.
33
+
34
+ Empty or ``None`` identifiers are rejected — a missing identifier
35
+ is an API-misuse condition, not a runtime error.
36
+
37
+ :param segment: The raw path segment.
38
+
39
+ :returns: The percent-encoded segment.
40
+
41
+ :raises: :class:`SemanticError` when the segment is empty/None.
42
+ """
43
+ if segment is None:
44
+ raise SemanticError("URL path segment must not be None.")
45
+ if len(segment) == 0:
46
+ raise SemanticError("URL path segment must not be empty.")
47
+
48
+ rtnval = quote(segment, safe="")
49
+ return rtnval
50
+
51
+
52
+ def compose_leaf(*, base: str, parts: List[str]) -> str:
53
+ """
54
+ Compose a Prism Central API leaf from a base prefix and per-segment
55
+ identifiers. Each identifier is percent-encoded independently.
56
+
57
+ :param base: The base leaf (e.g. ``/api/nutanix/v3/vms``).
58
+ :param parts: Trailing path identifiers (uuids, action names).
59
+
60
+ :returns: The composed leaf path beginning with ``/``.
61
+
62
+ :raises: :class:`SemanticError` when ``parts`` carries an empty
63
+ identifier.
64
+ """
65
+ if base is None or len(base) == 0:
66
+ raise SemanticError("compose_leaf requires a non-empty base.")
67
+
68
+ leaf = base
69
+ if parts is not None:
70
+ for part in parts:
71
+ encoded = encode_segment(segment=part)
72
+ leaf = leaf + "/" + encoded
73
+
74
+ rtnval = leaf
75
+ return rtnval
76
+
77
+
78
+ def raise_for_status(*, response: Response, action: str) -> None:
79
+ """
80
+ Raise a descriptive error for a non-2xx Prism Central response.
81
+
82
+ HTTP 401 / 403 are reclassified into
83
+ :class:`NutanixAuthenticationError`; every other non-2xx becomes a
84
+ :class:`SemanticError`.
85
+
86
+ :param response: The :class:`Response` to inspect.
87
+ :param action: A short description of the attempted action.
88
+
89
+ :returns: ``None``.
90
+
91
+ :raises: :class:`NutanixAuthenticationError`,
92
+ :class:`SemanticError`.
93
+ """
94
+ status = response.status_code
95
+ if 200 <= status < 300:
96
+ return
97
+
98
+ if status in (401, 403):
99
+ err_msg_lines = ["Nutanix Prism Central rejected the credential."]
100
+ err_msg_lines.append("ACTION: {}".format(action))
101
+ err_msg_lines.append("STATUS: {}".format(status))
102
+ err_msg_lines.append("RESPONSE:")
103
+ err_msg_lines.append(response.text)
104
+ err_msg = os.linesep.join(err_msg_lines)
105
+ raise NutanixAuthenticationError(err_msg)
106
+
107
+ err_msg_lines = ["Nutanix Prism Central API call failed: {}.".format(
108
+ action
109
+ )]
110
+ err_msg_lines.append("STATUS: {}".format(status))
111
+ err_msg_lines.append("RESPONSE:")
112
+ err_msg_lines.append(response.text)
113
+ err_msg = os.linesep.join(err_msg_lines)
114
+ raise SemanticError(err_msg)
115
+
116
+
117
+ def iter_list_entities(*, fetch_page: Callable[[int, int], Dict[str, Any]],
118
+ aspects: AspectsNutanix) -> List[Dict[str, Any]]:
119
+ """
120
+ Walk a Prism Central ``*/list`` endpoint, following the offset
121
+ cursor until every entity has been collected.
122
+
123
+ Prism v3 ``*/list`` endpoints accept an offset/length body and
124
+ return ``{"metadata": {"total_matches": N, "offset": O, ...},
125
+ "entities": [...]}``. This helper pages by offset using the page
126
+ length from ``aspects`` until the collected count reaches
127
+ ``total_matches`` (or a page returns no entities).
128
+
129
+ :param fetch_page: Callable accepting ``(offset, length)`` and
130
+ returning the parsed page dict.
131
+ :param aspects: Behaviour control supplying the page length.
132
+
133
+ :returns: A list of raw entity dicts in traversal order.
134
+ """
135
+ page_length = aspects.page_length
136
+ if page_length <= 0:
137
+ raise SemanticError("AspectsNutanix.page_length must be > 0.")
138
+
139
+ collected: List[Dict[str, Any]] = []
140
+ offset = 0
141
+ while True:
142
+ page = fetch_page(offset, page_length)
143
+
144
+ entities = page.get("entities", None)
145
+ if entities is None:
146
+ entities = []
147
+ for entity in entities:
148
+ collected.append(entity)
149
+
150
+ page_metadata = page.get("metadata", {})
151
+ total_matches = page_metadata.get("total_matches", None)
152
+
153
+ if len(entities) == 0:
154
+ break
155
+ if total_matches is not None and len(collected) >= total_matches:
156
+ break
157
+
158
+ offset = offset + page_length
159
+
160
+ rtnval = collected
161
+ return rtnval
@@ -0,0 +1,117 @@
1
+ """
2
+ .. module:: imagesgroup
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Module that contains the :class:`ImagesGroup` api group
5
+ for the Prism Central image catalog.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2026, Code Craft AI LLC"
12
+ __credits__ = []
13
+
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from ccai.restclient.base.restapigroup import RestApiGroup
18
+
19
+ from ccai.restclient.nutanix.aspectsnutanix import (
20
+ AspectsNutanix, DEFAULT_NUTANIX_ASPECTS
21
+ )
22
+ from ccai.restclient.nutanix.apigroups.groupsupport import iter_list_entities
23
+ from ccai.restclient.nutanix.model.nutaniximage import NutanixImage
24
+ from ccai.restclient.nutanix.model.nutanixtaskhandle import NutanixTaskHandle
25
+ from ccai.restclient.nutanix.specs.nutanixspecs import ImageSpec
26
+
27
+
28
+ class ImagesGroup(RestApiGroup):
29
+ """
30
+ :class:`ImagesGroup` exposes the Prism Central image-catalog
31
+ endpoints — upload, list (paged), and idempotent delete.
32
+ """
33
+
34
+ def list(self, *,
35
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
36
+ ) -> List[NutanixImage]:
37
+ """
38
+ Enumerate every image in the Prism Central catalog.
39
+
40
+ :param aspects: Behaviour control for the call.
41
+
42
+ :returns: A list of :class:`NutanixImage` records.
43
+ """
44
+ client = self.client
45
+
46
+ def _fetch_page(offset: int, length: int) -> Dict[str, Any]:
47
+ page = client.post_list(entity="images", offset=offset,
48
+ length=length, action="list images",
49
+ aspects=aspects)
50
+ return page
51
+
52
+ entities = iter_list_entities(fetch_page=_fetch_page, aspects=aspects)
53
+ rtnval: List[NutanixImage] = []
54
+ for entity in entities:
55
+ rtnval.append(NutanixImage.from_dict(entity))
56
+ return rtnval
57
+
58
+ def find_by_name(self, *, name: str,
59
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
60
+ ) -> Optional[NutanixImage]:
61
+ """
62
+ Return the image with the given name, or ``None``.
63
+
64
+ :param name: The image name to match.
65
+ :param aspects: Behaviour control for the call.
66
+
67
+ :returns: A :class:`NutanixImage` or ``None``.
68
+ """
69
+ for image in self.list(aspects=aspects):
70
+ if image.name == name:
71
+ rtnval: Optional[NutanixImage] = image
72
+ return rtnval
73
+ rtnval = None
74
+ return rtnval
75
+
76
+ def upload(self, *, spec: ImageSpec,
77
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
78
+ ) -> NutanixTaskHandle:
79
+ """
80
+ Upload (register) an image from an intent spec.
81
+
82
+ Read-before-create: when an image of the same name already
83
+ exists, an already-completed task handle wrapping its uuid is
84
+ returned.
85
+
86
+ :param spec: The :class:`ImageSpec` intent spec.
87
+ :param aspects: Behaviour control for the call.
88
+
89
+ :returns: A :class:`NutanixTaskHandle`.
90
+ """
91
+ existing = self.find_by_name(name=spec.name, aspects=aspects)
92
+ client = self.client
93
+ if existing is not None:
94
+ rtnval = client.completed_handle_for_entity(
95
+ entity_uuid=existing.uuid
96
+ )
97
+ return rtnval
98
+
99
+ payload = spec.to_payload()
100
+ rtnval = client.post_intent(entity="images", payload=payload,
101
+ action="upload image", aspects=aspects)
102
+ return rtnval
103
+
104
+ def delete(self, *, image_uuid: str,
105
+ aspects: AspectsNutanix = DEFAULT_NUTANIX_ASPECTS
106
+ ) -> None:
107
+ """
108
+ Delete an image by uuid; an unknown uuid is a no-op.
109
+
110
+ :param image_uuid: The image uuid.
111
+ :param aspects: Behaviour control for the call.
112
+
113
+ :returns: ``None``.
114
+ """
115
+ self.client.delete_intent(entity="images", entity_uuid=image_uuid,
116
+ action="delete image", aspects=aspects)
117
+ return