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,93 @@
1
+ """Synchronous Python client for the z/OSMF REST API.
2
+
3
+ The package root re-exports :class:`ZosmfClient`, :class:`ZosmfZcryptoClient`,
4
+ response models, and public exceptions. Create one client per server/configuration and
5
+ use it as a context manager to close the shared HTTP session when finished.
6
+ """
7
+
8
+ from zosmfrestapi.client import ZosmfClient
9
+ from zosmfrestapi.exceptions import ZosmfAuthError, ZosmfError
10
+ from zosmfrestapi.exceptions.authentication import ZosmfLoginError
11
+ from zosmfrestapi.exceptions.dataset import ZosmfDatasetNotFoundError
12
+ from zosmfrestapi.exceptions.software import (
13
+ ZosmfSoftwareInstanceNotFoundError,
14
+ ZosmfSoftwareManagementError,
15
+ ZosmfSoftwareManagementRequestError,
16
+ )
17
+ from zosmfrestapi.exceptions.workflow import ZosmfWorkflowBaseError, ZosmfWorkflowError
18
+ from zosmfrestapi.models.authentication import LoginOk
19
+ from zosmfrestapi.models.software import (
20
+ CsiEntry,
21
+ DataSet,
22
+ Feature,
23
+ Fmid,
24
+ NonSmpeManagedDataset,
25
+ Product,
26
+ SmpeManagedDataset,
27
+ SmpeManagedDatasetZoneDddef,
28
+ SmpeManagedUnixDataset,
29
+ SoftwareInstance,
30
+ SoftwareInstanceWorkflow,
31
+ WorkflowLocation,
32
+ )
33
+ from zosmfrestapi.models.software_updates import (
34
+ MissingFixcat,
35
+ MissingUpdate,
36
+ MissingUpdateResolver,
37
+ SoftwareUpdate,
38
+ SoftwareUpdateZone,
39
+ UpdateErrorResults,
40
+ UpdateProcess,
41
+ UpdateResult,
42
+ UpdateSelection,
43
+ )
44
+ from zosmfrestapi.models.topology import Group, System
45
+ from zosmfrestapi.models.workflow import (
46
+ WorkflowArchivedListItem,
47
+ WorkflowInstance,
48
+ WorkflowItem,
49
+ WorkflowListItem,
50
+ )
51
+ from zosmfrestapi.zcrypto_client import ZosmfZcryptoClient
52
+
53
+ __all__ = [
54
+ "ZosmfClient",
55
+ "ZosmfZcryptoClient",
56
+ "LoginOk",
57
+ "ZosmfDatasetNotFoundError",
58
+ "ZosmfError",
59
+ "ZosmfAuthError",
60
+ "ZosmfLoginError",
61
+ "System",
62
+ "Group",
63
+ "CsiEntry",
64
+ "DataSet",
65
+ "Feature",
66
+ "Fmid",
67
+ "NonSmpeManagedDataset",
68
+ "Product",
69
+ "SmpeManagedDataset",
70
+ "SmpeManagedDatasetZoneDddef",
71
+ "SmpeManagedUnixDataset",
72
+ "SoftwareInstance",
73
+ "SoftwareInstanceWorkflow",
74
+ "WorkflowLocation",
75
+ "ZosmfSoftwareManagementError",
76
+ "ZosmfSoftwareInstanceNotFoundError",
77
+ "ZosmfSoftwareManagementRequestError",
78
+ "MissingFixcat",
79
+ "MissingUpdate",
80
+ "MissingUpdateResolver",
81
+ "SoftwareUpdate",
82
+ "SoftwareUpdateZone",
83
+ "UpdateErrorResults",
84
+ "UpdateProcess",
85
+ "UpdateResult",
86
+ "UpdateSelection",
87
+ "WorkflowArchivedListItem",
88
+ "WorkflowInstance",
89
+ "WorkflowItem",
90
+ "WorkflowListItem",
91
+ "ZosmfWorkflowBaseError",
92
+ "ZosmfWorkflowError",
93
+ ]
zosmfrestapi/client.py ADDED
@@ -0,0 +1,47 @@
1
+ from zosmfrestapi.services.authentication import AuthenticationServices
2
+ from zosmfrestapi.services.dataset import DatasetServices
3
+ from zosmfrestapi.services.software import SoftwareServices
4
+ from zosmfrestapi.services.software_updates import SoftwareUpdatesServices
5
+ from zosmfrestapi.services.topology import TopologyServices
6
+ from zosmfrestapi.services.workflow import WorkflowServices
7
+
8
+
9
+ class ZosmfClient(
10
+ AuthenticationServices,
11
+ DatasetServices,
12
+ TopologyServices,
13
+ WorkflowServices,
14
+ SoftwareServices,
15
+ SoftwareUpdatesServices,
16
+ ):
17
+ """Synchronous client for the z/OSMF REST APIs.
18
+
19
+ The client combines authentication, datasets, topology, workflows,
20
+ software-instance management, and software-update operations around one
21
+ reusable HTTP session. Update-start and resume methods can change the
22
+ selected target zone; use them only when those changes are intended.
23
+
24
+ Use it as a context manager to close the underlying HTTP session reliably.
25
+
26
+ Examples:
27
+ Use a context manager when making requests:
28
+
29
+ with ZosmfClient("https://zosmf.example",
30
+ username="demouser", password="DEMOPASS") as client:
31
+ systems = client.get_systems()
32
+
33
+ Use a client without using a context manager:
34
+
35
+ client = ZosmfClient("https://zosmf.example",
36
+ username="demouser", password="DEMOPASS")
37
+ systems = client.get_systems()
38
+ client.close()
39
+
40
+ Use a client with mTLS authentication:
41
+
42
+ with ZosmfClient("https://zosmf.example",
43
+ username="demouser", password="DEMOPASS",
44
+ cert=("/path/to/cert", "/path/to/key"),
45
+ key_password="secret") as client:
46
+ systems = client.get_systems()
47
+ """
@@ -0,0 +1,18 @@
1
+ import httpx2
2
+
3
+
4
+ class ZosmfError(Exception):
5
+ """Base exception for errors raised by the z/OSMF REST API client."""
6
+
7
+ pass
8
+
9
+
10
+ class ZosmfAuthError(ZosmfError):
11
+ """Raised when z/OSMF rejects a request with HTTP 401.
12
+
13
+ The triggering ``httpx2.HTTPStatusError`` is available as
14
+ :attr:`http_exception`.
15
+ """
16
+
17
+ def __init__(self, exc: httpx2.HTTPStatusError) -> None:
18
+ self.http_exception = exc
@@ -0,0 +1,17 @@
1
+ import httpx2
2
+
3
+ from . import ZosmfError
4
+
5
+
6
+ class ZosmfLoginError(ZosmfError):
7
+ """Raised when the authentication endpoint rejects a login request.
8
+
9
+ The response's return code, reason code, and message are exposed as
10
+ ``return_code``, ``reason_code``, and ``message``.
11
+ """
12
+
13
+ def __init__(self, response: httpx2.Response) -> None:
14
+ self.http_exception = None
15
+ self.return_code = response.json()["returnCode"]
16
+ self.reason_code = response.json()["reasonCode"]
17
+ self.message = response.json()["message"]
@@ -0,0 +1,17 @@
1
+ import logging
2
+
3
+ import httpx2
4
+ from zosmfrestapi.exceptions import ZosmfError
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class ZosmfDatasetError(ZosmfError):
10
+ """Raised when a data set REST request fails with an HTTP status error."""
11
+
12
+
13
+ class ZosmfDatasetNotFoundError(ZosmfDatasetError):
14
+ """Raised when z/OSMF reports that the requested data set is not found."""
15
+
16
+ def __init__(self, response: httpx2.Response) -> None:
17
+ pass
@@ -0,0 +1,34 @@
1
+ from . import ZosmfError
2
+
3
+
4
+ class ZosmfSoftwareManagementError(ZosmfError):
5
+ """Base exception for z/OSMF Software Management operations."""
6
+
7
+
8
+ class ZosmfSoftwareManagementRequestError(ZosmfSoftwareManagementError):
9
+ """Raised when a Software Management request fails with an HTTP status.
10
+
11
+ When the response includes the corresponding fields, ``status_code``,
12
+ ``reason_code``, ``messages``, and ``body`` provide response details.
13
+ """
14
+
15
+ def __init__(self, error=None) -> None:
16
+ self.status_code = error.response.status_code if error else None
17
+ self.reason_code = error.response.json()["reason"] if error else None
18
+ self.messages = error.response.json()["messages"] if error else None
19
+ self.body = error.response.text if error else None
20
+
21
+
22
+ class ZosmfSoftwareInstanceNotFoundError(ZosmfSoftwareManagementError):
23
+ """Raised when a requested software instance does not exist."""
24
+
25
+ def __init__(self, response) -> None:
26
+ super().__init__(response)
27
+ self.messages = (
28
+ response.json().get("messages", [])
29
+ if response.headers.get("Content-Type").startswith("application/json")
30
+ else [response.text]
31
+ )
32
+
33
+ def __str__(self) -> str:
34
+ return ", ".join(self.messages) if self.messages else ""
@@ -0,0 +1,30 @@
1
+ import httpx2
2
+
3
+ from . import ZosmfError
4
+
5
+
6
+ class ZosmfWorkflowBaseError(ZosmfError):
7
+ """Base exception for z/OSMF workflow operations."""
8
+
9
+ pass
10
+
11
+
12
+ class ZosmfWorkflowError(ZosmfWorkflowBaseError):
13
+ """Raised when a workflow REST request fails with an HTTP status.
14
+
15
+ When present in the response, ``status_code``, ``message_id``, ``message``,
16
+ and ``body`` expose the z/OSMF error details.
17
+ """
18
+
19
+ def __init__(self, error: httpx2.HTTPStatusError | None = None) -> None:
20
+ self.exc = error
21
+ self.status_code = error.response.status_code if error else None
22
+ self.message_id = error.response.json()["messageID"] if error else None
23
+ self.message = error.response.json()["messageText"] if error else None
24
+ self.body = error.response.text if error else None
25
+
26
+
27
+ class ZosmfWorkflowCanceledError(ZosmfWorkflowBaseError):
28
+ """Reserved for workflow-specific cancellation failures."""
29
+
30
+ pass
File without changes
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class LoginOk:
6
+ """Successful response from the z/OSMF authentication endpoint.
7
+
8
+ The endpoint return fields are preserved alongside any LTPA or JWT token
9
+ cookies returned by the server.
10
+ """
11
+
12
+ returnCode: int
13
+ reasonCode: int
14
+ message: str
15
+ ltpa_token: str | None = None
16
+ jwt_token: str | None = None
@@ -0,0 +1,231 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ import pendulum
5
+
6
+
7
+ @dataclass
8
+ class DataSet:
9
+ """Reference to a data set and, optionally, the volume that contains it."""
10
+
11
+ dsname: str | None = None
12
+ volume: str | None = None
13
+
14
+
15
+ @dataclass
16
+ class SmpeManagedDatasetZoneDddef:
17
+ """DDDEF names associated with an SMP/E zone."""
18
+
19
+ zone: str | None = None
20
+ dddefs: list[str] = field(default_factory=list)
21
+
22
+
23
+ @dataclass
24
+ class SmpeManagedDataset:
25
+ """Inventory details for an SMP/E-managed MVS data set."""
26
+
27
+ dsname: str | None = None
28
+ volumes: list[str] | None = field(default_factory=list)
29
+ dstype: str | None = None
30
+ recfm: str | None = None
31
+ lrecl: str | None = None
32
+ blksize: str | None = None
33
+ tracks: str | None = None
34
+ used: str | None = None
35
+ extents: str | None = None
36
+ dscategory: list[str] = field(default_factory=list)
37
+ zones: list[str] = field(default_factory=list)
38
+ zonedddefs: list[SmpeManagedDatasetZoneDddef] | None = field(default_factory=list)
39
+ msgs: Any = None
40
+
41
+
42
+ @dataclass
43
+ class SmpeManagedUnixDataset:
44
+ """Inventory details for an SMP/E-managed UNIX file-system data set."""
45
+
46
+ dsname: str | None = None
47
+ unixdirs: list[str] | None = field(default_factory=list)
48
+ volumes: list[str] | None = field(default_factory=list)
49
+ dstype: str | None = None
50
+ recfm: str | None = None
51
+ lrecl: str | None = None
52
+ blksize: str | None = None
53
+ tracks: str | None = None
54
+ used: str | None = None
55
+ extents: str | None = None
56
+ dscategory: list[str] | None = field(default_factory=list)
57
+ zones: list[str] | None = field(default_factory=list)
58
+ zonedddefs: list[SmpeManagedDatasetZoneDddef] | None = field(default_factory=list)
59
+ msgs: list[str] | None = field(default_factory=list)
60
+
61
+
62
+ @dataclass
63
+ class NonSmpeManagedDataset:
64
+ """Inventory details for a data set not managed by SMP/E."""
65
+
66
+ dsname: str | None = None
67
+ volumes: list[str] | None = field(default_factory=list)
68
+ dstype: str | None = None
69
+ recfm: str | None = None
70
+ lrecl: str | None = None
71
+ blksize: str | None = None
72
+ tracks: str | None = None
73
+ used: str | None = None
74
+ extents: str | None = None
75
+ dscategory: list[str] | None = field(default_factory=list)
76
+ unixdirs: list[str] | None = field(default_factory=list)
77
+ msgs: list[str] | None = field(default_factory=list)
78
+
79
+
80
+ @dataclass
81
+ class CsiSubEntry:
82
+ """Arbitrary values returned for one CSI subentry."""
83
+
84
+ values: dict[str, Any]
85
+
86
+
87
+ @dataclass
88
+ class CsiEntry:
89
+ """CSI entry identity and its returned subentry values."""
90
+
91
+ entryname: str | None = None
92
+ entrytype: str | None = None
93
+ zonename: str | None = None
94
+ subentries: list[dict[str, Any]] = field(default_factory=list)
95
+
96
+
97
+ @dataclass
98
+ class Fmid:
99
+ """Function modification identifier and its description/target zones."""
100
+
101
+ fmid: str | None = None
102
+ description: str | None = None
103
+ targetzones: list[str] | None = field(default_factory=list)
104
+
105
+
106
+ @dataclass
107
+ class Feature:
108
+ """Software product feature and the FMIDs that implement it."""
109
+
110
+ feature: str | None = None
111
+ fmids: list[Fmid] = field(default_factory=list)
112
+
113
+
114
+ @dataclass
115
+ class Product:
116
+ """SMP/E-managed product metadata and service lifecycle dates."""
117
+
118
+ prodname: str | None = None
119
+ productid: str | None = None
120
+ release: str | None = None
121
+ vendor: str | None = None
122
+ generalavailability: pendulum.DateTime | None = None
123
+ endofservice: pendulum.DateTime | None = None
124
+ url: str | None = None
125
+ productinfofileversion: str | None = None
126
+ features: list[Feature] = field(default_factory=list)
127
+
128
+
129
+ @dataclass
130
+ class NonSmpeProduct:
131
+ """Product metadata for a product not managed by SMP/E."""
132
+
133
+ prodname: str | None = None
134
+ productid: str | None = None
135
+ release: str | None = None
136
+ vendor: str | None = None
137
+ url: str | None = None
138
+ features: list[str] = field(default_factory=list)
139
+ generalavailability: pendulum.DateTime | None = None
140
+ endofservice: pendulum.DateTime | None = None
141
+
142
+
143
+ @dataclass
144
+ class WorkflowLocation:
145
+ """Location details for a workflow associated with a software instance."""
146
+
147
+ smptype: str | None = None
148
+ smpname: str | None = None
149
+ dsname: str | None = None
150
+ path: str | None = None
151
+
152
+
153
+ @dataclass
154
+ class SoftwareInstanceWorkflow:
155
+ """Workflow definition associated with a software instance."""
156
+
157
+ name: str | None = None
158
+ description: str | None = None
159
+ location: WorkflowLocation = field(default_factory=WorkflowLocation)
160
+ performonhostsystem: bool | None = None
161
+
162
+
163
+ @dataclass
164
+ class DataSetProperty:
165
+ """Property values for a data set DDDEF in a software instance."""
166
+
167
+ dddefname: str | None = None
168
+ zone: str | None = None
169
+ dsname: str | None = None
170
+ volume: str | None = None
171
+ dstype: str | None = None
172
+ properties: list[dict[str, str]] = field(default_factory=list)
173
+
174
+
175
+ @dataclass
176
+ class DataSetPropertyLabel:
177
+ """Display label associated with a data set property name."""
178
+
179
+ propertyname: str | None = None
180
+ label: str | None = None
181
+
182
+
183
+ @dataclass
184
+ class ProductProperty:
185
+ """Property values associated with a product and release."""
186
+
187
+ prodid: str | None = None
188
+ release: str | None = None
189
+ prodname: str | None = None
190
+ properties: list[dict[str, str]] = field(default_factory=list)
191
+
192
+
193
+ @dataclass
194
+ class SoftwareInstance:
195
+ """A z/OSMF software instance and its associated inventory.
196
+
197
+ Construction requires either a global zone with at least one target zone,
198
+ or data-set references when no global zone is configured.
199
+
200
+ Raises:
201
+ ValueError: If neither a global zone nor data sets are provided, or if
202
+ a global zone is provided without target zones.
203
+ """
204
+
205
+ name: str
206
+ system: str
207
+ description: str | None = ""
208
+ uuid: str | None = field(default="", metadata={"json_name": "swiuuid"})
209
+ globalzone: str | None = None
210
+ targetzones: list[str] = field(default_factory=list)
211
+ categories: list[str] = field(default_factory=list)
212
+ productinforetrieved: pendulum.DateTime | None = None
213
+ lastmodified: pendulum.DateTime | None = None
214
+ modifiedby: str | None = None
215
+ created: pendulum.DateTime | None = None
216
+ createdby: str | None = None
217
+ locked: pendulum.DateTime | None = None
218
+ lockedby: str | None = None
219
+ datasets: list[DataSet] = field(default_factory=list)
220
+ products: list[Product] = field(default_factory=list)
221
+ nonsmpeproducts: list[NonSmpeProduct] = field(default_factory=list)
222
+ workflows: list[SoftwareInstanceWorkflow] = field(default_factory=list)
223
+ datasetproperties: list[DataSetProperty] = field(default_factory=list)
224
+ datasetpropertylabels: list[DataSetPropertyLabel] = field(default_factory=list)
225
+ productproperties: list[ProductProperty] = field(default_factory=list)
226
+
227
+ def __post_init__(self) -> None:
228
+ if not self.globalzone and not self.datasets:
229
+ raise ValueError("Either globalzone or datasets must be specified")
230
+ elif self.globalzone and not self.targetzones:
231
+ raise ValueError("Target zones must be specified when globalzone is set")
@@ -0,0 +1,99 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ import pendulum
4
+
5
+
6
+ @dataclass
7
+ class UpdateSelection:
8
+ """SYSMOD updates selected for a software-update process."""
9
+
10
+ updates: list[str] = field(default_factory=list)
11
+
12
+
13
+ @dataclass
14
+ class UpdateResult:
15
+ """Selection and application result for one update in a process."""
16
+
17
+ fmid: str | None = None
18
+ selectStatus: str | None = field(default=None)
19
+ name: str | None = None
20
+
21
+
22
+ @dataclass
23
+ class UpdateErrorResults:
24
+ """SMP/E return code and messages associated with a failed update."""
25
+
26
+ smpe_rc: int
27
+ message: str
28
+ detailedMessage: str
29
+
30
+
31
+ @dataclass
32
+ class UpdateProcess:
33
+ """Status, selection, timing, and results for a software-update process."""
34
+
35
+ targetzone: str
36
+ selection: UpdateSelection
37
+ notes: str
38
+ processid: int
39
+ started: pendulum.DateTime
40
+ status: str
41
+ step: str
42
+ lastmodified: pendulum.DateTime | None = None
43
+ completed: pendulum.DateTime | None = None
44
+ error_results: UpdateErrorResults | None = None
45
+ updates: list[UpdateResult] = field(default_factory=list)
46
+ holds: list[str] = field(default_factory=list)
47
+
48
+
49
+ @dataclass
50
+ class SoftwareUpdateZone:
51
+ """Installation status of a software update in one zone."""
52
+
53
+ zone: str
54
+ status: str
55
+ installed: pendulum.DateTime | None
56
+
57
+
58
+ @dataclass
59
+ class SoftwareUpdate:
60
+ """Software-update metadata and its status across zones."""
61
+
62
+ name: str | None = None
63
+ type: str | None = None
64
+ fmid: str | None = None
65
+ zones: list[SoftwareUpdateZone] = field(default_factory=list)
66
+
67
+
68
+ @dataclass
69
+ class MissingUpdateResolver:
70
+ """Update that can resolve a hold on a missing update."""
71
+
72
+ name: str | None = None
73
+ received: bool | None = None
74
+
75
+
76
+ @dataclass
77
+ class MissingUpdate:
78
+ """Critical missing update, its hold details, and resolving updates."""
79
+
80
+ name: str | None = None
81
+ holdclass: str | None = None
82
+ holdsymptom: str | None = None
83
+ heldsysmod: str | None = None
84
+ fmid: str | None = None
85
+ fmiddesc: str | None = None
86
+ resolvers: list[MissingUpdateResolver] | None = field(default_factory=list)
87
+ tgtzones: list[str] = field(default_factory=list)
88
+
89
+
90
+ @dataclass
91
+ class MissingFixcat:
92
+ """Fix category missing from an instance and its resolving updates."""
93
+
94
+ name: str | None = None
95
+ fixcats: list[str] = field(default_factory=list)
96
+ fmid: str | None = None
97
+ fmiddesc: str | None = None
98
+ resolvers: list[MissingUpdateResolver] | None = field(default_factory=list)
99
+ tgtzones: list[str] = field(default_factory=list)
@@ -0,0 +1,28 @@
1
+ from dataclasses import dataclass, field
2
+
3
+
4
+ @dataclass
5
+ class System:
6
+ """System record returned by the z/OSMF topology API."""
7
+
8
+ name: str = field(metadata={"json_name": "systemName"})
9
+ nickName: str = field(metadata={"json_name": "systemNickName"})
10
+ sysplex: str = field(metadata={"json_name": "sysplexName"})
11
+ groups: str = field(default="", metadata={"json_name": "groupNames"})
12
+ cpcSerial: str = ""
13
+ zosVR: str = ""
14
+ jesType: str = ""
15
+ jesMemberName: str = ""
16
+ httpProxyName: str = ""
17
+ ftpDestinationName: str = ""
18
+ url: str = ""
19
+ cpcName: str = ""
20
+
21
+
22
+ @dataclass
23
+ class Group:
24
+ """Group record and the system nicknames that belong to it."""
25
+
26
+ name: str
27
+ systems: str = field(metadata={"json_name": "systemNickNames"})
28
+ description: str = ""