-
Notifications
You must be signed in to change notification settings - Fork 393
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
794: Refactor docker and cross-util commands. r=Emilgardis a=Alexhuszagh Refactor the internals of the docker module and separate the cross-util commands from the actual binary. Will simplify the internals for adding remote container support later. Co-authored-by: Alex Huszagh <ahuszagh@gmail.com>
- Loading branch information
Showing
11 changed files
with
702 additions
and
485 deletions.
There are no files selected for viewing
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,213 @@ | ||
use clap::Args; | ||
use cross::CommandExt; | ||
|
||
// known image prefixes, with their registry | ||
// the docker.io registry can also be implicit | ||
const GHCR_IO: &str = cross::docker::CROSS_IMAGE; | ||
const RUST_EMBEDDED: &str = "rustembedded/cross:"; | ||
const DOCKER_IO: &str = "docker.io/rustembedded/cross:"; | ||
const IMAGE_PREFIXES: &[&str] = &[GHCR_IO, DOCKER_IO, RUST_EMBEDDED]; | ||
|
||
#[derive(Args, Debug)] | ||
pub struct ListImages { | ||
/// Provide verbose diagnostic output. | ||
#[clap(short, long)] | ||
pub verbose: bool, | ||
/// Container engine (such as docker or podman). | ||
#[clap(long)] | ||
pub engine: Option<String>, | ||
} | ||
|
||
#[derive(Args, Debug)] | ||
pub struct RemoveImages { | ||
/// If not provided, remove all images. | ||
pub targets: Vec<String>, | ||
/// Remove images matching provided targets. | ||
#[clap(short, long)] | ||
pub verbose: bool, | ||
/// Force removal of images. | ||
#[clap(short, long)] | ||
pub force: bool, | ||
/// Remove local (development) images. | ||
#[clap(short, long)] | ||
pub local: bool, | ||
/// Remove images. Default is a dry run. | ||
#[clap(short, long)] | ||
pub execute: bool, | ||
/// Container engine (such as docker or podman). | ||
#[clap(long)] | ||
pub engine: Option<String>, | ||
} | ||
|
||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] | ||
struct Image { | ||
repository: String, | ||
tag: String, | ||
// need to remove images by ID, not just tag | ||
id: String, | ||
} | ||
|
||
impl Image { | ||
fn name(&self) -> String { | ||
format!("{}:{}", self.repository, self.tag) | ||
} | ||
} | ||
|
||
fn parse_image(image: &str) -> Image { | ||
// this cannot panic: we've formatted our image list as `${repo}:${tag} ${id}` | ||
let (repository, rest) = image.split_once(':').unwrap(); | ||
let (tag, id) = rest.split_once(' ').unwrap(); | ||
Image { | ||
repository: repository.to_string(), | ||
tag: tag.to_string(), | ||
id: id.to_string(), | ||
} | ||
} | ||
|
||
fn is_cross_image(repository: &str) -> bool { | ||
IMAGE_PREFIXES.iter().any(|i| repository.starts_with(i)) | ||
} | ||
|
||
fn is_local_image(tag: &str) -> bool { | ||
tag.starts_with("local") | ||
} | ||
|
||
fn get_cross_images( | ||
engine: &cross::docker::Engine, | ||
verbose: bool, | ||
local: bool, | ||
) -> cross::Result<Vec<Image>> { | ||
let stdout = cross::docker::subcommand(engine, "images") | ||
.arg("--format") | ||
.arg("{{.Repository}}:{{.Tag}} {{.ID}}") | ||
.run_and_get_stdout(verbose)?; | ||
|
||
let mut images: Vec<Image> = stdout | ||
.lines() | ||
.map(parse_image) | ||
.filter(|image| is_cross_image(&image.repository)) | ||
.filter(|image| local || !is_local_image(&image.tag)) | ||
.collect(); | ||
images.sort(); | ||
|
||
Ok(images) | ||
} | ||
|
||
// the old rustembedded targets had the following format: | ||
// repository = (${registry}/)?rustembedded/cross | ||
// tag = ${target}(-${version})? | ||
// the last component must match `[A-Za-z0-9_-]` and | ||
// we must have at least 3 components. the first component | ||
// may contain other characters, such as `thumbv8m.main-none-eabi`. | ||
fn rustembedded_target(tag: &str) -> String { | ||
let is_target_char = |c: char| c == '_' || c.is_ascii_alphanumeric(); | ||
let mut components = vec![]; | ||
for (index, component) in tag.split('-').enumerate() { | ||
if index <= 2 || (!component.is_empty() && component.chars().all(is_target_char)) { | ||
components.push(component) | ||
} else { | ||
break; | ||
} | ||
} | ||
|
||
components.join("-") | ||
} | ||
|
||
fn get_image_target(image: &Image) -> cross::Result<String> { | ||
if let Some(stripped) = image.repository.strip_prefix(GHCR_IO) { | ||
Ok(stripped.to_string()) | ||
} else if let Some(tag) = image.tag.strip_prefix(RUST_EMBEDDED) { | ||
Ok(rustembedded_target(tag)) | ||
} else if let Some(tag) = image.tag.strip_prefix(DOCKER_IO) { | ||
Ok(rustembedded_target(tag)) | ||
} else { | ||
eyre::bail!("cannot get target for image {}", image.name()) | ||
} | ||
} | ||
|
||
pub fn list_images( | ||
ListImages { verbose, .. }: ListImages, | ||
engine: &cross::docker::Engine, | ||
) -> cross::Result<()> { | ||
get_cross_images(engine, verbose, true)? | ||
.iter() | ||
.for_each(|line| println!("{}", line.name())); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn remove_images( | ||
engine: &cross::docker::Engine, | ||
images: &[&str], | ||
verbose: bool, | ||
force: bool, | ||
execute: bool, | ||
) -> cross::Result<()> { | ||
let mut command = cross::docker::subcommand(engine, "rmi"); | ||
if force { | ||
command.arg("--force"); | ||
} | ||
command.args(images); | ||
if execute { | ||
command.run(verbose).map_err(Into::into) | ||
} else { | ||
println!("{:?}", command); | ||
Ok(()) | ||
} | ||
} | ||
|
||
pub fn remove_all_images( | ||
RemoveImages { | ||
verbose, | ||
force, | ||
local, | ||
execute, | ||
.. | ||
}: RemoveImages, | ||
engine: &cross::docker::Engine, | ||
) -> cross::Result<()> { | ||
let images = get_cross_images(engine, verbose, local)?; | ||
let ids: Vec<&str> = images.iter().map(|i| i.id.as_ref()).collect(); | ||
remove_images(engine, &ids, verbose, force, execute) | ||
} | ||
|
||
pub fn remove_target_images( | ||
RemoveImages { | ||
targets, | ||
verbose, | ||
force, | ||
local, | ||
execute, | ||
.. | ||
}: RemoveImages, | ||
engine: &cross::docker::Engine, | ||
) -> cross::Result<()> { | ||
let images = get_cross_images(engine, verbose, local)?; | ||
let mut ids = vec![]; | ||
for image in images.iter() { | ||
let target = get_image_target(image)?; | ||
if targets.contains(&target) { | ||
ids.push(image.id.as_ref()); | ||
} | ||
} | ||
remove_images(engine, &ids, verbose, force, execute) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn parse_rustembedded_target() { | ||
let targets = [ | ||
"x86_64-unknown-linux-gnu", | ||
"x86_64-apple-darwin", | ||
"thumbv8m.main-none-eabi", | ||
]; | ||
for target in targets { | ||
let versioned = format!("{target}-0.2.1"); | ||
assert_eq!(rustembedded_target(target), target.to_string()); | ||
assert_eq!(rustembedded_target(&versioned), target.to_string()); | ||
} | ||
} | ||
} |
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,3 @@ | ||
mod images; | ||
|
||
pub use self::images::*; |
Oops, something went wrong.