#![allow(missing_docs)]
#![allow(clippy::missing_docs_in_private_items)]
use anyhow::Result;
use anyhow::anyhow;
use clap::Parser;
use clap::Subcommand;
use clap_verbosity_flag::Verbosity;
use crankshaft_docker::Container;
use crankshaft_docker::Docker;
use tokio_util::sync::CancellationToken;
use tracing_log::AsTrace;
use tracing_subscriber::EnvFilter;
#[derive(clap::Parser)]
struct Args {
#[command(subcommand)]
command: Command,
#[command(flatten)]
verbose: Verbosity,
}
#[derive(Subcommand)]
enum Command {
CreateContainer {
image: String,
name: String,
#[arg(short, long, default_value = "latest")]
tag: String,
},
RunContainer {
image: String,
name: String,
command: String,
#[arg(short, long, default_value = "latest")]
tag: String,
},
RemoveContainer {
name: String,
#[arg(short, long, default_value_t = false)]
force: bool,
},
EnsureImage {
image: String,
},
ListImages,
RemoveImage {
image: String,
#[arg(short, long, default_value = "latest")]
tag: String,
},
RemoveAllImages,
}
async fn create_container(
docker: Docker,
image: impl AsRef<str>,
tag: impl AsRef<str>,
name: impl Into<String>,
program: impl Into<String>,
args: impl IntoIterator<Item = impl Into<String>>,
) -> Result<Container> {
Ok(docker
.container_builder()
.name(name)
.image(format!(
"{image}:{tag}",
image = image.as_ref(),
tag = tag.as_ref()
))
.program(program)
.args(args)
.try_build()
.await?)
}
async fn run(args: Args) -> Result<()> {
let docker = Docker::with_defaults().unwrap();
match args.command {
Command::CreateContainer { image, name, tag } => {
create_container(
docker,
image,
tag,
name,
"/usr/bin/env",
["bash", "-c", "echo 'hello, world!'"],
)
.await?;
}
Command::RunContainer {
image,
name,
command,
tag,
} => {
let mut command =
shlex::split(&command).ok_or_else(|| anyhow!("invalid command `{command}`"))?;
let args = command.split_off(1);
let container =
create_container(docker, image, tag, &name, command.remove(0), args).await?;
let result = container.run(&name, None).await?;
println!("exit code: {}", result.status);
}
Command::RemoveContainer { name, force } => {
let container = docker.container_from_name(name, None, None);
if force {
container.force_remove().await?;
} else {
container.remove().await?;
}
}
Command::EnsureImage { image } => {
docker
.ensure_image(image, CancellationToken::new(), None)
.await?
.expect("pull should not be cancelled");
}
Command::ListImages => {
docker.list_images().await?;
}
Command::RemoveImage { image, tag } => {
docker.remove_image(image, tag).await?;
}
Command::RemoveAllImages => {
docker.remove_all_images().await?;
}
};
Ok(())
}
pub fn main() -> Result<()> {
let args = Args::parse();
match std::env::var("RUST_LOG") {
Ok(_) => tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init(),
Err(_) => tracing_subscriber::fmt()
.with_max_level(args.verbose.log_level_filter().as_trace())
.init(),
};
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(run(args))
}