use std::borrow::Cow;
use std::ffi::OsString;
use std::fs::{self, DirEntry, ReadDir};
use std::path::PathBuf;
use std::str::FromStr;
use brane_cfg::info::Info as _;
use brane_cfg::node::{NodeConfig, NodeKind, NodeSpecificConfig};
use brane_tsk::docker;
use log::{debug, info, warn};
use specifications::version::Version;
pub use crate::errors::PackagesError as Error;
pub async fn hash(node_config_path: impl Into<PathBuf>, image: impl Into<String>) -> Result<(), Error> {
let node_config_path: PathBuf = node_config_path.into();
let image: String = image.into();
info!("Computing hash for image '{}'...", image);
debug!("Loading node config file '{}'...", node_config_path.display());
let node_config: NodeConfig = match NodeConfig::from_path(&node_config_path) {
Ok(config) => config,
Err(err) => {
return Err(Error::NodeConfigLoadError { err });
},
};
let packages_path: PathBuf = match node_config.node {
NodeSpecificConfig::Central(node) => node.paths.packages,
NodeSpecificConfig::Worker(node) => node.paths.packages,
NodeSpecificConfig::Proxy(_) => return Err(Error::UnsupportedNode { what: "compute a package hash", kind: NodeKind::Proxy }),
};
debug!("Resolving image...");
let mut image_path: PathBuf = PathBuf::from(&image);
if image_path.exists() {
if !image_path.is_file() {
return Err(Error::FileNotAFile { path: image_path });
}
} else {
let (name, version): (String, Version) = match Version::from_package_pair(&image) {
Ok(res) => res,
Err(err) => {
return Err(Error::IllegalNameVersionPair { raw: image, err });
},
};
let entries: ReadDir = match fs::read_dir(&packages_path) {
Ok(entries) => entries,
Err(err) => {
return Err(Error::DirReadError { what: "packages", path: packages_path, err });
},
};
let mut file: Option<(PathBuf, Version)> = None;
for (i, entry) in entries.enumerate() {
let entry: DirEntry = match entry {
Ok(entry) => entry,
Err(err) => {
return Err(Error::DirEntryReadError { what: "packages", entry: i, path: packages_path, err });
},
};
let entry_name: OsString = entry.file_name();
let entry_name: Cow<str> = entry_name.to_string_lossy();
let dash_pos: usize = match entry_name.find('-') {
Some(pos) => pos,
None => {
warn!("Missing dash ('-') in file '{}' (skipping)", entry.path().display());
continue;
},
};
let dot_pos: usize = match entry_name.rfind('.') {
Some(pos) => pos,
None => {
warn!("Missing extension dot ('.') in file '{}' (skipping)", entry.path().display());
continue;
},
};
let ename: &str = &entry_name[..dash_pos];
let eversion: &str = &entry_name[dash_pos + 1..dot_pos];
let eversion: Version = match Version::from_str(eversion) {
Ok(eversion) => eversion,
Err(err) => {
warn!("File '{}' has illegal version number '{}': {} (skipping)", entry.path().display(), eversion, err);
continue;
},
};
if name == ename {
if version.is_latest() {
if eversion.is_latest() || file.is_none() || eversion > file.as_ref().unwrap().1 {
let is_latest: bool = eversion.is_latest();
file = Some((entry.path(), eversion));
if is_latest {
break;
}
}
} else if version == eversion {
file = Some((entry.path(), eversion));
break;
}
}
}
if let Some((path, _)) = file {
image_path = path;
} else {
return Err(Error::UnknownImage { path: packages_path, name, version });
}
}
debug!("Hashing image '{}'...", image_path.display());
let hash: String = match docker::hash_container(&image_path).await {
Ok(hash) => hash,
Err(err) => {
return Err(Error::HashError { err });
},
};
println!("{hash}");
Ok(())
}