From e80885a3fe25c32d56f23eddb7c6ab2f0c1ed35f Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 1 Sep 2023 23:45:15 +0300 Subject: [PATCH] feat(si): Recreate SDF or Web containers when port mappings change If you want to change the port map for SDF or Web, then you'd currently need to stop and delete the containers manually then re-run start With this PR we can now do the following: ``` si --sdf-port 5157 start ``` This caused the Launcher to act as follows: ``` Container Port Mappings have changed for local-web-1 so recreating Stopping container /local-web-1 Deleting container: local-web-1 (4d15095f701d8f2872a4e5a838f1187d1e0b980297bd23d15c21bc966eb41188) Starting systeminit/web:stable as local-web-1 All system components running... System Initiative is alive! You can now use the `si launch` command to open the System Initiative web portal: ``` --- lib/si-cli/src/cmd/start.rs | 96 ++++++++++++++++++++++++++++++++----- lib/si-cli/src/engine.rs | 44 +++++++++++++++++ 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/lib/si-cli/src/cmd/start.rs b/lib/si-cli/src/cmd/start.rs index 9e716751fe..150becec03 100644 --- a/lib/si-cli/src/cmd/start.rs +++ b/lib/si-cli/src/cmd/start.rs @@ -311,17 +311,53 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { .get_existing_container(container_name.clone()) .await?; if let Some(existing) = container_summary { + let mut needs_recreated = false; + if let Some(ports) = existing.ports { + if !ports.is_empty() { + let port = ports.first().unwrap().clone(); + let public_port = port.public_port.unwrap_or(0); + let ip = port.ip.clone().unwrap_or("".to_string()); + + if public_port as u32 != app.sdf_port() || ip != app.sdf_host() { + needs_recreated = true; + } + } + } else { + // No ports suggest that the container isn't in a started state + needs_recreated = true + } + // it means we have an existing container // If it's running, we have nothing to do here - if existing.state.as_ref().unwrap() == "running" { + if existing.state.as_ref().unwrap() == "running" && !needs_recreated { continue; } - println!("Starting existing {0}", container_name.clone()); - app.container_engine() - .start_container(existing.id.as_ref().unwrap().to_string()) - .await?; - continue; + if needs_recreated { + println!( + "Container Port Mappings have changed for {0} so recreating", + container_name.clone() + ); + + if existing.state.as_ref().unwrap() == "running" { + app.container_engine() + .stop_container(existing.id.as_ref().unwrap().to_string()) + .await?; + } + + app.container_engine() + .delete_container( + existing.id.as_ref().unwrap().to_string(), + container_name.clone(), + ) + .await?; + } else { + println!("Starting existing {0}", container_name.clone()); + app.container_engine() + .start_container(existing.id.as_ref().unwrap().to_string()) + .await?; + continue; + } } if is_preview { @@ -354,17 +390,53 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { .get_existing_container(container_name.clone()) .await?; if let Some(existing) = container_summary { + let mut needs_recreated = false; + if let Some(ports) = existing.ports { + if !ports.is_empty() { + let port = ports.first().unwrap().clone(); + let public_port = port.public_port.unwrap_or(0); + let ip = port.ip.clone().unwrap_or("".to_string()); + + if public_port as u32 != app.web_port() || ip != app.web_host() { + needs_recreated = true; + } + } + } else { + // No ports suggest that the container isn't in a started state + needs_recreated = true + } + // it means we have an existing container // If it's running, we have nothing to do here - if existing.state.as_ref().unwrap() == "running" { + if existing.state.as_ref().unwrap() == "running" && !needs_recreated { continue; } - println!("Starting existing {0}", container_name.clone()); - app.container_engine() - .start_container(existing.id.as_ref().unwrap().to_string()) - .await?; - continue; + if needs_recreated { + println!( + "Container Port Mappings have changed for {0} so recreating", + container_name.clone() + ); + + if existing.state.as_ref().unwrap() == "running" { + app.container_engine() + .stop_container(existing.id.as_ref().unwrap().to_string()) + .await?; + } + + app.container_engine() + .delete_container( + existing.id.as_ref().unwrap().to_string(), + container_name.clone(), + ) + .await?; + } else { + println!("Starting existing {0}", container_name.clone()); + app.container_engine() + .start_container(existing.id.as_ref().unwrap().to_string()) + .await?; + continue; + } } if is_preview { diff --git a/lib/si-cli/src/engine.rs b/lib/si-cli/src/engine.rs index 5afca08728..4ed8dbaabe 100644 --- a/lib/si-cli/src/engine.rs +++ b/lib/si-cli/src/engine.rs @@ -63,6 +63,7 @@ pub struct ContainerReleaseInfo { pub version: String, } +#[derive(Debug)] pub struct SiContainerSummary { pub created: Option, pub id: Option, @@ -70,8 +71,17 @@ pub struct SiContainerSummary { pub labels: Option>, pub status: Option, pub state: Option, + pub ports: Option>, +} + +#[derive(Debug)] +pub struct SiPort { + pub ip: Option, + pub private_port: u16, + pub public_port: Option, } +#[derive(Debug)] pub struct SiImageSummary { pub containers: isize, pub created: isize, @@ -94,6 +104,12 @@ impl From for SiImageSummary { impl From for SiContainerSummary { fn from(container: docker_api::models::ContainerSummary) -> SiContainerSummary { + let mut mapped_ports = Vec::new(); + if let Some(ports) = container.ports { + for port in ports { + mapped_ports.push(SiPort::from(port)) + } + } SiContainerSummary { created: container.created, id: container.id, @@ -101,6 +117,27 @@ impl From for SiContainerSummary { labels: container.labels, status: container.status, state: container.state, + ports: Some(mapped_ports), + } + } +} + +impl From for SiPort { + fn from(port: docker_api::models::Port) -> SiPort { + SiPort { + ip: port.ip, + private_port: port.private_port, + public_port: port.public_port, + } + } +} + +impl From for SiPort { + fn from(port: podman_api::models::PortMapping) -> SiPort { + SiPort { + ip: port.host_ip, + private_port: port.container_port.unwrap_or(0), + public_port: port.host_port, } } } @@ -144,6 +181,12 @@ impl From for SiImageSummary { impl From for SiContainerSummary { fn from(container: podman_api::models::ListContainer) -> SiContainerSummary { + let mut mapped_ports = Vec::new(); + if let Some(ports) = container.ports { + for port in ports { + mapped_ports.push(SiPort::from(port)) + } + } SiContainerSummary { created: container.created.map(|created| created.timestamp()), id: container.id, @@ -151,6 +194,7 @@ impl From for SiContainerSummary { labels: container.labels, status: container.status, state: container.state, + ports: Some(mapped_ports), } } }