-
Notifications
You must be signed in to change notification settings - Fork 138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add udp and sctp support to port mappings #246
Changes from all commits
9581810
6e70ec8
b715f9c
7fdedf2
f86e039
95a4dff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,10 @@ | ||
use crate::{ | ||
core::{docker::Docker, env::Command, image::WaitFor}, | ||
core::{ | ||
docker::Docker, | ||
env::Command, | ||
image::WaitFor, | ||
ports::{MapToHostPort, Ports}, | ||
}, | ||
Image, | ||
}; | ||
use std::{fmt, marker::PhantomData}; | ||
|
@@ -128,13 +133,17 @@ impl<'d, I> Container<'d, I> { | |
/// This method panics if the given port is not mapped. | ||
/// Testcontainers is designed to be used in tests only. If a certain port is not mapped, the container | ||
/// is unlikely to be useful. | ||
pub fn get_host_port(&self, internal_port: u16) -> u16 { | ||
pub fn get_host_port<T>(&self, internal_port: T) -> T | ||
where | ||
T: fmt::Debug, | ||
Ports: MapToHostPort<T>, | ||
{ | ||
self.docker_client | ||
.ports(&self.id) | ||
.map_to_host_port(internal_port) | ||
.map_to_host_port(&internal_port) | ||
.unwrap_or_else(|| { | ||
panic!( | ||
"container {} does not expose port {}", | ||
"container {:?} does not expose port {:?}", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to print |
||
self.id, internal_port | ||
) | ||
}) | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,18 +1,33 @@ | ||||||
use std::collections::HashMap; | ||||||
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash)] | ||||||
struct Sctp(u16); | ||||||
#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash)] | ||||||
struct Tcp(u16); | ||||||
#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash)] | ||||||
struct Udp(u16); | ||||||
|
||||||
/// The exposed ports of a running container. | ||||||
#[derive(Debug, PartialEq, Default)] | ||||||
pub struct Ports { | ||||||
mapping: HashMap<u16, u16>, | ||||||
tcp: HashMap<Tcp, Tcp>, | ||||||
udp: HashMap<Udp, Udp>, | ||||||
sctp: HashMap<Sctp, Sctp>, | ||||||
} | ||||||
|
||||||
impl Ports { | ||||||
pub fn new(ports: HashMap<String, Option<Vec<HashMap<String, String>>>>) -> Self { | ||||||
let mapping = ports | ||||||
let tcp = HashMap::new(); | ||||||
let udp = HashMap::new(); | ||||||
let sctp = HashMap::new(); | ||||||
|
||||||
ports | ||||||
.into_iter() | ||||||
.filter_map(|(internal, external)| { | ||||||
// internal is '8332/tcp', split off the protocol ... | ||||||
let internal = internal.split('/').next()?; | ||||||
// internal is ')8332/tcp', split off the protocol ... | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A typo got into this comment :)
Suggested change
|
||||||
let mut iter = internal.split('/'); | ||||||
let internal = iter.next()?; | ||||||
let protocol = iter.next()?.to_string(); | ||||||
|
||||||
// external is a an optional list of maps: [ { "HostIp": "0.0.0.0", "HostPort": "33078" } ] | ||||||
// get the first entry and get the value of the `HostPort` field | ||||||
|
@@ -21,18 +36,60 @@ impl Ports { | |||||
let internal = parse_port(internal); | ||||||
let external = parse_port(&external); | ||||||
|
||||||
log::debug!("Registering port mapping: {} -> {}", internal, external); | ||||||
log::debug!( | ||||||
"Registering port mapping: {} -> {} / {}", | ||||||
internal, | ||||||
external, | ||||||
protocol | ||||||
); | ||||||
|
||||||
Some((internal, external)) | ||||||
Some((protocol, internal, external)) | ||||||
}) | ||||||
.fold(Self { tcp, udp, sctp }, |mut mappings, val| { | ||||||
let (protocol, internal, external) = val; | ||||||
match protocol.as_str() { | ||||||
"tcp" => { | ||||||
mappings.tcp.insert(Tcp(internal), Tcp(external)); | ||||||
} | ||||||
"udp" => { | ||||||
mappings.udp.insert(Udp(internal), Udp(external)); | ||||||
} | ||||||
"sctp" => { | ||||||
mappings.sctp.insert(Sctp(internal), Sctp(external)); | ||||||
} | ||||||
_ => panic!("Not a valid port mapping."), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
}; | ||||||
mappings | ||||||
}) | ||||||
.collect::<HashMap<_, _>>(); | ||||||
|
||||||
Self { mapping } | ||||||
} | ||||||
} | ||||||
|
||||||
pub trait MapToHostPort<T> { | ||||||
/// Returns the host port for the given internal port. | ||||||
pub fn map_to_host_port(&self, internal_port: u16) -> Option<u16> { | ||||||
self.mapping.get(&internal_port).cloned() | ||||||
fn map_to_host_port(&self, internal_port: &T) -> Option<T>; | ||||||
} | ||||||
|
||||||
impl MapToHostPort<u16> for Ports { | ||||||
fn map_to_host_port(&self, internal_port: &u16) -> Option<u16> { | ||||||
self.map_to_host_port(&Tcp(*internal_port)).map(|p| p.0) | ||||||
} | ||||||
} | ||||||
|
||||||
impl MapToHostPort<Tcp> for Ports { | ||||||
fn map_to_host_port(&self, internal_port: &Tcp) -> Option<Tcp> { | ||||||
self.tcp.get(internal_port).copied() | ||||||
} | ||||||
} | ||||||
|
||||||
impl MapToHostPort<Udp> for Ports { | ||||||
fn map_to_host_port(&self, internal_port: &Udp) -> Option<Udp> { | ||||||
self.udp.get(internal_port).copied() | ||||||
} | ||||||
} | ||||||
|
||||||
impl MapToHostPort<Sctp> for Ports { | ||||||
fn map_to_host_port(&self, internal_port: &Sctp) -> Option<Sctp> { | ||||||
self.sctp.get(internal_port).copied() | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -281,10 +338,10 @@ mod tests { | |||||
.unwrap_or_default(); | ||||||
|
||||||
let mut expected_ports = Ports::default(); | ||||||
expected_ports.mapping.insert(18332, 33076); | ||||||
expected_ports.mapping.insert(18333, 33075); | ||||||
expected_ports.mapping.insert(8332, 33078); | ||||||
expected_ports.mapping.insert(8333, 33077); | ||||||
expected_ports.tcp.insert(Tcp(18332), Tcp(33076)); | ||||||
expected_ports.tcp.insert(Tcp(18333), Tcp(33075)); | ||||||
expected_ports.tcp.insert(Tcp(8332), Tcp(33078)); | ||||||
expected_ports.tcp.insert(Tcp(8333), Tcp(33077)); | ||||||
|
||||||
assert_eq!(parsed_ports, expected_ports) | ||||||
} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does one go about calling this API? From what I can see, everything that implements
MapToHostPort
is actually private and can't be used by users outside of the library.To make sure this doesn't happen, can you add an example in
examples/
(dir doesn't exist yet). Rust examples are compiled as part of the tests so that will be a good way of ensuring people can actually use the API :)