Skip to content

Commit d836198

Browse files
add method to issue ssh commands
Signed-off-by: Olamide Ojo <[email protected]>
1 parent 91366a0 commit d836198

File tree

43 files changed

+741
-136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+741
-136
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"./src/zos_files",
1313
"./src/zos_jobs",
1414
"./src/zos_tso",
15-
"./src/zosmf"
15+
"./src/zosmf",
16+
"./src/zos_uss"
1617
],
1718
"python.testing.pytestArgs": ["tests"],
1819
"python.testing.pytestEnabled": true,

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to the Zowe Client Python SDK will be documented in this file.
44

5+
## Recent Changes
6+
7+
- Added execute_command method to issue SSH commands. [#253](https://github.com/zowe/zowe-client-python-sdk/issues/253)
8+
59
## `1.0.0-dev22`
610

711
### Enhancements

docs/source/_ext/zowe_autodoc.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ def main():
5858
if len(class_names) == 1:
5959
rst_name = f"{py_name[:-3]}.rst"
6060
rst_contents = render_template(
61-
CLASS_TEMPLATE, {"fullname": f"{sdk_name}.{pkg_name}.{class_names[0]}", "header": class_names[0]}
61+
CLASS_TEMPLATE,
62+
{
63+
"fullname": f"{sdk_name}.{pkg_name}.{class_names[0]}",
64+
"header": class_names[0],
65+
},
6266
)
6367
with open(f"docs/source/classes/{sdk_name}/{rst_name}", "w", encoding="utf-8") as f:
6468
f.write(rst_contents)
@@ -71,9 +75,16 @@ def main():
7175
rst_name = f"{class_name.lower()}.rst"
7276
rst_contents = render_template(
7377
CLASS_TEMPLATE,
74-
{"fullname": f"{sdk_name}.{pkg_name}.{module_name}.{class_name}", "header": class_name},
78+
{
79+
"fullname": f"{sdk_name}.{pkg_name}.{module_name}.{class_name}",
80+
"header": class_name,
81+
},
7582
)
76-
with open(f"docs/source/classes/{sdk_name}/{module_name}/{rst_name}", "w", encoding="utf-8") as f:
83+
with open(
84+
f"docs/source/classes/{sdk_name}/{module_name}/{rst_name}",
85+
"w",
86+
encoding="utf-8",
87+
) as f:
7788
f.write(rst_contents)
7889
child_rst_names.append(rst_name)
7990
rst_name = f"{module_name}/index.rst"
@@ -104,7 +115,11 @@ def main():
104115

105116
rst_contents = render_template(
106117
INDEX_TEMPLATE,
107-
{"filelist": "\n ".join(f"{name}/index" for name in sdk_names), "header": "Classes", "maxdepth": 3},
118+
{
119+
"filelist": "\n ".join(f"{name}/index" for name in sdk_names),
120+
"header": "Classes",
121+
"maxdepth": 3,
122+
},
108123
)
109124
with open(f"docs/source/classes/index.rst", "w", encoding="utf-8") as f:
110125
f.write(rst_contents)

docs/source/packages/files.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,27 @@ Reference
1818
:maxdepth: 2
1919

2020
../classes/zos_files/index
21+
22+
Interaction with USS via z/OSMF
23+
===============================
24+
25+
The Zowe Client Python SDK leverages the z/OS Management Facility (z/OSMF) REST interface to interact with Unix System Services (USS) on z/OS.
26+
Rather than connecting directly to USS, our implementation uses z/OSMF as a standardized conduit for file operations and other USS functionalities.
27+
This design offers several benefits:
28+
29+
- **Standardization:** z/OSMF provides a consistent REST API for interacting with various z/OS components, including USS.
30+
- **Security & Maintainability:** By utilizing z/OSMF, we benefit from its built-in authentication, logging, and error-handling mechanisms, making integration more robust.
31+
- **Simplified Integration:** The REST-based approach reduces the complexity of direct USS interactions, allowing for easier maintenance and future enhancements.
32+
33+
In summary, while it might appear that all USS functionality is routed through z/OSMF, this approach is intentional, providing a secure and manageable interface to z/OS USS.
34+
35+
Paramiko and Encoding Considerations
36+
======================================
37+
38+
When using Paramiko to interact with z/OS UNIX System Services (USS), it is important to consider encoding and the handling of special characters.
39+
By default, Paramiko decodes responses using UTF-8, but z/OS USS may return data in an EBCDIC codepage (e.g., IBM-1047, IBM-037, etc.).
40+
This mismatch can result in unexpected output, particularly with special characters like ``ööö``, ``👍``, or ``🔟``.
41+
42+
If you experience unexpected characters in your output, please check your terminal's encoding settings (for example, using ``locale`` on Linux).
43+
Note that certain commands may change the terminal's codepage, which can affect subsequent outputs.
44+
In such cases, resetting the terminal's encoding after command execution is recommended.

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ deepmerge==1.1.0
33
jsonschema==4.17.3
44
PyYAML==6.0.1
55
requests==2.32.0
6+
paramiko==3.5.0
67

78
# Dev deps
89
black
@@ -24,3 +25,4 @@ wheel
2425
-e ./src/zos_jobs
2526
-e ./src/zos_tso
2627
-e ./src/zosmf
28+
-e ./src/zos_uss

src/core/zowe/core_for_zowe_sdk/config_file.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1010
Copyright Contributors to the Zowe Project.
1111
"""
12+
1213
import json
1314
import os.path
1415
import re

src/core/zowe/core_for_zowe_sdk/credential_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ def save_secure_props() -> None:
7979
# Delete the existing credential
8080
CredentialManager._delete_credential(constants["ZoweServiceName"], constants["ZoweAccountName"])
8181
CredentialManager._set_credential(
82-
constants["ZoweServiceName"], constants["ZoweAccountName"], encoded_credential
82+
constants["ZoweServiceName"],
83+
constants["ZoweAccountName"],
84+
encoded_credential,
8385
)
8486

8587
@staticmethod

src/core/zowe/core_for_zowe_sdk/logger.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ class Log:
3939
file_handler: logging.FileHandler = logging.FileHandler(os.path.join(dirname, "python_sdk_logs.log"))
4040
file_handler.setLevel(logging.INFO)
4141
file_handler.setFormatter(
42-
logging.Formatter("[%(asctime)s] [%(levelname)s] [%(name)s] - %(message)s", "%m/%d/%Y %I:%M:%S %p")
42+
logging.Formatter(
43+
"[%(asctime)s] [%(levelname)s] [%(name)s] - %(message)s",
44+
"%m/%d/%Y %I:%M:%S %p",
45+
)
4346
)
4447
console_handler: logging.StreamHandler = logging.StreamHandler()
4548

src/core/zowe/core_for_zowe_sdk/profile_manager.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,14 @@
1717
from typing import Optional
1818

1919
from deepmerge import always_merger
20-
from jsonschema.exceptions import (
21-
FormatError,
22-
SchemaError,
23-
UndefinedTypeCheck,
24-
UnknownType,
25-
ValidationError,
26-
)
20+
from jsonschema.exceptions import FormatError, SchemaError, UndefinedTypeCheck, UnknownType, ValidationError
2721

2822
from .config_file import ConfigFile, Profile
2923
from .credential_manager import CredentialManager
30-
from .custom_warnings import (
31-
ConfigNotFoundWarning,
32-
ProfileNotFoundWarning,
33-
SecurePropsNotFoundWarning,
34-
)
24+
from .custom_warnings import ConfigNotFoundWarning, ProfileNotFoundWarning, SecurePropsNotFoundWarning
3525
from .exceptions import ProfileNotFound, SecureProfileLoadFailed, SecureValuesNotFound
3626
from .logger import Log
37-
from .profile_constants import (
38-
BASE_PROFILE,
39-
GLOBAL_CONFIG_NAME,
40-
TEAM_CONFIG,
41-
USER_CONFIG,
42-
)
27+
from .profile_constants import BASE_PROFILE, GLOBAL_CONFIG_NAME, TEAM_CONFIG, USER_CONFIG
4328

4429
HAS_KEYRING = True
4530

@@ -272,7 +257,9 @@ def get_profile(
272257
cfg_profile = Profile()
273258
try:
274259
cfg_profile = cfg.get_profile(
275-
profile_name=profile_name, profile_type=profile_type, validate_schema=validate_schema
260+
profile_name=profile_name,
261+
profile_type=profile_type,
262+
validate_schema=validate_schema,
276263
)
277264
except ValidationError as exc:
278265
logger.error(f"Instance was invalid under the provided $schema property, {exc}")
@@ -485,7 +472,12 @@ def get_highest_priority_layer(self, json_path: str) -> Optional[ConfigFile]:
485472
"""
486473
highest_layer = None
487474
longest_match = ""
488-
layers = [self.__project_user_config, self.__project_config, self.__global_user_config, self.__global_config]
475+
layers = [
476+
self.__project_user_config,
477+
self.__project_config,
478+
self.__global_user_config,
479+
self.__global_config,
480+
]
489481

490482
original_name = layers[0].get_profile_name_from_path(json_path)
491483

@@ -556,7 +548,12 @@ def set_profile(self, profile_path: str, profile_data: dict) -> None:
556548

557549
def save(self) -> None:
558550
"""Save the layers (configuration files) to disk."""
559-
layers = [self.__project_user_config, self.__project_config, self.__global_user_config, self.__global_config]
551+
layers = [
552+
self.__project_user_config,
553+
self.__project_config,
554+
self.__global_user_config,
555+
self.__global_config,
556+
]
560557

561558
for layer in layers:
562559
layer.save(False)

src/core/zowe/core_for_zowe_sdk/request_handler.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ def __handle_ssl_warnings(self):
4444
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
4545

4646
def perform_request(
47-
self, method: str, request_arguments: dict, expected_code: list = [200], stream: bool = False
47+
self,
48+
method: str,
49+
request_arguments: dict,
50+
expected_code: list = [200],
51+
stream: bool = False,
4852
) -> Union[str, bytes, dict, None]:
4953
"""Execute an HTTP/HTTPS requests from given arguments and return validated response (JSON).
5054
@@ -99,7 +103,11 @@ def __send_request(self, stream: bool = False):
99103
Flag indicates whether it is a streaming requests.
100104
"""
101105
self.__response = self.session.request(
102-
method=self.__method, stream=stream, **self.session_arguments, **self.__request_arguments)
106+
method=self.__method,
107+
stream=stream,
108+
**self.session_arguments,
109+
**self.__request_arguments,
110+
)
103111

104112
def __del__(self):
105113
"""Clean up the REST session object once it is no longer needed anymore."""
@@ -123,7 +131,11 @@ def __validate_response(self):
123131
f"Expected: {self.__response.status_code}\n"
124132
f"Request output: {self.__response.text}"
125133
)
126-
raise UnexpectedStatus(self.__expected_code, self.__response.status_code, self.__response.text)
134+
raise UnexpectedStatus(
135+
self.__expected_code,
136+
self.__response.status_code,
137+
self.__response.text,
138+
)
127139
else:
128140
output_str = str(self.__response.request.url)
129141
output_str += "\n" + str(self.__response.request.headers)

0 commit comments

Comments
 (0)