-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
260 additions
and
179 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use anyhow::Result; | ||
|
||
pub(crate) async fn pull_image(image: &str) -> Result<()> { | ||
run_docker_command(&["pull", image]).await?; | ||
Ok(()) | ||
} | ||
|
||
async fn run_docker_command(args: &[&str]) -> Result<Vec<u8>> { | ||
let output = tokio::process::Command::new("docker") | ||
.args(args) | ||
.output() | ||
.await | ||
.map_err(|e| { | ||
anyhow::anyhow!( | ||
"Failed to run docker {args}: {e}", | ||
args = args.join(" "), | ||
e = e | ||
) | ||
})?; | ||
|
||
if !output.status.success() { | ||
return Err(anyhow::anyhow!( | ||
"Failed to run docker {args}: {stderr}", | ||
args = args.join(" "), | ||
stderr = String::from_utf8_lossy(&output.stderr), | ||
)); | ||
} | ||
|
||
Ok(output.stdout) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use crate::envs::*; | ||
use anyhow::Result; | ||
use bollard::{container::ListContainersOptions, Docker}; | ||
|
||
pub(crate) struct DockerEngine { | ||
docker: Docker, | ||
} | ||
|
||
impl DockerEngine { | ||
pub(crate) fn new() -> Result<Self> { | ||
Ok(Self { | ||
docker: Docker::connect_with_local_defaults()?, | ||
}) | ||
} | ||
|
||
pub(crate) async fn get_local_image_digest(&self, image: &str) -> Result<String> { | ||
let output = self.docker.inspect_image(image).await?; | ||
|
||
Ok(output.id.unwrap()) | ||
} | ||
|
||
pub(crate) async fn get_running_container_image_digest( | ||
&self, | ||
container_name: &str, | ||
) -> Result<Option<String>> { | ||
Ok(self | ||
.docker | ||
.list_containers(Some(ListContainersOptions::<String> { | ||
all: true, | ||
..Default::default() | ||
})) | ||
.await? | ||
.into_iter() | ||
.find_map(|container| { | ||
container | ||
.names | ||
.unwrap_or_default() | ||
.contains(&format!("/{container_name}")) | ||
.then(|| container.image_id.unwrap()) | ||
})) | ||
} | ||
|
||
pub(crate) async fn stop_running_container(&self) -> Result<()> { | ||
self.docker | ||
.stop_container( | ||
CONTAINER_NAME, | ||
Some(bollard::container::StopContainerOptions { | ||
t: GRACEFUL_SHUTDOWN_TIMEOUT_SECS, | ||
}), | ||
) | ||
.await?; | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn run_new_container(&self, image: &str) -> Result<()> { | ||
println!("removing container {CONTAINER_NAME}"); | ||
let remove_container_result = self | ||
.docker | ||
.remove_container( | ||
CONTAINER_NAME, | ||
Some(bollard::container::RemoveContainerOptions { | ||
force: true, | ||
..Default::default() | ||
}), | ||
) | ||
.await; | ||
|
||
if let Err(err) = remove_container_result { | ||
match err { | ||
bollard::errors::Error::DockerResponseServerError { | ||
status_code, | ||
message, | ||
} => if status_code == 404 && message.contains("No such container") {}, | ||
_ => return Err(err.into()), | ||
} | ||
}; | ||
|
||
println!("creating container {CONTAINER_NAME}"); | ||
self.docker | ||
.create_container( | ||
Some(bollard::container::CreateContainerOptions { | ||
name: CONTAINER_NAME, | ||
platform: None, | ||
}), | ||
bollard::container::Config { | ||
image: Some(image.to_string()), | ||
host_config: Some(bollard::models::HostConfig { | ||
log_config: Some(bollard::models::HostConfigLogConfig { | ||
typ: Some("awslogs".to_string()), | ||
config: Some(std::collections::HashMap::from_iter([ | ||
("awslogs-group".to_string(), format!("oioi-{}", *GROUP_NAME)), | ||
( | ||
"awslogs-stream".to_string(), | ||
format!("oioi-{}-{}", *GROUP_NAME, *EC2_INSTANCE_ID), | ||
), | ||
("awslogs-create-group".to_string(), "true".to_string()), | ||
])), | ||
}), | ||
port_bindings: Some(std::collections::HashMap::from_iter( | ||
PORT_MAPPINGS | ||
.iter() | ||
.map(|mapping| { | ||
println!("mapping: {:?}", mapping); | ||
( | ||
format!("{}/{}", mapping.container_port, mapping.protocol), | ||
Some(vec![bollard::models::PortBinding { | ||
host_ip: None, | ||
host_port: Some(mapping.host_port.to_string()), | ||
}]), | ||
) | ||
}) | ||
.collect::<Vec<_>>(), | ||
)), | ||
..Default::default() | ||
}), | ||
..Default::default() | ||
}, | ||
) | ||
.await?; | ||
|
||
println!("starting container {CONTAINER_NAME}"); | ||
self.docker | ||
.start_container( | ||
CONTAINER_NAME, | ||
None::<bollard::container::StartContainerOptions<String>>, | ||
) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn docker_prune(&self) -> Result<()> { | ||
self.docker | ||
.prune_containers(None::<bollard::container::PruneContainersOptions<String>>) | ||
.await?; | ||
|
||
self.docker | ||
.prune_images(None::<bollard::image::PruneImagesOptions<String>>) | ||
.await?; | ||
|
||
self.docker | ||
.prune_networks(None::<bollard::network::PruneNetworksOptions<String>>) | ||
.await?; | ||
|
||
self.docker | ||
.prune_volumes(None::<bollard::volume::PruneVolumesOptions<String>>) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
pub(crate) const INTERVAL: std::time::Duration = std::time::Duration::from_secs(5); | ||
pub(crate) const CONTAINER_NAME: &str = "oioi"; | ||
pub(crate) const GRACEFUL_SHUTDOWN_TIMEOUT_SECS: i64 = 30; | ||
|
||
lazy_static::lazy_static! { | ||
pub(crate) static ref GROUP_NAME: String = std::env::var("GROUP_NAME").expect("GROUP_NAME env var not set"); | ||
pub(crate) static ref EC2_INSTANCE_ID: String = std::env::var("EC2_INSTANCE_ID").expect("EC2_INSTANCE_ID env var not set"); | ||
pub(crate) static ref PORT_MAPPINGS: Vec<PortMapping> = std::env::var("PORT_MAPPINGS").map(|env_string| { | ||
env_string.split(',').map(|mapping| { | ||
let mut parts = mapping.split(&[':', '/']); | ||
let container_port = parts.next().expect("container port not found").parse::<u16>().expect("container port is not a number"); | ||
let host_port = parts.next().expect("host port not found").parse::<u16>().expect("host port is not a number"); | ||
let protocol = parts.next().expect("protocol not found").to_string(); | ||
|
||
PortMapping { | ||
container_port, | ||
host_port, | ||
protocol, | ||
} | ||
}).collect() | ||
}).expect("PORT_MAPPINGS env var not set"); | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct PortMapping { | ||
pub(crate) container_port: u16, | ||
pub(crate) host_port: u16, | ||
pub(crate) protocol: String, | ||
} |
Oops, something went wrong.