syneto_api 0.3.76__tar.gz → 0.3.78__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syneto_api
3
- Version: 0.3.76
3
+ Version: 0.3.78
4
4
  Summary: Syneto Client API library
5
5
  License: Proprietary
6
6
  Author: Syneto
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "syneto_api"
3
- version = "0.3.76"
3
+ version = "0.3.78"
4
4
  description = "Syneto Client API library"
5
5
  authors = ["Syneto <developers@syneto.eu>"]
6
6
  license = "Proprietary"
@@ -0,0 +1,181 @@
1
+ import os
2
+
3
+ from .api_client import APIClientBase, InvalidInputException
4
+
5
+ # Sentinel used by update_identity to distinguish "field not provided"
6
+ # (omit from the JSON body) from "explicit null" (include as JSON null).
7
+ _UNSET = object()
8
+
9
+
10
+ class Apollo(APIClientBase):
11
+ """Client for the Apollo networking service.
12
+
13
+ Apollo is a host-local service providing network management (vSwitches, port groups, NICs,
14
+ hostname, service address resolution).
15
+
16
+ By default, connects directly to Apollo at the APOLLO_SERVICE env var (e.g. http://172.16.254.2:6000).
17
+
18
+ TODO: Add Minerva proxy support. When Minerva proxy is available, Apollo endpoints will be
19
+ accessed via /api/virtualization/v1/computeHosts/{id}/network/v1/... through Minerva.
20
+ """
21
+
22
+ def __init__(self, url_base=None, custom_retry_config=None, **kwargs):
23
+ super().__init__(url_base or os.environ.get("APOLLO_SERVICE", ""), **kwargs)
24
+ self._custom_retry_config = custom_retry_config
25
+
26
+ @property
27
+ def _retry_config(self):
28
+ return self._custom_retry_config or super()._retry_config
29
+
30
+ # --- vSwitches ---
31
+
32
+ def get_vswitches(self):
33
+ """List all vSwitches."""
34
+ return self.get_request("/v1/vswitches")
35
+
36
+ def get_vswitch(self, vswitch_id: str):
37
+ """Get a single vSwitch by ID."""
38
+ if not vswitch_id:
39
+ raise InvalidInputException(f"Expected a valid vswitch id, received: `{vswitch_id}`")
40
+ return self.get_request("/v1/vswitches/{id}", id=vswitch_id)
41
+
42
+ # --- Port Groups ---
43
+
44
+ def get_port_groups(self, tag: str = None):
45
+ """List all port groups, with optional tag filter."""
46
+ query_args = {}
47
+ if tag:
48
+ query_args["tag"] = tag
49
+ return self.get_request("/v1/port-groups", query_args=query_args or None)
50
+
51
+ def get_port_group(self, port_group_id: str):
52
+ """Get a single port group by ID."""
53
+ if not port_group_id:
54
+ raise InvalidInputException(f"Expected a valid port group id, received: `{port_group_id}`")
55
+ return self.get_request("/v1/port-groups/{id}", id=port_group_id)
56
+
57
+ def patch_port_group(self, vswitch_id: str, port_group_id: str, body: dict):
58
+ """PATCH a port group's configuration.
59
+
60
+ The body is passed through to Apollo unchanged. For a vmkernel PG's
61
+ ``networkConfig``, the request shape is flat:
62
+
63
+ .. code-block:: python
64
+
65
+ {
66
+ "networkConfig": {
67
+ "method": "dhcp", # "dhcp" / "static" / "none"
68
+ "dnsServers": ["8.8.8.8"], # configured override
69
+ "searchDomains": ["override.lan"], # configured override
70
+ "ignoreAutoDns": True, # only on dhcp; replace lease DNS
71
+ }
72
+ }
73
+
74
+ Response shape carries DHCP-pushed values for read-only inspection:
75
+
76
+ .. code-block:: python
77
+
78
+ "networkConfig": {
79
+ "dnsServers": {"configured": [...], "fromLease": [...]},
80
+ "searchDomains": {"configured": [...], "fromLease": [...]},
81
+ "ignoreAutoDns": True | False | None,
82
+ }
83
+
84
+ Other fields the request accepts: ``name``, ``tags``, ``vlanConfig``.
85
+ Apollo enforces strict validation (extra keys rejected with 422).
86
+ """
87
+ if not vswitch_id:
88
+ raise InvalidInputException(f"Expected a valid vswitch id, received: `{vswitch_id}`")
89
+ if not port_group_id:
90
+ raise InvalidInputException(f"Expected a valid port group id, received: `{port_group_id}`")
91
+ if not isinstance(body, dict) or not body:
92
+ raise InvalidInputException("Expected a non-empty body dict for port-group PATCH")
93
+ return self.patch_request(
94
+ "/v1/vswitches/{id}/port-groups/{pg_id}",
95
+ body=body,
96
+ id=vswitch_id,
97
+ pg_id=port_group_id,
98
+ )
99
+
100
+ # --- NICs ---
101
+
102
+ def get_nics(self):
103
+ """List all NICs."""
104
+ return self.get_request("/v1/nics")
105
+
106
+ def get_nic(self, name: str):
107
+ """Get a single NIC by name."""
108
+ if not name:
109
+ raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
110
+ return self.get_request("/v1/nics/{name}", name=name)
111
+
112
+ def enable_nic(self, name: str):
113
+ """Enable a NIC."""
114
+ if not name:
115
+ raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
116
+ return self.post_request("/v1/nics/{name}:enable", name=name)
117
+
118
+ def disable_nic(self, name: str):
119
+ """Disable a NIC."""
120
+ if not name:
121
+ raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
122
+ return self.post_request("/v1/nics/{name}:disable", name=name)
123
+
124
+ # --- System ---
125
+
126
+ def get_service_addresses(self, tag: str):
127
+ """Resolve service addresses for a given tag."""
128
+ if not tag:
129
+ raise InvalidInputException(f"Expected a valid tag, received: `{tag}`")
130
+ return self.get_request("/v1/system/service-addresses/{tag}", tag=tag)
131
+
132
+ def get_identity(self):
133
+ """Get the system identity (short hostname + primary domain + derived FQDN).
134
+
135
+ Response keys:
136
+ - ``shortHostname``: the short RFC 1123 label
137
+ - ``primaryDomain``: the configured primary domain, or ``None`` when the
138
+ FQDN collapses to the short hostname
139
+ - ``fqdn``: the derived fully-qualified name (read from ``hostname -f``)
140
+ - ``orphaned``: ``True`` when the configured primary no longer appears
141
+ in the management-PG candidate union
142
+ - ``primaryDomainCandidates``: deduplicated, lexicographically ordered
143
+ list of ``{"domain": str, "origin": list[str]}`` entries from
144
+ management-tagged PG ``ipv4.dns-search`` values (and DHCP-supplied
145
+ search domains for DHCP-method connections)
146
+ """
147
+ return self.get_request("/v1/system/identity")
148
+
149
+ def update_identity(self, short_hostname=_UNSET, primary_domain=_UNSET):
150
+ """Update the system identity.
151
+
152
+ Each field is independently optional. Two distinct signals exist for
153
+ ``primary_domain``:
154
+
155
+ - omitted (default sentinel) -- "do not touch the primary domain";
156
+ the field is left out of the JSON body entirely
157
+ - ``None`` -- "clear the primary domain"; the field is sent as JSON
158
+ ``null`` so the FQDN collapses to the short hostname
159
+ - a string -- set the primary domain to that value (must be one of
160
+ the candidates returned by :py:meth:`get_identity`)
161
+
162
+ ``short_hostname`` follows the same shape: omit to leave unchanged, or
163
+ pass a single RFC 1123 label (no dots) to update it. At least one of
164
+ the two fields must be provided; passing neither sends an empty body
165
+ and is rejected by the server.
166
+
167
+ Response keys:
168
+ - ``shortHostname``, ``primaryDomain``, ``fqdn``: the new state
169
+ - ``rebootRecommended``: ``True`` after any identity change
170
+ - ``sideEffects``: ``{"hostsFileUpdated": bool, "certificateRenewed": bool}``
171
+ """
172
+ body: dict = {}
173
+ if short_hostname is not _UNSET:
174
+ if not short_hostname:
175
+ raise InvalidInputException(f"Expected a valid short hostname, received: `{short_hostname}`")
176
+ body["shortHostname"] = short_hostname
177
+ if primary_domain is not _UNSET:
178
+ body["primaryDomain"] = primary_domain
179
+ if not body:
180
+ raise InvalidInputException("Expected at least one of short_hostname or primary_domain to be provided")
181
+ return self.patch_request("/v1/system/identity", body=body)
@@ -9,8 +9,9 @@ class Storage(APIClientBase):
9
9
  def __init__(self, url_base=None, **kwargs):
10
10
  super().__init__(url_base or os.environ.get("STORAGE_SERVICE", ""), **kwargs)
11
11
 
12
- def get_pools(self):
13
- return self.get_request("/pools")
12
+ def get_pools(self, fields: list = None):
13
+ fields = {"fields": ",".join(fields)} if fields else None
14
+ return self.get_request("/pools", query_args=fields)
14
15
 
15
16
  def get_importable_pools(self):
16
17
  return self.get_request("/pools/importable")
@@ -1,94 +0,0 @@
1
- import os
2
-
3
- from .api_client import APIClientBase, InvalidInputException
4
-
5
-
6
- class Apollo(APIClientBase):
7
- """Client for the Apollo networking service.
8
-
9
- Apollo is a host-local service providing network management (vSwitches, port groups, NICs,
10
- hostname, service address resolution).
11
-
12
- By default, connects directly to Apollo at the APOLLO_SERVICE env var (e.g. http://172.16.254.2:6000).
13
-
14
- TODO: Add Minerva proxy support. When Minerva proxy is available, Apollo endpoints will be
15
- accessed via /api/virtualization/v1/computeHosts/{id}/network/v1/... through Minerva.
16
- """
17
-
18
- def __init__(self, url_base=None, custom_retry_config=None, **kwargs):
19
- super().__init__(url_base or os.environ.get("APOLLO_SERVICE", ""), **kwargs)
20
- self._custom_retry_config = custom_retry_config
21
-
22
- @property
23
- def _retry_config(self):
24
- return self._custom_retry_config or super()._retry_config
25
-
26
- # --- vSwitches ---
27
-
28
- def get_vswitches(self):
29
- """List all vSwitches."""
30
- return self.get_request("/v1/vswitches")
31
-
32
- def get_vswitch(self, vswitch_id: str):
33
- """Get a single vSwitch by ID."""
34
- if not vswitch_id:
35
- raise InvalidInputException(f"Expected a valid vswitch id, received: `{vswitch_id}`")
36
- return self.get_request("/v1/vswitches/{id}", id=vswitch_id)
37
-
38
- # --- Port Groups ---
39
-
40
- def get_port_groups(self, tag: str = None):
41
- """List all port groups, with optional tag filter."""
42
- query_args = {}
43
- if tag:
44
- query_args["tag"] = tag
45
- return self.get_request("/v1/port-groups", query_args=query_args or None)
46
-
47
- def get_port_group(self, port_group_id: str):
48
- """Get a single port group by ID."""
49
- if not port_group_id:
50
- raise InvalidInputException(f"Expected a valid port group id, received: `{port_group_id}`")
51
- return self.get_request("/v1/port-groups/{id}", id=port_group_id)
52
-
53
- # --- NICs ---
54
-
55
- def get_nics(self):
56
- """List all NICs."""
57
- return self.get_request("/v1/nics")
58
-
59
- def get_nic(self, name: str):
60
- """Get a single NIC by name."""
61
- if not name:
62
- raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
63
- return self.get_request("/v1/nics/{name}", name=name)
64
-
65
- def enable_nic(self, name: str):
66
- """Enable a NIC."""
67
- if not name:
68
- raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
69
- return self.post_request("/v1/nics/{name}:enable", name=name)
70
-
71
- def disable_nic(self, name: str):
72
- """Disable a NIC."""
73
- if not name:
74
- raise InvalidInputException(f"Expected a valid NIC name, received: `{name}`")
75
- return self.post_request("/v1/nics/{name}:disable", name=name)
76
-
77
- # --- System ---
78
-
79
- def get_service_addresses(self, tag: str):
80
- """Resolve service addresses for a given tag."""
81
- if not tag:
82
- raise InvalidInputException(f"Expected a valid tag, received: `{tag}`")
83
- return self.get_request("/v1/system/service-addresses/{tag}", tag=tag)
84
-
85
- def get_hostname(self):
86
- """Get the system hostname."""
87
- return self.get_request("/v1/system/hostname")
88
-
89
- def update_hostname(self, hostname: str):
90
- """Update the system hostname."""
91
- if not hostname:
92
- raise InvalidInputException(f"Expected a valid hostname, received: `{hostname}`")
93
- body = {"hostname": hostname}
94
- return self.patch_request("/v1/system/hostname", body=body)
File without changes