Skip to content

Commit 96246b2

Browse files
committedJun 14, 2022
Refactor docker and cross-util commands.
Refactor the internals of the docker module and separate the cross-util commands from the actual binary. Will simplify the internals for adding cross-remote support later.
1 parent 150c892 commit 96246b2

File tree

11 files changed

+702
-485
lines changed

11 files changed

+702
-485
lines changed
 

‎src/bin/commands/images.rs

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
use clap::Args;
2+
use cross::CommandExt;
3+
4+
// known image prefixes, with their registry
5+
// the docker.io registry can also be implicit
6+
const GHCR_IO: &str = cross::docker::CROSS_IMAGE;
7+
const RUST_EMBEDDED: &str = "rustembedded/cross:";
8+
const DOCKER_IO: &str = "docker.io/rustembedded/cross:";
9+
const IMAGE_PREFIXES: &[&str] = &[GHCR_IO, DOCKER_IO, RUST_EMBEDDED];
10+
11+
#[derive(Args, Debug)]
12+
pub struct ListImages {
13+
/// Provide verbose diagnostic output.
14+
#[clap(short, long)]
15+
pub verbose: bool,
16+
/// Container engine (such as docker or podman).
17+
#[clap(long)]
18+
pub engine: Option<String>,
19+
}
20+
21+
#[derive(Args, Debug)]
22+
pub struct RemoveImages {
23+
/// If not provided, remove all images.
24+
pub targets: Vec<String>,
25+
/// Remove images matching provided targets.
26+
#[clap(short, long)]
27+
pub verbose: bool,
28+
/// Force removal of images.
29+
#[clap(short, long)]
30+
pub force: bool,
31+
/// Remove local (development) images.
32+
#[clap(short, long)]
33+
pub local: bool,
34+
/// Remove images. Default is a dry run.
35+
#[clap(short, long)]
36+
pub execute: bool,
37+
/// Container engine (such as docker or podman).
38+
#[clap(long)]
39+
pub engine: Option<String>,
40+
}
41+
42+
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
43+
struct Image {
44+
repository: String,
45+
tag: String,
46+
// need to remove images by ID, not just tag
47+
id: String,
48+
}
49+
50+
impl Image {
51+
fn name(&self) -> String {
52+
format!("{}:{}", self.repository, self.tag)
53+
}
54+
}
55+
56+
fn parse_image(image: &str) -> Image {
57+
// this cannot panic: we've formatted our image list as `${repo}:${tag} ${id}`
58+
let (repository, rest) = image.split_once(':').unwrap();
59+
let (tag, id) = rest.split_once(' ').unwrap();
60+
Image {
61+
repository: repository.to_string(),
62+
tag: tag.to_string(),
63+
id: id.to_string(),
64+
}
65+
}
66+
67+
fn is_cross_image(repository: &str) -> bool {
68+
IMAGE_PREFIXES.iter().any(|i| repository.starts_with(i))
69+
}
70+
71+
fn is_local_image(tag: &str) -> bool {
72+
tag.starts_with("local")
73+
}
74+
75+
fn get_cross_images(
76+
engine: &cross::docker::Engine,
77+
verbose: bool,
78+
local: bool,
79+
) -> cross::Result<Vec<Image>> {
80+
let stdout = cross::docker::subcommand(engine, "images")
81+
.arg("--format")
82+
.arg("{{.Repository}}:{{.Tag}} {{.ID}}")
83+
.run_and_get_stdout(verbose)?;
84+
85+
let mut images: Vec<Image> = stdout
86+
.lines()
87+
.map(parse_image)
88+
.filter(|image| is_cross_image(&image.repository))
89+
.filter(|image| local || !is_local_image(&image.tag))
90+
.collect();
91+
images.sort();
92+
93+
Ok(images)
94+
}
95+
96+
// the old rustembedded targets had the following format:
97+
// repository = (${registry}/)?rustembedded/cross
98+
// tag = ${target}(-${version})?
99+
// the last component must match `[A-Za-z0-9_-]` and
100+
// we must have at least 3 components. the first component
101+
// may contain other characters, such as `thumbv8m.main-none-eabi`.
102+
fn rustembedded_target(tag: &str) -> String {
103+
let is_target_char = |c: char| c == '_' || c.is_ascii_alphanumeric();
104+
let mut components = vec![];
105+
for (index, component) in tag.split('-').enumerate() {
106+
if index <= 2 || (!component.is_empty() && component.chars().all(is_target_char)) {
107+
components.push(component)
108+
} else {
109+
break;
110+
}
111+
}
112+
113+
components.join("-")
114+
}
115+
116+
fn get_image_target(image: &Image) -> cross::Result<String> {
117+
if let Some(stripped) = image.repository.strip_prefix(GHCR_IO) {
118+
Ok(stripped.to_string())
119+
} else if let Some(tag) = image.tag.strip_prefix(RUST_EMBEDDED) {
120+
Ok(rustembedded_target(tag))
121+
} else if let Some(tag) = image.tag.strip_prefix(DOCKER_IO) {
122+
Ok(rustembedded_target(tag))
123+
} else {
124+
eyre::bail!("cannot get target for image {}", image.name())
125+
}
126+
}
127+
128+
pub fn list_images(
129+
ListImages { verbose, .. }: ListImages,
130+
engine: &cross::docker::Engine,
131+
) -> cross::Result<()> {
132+
get_cross_images(engine, verbose, true)?
133+
.iter()
134+
.for_each(|line| println!("{}", line.name()));
135+
136+
Ok(())
137+
}
138+
139+
fn remove_images(
140+
engine: &cross::docker::Engine,
141+
images: &[&str],
142+
verbose: bool,
143+
force: bool,
144+
execute: bool,
145+
) -> cross::Result<()> {
146+
let mut command = cross::docker::subcommand(engine, "rmi");
147+
if force {
148+
command.arg("--force");
149+
}
150+
command.args(images);
151+
if execute {
152+
command.run(verbose).map_err(Into::into)
153+
} else {
154+
println!("{:?}", command);
155+
Ok(())
156+
}
157+
}
158+
159+
pub fn remove_all_images(
160+
RemoveImages {
161+
verbose,
162+
force,
163+
local,
164+
execute,
165+
..
166+
}: RemoveImages,
167+
engine: &cross::docker::Engine,
168+
) -> cross::Result<()> {
169+
let images = get_cross_images(engine, verbose, local)?;
170+
let ids: Vec<&str> = images.iter().map(|i| i.id.as_ref()).collect();
171+
remove_images(engine, &ids, verbose, force, execute)
172+
}
173+
174+
pub fn remove_target_images(
175+
RemoveImages {
176+
targets,
177+
verbose,
178+
force,
179+
local,
180+
execute,
181+
..
182+
}: RemoveImages,
183+
engine: &cross::docker::Engine,
184+
) -> cross::Result<()> {
185+
let images = get_cross_images(engine, verbose, local)?;
186+
let mut ids = vec![];
187+
for image in images.iter() {
188+
let target = get_image_target(image)?;
189+
if targets.contains(&target) {
190+
ids.push(image.id.as_ref());
191+
}
192+
}
193+
remove_images(engine, &ids, verbose, force, execute)
194+
}
195+
196+
#[cfg(test)]
197+
mod tests {
198+
use super::*;
199+
200+
#[test]
201+
fn parse_rustembedded_target() {
202+
let targets = [
203+
"x86_64-unknown-linux-gnu",
204+
"x86_64-apple-darwin",
205+
"thumbv8m.main-none-eabi",
206+
];
207+
for target in targets {
208+
let versioned = format!("{target}-0.2.1");
209+
assert_eq!(rustembedded_target(target), target.to_string());
210+
assert_eq!(rustembedded_target(&versioned), target.to_string());
211+
}
212+
}
213+
}

‎src/bin/commands/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod images;
2+
3+
pub use self::images::*;

0 commit comments

Comments
 (0)