zosmfrestapi 0.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.
@@ -0,0 +1,477 @@
1
+ import logging
2
+ import time
3
+ from typing import Any
4
+ from dataclasses import asdict
5
+
6
+ import pendulum
7
+ from dacite import Config, from_dict
8
+
9
+ from zosmfrestapi.exceptions.software import (
10
+ ZosmfSoftwareInstanceNotFoundError,
11
+ ZosmfSoftwareManagementError,
12
+ ZosmfSoftwareManagementRequestError,
13
+ )
14
+ from zosmfrestapi.models.software import (
15
+ CsiEntry,
16
+ DataSet,
17
+ DataSetProperty,
18
+ DataSetPropertyLabel,
19
+ NonSmpeManagedDataset,
20
+ NonSmpeProduct,
21
+ Product,
22
+ ProductProperty,
23
+ SmpeManagedDataset,
24
+ SmpeManagedUnixDataset,
25
+ SoftwareInstance,
26
+ SoftwareInstanceWorkflow,
27
+ )
28
+ from zosmfrestapi.utils import _parse_datetime, remap
29
+
30
+ from . import ZosmfBase
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class SoftwareServices(ZosmfBase):
36
+ """Manage z/OSMF software instances and query their SMP/E CSI data."""
37
+
38
+ def list_swis(self) -> list[SoftwareInstance]:
39
+ """List the software instances available on the z/OSMF server.
40
+
41
+ Returns:
42
+ A list of ``SoftwareInstance`` objects.
43
+
44
+ Raises:
45
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
46
+ ZosmfError: If z/OSMF returns another unsuccessful HTTP status.
47
+ httpx2.RequestError: If the request fails before an HTTP response
48
+ is received.
49
+ """
50
+ resp = self.http.get("/zosmf/swmgmt/swi")
51
+ self._raise_for_status(resp)
52
+ return [
53
+ from_dict(
54
+ data_class=SoftwareInstance,
55
+ data=remap(swi, SoftwareInstance),
56
+ config=Config(
57
+ type_hooks={
58
+ pendulum.DateTime: _parse_datetime,
59
+ }
60
+ ),
61
+ )
62
+ for swi in resp.json()["swilist"]
63
+ ]
64
+
65
+ def _get_swi(self, path: str) -> SoftwareInstance:
66
+ resp = self.http.get(path)
67
+ if resp.status_code == 404:
68
+ raise ZosmfSoftwareInstanceNotFoundError(resp)
69
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementError)
70
+ return from_dict(
71
+ data_class=SoftwareInstance,
72
+ data=remap(resp.json(), SoftwareInstance),
73
+ config=Config(
74
+ type_hooks={
75
+ pendulum.DateTime: _parse_datetime,
76
+ list[DataSet]: lambda v: [] if v is None else v,
77
+ list[Product]: lambda v: [] if v is None else v,
78
+ list[NonSmpeProduct]: lambda v: [] if v is None else v,
79
+ list[SoftwareInstanceWorkflow]: lambda v: [] if v is None else v,
80
+ list[DataSetProperty]: lambda v: [] if v is None else v,
81
+ list[DataSetPropertyLabel]: lambda v: [] if v is None else v,
82
+ list[ProductProperty]: lambda v: [] if v is None else v,
83
+ }
84
+ ),
85
+ )
86
+
87
+ def get_swi_by_uuid(self, swiuuid: str) -> SoftwareInstance:
88
+ """Retrieve a software instance by UUID.
89
+
90
+ Args:
91
+ swiuuid: UUID of the software instance.
92
+
93
+ Returns:
94
+ The matching ``SoftwareInstance``.
95
+
96
+ Raises:
97
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
98
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
99
+ ZosmfSoftwareManagementError: If z/OSMF returns another
100
+ unsuccessful HTTP status.
101
+ httpx2.RequestError: If the request fails before an HTTP response
102
+ is received.
103
+ """
104
+ return self._get_swi(f"/zosmf/swmgmt/swi/{swiuuid}")
105
+
106
+ def get_swi_by_name(self, name: str, system: str) -> SoftwareInstance:
107
+ """Retrieve a software instance by name and system.
108
+
109
+ Args:
110
+ name: Name of the software instance.
111
+ system: System where the software instance is defined.
112
+
113
+ Returns:
114
+ The matching ``SoftwareInstance``.
115
+
116
+ Raises:
117
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
118
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
119
+ ZosmfSoftwareManagementError: If z/OSMF returns another
120
+ unsuccessful HTTP status.
121
+ httpx2.RequestError: If the request fails before an HTTP response
122
+ is received.
123
+ """
124
+ return self._get_swi(f"/zosmf/swmgmt/swi/{system}/{name}")
125
+
126
+ def _get_swi_datasets(self, path: str) -> dict[
127
+ str,
128
+ list[SmpeManagedDataset] | list[SmpeManagedUnixDataset] | list[NonSmpeManagedDataset],
129
+ ]:
130
+ resp = self.http.post(path)
131
+ if resp.status_code == 404:
132
+ raise ZosmfSoftwareInstanceNotFoundError(resp)
133
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementError)
134
+ status_monitor_url = resp.json()["statusurl"]
135
+ status = self.http.get(status_monitor_url)
136
+ while status.json()["status"] == "running":
137
+ time.sleep(2)
138
+ status = self.http.get(status_monitor_url)
139
+ self._raise_for_status(status, exc_handler=ZosmfSoftwareManagementError)
140
+ return {
141
+ "smpemanageddatasets": [
142
+ from_dict(data_class=SmpeManagedDataset, data=dataset)
143
+ for dataset in status.json()["smpemanageddatasets"] or []
144
+ ],
145
+ "smpemanagedunixdatasets": [
146
+ from_dict(data_class=SmpeManagedUnixDataset, data=missing)
147
+ for missing in status.json()["smpemanagedunixdatasets"] or []
148
+ ],
149
+ "nonsmpemanageddatasets": [
150
+ from_dict(data_class=NonSmpeManagedDataset, data=missing)
151
+ for missing in status.json()["nonsmpemanageddatasets"] or []
152
+ ],
153
+ }
154
+
155
+ def get_swi_datasets_by_uuid(self, swiuuid: str) -> dict[
156
+ str,
157
+ list[SmpeManagedDataset] | list[SmpeManagedUnixDataset] | list[NonSmpeManagedDataset],
158
+ ]:
159
+ """Retrieve dataset inventories for a software instance by UUID.
160
+
161
+ z/OSMF processes this request asynchronously; this method polls its
162
+ status endpoint until the inventory is ready.
163
+
164
+ Args:
165
+ swiuuid: UUID of the software instance.
166
+
167
+ Returns:
168
+ A dictionary with ``smpemanageddatasets``,
169
+ ``smpemanagedunixdatasets``, and ``nonsmpemanageddatasets`` keys.
170
+ Each value is a list of the corresponding parsed dataset model.
171
+
172
+ Raises:
173
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
174
+ ZosmfAuthError: If a request returns HTTP 401.
175
+ ZosmfSoftwareManagementRequestError: If a request returns another
176
+ unsuccessful HTTP status.
177
+ httpx2.RequestError: If a request fails before an HTTP response is
178
+ received.
179
+ """
180
+ return self._get_swi_datasets(f"/zosmf/swmgmt/swi/{swiuuid}/datasets")
181
+
182
+ def get_swi_datasets_by_name(self, name: str, system: str) -> dict[
183
+ str,
184
+ list[SmpeManagedDataset] | list[SmpeManagedUnixDataset] | list[NonSmpeManagedDataset],
185
+ ]:
186
+ """Retrieve dataset inventories for a software instance by name.
187
+
188
+ z/OSMF processes this request asynchronously; this method polls its
189
+ status endpoint until the inventory is ready.
190
+
191
+ Args:
192
+ name: Name of the software instance.
193
+ system: System where the software instance is defined.
194
+
195
+ Returns:
196
+ A dictionary with ``smpemanageddatasets``,
197
+ ``smpemanagedunixdatasets``, and ``nonsmpemanageddatasets`` keys.
198
+ Each value is a list of the corresponding parsed dataset model.
199
+
200
+ Raises:
201
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
202
+ ZosmfAuthError: If a request returns HTTP 401.
203
+ ZosmfSoftwareManagementRequestError: If a request returns another
204
+ unsuccessful HTTP status.
205
+ httpx2.RequestError: If a request fails before an HTTP response is
206
+ received.
207
+ """
208
+ return self._get_swi_datasets(f"/zosmf/swmgmt/swi/{system}/{name}/datasets")
209
+
210
+ def add_swi(self, swi: SoftwareInstance) -> bool:
211
+ """Add a new software instance.
212
+
213
+ Args:
214
+ swi: Validated software-instance data to submit, including its name
215
+ and system and any configured zones, data sets, products, or
216
+ workflows.
217
+
218
+ Returns:
219
+ ``True`` when z/OSMF responds with HTTP 201; ``False`` for another
220
+ successful response status.
221
+
222
+ Raises:
223
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
224
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
225
+ unsuccessful HTTP status.
226
+ httpx2.RequestError: If the request fails before an HTTP response
227
+ is received.
228
+ """
229
+ resp = self.http.post(
230
+ "/zosmf/swmgmt/swi",
231
+ json={
232
+ "name": swi.name,
233
+ "system": swi.system,
234
+ "description": swi.description if swi.description else "",
235
+ "globalzone": swi.globalzone if swi.globalzone else "",
236
+ "targetzones": swi.targetzones if swi.targetzones else [],
237
+ "categories": swi.categories if swi.categories else [],
238
+ "datasets": swi.datasets if swi.datasets else [],
239
+ "products": (
240
+ [
241
+ {
242
+ "prodname": product.prodname,
243
+ "prodid": product.productid,
244
+ "release": product.release,
245
+ "vendor": product.vendor,
246
+ "url": product.url,
247
+ "features": (
248
+ [feature.feature for feature in product.features]
249
+ if product.features
250
+ else []
251
+ ),
252
+ "generalavailability": product.generalavailability,
253
+ "endofservice": product.endofservice,
254
+ }
255
+ for product in swi.products
256
+ ]
257
+ if swi.products
258
+ else []
259
+ ),
260
+ "workflows": (
261
+ [asdict(workflow) for workflow in swi.workflows] if swi.workflows else []
262
+ ),
263
+ },
264
+ )
265
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementRequestError)
266
+ return resp.status_code == 201
267
+
268
+ def _remove_sw_instance(self, path: str) -> bool:
269
+ resp = self.http.delete(path)
270
+ if resp.status_code == 404:
271
+ raise ZosmfSoftwareInstanceNotFoundError(resp)
272
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementRequestError)
273
+ return resp.status_code == 200
274
+
275
+ def remove_sw_instance_by_uuid(self, swiuuid: str) -> bool:
276
+ """Delete a software instance identified by UUID.
277
+
278
+ Args:
279
+ swiuuid: UUID of the software instance to delete.
280
+
281
+ Returns:
282
+ ``True`` when z/OSMF responds with HTTP 200; ``False`` for another
283
+ successful response status.
284
+
285
+ Raises:
286
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
287
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
288
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
289
+ unsuccessful HTTP status.
290
+ httpx2.RequestError: If the request fails before an HTTP response
291
+ is received.
292
+ """
293
+ return self._remove_sw_instance(f"/zosmf/swmgmt/swi/{swiuuid}")
294
+
295
+ def remove_sw_instance_by_name(self, name: str, system: str) -> bool:
296
+ """Delete a software instance identified by name.
297
+
298
+ Args:
299
+ name: Name of the software instance to delete.
300
+ system: System where the software instance is defined.
301
+
302
+ Returns:
303
+ ``True`` when z/OSMF responds with HTTP 200; ``False`` for another
304
+ successful response status.
305
+
306
+ Raises:
307
+ ZosmfSoftwareInstanceNotFoundError: If z/OSMF returns HTTP 404.
308
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
309
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
310
+ unsuccessful HTTP status.
311
+ httpx2.RequestError: If the request fails before an HTTP response
312
+ is received.
313
+ """
314
+ return self._remove_sw_instance(f"/zosmf/swmgmt/swi/{system}/{name}")
315
+
316
+ def _query_csi(
317
+ self,
318
+ path: str,
319
+ zones: list[str],
320
+ entries: list[str],
321
+ subentries: list[str],
322
+ filter: str | None,
323
+ zosmf_auth: tuple[str, ...],
324
+ proxy_auth: tuple[str, ...],
325
+ wait: bool,
326
+ ) -> list[CsiEntry] | dict[str, Any]:
327
+ if "*" in entries and len(entries) > 1:
328
+ raise ZosmfSoftwareManagementError("'*' cannot be combined with other entry types.")
329
+ elif "*" in subentries and len(subentries) > 1:
330
+ raise ZosmfSoftwareManagementError(
331
+ "'*' cannot be combined with other subentry types."
332
+ )
333
+ resp = self.http.post(
334
+ path,
335
+ json={
336
+ "zones": zones,
337
+ "entries": entries,
338
+ "subentries": subentries,
339
+ "filter": filter or "",
340
+ },
341
+ )
342
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementRequestError)
343
+ if wait:
344
+ status = self.get_query_csi_status(resp.json()["statusurl"], wait=True)
345
+ return status
346
+ return resp.json()
347
+
348
+ def query_csi_by_uuid(
349
+ self,
350
+ uuid: str,
351
+ zones: list[str],
352
+ entries: list[str],
353
+ subentries: list[str] = ["*"],
354
+ filter: str | None = None,
355
+ zosmf_auth: tuple[str, ...] = (),
356
+ proxy_auth: tuple[str, ...] = (),
357
+ wait: bool = True,
358
+ ) -> list[CsiEntry] | dict[str, Any]:
359
+ """Query CSI data for a software instance identified by its UUID.
360
+
361
+ Args:
362
+ uuid: UUID of the software instance to query.
363
+ zones: CSI zones to search.
364
+ entries: Entry types to retrieve. ``"*"`` cannot be combined with
365
+ other entry types.
366
+ subentries: Subentry types to retrieve. ``"*"`` cannot be combined
367
+ with other subentry types.
368
+ filter: Optional filter expression sent to z/OSMF.
369
+ zosmf_auth: Reserved compatibility argument; currently not applied.
370
+ proxy_auth: Reserved compatibility argument; currently not applied.
371
+ wait: If true, poll until the query completes and return its entries.
372
+
373
+ Returns:
374
+ A list of ``CsiEntry`` objects when ``wait`` is true; otherwise,
375
+ the initial response dictionary from z/OSMF.
376
+
377
+ Raises:
378
+ ZosmfSoftwareManagementError: If ``"*"`` is combined with
379
+ other entry or subentry types.
380
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
381
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
382
+ unsuccessful HTTP status.
383
+ httpx2.RequestError: If a request fails before an HTTP response is
384
+ received.
385
+ """
386
+ return self._query_csi(
387
+ f"/zosmf/swmgmt/swi/{uuid}/csiquery",
388
+ zones,
389
+ entries,
390
+ subentries,
391
+ filter,
392
+ zosmf_auth=zosmf_auth,
393
+ proxy_auth=proxy_auth,
394
+ wait=wait,
395
+ )
396
+
397
+ def query_csi_by_name(
398
+ self,
399
+ name: str,
400
+ system: str,
401
+ zones: list[str],
402
+ entries: list[str],
403
+ subentries: list[str] = ["*"],
404
+ filter: str | None = None,
405
+ zosmf_auth: tuple[str, ...] = (),
406
+ proxy_auth: tuple[str, ...] = (),
407
+ wait: bool = True,
408
+ ) -> list[CsiEntry] | dict[str, Any]:
409
+ """Query CSI data for a software instance identified by name and system.
410
+
411
+ Args:
412
+ name: Name of the software instance to query.
413
+ system: System where the software instance is defined.
414
+ zones: CSI zones to search.
415
+ entries: Entry types to retrieve. ``"*"`` cannot be combined with
416
+ other entry types.
417
+ subentries: Subentry types to retrieve. ``"*"`` cannot be combined
418
+ with other subentry types.
419
+ filter: Optional filter expression sent to z/OSMF.
420
+ zosmf_auth: Reserved compatibility argument; currently not applied.
421
+ proxy_auth: Reserved compatibility argument; currently not applied.
422
+ wait: If true, poll until the query completes and return its entries.
423
+
424
+ Returns:
425
+ A list of ``CsiEntry`` objects when ``wait`` is true; otherwise,
426
+ the initial response dictionary from z/OSMF.
427
+
428
+ Raises:
429
+ ZosmfSoftwareManagementError: If ``"*"`` is combined with
430
+ other entry or subentry types.
431
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
432
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
433
+ unsuccessful HTTP status.
434
+ httpx2.RequestError: If a request fails before an HTTP response is
435
+ received.
436
+ """
437
+ return self._query_csi(
438
+ f"/zosmf/swmgmt/swi/{system}/{name}/csiquery",
439
+ zones,
440
+ entries,
441
+ subentries,
442
+ filter,
443
+ zosmf_auth=zosmf_auth,
444
+ proxy_auth=proxy_auth,
445
+ wait=wait,
446
+ )
447
+
448
+ def _get_query_csi_status(self, statusurl: str) -> dict[str, Any]:
449
+ resp = self.http.get(statusurl)
450
+ self._raise_for_status(resp, exc_handler=ZosmfSoftwareManagementRequestError)
451
+ return resp.json()
452
+
453
+ def get_query_csi_status(self, statusurl: str, wait: bool = True) -> list[CsiEntry]:
454
+ """Retrieve the entries for a CSI query, optionally waiting for completion.
455
+
456
+ Args:
457
+ statusurl: Status URL returned by a CSI query submission.
458
+ wait: Poll every two seconds while the status is ``"running"`` when
459
+ true. When false, parse the entries in the first status response.
460
+
461
+ Returns:
462
+ Parsed ``CsiEntry`` objects from the final status response, or from
463
+ the initial response when ``wait`` is false.
464
+
465
+ Raises:
466
+ ZosmfAuthError: If z/OSMF returns HTTP 401.
467
+ ZosmfSoftwareManagementRequestError: If z/OSMF returns another
468
+ unsuccessful HTTP status.
469
+ httpx2.RequestError: If a request fails before an HTTP response is
470
+ received.
471
+ """
472
+ status = self._get_query_csi_status(statusurl)
473
+ if wait:
474
+ while status["status"] == "running":
475
+ time.sleep(2)
476
+ status = self._get_query_csi_status(statusurl)
477
+ return [from_dict(data_class=CsiEntry, data=entry) for entry in status["entries"]]