Skip to content

Commit

Permalink
Merge pull request #40 from anvilpete/port0
Browse files Browse the repository at this point in the history
Allow binding to local port 0
  • Loading branch information
corka149 authored Oct 18, 2024
2 parents caed8f8 + 5b589ba commit d9ee0ef
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 6 deletions.
12 changes: 9 additions & 3 deletions python/portforward/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def forward(
:param namespace: Target namespace
:param pod_or_service: Name of target Pod or service
:param from_port: Local port
:param from_port: Local port, or 0 to use any free port
:param to_port: Port inside the pod
:param config_path: Path for loading kube config
:param waiting: Delay in seconds
Expand Down Expand Up @@ -129,6 +129,11 @@ def stop(self):
def is_stopped(self):
return self._async_forwarder.is_stopped

@property
def from_port(self):
"""The local port that was actually used for the portforward."""
return self._async_forwarder.from_port


class AsyncPortForwarder:
"""Use the same args as the `portforward.forward` method."""
Expand Down Expand Up @@ -158,11 +163,12 @@ def __init__(
bind_ip = _validate_ip_address(bind_ip)

self.actual_pod_name: str = ""
self.from_port: int = 0
self._is_stopped: bool = False
self.bind_address: str = f"{bind_ip}:{from_port}"

async def forward(self):
self.actual_pod_name = await _portforward.forward(
(self.actual_pod_name, self.from_port) = await _portforward.forward(
self.namespace,
self.pod_or_service,
self.bind_address,
Expand Down Expand Up @@ -200,7 +206,7 @@ def _validate_str(arg_name, arg) -> str:


def _validate_port(arg_name, arg) -> int:
in_range = arg and 0 < arg < 65536
in_range = arg is not None and 0 <= arg < 65536
if arg is None or not isinstance(arg, int) or not in_range:
raise ValueError(f"{arg_name}={arg} is not a valid port")

Expand Down
8 changes: 5 additions & 3 deletions src/portforward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ pub struct ForwardConfig {
kube_context: String,
}

/// Creates a connection to a pod. It returns the actual pod name for the portforward.
/// Creates a connection to a pod. It returns a `(pod_name, from_port)` tuple
/// with the actual pod name and local port used for the portforward.
/// It differs from `pod_or_service` when `pod_or_service` represents a service.
pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {
pub async fn forward(config: ForwardConfig) -> anyhow::Result<(String, u16)> {
debug!("{:?}", config);

let client_config = load_config(&config.config_path, &config.kube_context).await?;
Expand All @@ -58,6 +59,7 @@ pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {

let addr = SocketAddr::from_str(&config.bind_address).with_context(move || config.bind_address)?;
let tcp_listener = TcpListener::bind(addr).await?;
let from_port = tcp_listener.local_addr()?.port();
let forward_task = setup_forward_task(
tcp_listener,
rx,
Expand All @@ -68,7 +70,7 @@ pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {

tokio::spawn(forward_task);

return Ok(q_name.pod_name);
return Ok((q_name.pod_name, from_port));
}

async fn load_config(
Expand Down
27 changes: 27 additions & 0 deletions tests/test_portforward.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,33 @@ def test_service_portforward_with_success(kind_cluster: KindCluster):
response: requests.Response = requests.get(url_2)
pytest.fail("Portforward should be closed after leaving the context manager")

def test_portforward_from_port_zero_assigns_port(kind_cluster: KindCluster):
# Arrange
_create_test_resources(kind_cluster)

pod_name = "test-pod"
config = str(kind_cluster.kubeconfig_path.absolute())

local_port = 0 # from port
pod_port = 3000 # to port

pf = portforward.forward(
TEST_NAMESPACE,
pod_name,
local_port,
pod_port,
config_path=config,
kube_context=TEST_CONTEXT,
)

# Act & Assert
with pf as forwarder:
assert not forwarder.is_stopped()
assert forwarder.from_port != 0
url = f"http://localhost:{forwarder.from_port}/ping"
response: requests.Response = requests.get(url)
assert response.status_code == 200


@pytest.mark.parametrize(
"namespace,pod,from_port,to_port",
Expand Down

0 comments on commit d9ee0ef

Please sign in to comment.