portforward 0.7.3__cp311-cp311-musllinux_1_2_x86_64.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.
Potentially problematic release.
This version of portforward might be problematic. Click here for more details.
- portforward/__init__.py +253 -0
- portforward/_portforward.cpython-311-x86_64-linux-musl.so +0 -0
- portforward/_portforward.pyi +17 -0
- portforward/py.typed +0 -0
- portforward-0.7.3.dist-info/METADATA +138 -0
- portforward-0.7.3.dist-info/RECORD +10 -0
- portforward-0.7.3.dist-info/WHEEL +4 -0
- portforward-0.7.3.dist-info/licenses/AUTHORS.rst +13 -0
- portforward-0.7.3.dist-info/licenses/LICENSE +21 -0
- portforward.libs/libgcc_s-1e52349c.so.1 +0 -0
portforward/__init__.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Easy Kubernetes Port-Forward For Python
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.7.3"
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import contextlib
|
|
9
|
+
import ipaddress
|
|
10
|
+
import os
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Generator, Optional, Union
|
|
14
|
+
|
|
15
|
+
from portforward import _portforward
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PortforwardError(Exception):
|
|
19
|
+
"""Will be raised when something went wrong while the port-forward process."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LogLevel(Enum):
|
|
23
|
+
DEBUG = 0
|
|
24
|
+
INFO = 1
|
|
25
|
+
WARN = 2
|
|
26
|
+
ERROR = 3
|
|
27
|
+
OFF = 4
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@contextlib.contextmanager
|
|
31
|
+
def forward(
|
|
32
|
+
namespace: str,
|
|
33
|
+
pod_or_service: str,
|
|
34
|
+
from_port: int,
|
|
35
|
+
to_port: int,
|
|
36
|
+
config_path: Optional[str] = None,
|
|
37
|
+
waiting: float = 0.1,
|
|
38
|
+
log_level: LogLevel = LogLevel.INFO,
|
|
39
|
+
kube_context: str = "",
|
|
40
|
+
bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
|
|
41
|
+
) -> Generator["PortForwarder", None, None]:
|
|
42
|
+
"""
|
|
43
|
+
Connects to a **pod or service** and tunnels traffic from a local port to
|
|
44
|
+
this target. It uses the kubectl kube config from the home dir if no path
|
|
45
|
+
is provided.
|
|
46
|
+
|
|
47
|
+
The libary will figure out for you if it has to target a pod or service.
|
|
48
|
+
|
|
49
|
+
It will fall back to in-cluster-config in case no kube config file exists.
|
|
50
|
+
|
|
51
|
+
(Best consumed as context manager.)
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> import portforward
|
|
55
|
+
>>> with portforward.forward("test", "web-svc", 9000, 80):
|
|
56
|
+
>>> # Do work
|
|
57
|
+
|
|
58
|
+
:param namespace: Target namespace
|
|
59
|
+
:param pod_or_service: Name of target Pod or service
|
|
60
|
+
:param from_port: Local port, or 0 to use any free port
|
|
61
|
+
:param to_port: Port inside the pod
|
|
62
|
+
:param config_path: Path for loading kube config
|
|
63
|
+
:param waiting: Delay in seconds
|
|
64
|
+
:param log_level: Level of logging
|
|
65
|
+
:param kube_context: Target kubernetes context (fallback is current context)
|
|
66
|
+
:param bind_ip: To which IP shall the portforward be bind
|
|
67
|
+
:return: forwarder to manual stop the forwarding
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
forwarder = PortForwarder(
|
|
71
|
+
namespace,
|
|
72
|
+
pod_or_service,
|
|
73
|
+
from_port,
|
|
74
|
+
to_port,
|
|
75
|
+
config_path,
|
|
76
|
+
waiting,
|
|
77
|
+
log_level,
|
|
78
|
+
kube_context,
|
|
79
|
+
bind_ip,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
forwarder.forward()
|
|
84
|
+
|
|
85
|
+
yield forwarder
|
|
86
|
+
|
|
87
|
+
except RuntimeError as err:
|
|
88
|
+
# Suppress extension exception
|
|
89
|
+
raise PortforwardError(err) from None
|
|
90
|
+
|
|
91
|
+
finally:
|
|
92
|
+
forwarder.stop()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class PortForwarder:
|
|
96
|
+
"""Use the same args as the `portforward.forward` method."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
namespace: str,
|
|
101
|
+
pod_or_service: str,
|
|
102
|
+
from_port: int,
|
|
103
|
+
to_port: int,
|
|
104
|
+
config_path: Optional[str] = None,
|
|
105
|
+
waiting: float = 0.1,
|
|
106
|
+
log_level: LogLevel = LogLevel.INFO,
|
|
107
|
+
kube_context: str = "",
|
|
108
|
+
bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
self._async_forwarder = AsyncPortForwarder(
|
|
111
|
+
namespace,
|
|
112
|
+
pod_or_service,
|
|
113
|
+
from_port,
|
|
114
|
+
to_port,
|
|
115
|
+
config_path,
|
|
116
|
+
waiting,
|
|
117
|
+
log_level,
|
|
118
|
+
kube_context,
|
|
119
|
+
bind_ip,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def forward(self):
|
|
123
|
+
asyncio.run(self._async_forwarder.forward())
|
|
124
|
+
|
|
125
|
+
def stop(self):
|
|
126
|
+
asyncio.run(self._async_forwarder.stop())
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def is_stopped(self):
|
|
130
|
+
return self._async_forwarder.is_stopped
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def from_port(self):
|
|
134
|
+
"""The local port that was actually used for the portforward."""
|
|
135
|
+
return self._async_forwarder.from_port
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class AsyncPortForwarder:
|
|
139
|
+
"""Use the same args as the `portforward.forward` method."""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
namespace: str,
|
|
144
|
+
pod_or_service: str,
|
|
145
|
+
from_port: int,
|
|
146
|
+
to_port: int,
|
|
147
|
+
config_path: Optional[str] = None,
|
|
148
|
+
waiting: float = 0.1,
|
|
149
|
+
log_level: LogLevel = LogLevel.INFO,
|
|
150
|
+
kube_context: str = "",
|
|
151
|
+
bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
self.namespace: str = _validate_str("namespace", namespace)
|
|
154
|
+
self.pod_or_service: str = _validate_str("pod_or_service", pod_or_service)
|
|
155
|
+
self.to_port: int = _validate_port("to_port", to_port)
|
|
156
|
+
self.log_level: LogLevel = _validate_log(log_level)
|
|
157
|
+
self.waiting: float = waiting
|
|
158
|
+
|
|
159
|
+
self.config_path: str = _config_path(config_path)
|
|
160
|
+
self.kube_context: str = _kube_context(kube_context)
|
|
161
|
+
|
|
162
|
+
_validate_port("from_port", from_port)
|
|
163
|
+
bind_ip = _validate_ip_address(bind_ip)
|
|
164
|
+
|
|
165
|
+
self.actual_pod_name: str = ""
|
|
166
|
+
self.from_port: int = 0
|
|
167
|
+
self._is_stopped: bool = False
|
|
168
|
+
self.bind_address: str = f"{bind_ip}:{from_port}"
|
|
169
|
+
|
|
170
|
+
async def forward(self):
|
|
171
|
+
(self.actual_pod_name, self.from_port) = await _portforward.forward(
|
|
172
|
+
self.namespace,
|
|
173
|
+
self.pod_or_service,
|
|
174
|
+
self.bind_address,
|
|
175
|
+
self.to_port,
|
|
176
|
+
self.config_path,
|
|
177
|
+
self.log_level.value,
|
|
178
|
+
self.kube_context,
|
|
179
|
+
)
|
|
180
|
+
self._is_stopped = False
|
|
181
|
+
|
|
182
|
+
async def stop(self):
|
|
183
|
+
await _portforward.stop(
|
|
184
|
+
self.namespace, self.actual_pod_name, self.to_port, self.log_level.value
|
|
185
|
+
)
|
|
186
|
+
self._is_stopped = True
|
|
187
|
+
|
|
188
|
+
def is_stopped(self):
|
|
189
|
+
return self._is_stopped
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ===== PRIVATE =====
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _validate_str(arg_name, arg) -> str:
|
|
196
|
+
if arg is None or not isinstance(arg, str):
|
|
197
|
+
raise ValueError(f"{arg_name}={arg} is not a valid str")
|
|
198
|
+
|
|
199
|
+
if len(arg) == 0:
|
|
200
|
+
raise ValueError(f"{arg_name} cannot be an empty str")
|
|
201
|
+
|
|
202
|
+
return arg
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _validate_port(arg_name, arg) -> int:
|
|
206
|
+
in_range = arg is not None and 0 <= arg < 65536
|
|
207
|
+
if arg is None or not isinstance(arg, int) or not in_range:
|
|
208
|
+
raise ValueError(f"{arg_name}={arg} is not a valid port")
|
|
209
|
+
|
|
210
|
+
return arg
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _validate_log(log_level):
|
|
214
|
+
if not isinstance(log_level, LogLevel):
|
|
215
|
+
raise ValueError(f"log_level={log_level} is not a valid LogLevel")
|
|
216
|
+
|
|
217
|
+
return log_level
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _validate_ip_address(ip_address):
|
|
221
|
+
if not ip_address:
|
|
222
|
+
return "127.0.0.1"
|
|
223
|
+
|
|
224
|
+
if isinstance(ip_address, ipaddress.IPv4Address) or isinstance(
|
|
225
|
+
ip_address, ipaddress.IPv4Address
|
|
226
|
+
):
|
|
227
|
+
return str(ip_address)
|
|
228
|
+
|
|
229
|
+
return str(ipaddress.ip_address(ip_address))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _config_path(config_path_arg) -> str:
|
|
233
|
+
if config_path_arg and not isinstance(config_path_arg, str):
|
|
234
|
+
raise ValueError(f"config_path={config_path_arg} is not a valid str")
|
|
235
|
+
|
|
236
|
+
elif config_path_arg:
|
|
237
|
+
return config_path_arg
|
|
238
|
+
|
|
239
|
+
alt_path = str(Path.home() / ".kube" / "config")
|
|
240
|
+
|
|
241
|
+
config_path = os.environ.get("KUBECONFIG", alt_path)
|
|
242
|
+
|
|
243
|
+
return config_path if os.path.isfile(config_path) else ""
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _kube_context(context):
|
|
247
|
+
if not context:
|
|
248
|
+
return ""
|
|
249
|
+
|
|
250
|
+
if not isinstance(context, str):
|
|
251
|
+
raise ValueError(f"kube_context={context} is not a valid str")
|
|
252
|
+
|
|
253
|
+
return context
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rust native module / Python C Extension
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
async def forward(
|
|
6
|
+
namespace: str,
|
|
7
|
+
pod_or_service: str,
|
|
8
|
+
bind_address: str,
|
|
9
|
+
to_port: int,
|
|
10
|
+
config_path: str,
|
|
11
|
+
log_level: int,
|
|
12
|
+
kube_context: str,
|
|
13
|
+
) -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
async def stop(namespace: str, actual_pod: str, to_port: int, log_level: int) -> None:
|
|
17
|
+
pass
|
portforward/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: portforward
|
|
3
|
+
Version: 0.7.3
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
License-File: AUTHORS.rst
|
|
15
|
+
Summary: Easy Kubernetes Port-Forward For Python
|
|
16
|
+
Keywords: portforward,kubernetes,k8s
|
|
17
|
+
Author-email: Sebastian Ziemann <corka149@mailbox.org>
|
|
18
|
+
License: MIT License
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
21
|
+
Project-URL: Documentation, https://portforward.readthedocs.io
|
|
22
|
+
Project-URL: Repository, https://github.com/pytogo/portforward.git
|
|
23
|
+
Project-URL: Changelog, https://github.com/pytogo/portforward/blob/main/HISTORY.rst
|
|
24
|
+
|
|
25
|
+
===========
|
|
26
|
+
portforward
|
|
27
|
+
===========
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
.. image:: https://img.shields.io/pypi/v/portforward.svg
|
|
31
|
+
:target: https://pypi.python.org/pypi/portforward
|
|
32
|
+
|
|
33
|
+
.. image:: https://img.shields.io/pypi/status/portforward.svg
|
|
34
|
+
:target: https://pypi.python.org/pypi/portforward
|
|
35
|
+
|
|
36
|
+
.. image:: https://img.shields.io/pypi/dm/portforward
|
|
37
|
+
:alt: PyPI - Downloads
|
|
38
|
+
|
|
39
|
+
.. image:: https://readthedocs.org/projects/portforward/badge/?version=latest
|
|
40
|
+
:target: https://portforward.readthedocs.io/en/latest/?version=latest
|
|
41
|
+
:alt: Documentation Status
|
|
42
|
+
|
|
43
|
+
.. image:: https://github.com/pytogo/portforward/actions/workflows/python-app.yml/badge.svg
|
|
44
|
+
:target: https://github.com/pytogo/portforward/actions
|
|
45
|
+
:alt: Build status
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Easy Kubernetes Port-Forward For Python
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
* Free software: MIT license
|
|
53
|
+
* Documentation: https://portforward.readthedocs.io.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Installation
|
|
57
|
+
-----------------------------
|
|
58
|
+
|
|
59
|
+
Wheels are available for:
|
|
60
|
+
|
|
61
|
+
* **Windows** (architectures: ``x64``, ``x86``)
|
|
62
|
+
* **macOS** (architectures: ``x86_64``, ``aarch64``)
|
|
63
|
+
* **Linux** (architectures: ``x86_64``, ``x86``, ``aarch64``, ``armv7``, ``s390x``, ``ppc64le``)
|
|
64
|
+
|
|
65
|
+
Musllinux wheels (Alpine‑compatible) are provided for ``x86_64``, ``x86``, ``aarch64`` and ``armv7``.
|
|
66
|
+
|
|
67
|
+
with Python versions:
|
|
68
|
+
|
|
69
|
+
* 3.9
|
|
70
|
+
* 3.10
|
|
71
|
+
* 3.11
|
|
72
|
+
* 3.12
|
|
73
|
+
* 3.13
|
|
74
|
+
|
|
75
|
+
**Requirements for installation from source**
|
|
76
|
+
|
|
77
|
+
The following things are required when there is no wheel available for the target system.
|
|
78
|
+
|
|
79
|
+
* `Rust` installed and available in the path (https://www.rust-lang.org/tools/install)
|
|
80
|
+
* `Python` (at least v3.7 - below was never tested but might work)
|
|
81
|
+
|
|
82
|
+
Pip knows how to install ``portforward``.
|
|
83
|
+
|
|
84
|
+
.. code-block::
|
|
85
|
+
|
|
86
|
+
pip install portforward
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Quickstart
|
|
90
|
+
----------
|
|
91
|
+
|
|
92
|
+
.. code-block:: Python
|
|
93
|
+
|
|
94
|
+
import requests
|
|
95
|
+
|
|
96
|
+
import portforward
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
namespace = "test"
|
|
101
|
+
pod_name = "web" # You can also use a service name instead
|
|
102
|
+
local_port = 9000 # from port
|
|
103
|
+
pod_port = 80 # to port
|
|
104
|
+
|
|
105
|
+
# No path to kube config provided - will use default from $HOME/.kube/config
|
|
106
|
+
with portforward.forward(namespace, pod_name, local_port, pod_port):
|
|
107
|
+
response = requests.get("http://localhost:9000")
|
|
108
|
+
print(f"Done: \n'{response.status_code}'\n'{response.text[:20]}...'")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
main()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
Features
|
|
116
|
+
--------
|
|
117
|
+
|
|
118
|
+
* Native Kubernetes port-forwarding with the ``.kube/config`` from the home dir
|
|
119
|
+
or any other path to config.
|
|
120
|
+
* Portforward for pods and services - the lib will first look for a pod with matching name then for
|
|
121
|
+
a service
|
|
122
|
+
* Waiting for a pod to become ready
|
|
123
|
+
* Multiple forwards per pod or service
|
|
124
|
+
* As context manager, sync or async client
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Development
|
|
128
|
+
-----------
|
|
129
|
+
|
|
130
|
+
In case you want to develop on this library itself please take a look at the CONTRIBUTING page.
|
|
131
|
+
|
|
132
|
+
Credits
|
|
133
|
+
-------
|
|
134
|
+
|
|
135
|
+
This project is enabled by PyO3_.
|
|
136
|
+
|
|
137
|
+
.. _PyO3: https://pyo3.rs
|
|
138
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
portforward-0.7.3.dist-info/METADATA,sha256=Wd7jjideXh0I793Cl3OIZ5klfMP0F61XnQHIyy3u82w,3916
|
|
2
|
+
portforward-0.7.3.dist-info/WHEEL,sha256=-kBN3RVDl5guKmGr7STEq-XPtEYPLPgpYYDb8tgmlOc,107
|
|
3
|
+
portforward-0.7.3.dist-info/licenses/LICENSE,sha256=n2aq0UH0YZkyF_5hougjNi5gzXIUapEEqEUn6FwAcFE,1075
|
|
4
|
+
portforward-0.7.3.dist-info/licenses/AUTHORS.rst,sha256=YQwE3_FUEuGVHbxYUa4NQMlG28QBGwcCYEoa2MP9Xgk,163
|
|
5
|
+
portforward.libs/libgcc_s-1e52349c.so.1,sha256=Ani0u_W_tB8ESsFePO4D2mn2lrgWLhyFdw6RETxBTYM,437537
|
|
6
|
+
portforward/_portforward.pyi,sha256=KZSR_2INAUDtpwbVFA9e6cLs3KBwgMw-hgtirmMRZgY,336
|
|
7
|
+
portforward/__init__.py,sha256=sys9oCQQ2o2z6HcyEo3ioPhgvxQ6oVn3BP5nS0G5Qf0,6872
|
|
8
|
+
portforward/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
portforward/_portforward.cpython-311-x86_64-linux-musl.so,sha256=XeA9r9Mpt5cle8sw3y2bNwz6di6bb-_swZlBaeyhro0,16253457
|
|
10
|
+
portforward-0.7.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021, Sebastian Ziemann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
Binary file
|