diff --git a/Cargo.toml b/Cargo.toml index 3860d25..f8e03af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,11 @@ homepage = "https://github.com/navidys/ocibuilder" repository = "https://github.com/navidys/ocibuilder" [dependencies] +clap = { version = "4.5.18", features = ["derive"] } +env_logger = "0.11.5" +fs2 = "0.4.3" +home = "0.5.9" +log = "0.4.22" +oci-spec = "0.7.0" +thiserror = "1.0.63" +url = "2.5.2" diff --git a/README.md b/README.md index 5acc33c..d8130fe 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,4 @@ The project is under development and not ready for usage (feel free to contribut | Command | Description | | ---------- | ----------- | +| reset | Reset local storage. diff --git a/src/builder/mod.rs b/src/builder/mod.rs new file mode 100644 index 0000000..0ac1c31 --- /dev/null +++ b/src/builder/mod.rs @@ -0,0 +1,2 @@ +pub mod oci; +pub mod reset; diff --git a/src/builder/oci.rs b/src/builder/oci.rs new file mode 100644 index 0000000..66f4326 --- /dev/null +++ b/src/builder/oci.rs @@ -0,0 +1,87 @@ +use fs2::FileExt; + +use std::{ + fs::{self, File}, + path::PathBuf, +}; + +use log::debug; + +use crate::{ + container::store::ContainerStore, + error::{BuilderError, BuilderResult}, + image::store::ImageStore, + layer::store::LayerStore, +}; + +pub struct OCIBuilder { + image_store: ImageStore, + container_store: ContainerStore, + layer_store: LayerStore, + _root_dir: PathBuf, + _tmp_dir: PathBuf, + lock_file: File, +} + +impl OCIBuilder { + pub fn new(root_dir: PathBuf) -> BuilderResult { + debug!("root directory: {:?}", root_dir); + + // builder tmp directory + let mut tmp_dir = PathBuf::from(&root_dir); + tmp_dir.push("tmp/"); + + match fs::create_dir_all(&tmp_dir) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(tmp_dir, err)), + } + + // builder lock file + let mut lfile = PathBuf::from(&root_dir); + lfile.push("builder.lock"); + + let lock_file = match File::create(&lfile) { + Ok(f) => f, + Err(err) => return Err(BuilderError::IoError(lfile, err)), + }; + + let image_store = ImageStore::new(&root_dir)?; + let container_store = ContainerStore::new(&root_dir)?; + let layer_store = LayerStore::new(&root_dir)?; + + Ok(Self { + image_store, + container_store, + layer_store, + lock_file, + _tmp_dir: tmp_dir, + _root_dir: root_dir, + }) + } + + pub fn image_store(&self) -> &ImageStore { + &self.image_store + } + + pub fn container_store(&self) -> &ContainerStore { + &self.container_store + } + + pub fn layer_store(&self) -> &LayerStore { + &self.layer_store + } + + pub fn lock(&self) -> BuilderResult<()> { + match self.lock_file.lock_exclusive() { + Ok(_) => Ok(()), + Err(err) => Err(BuilderError::BuilderLockError(err)), + } + } + + pub fn unlock(&self) -> BuilderResult<()> { + match self.lock_file.unlock() { + Ok(_) => Ok(()), + Err(err) => Err(BuilderError::BuilderLockError(err)), + } + } +} diff --git a/src/builder/reset.rs b/src/builder/reset.rs new file mode 100644 index 0000000..54d8212 --- /dev/null +++ b/src/builder/reset.rs @@ -0,0 +1,36 @@ +use std::fs; + +use crate::error::{BuilderError, BuilderResult}; + +use super::oci::OCIBuilder; + +impl OCIBuilder { + pub fn reset(&self) -> BuilderResult<()> { + self.lock()?; + + // remove image store directory content + let istore_path = self.image_store().istore_path(); + match fs::remove_dir_all(istore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(istore_path.clone(), err)), + } + + // remove container store directory content + let cstore_path = self.container_store().cstore_path(); + match fs::remove_dir_all(cstore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(cstore_path.clone(), err)), + } + + // remove layer store directory content + let lstore_path = self.layer_store().lstore_path(); + match fs::remove_dir_all(lstore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(lstore_path.clone(), err)), + } + + self.unlock()?; + + Ok(()) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..9778ae8 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod pull; +pub mod reset; diff --git a/src/commands/pull.rs b/src/commands/pull.rs new file mode 100644 index 0000000..a499a80 --- /dev/null +++ b/src/commands/pull.rs @@ -0,0 +1,26 @@ +use std::ffi::OsString; + +use clap::Parser; +use log::debug; + +use crate::{builder, error::BuilderResult, utils}; + +#[derive(Parser, Debug)] +pub struct Pull { + image_name: String, +} + +impl Pull { + pub fn new(image_name: String) -> Self { + Self { image_name } + } + + pub fn exec(&self, root_dir: Option) -> BuilderResult<()> { + debug!("pulling image..."); + + let root_dir_path = utils::get_root_dir(root_dir); + let _builder = builder::oci::OCIBuilder::new(root_dir_path)?; + + Ok(()) + } +} diff --git a/src/commands/reset.rs b/src/commands/reset.rs new file mode 100644 index 0000000..a9cf237 --- /dev/null +++ b/src/commands/reset.rs @@ -0,0 +1,46 @@ +use std::{ffi::OsString, io}; + +use clap::Parser; +use log::debug; + +use crate::{builder, error::BuilderResult, utils}; + +#[derive(Parser, Debug)] +pub struct Reset { + /// Do not prompt for confirmation + #[clap(short, long)] + force: bool, +} + +impl Reset { + pub fn new(force: bool) -> Self { + Self { force } + } + + pub fn exec(&self, root_dir: Option) -> BuilderResult<()> { + debug!("resetting storage..."); + + let root_dir_path = utils::get_root_dir(root_dir); + let builder = builder::oci::OCIBuilder::new(root_dir_path)?; + + if self.force { + builder.reset()?; + + return Ok(()); + } + + println!("WARNING! this will remove all containers, images and layers."); + println!("Are you sure you want to continue? [y/N]"); + + let mut user_input = String::new(); + io::stdin() + .read_line(&mut user_input) + .expect("Failed to read input"); + + if user_input.to_lowercase() == "y\n" { + builder.reset()? + } + + Ok(()) + } +} diff --git a/src/container/mod.rs b/src/container/mod.rs new file mode 100644 index 0000000..55c88cb --- /dev/null +++ b/src/container/mod.rs @@ -0,0 +1 @@ +pub mod store; diff --git a/src/container/store.rs b/src/container/store.rs new file mode 100644 index 0000000..a09a61e --- /dev/null +++ b/src/container/store.rs @@ -0,0 +1,25 @@ +use std::{fs, path::PathBuf}; + +use crate::error::{BuilderError, BuilderResult}; + +pub struct ContainerStore { + cstore_path: PathBuf, +} + +impl ContainerStore { + pub fn new(root_dir: &PathBuf) -> BuilderResult { + let mut cstore_path = PathBuf::from(&root_dir); + cstore_path.push("overlay-containers/"); + + match fs::create_dir_all(&cstore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(cstore_path, err)), + } + + Ok(Self { cstore_path }) + } + + pub fn cstore_path(&self) -> &PathBuf { + &self.cstore_path + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..0d35a41 --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,20 @@ +use std::{io, path::PathBuf}; + +use thiserror::Error; + +pub type BuilderResult = std::result::Result; + +#[derive(Debug, Error)] +pub enum BuilderError { + #[error("url {0} parse error: because {1}")] + UrlParseError(String, url::ParseError), + + #[error("client call error: {0}")] + ClientCallError(String), + + #[error("io error {0}: {1}")] + IoError(PathBuf, io::Error), + + #[error("builder lock error: {0}")] + BuilderLockError(io::Error), +} diff --git a/src/image/mod.rs b/src/image/mod.rs new file mode 100644 index 0000000..55c88cb --- /dev/null +++ b/src/image/mod.rs @@ -0,0 +1 @@ +pub mod store; diff --git a/src/image/store.rs b/src/image/store.rs new file mode 100644 index 0000000..b0191da --- /dev/null +++ b/src/image/store.rs @@ -0,0 +1,25 @@ +use std::{fs, path::PathBuf}; + +use crate::error::{BuilderError, BuilderResult}; + +pub struct ImageStore { + istore_path: PathBuf, +} + +impl ImageStore { + pub fn new(root_dir: &PathBuf) -> BuilderResult { + let mut istore_path = PathBuf::from(&root_dir); + istore_path.push("overlay-images/"); + + match fs::create_dir_all(&istore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(istore_path, err)), + } + + Ok(Self { istore_path }) + } + + pub fn istore_path(&self) -> &PathBuf { + &self.istore_path + } +} diff --git a/src/layer/mod.rs b/src/layer/mod.rs new file mode 100644 index 0000000..55c88cb --- /dev/null +++ b/src/layer/mod.rs @@ -0,0 +1 @@ +pub mod store; diff --git a/src/layer/store.rs b/src/layer/store.rs new file mode 100644 index 0000000..199c264 --- /dev/null +++ b/src/layer/store.rs @@ -0,0 +1,25 @@ +use std::{fs, path::PathBuf}; + +use crate::error::{BuilderError, BuilderResult}; + +pub struct LayerStore { + lstore_path: PathBuf, +} + +impl LayerStore { + pub fn new(root_dir: &PathBuf) -> BuilderResult { + let mut lstore_path = PathBuf::from(&root_dir); + lstore_path.push("overlay-layers/"); + + match fs::create_dir_all(&lstore_path) { + Ok(_) => {} + Err(err) => return Err(BuilderError::IoError(lstore_path, err)), + } + + Ok(Self { lstore_path }) + } + + pub fn lstore_path(&self) -> &PathBuf { + &self.lstore_path + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4235a64 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod builder; +pub mod commands; +pub mod container; +pub mod error; +pub mod image; +pub mod layer; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index e7a11a9..074eb66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,45 @@ +use std::ffi::OsString; + +use clap::{Parser, Subcommand}; +use ocibuilder::commands::reset; + +#[derive(Parser, Debug)] +#[clap(version = env!("CARGO_PKG_VERSION"), about)] +struct Opts { + /// Path to storage root directory + #[clap(short, long)] + root: Option, + + /// ocibuilder commands + #[clap(subcommand)] + subcmd: SubCommand, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Subcommand, Debug)] +enum SubCommand { + /// Pull an image from specified registry + // Pull(pull::Pull), + + /// Reset local storage + Reset(reset::Reset), +} + fn main() { - println!("Hello, world!"); + env_logger::builder().format_timestamp(None).init(); + + let opts = Opts::parse(); + let root_dir = opts.root; + + let result = match opts.subcmd { + SubCommand::Reset(reset) => reset.exec(root_dir), + }; + + match result { + Ok(_) => {} + Err(err) => { + log::error!("{}", err); + std::process::exit(1); + } + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..de716f7 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,21 @@ +use std::{ + ffi::OsString, + path::{Path, PathBuf}, +}; + +pub fn get_root_dir(root_dir: Option) -> PathBuf { + match root_dir { + Some(root) => PathBuf::from(root), + None => { + let default_sub_dir = Path::new(".local/share/ocibuilder/"); + let mut home_path = match home::home_dir() { + Some(hpath) => hpath, + None => PathBuf::from("/tmp"), + }; + + home_path.push(default_sub_dir); + + home_path + } + } +}