Add image ls and image rm commands
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use crate::registry::{registry_base_dir, validate_image_name};
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum ImageCmd {
|
||||||
|
Ls,
|
||||||
|
Rm { image: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(cmd: ImageCmd) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ImageCmd::Ls => ls(),
|
||||||
|
ImageCmd::Rm { image } => rm(&image),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rm(image: &str) -> Result<(), anyhow::Error> {
|
||||||
|
validate_image_name(image)?;
|
||||||
|
let image_dir = registry_base_dir()?.join(image);
|
||||||
|
fs::remove_dir_all(image_dir).context("Failed to remove image dir")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ls() -> anyhow::Result<()> {
|
||||||
|
let dir = fs::read_dir(registry_base_dir()?)?;
|
||||||
|
for entry in dir {
|
||||||
|
let entry = entry?.file_name();
|
||||||
|
println!(" {}", entry.to_string_lossy());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
mod build;
|
mod build;
|
||||||
|
mod image;
|
||||||
mod qemu;
|
mod qemu;
|
||||||
mod registry;
|
mod registry;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use crate::image::ImageCmd;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(about = "Build bootable initrd VMs from container images")]
|
#[command(about = "Build bootable initrd VMs from container images")]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
@@ -27,6 +30,9 @@ enum Commands {
|
|||||||
/// Image tag / registry subdir name
|
/// Image tag / registry subdir name
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
/// List slim images in the registry
|
||||||
|
#[command(subcommand)]
|
||||||
|
Image(ImageCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
@@ -38,6 +44,9 @@ fn main() -> Result<()> {
|
|||||||
Commands::Run { name } => {
|
Commands::Run { name } => {
|
||||||
qemu::run(&name)?;
|
qemu::run(&name)?;
|
||||||
}
|
}
|
||||||
|
Commands::Image(cmd) => {
|
||||||
|
image::run(cmd)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-23
@@ -1,8 +1,8 @@
|
|||||||
//! Registry: local storage of built VM artifacts under
|
//! Registry: local storage of built VM artifacts under
|
||||||
//! `$XDG_DATA_HOME/slim-rs/registry/<image>/`.
|
//! `$XDG_DATA_HOME/slim-rs/registry/<image>/`.
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use std::path::PathBuf;
|
use std::{fs, io, path::PathBuf};
|
||||||
|
|
||||||
/// Validate a user-supplied image name before it is used in a registry path.
|
/// Validate a user-supplied image name before it is used in a registry path.
|
||||||
///
|
///
|
||||||
@@ -26,30 +26,25 @@ pub(crate) fn validate_image_name(name: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn registry_base_dir() -> Result<PathBuf> {
|
||||||
|
let base = xdg::BaseDirectories::with_prefix("slim-rs");
|
||||||
|
base.create_data_directory("registry")
|
||||||
|
.context("Failed to create XDG_DATA_HOME subdirectory")
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn registry_dir(image: &str) -> Result<PathBuf> {
|
pub(crate) fn registry_dir(image: &str) -> Result<PathBuf> {
|
||||||
validate_image_name(image)?;
|
validate_image_name(image)?;
|
||||||
let base = xdg::BaseDirectories::with_prefix("slim-rs");
|
let base = registry_base_dir()?;
|
||||||
let initrd_path = base
|
|
||||||
.place_data_file(format!("registry/{image}/initrd"))
|
let reg_dir = base.join(image);
|
||||||
.context("Failed to write to XDG_DATA_HOME")?;
|
fs::create_dir(®_dir)
|
||||||
let reg_dir = initrd_path
|
.or_else(|e| {
|
||||||
.parent()
|
(e.kind() == io::ErrorKind::AlreadyExists)
|
||||||
.expect("data file has a parent")
|
.then_some(())
|
||||||
.to_owned();
|
.ok_or(e)
|
||||||
|
})
|
||||||
|
.with_context(|| anyhow!("Failed to create {reg_dir:?}"))?;
|
||||||
|
|
||||||
// Defense in depth: the constructed path must stay inside the registry.
|
|
||||||
let registry_root = base
|
|
||||||
.place_data_file("registry/.root-check")
|
|
||||||
.context("Failed to write to XDG_DATA_HOME")?
|
|
||||||
.parent()
|
|
||||||
.expect("data file has a parent")
|
|
||||||
.to_owned();
|
|
||||||
if !reg_dir.starts_with(®istry_root) {
|
|
||||||
bail!(
|
|
||||||
"invalid image name '{image}': registry path escapes {}",
|
|
||||||
registry_root.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(reg_dir)
|
Ok(reg_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user