Skip to content

Commit

Permalink
feat: allow retrieve host port for generic image (#32)
Browse files Browse the repository at this point in the history
* feat: allow retrieve host port for generic image
* fix(compose): skip wait and port mapping if a compose service not found

Replace a panic by a warning
  • Loading branch information
ilaborie authored Sep 21, 2024
1 parent 370dc49 commit cfc2a6a
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 9 deletions.
26 changes: 19 additions & 7 deletions rustainers/src/compose/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::Path;
use std::time::Duration;

use async_trait::async_trait;
use tracing::info;
use tracing::{info, warn};

use crate::cmd::Cmd;
use crate::runner::InnerRunner;
Expand Down Expand Up @@ -45,17 +45,29 @@ pub(crate) trait InnerComposeRunner: InnerRunner {
// Wait
let interval = options.wait_interval;
for (service, wait) in wait_strategies {
debug_assert!(services.contains(service));
#[allow(clippy::indexing_slicing)]
let id = services[service];
let Some(id) = services.get(service) else {
warn!(
?service,
?wait,
?services,
"Compose service {service} not found, skip wait strategy"
);
continue;
};
self.wait_service_ready(service, id, wait, interval).await?;
}

// Port mapping
for (service, mapping) in port_mappings {
debug_assert!(services.contains(service));
#[allow(clippy::indexing_slicing)]
let id = services[service];
let Some(id) = services.get(service) else {
warn!(
?service,
?mapping,
?services,
"Compose service {service} not found, skip port mapping"
);
continue;
};
let port = self.port(id, mapping.container_port).await?;
mapping.bind_port(port).await;
}
Expand Down
5 changes: 5 additions & 0 deletions rustainers/src/compose/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ impl Services {
pub fn contains_all(&self, services: &[ComposeService]) -> bool {
services.iter().all(|svc| self.contains(svc))
}

/// Get the container id of a service
pub fn get(&self, service: &ComposeService) -> Option<ContainerId> {
self.0.get(service).copied()
}
}

impl Index<&ComposeService> for Services {
Expand Down
44 changes: 44 additions & 0 deletions rustainers/src/images/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
mod postgres;
use indexmap::IndexMap;

use crate::Container;
use crate::ContainerStatus;
use crate::ExposedPort;
use crate::ImageReference;
use crate::Port;
use crate::PortError;
use crate::RunnableContainer;
use crate::RunnableContainerBuilder;
use crate::ToRunnableContainer;
Expand All @@ -32,6 +35,27 @@ mod nats;
pub use self::nats::*;

/// A Generic Image
///
/// ```rust, no_run
/// # async fn run() -> anyhow::Result<()> {
/// use rustainers::{ImageName, WaitStrategy};
/// use rustainers::images::GenericImage;
///
/// let name = ImageName::new("docker.io/nginx");
/// let container_port = 80;
///
/// let mut nginx = GenericImage::new(name);
/// nginx.add_port_mapping(container_port);
/// nginx.set_wait_strategy(WaitStrategy::http("/"));
///
/// # let runner = rustainers::runner::Runner::auto()?;
/// let container = runner.start(nginx).await?;
///
/// let port = container.host_port(container_port).await?;
/// // ...
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct GenericImage(RunnableContainer);

Expand Down Expand Up @@ -92,3 +116,23 @@ impl ToRunnableContainer for GenericImage {
}
}
}

impl Container<GenericImage> {
/// Find the host port for a container port
///
/// # Errors
///
/// Fail if there is no mapping with the container port
/// Could fail if the port is not bind
pub async fn host_port(&self, container_port: impl Into<Port>) -> Result<Port, PortError> {
let container_port = container_port.into();

for mapping in &self.0.port_mappings {
if mapping.container_port == container_port {
return mapping.host_port().await;
}
}

Err(PortError::ContainerPortNotFound(container_port))
}
}
4 changes: 4 additions & 0 deletions rustainers/src/port/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub enum PortError {
#[error("Container port {0} not bind")]
PortNotBindYet(Port),

/// The container port not found
#[error("Container port {0} not found")]
ContainerPortNotFound(Port),

/// The container is failing
#[error(transparent)]
RunnerError(#[from] RunnerError),
Expand Down
23 changes: 21 additions & 2 deletions rustainers/tests/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use rstest::rstest;
use tokio::task::JoinSet;
use tracing::{debug, info};

use rustainers::images::{Minio, Mongo, Mosquitto, Nats, Postgres, Redis};
use rustainers::images::{GenericImage, Minio, Mongo, Mosquitto, Nats, Postgres, Redis};
use rustainers::runner::{RunOption, Runner};
use rustainers::{ExposedPort, Port};
use rustainers::{ExposedPort, ImageName, Port, WaitStrategy};

mod common;
pub use self::common::*;
Expand Down Expand Up @@ -228,3 +228,22 @@ async fn test_mosquitto_endpoint(runner: &Runner) -> anyhow::Result<()> {
check!(endpoint == "mqtt://127.0.0.1:9127");
Ok(())
}

#[rstest]
#[tokio::test]
async fn test_generic_image(runner: &Runner) -> anyhow::Result<()> {
let options = RunOption::builder().with_remove(true).build();
let name = ImageName::new("docker.io/nginx");
let mut nginx = GenericImage::new(name);
let container_port = 80;
nginx.add_port_mapping(container_port);
nginx.set_wait_strategy(WaitStrategy::http("/"));

let container = runner.start_with_options(nginx, options).await?;
debug!("Started {container}");

let host_port = container.host_port(container_port).await;
let_assert!(Ok(_) = host_port);

Ok(())
}

0 comments on commit cfc2a6a

Please sign in to comment.