├── .gitignore ├── Cargo.toml ├── README.md ├── crane ├── .gitignore ├── Cargo.toml └── src │ ├── image.rs │ └── lib.rs └── src ├── image.rs ├── main.rs ├── run.rs ├── tarfile.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tmp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | nix = "*" 10 | rand = "*" 11 | log = "*" 12 | serde_json = "*" 13 | serde = {version = "*", features = ["derive"]} 14 | env_logger = "*" 15 | tar = "*" 16 | crane = { path = "./crane" } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rsdocker: A mini Docker written in Rust 2 | 3 | This project reference to [containers-the-hard-way](https://github.com/shuveb/containers-the-hard-way) 4 | -------------------------------------------------------------------------------- /crane/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /crane/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crane" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = {version = "*", features = ["derive"]} 10 | serde_json = "*" 11 | tempfile = "*" 12 | reqwest = { version = "0.11.13", features = ["json", "blocking"] } -------------------------------------------------------------------------------- /crane/src/image.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::io::Cursor; 3 | use std::fs::{create_dir_all, File}; 4 | use serde::{Deserialize, Serialize}; 5 | use reqwest::header::{CONTENT_TYPE, ETAG}; 6 | 7 | #[derive(Debug, PartialEq, PartialOrd)] 8 | pub struct Image { 9 | pub registry: String, 10 | pub repository: String, 11 | pub name: String, 12 | pub reference: String, 13 | } 14 | impl Default for Image { 15 | fn default() -> Image { 16 | Image { 17 | registry: String::from("registry.hub.docker.com"), 18 | repository: String::from("library"), 19 | name: String::from(""), 20 | reference: String::from(""), 21 | } 22 | } 23 | } 24 | 25 | impl Image { 26 | pub fn get_manifest(&self, token: Option<&String>) -> Result { 27 | let client = reqwest::blocking::Client::new(); 28 | let builder = client.get(format!( 29 | "https://{}/v2/{}/{}/manifests/{}", 30 | self.registry, self.repository, self.name, self.reference 31 | )); 32 | let builder = match token { 33 | Some(token) => { 34 | builder.header("Authorization", format!("Bearer {}", &token)) 35 | }, 36 | None => builder, 37 | }; 38 | let response = builder.send()?; 39 | let headers = response.headers().clone(); 40 | let mut manifest: Manifest; 41 | 42 | if let Some(content_type) = headers.get(CONTENT_TYPE) { 43 | match content_type.to_str() { 44 | Ok(content_type_str) => { 45 | if content_type_str == "application/vnd.oci.image.index.v1+json" { 46 | // manifest list type 47 | // TODO: 48 | todo!("manifest list") 49 | } else if content_type_str == "application/vnd.docker.distribution.manifest.v1+prettyjws" { 50 | // image manifest type 51 | manifest = response.json()?; 52 | } else { 53 | panic!("Received content with unknown or different Content-Type."); 54 | } 55 | } 56 | Err(_) => { 57 | panic!("Invalid Content-Type header."); 58 | } 59 | } 60 | } else { 61 | panic!("No Content-Type header found."); 62 | } 63 | 64 | manifest.etag = Some(String::from(headers.get(ETAG).unwrap().to_str().unwrap()).trim_start_matches("\"sha256:").chars().take(64).collect::()); 65 | Ok(manifest) 66 | } 67 | 68 | pub fn download_layers( 69 | &self, 70 | path: &str, 71 | token: Option<&String>, 72 | manifest: &Manifest 73 | ) -> Result<(), Box> { 74 | let path = Path::new(path); 75 | create_dir_all(path)?; 76 | let layer_dir = tempfile::tempdir().expect("Failed to create temporary directory."); 77 | let layer_file_path = PathBuf::from(layer_dir.path()).join("layer.tar"); 78 | let client = reqwest::blocking::Client::new(); 79 | for layer in manifest.layers.iter() { 80 | let builder = client.get(&format!( 81 | "https://{}/v2/{}/{}/blobs/{}", 82 | self.registry, self.repository, self.name, layer.digest 83 | )); 84 | let builder = match token { 85 | Some(token) => builder.bearer_auth(token), 86 | None => builder, 87 | }; 88 | let response = builder.send().expect("Failed to get layer."); 89 | let mut content = Cursor::new(response.bytes().expect("Failed to get content")); 90 | let mut file = File::create(&layer_file_path).expect("Failed to create temporary file"); 91 | std::io::copy(&mut content, &mut file) 92 | .expect(format!("Failed to download layer {}", &layer.digest).as_str()); 93 | // extract the layer 94 | std::process::Command::new("tar") 95 | .current_dir("/bin") 96 | .args([ 97 | "-xf", 98 | &layer_file_path.to_str().unwrap(), 99 | "-C", 100 | &path.to_str().unwrap(), 101 | ]) 102 | .output()?; 103 | } 104 | layer_dir.close()?; 105 | Ok(()) 106 | } 107 | } 108 | 109 | #[derive(Debug, Deserialize)] 110 | pub struct AuthResponse { 111 | pub access_token: String, 112 | } 113 | #[derive(Debug, Deserialize, Serialize)] 114 | pub struct FsLayer { 115 | #[serde(rename(deserialize = "blobSum", serialize = "blobSum"))] 116 | pub digest: String, 117 | } 118 | #[derive(Debug, Deserialize, Serialize, Default)] 119 | pub struct Manifest { 120 | #[serde(rename(deserialize = "fsLayers", serialize = "fsLayers"))] 121 | pub layers: Vec, 122 | // #[serde(skip)] 123 | pub etag: Option 124 | } 125 | 126 | impl Manifest { 127 | pub fn new() -> Self { 128 | Manifest { ..Default::default() } 129 | } 130 | } 131 | 132 | pub fn authenticate(image: &Image) -> Result { 133 | let client = reqwest::blocking::Client::new(); 134 | let auth_response: AuthResponse = client 135 | .get(format!( 136 | "https://auth.docker.io/token?service=registry.docker.io&scope=repository:{}/{}:pull", 137 | image.repository, image.name 138 | )) 139 | .send()? 140 | .json()?; 141 | Ok(auth_response) 142 | } 143 | 144 | 145 | pub fn parse_image_reference(reference: &str) -> Result { 146 | let image_and_version: Vec<&str> = reference.split(":").collect(); 147 | let version: Result, &str> = match image_and_version.len() { 148 | 1 => Ok(None), 149 | 2 => Ok(Some(image_and_version[1])), 150 | _ => Err("Invalid image format"), 151 | }; 152 | let version = version?.unwrap_or("latest"); 153 | let tokens: Vec<&str> = image_and_version[0].split("/").collect(); 154 | match tokens.len() { 155 | 1 => Ok(Image { 156 | name: tokens[0].to_string(), 157 | reference: version.to_string(), 158 | ..Default::default() 159 | }), 160 | 2 => Ok(Image { 161 | repository: tokens[0].to_string(), 162 | name: tokens[1].to_string(), 163 | reference: version.to_string(), 164 | ..Default::default() 165 | }), 166 | _ => Err(format!("Invalid image format {}", reference)), 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /crane/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod image; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | #[test] 6 | fn it_works() { 7 | // TODO 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::{self, File}; 3 | use serde_json; 4 | use crane::image::*; 5 | use crate::utils::*; 6 | 7 | type ImageEntries = HashMap; 8 | type ImagesDB = HashMap; 9 | 10 | pub fn get_base_path_for_image(image_sha_hex: &String) -> String { 11 | return get_rsdocker_images_path() + image_sha_hex; 12 | } 13 | 14 | pub fn get_manifest_path_for_image(image_sha_hex: &String) -> String { 15 | return get_base_path_for_image(image_sha_hex) + "/manifest.json"; 16 | } 17 | 18 | fn get_image_name_and_tag(src: &str) -> (&str, &str) { 19 | let mut image_name = ""; 20 | let mut image_tag = ""; 21 | let mut image_name_and_tag = src.split(":"); 22 | image_name = image_name_and_tag.next().unwrap(); 23 | image_tag = image_name_and_tag.next().unwrap_or("latest"); 24 | (image_name, image_tag) 25 | } 26 | 27 | fn parse_images_metdata(idb: &mut ImagesDB) -> () { 28 | let images_db_path = get_rsdocker_images_path(); 29 | let images_db_file_path = format!("{}/images.json", images_db_path); 30 | 31 | if !fs::metadata(&images_db_file_path).is_ok() { 32 | File::create(&images_db_file_path).expect("Unable to create file"); 33 | fs::write(&images_db_file_path, "{}").expect("Unable to write file"); 34 | } 35 | let mut content = fs::read_to_string(&images_db_file_path).expect("Unable to read file"); 36 | let tmp: ImagesDB = serde_json::from_str(&mut content).expect("Unable to parse json"); 37 | *idb = tmp; 38 | } 39 | 40 | fn image_exist_by_tag(image_name: &str, tag_name: &str) -> (bool, String) { 41 | let idb: &mut ImagesDB = &mut HashMap::new(); 42 | parse_images_metdata(idb); 43 | for (k, v) in idb { 44 | if k == image_name { 45 | for (k, v) in v { 46 | if k == tag_name { 47 | return (true, v.to_string()); 48 | } 49 | } 50 | } 51 | } 52 | return (false, "".to_string()); 53 | } 54 | 55 | fn image_exists_by_hash(image_sha_hex: &String) -> (String, String) { 56 | let idb: &mut ImagesDB = &mut HashMap::new(); 57 | parse_images_metdata(idb); 58 | for (img_name, avl_images) in idb { 59 | for (img_tag, img_hash) in avl_images { 60 | if img_hash == image_sha_hex { 61 | return (img_name.to_string(), img_tag.to_string()); 62 | } 63 | } 64 | } 65 | return ("".to_string(), "".to_string()); 66 | } 67 | 68 | fn marshal_image_metadata(idb: &ImagesDB) -> () { 69 | let file_bytes = serde_json::to_string(idb).expect("Unable to marshal json"); 70 | let images_db_path = get_rsdocker_images_path() + "images.json"; 71 | 72 | fs::write(&images_db_path, file_bytes).expect("Unable to write file"); 73 | } 74 | 75 | fn store_image_metadata(image: &str, tag: &str, image_sha_hex: &String) -> () { 76 | let mut idb = &mut HashMap::new(); 77 | let ientry = &mut HashMap::new(); 78 | 79 | parse_images_metdata(&mut idb); 80 | if idb.contains_key(image) { 81 | *ientry = idb.get_mut(image).unwrap().clone(); 82 | } 83 | ientry.insert(tag.to_string(), image_sha_hex.to_string()); 84 | idb.insert(image.to_string(), ientry.to_owned()); 85 | 86 | marshal_image_metadata(idb); 87 | } 88 | 89 | fn process_layer_tarballs(manifest: &crane::image::Manifest, image_sha_hex: &String, full_image_hex: &String) { 90 | let tmp_path_dir = get_rsdocker_tmp_path() + image_sha_hex; 91 | let path_manifest = get_rsdocker_tmp_path() + "/manifest.json"; 92 | let path_config = tmp_path_dir.clone() + "/" + full_image_hex + ".json"; 93 | 94 | let mut mani = Manifest::new(); 95 | parse_manifest(path_manifest, &mut mani); 96 | 97 | // let images_dir = get_rsdocker_images_path() + image_sha_hex; 98 | // for layer in manifest.layers.iter() { 99 | // // let image_layer_dir = images_dir + "/" + layer[:12] +"/fs"; 100 | // let layer_hash = layer.digest.trim_start_matches("sha256:").chars().take(64).collect::(); 101 | // let image_layer_dir = images_dir.clone() + "/" + layer_hash.chars().take(16).collect::().as_str() + "fs"; 102 | 103 | // log::info!("Uncompressing layer to: {}", image_layer_dir); 104 | // if !fs::metadata(&image_layer_dir).is_ok() { 105 | // fs::create_dir(&image_layer_dir).unwrap(); 106 | // } 107 | 108 | // } 109 | // TODO: 110 | } 111 | 112 | fn delete_temp_image_files(image_sha_hex: &String) { 113 | let tmp_path = get_rsdocker_tmp_path() + image_sha_hex; 114 | do_or_die_with_msg(fs::remove_dir_all(tmp_path).err(), "Unable to remove temporary image files"); 115 | } 116 | 117 | fn store_manifest_metadata(manifest: &Manifest) { 118 | let manifest_path = get_rsdocker_tmp_path() + "manifest.json"; 119 | let metadata = serde_json::to_string(manifest).expect("Unable to manifest json"); 120 | fs::write(&manifest_path, metadata).expect("Unable to write file"); 121 | } 122 | 123 | pub fn down_load_image_if_required(src: &str) -> String { 124 | let (img_name, tag_name) = get_image_name_and_tag(src); 125 | log::info!("image_name = {}, image_tag = {}", img_name, tag_name); 126 | 127 | let (image_is_exist, mut image_sha_hex) = image_exist_by_tag(img_name, tag_name); 128 | log::info!("image_is_exist = {}, image_sha_hex = {}", image_is_exist, image_sha_hex); 129 | 130 | if !image_is_exist { 131 | log::info!("Downloading metadata for {}:{}, please wait...", img_name, tag_name); 132 | let image: Image = Image {name: img_name.to_string(), reference: tag_name.to_string(), ..Default::default() }; 133 | let auth = authenticate(&image).expect("Failed to authenticate."); 134 | let manifest = image.get_manifest(Some(&auth.access_token)).expect("Failed to retrieve manifest."); 135 | let etag = manifest.etag.as_ref(); 136 | image_sha_hex = etag.unwrap().trim_start_matches("\"sha256:").chars().take(64).collect(); 137 | log::info!("image_sha_hex: {:#?}", image_sha_hex); 138 | log::info!("Checking if image exists under another name..."); 139 | let (alt_img_name, alt_img_tag) = image_exists_by_hash(&image_sha_hex); 140 | 141 | if alt_img_name != "" && alt_img_tag != "" { 142 | log::info!("The image you requested {}:{} is the same as {}:{}", img_name, tag_name, alt_img_name, alt_img_tag); 143 | store_image_metadata(img_name, tag_name, &image_sha_hex); 144 | () 145 | } 146 | else{ 147 | log::info!("Image doesn't exist. Downloading..."); 148 | let down_path = get_rsdocker_tmp_path() + &image_sha_hex; 149 | image.download_layers(&down_path, Some(&auth.access_token), &manifest).unwrap(); 150 | log::info!("Successfully downloaded {}", down_path); 151 | // TODO: 152 | store_manifest_metadata(&manifest); 153 | // process_layer_tarballs(&manifest, &image_sha_hex, &image_sha_hex); 154 | store_image_metadata(img_name, tag_name, &image_sha_hex); 155 | // delete_temp_image_files(&image_sha_hex); 156 | () 157 | } 158 | }else{ 159 | log::info!("Image already exists. Not downloading."); 160 | () 161 | } 162 | image_sha_hex 163 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod image; 2 | mod run; 3 | mod tarfile; 4 | mod utils; 5 | 6 | use std::{env, process}; 7 | use nix::unistd::geteuid; 8 | use run::init_container; 9 | use utils::init_rsdocker_dirs; 10 | 11 | fn usage() -> () { 12 | println!("Welcome to Rsdocker!"); 13 | println!("Supported commands:"); 14 | println!("rsdocker run [--mem] [--swap] [--pids] [--cpus] "); 15 | println!("rsdocker exec "); 16 | println!("rsdocker images"); 17 | println!("rsdocker rmi "); 18 | println!("rsdocker ps"); 19 | } 20 | 21 | fn main() -> () { 22 | let args: Vec = env::args().collect(); 23 | let options = ["run", "child-mode", "setup-netns", "setup-veth", "ps", "exec", "images", "rmi"]; 24 | 25 | if env::var("RUST_LOG").is_err() { 26 | env::set_var("RUST_LOG", "info"); 27 | } 28 | if env::var("https_proxy").is_err() { 29 | env::set_var("https_proxy", "http://127.0.0.1:7890"); 30 | env::set_var("http_proxy", "http://127.0.0.1:7890"); 31 | env::set_var("all_proxy", "socks5://127.0.0.1:7890"); 32 | } 33 | env_logger::init(); 34 | 35 | // 检查参数数量 36 | if args.len() < 2 || !options.contains(&args[1].as_str()) { 37 | log::error!("Args length is less than 2 or invalid option"); 38 | usage(); 39 | process::exit(1); 40 | } 41 | 42 | if geteuid().is_root() == false { 43 | println!("You need root privileges to run this program."); 44 | process::exit(1); 45 | } 46 | log::info!("args: {:?}", args); 47 | 48 | init_rsdocker_dirs().expect("Unable to create requisite directories"); 49 | match args[1].as_str() { 50 | "run" => { 51 | init_container("centos"); 52 | }, 53 | "exec" => { 54 | todo!("exec"); 55 | }, 56 | "images" => { 57 | todo!("images"); 58 | }, 59 | "rmi" => { 60 | todo!("rmi"); 61 | }, 62 | "ps" => { 63 | todo!("ps"); 64 | }, 65 | _ => { 66 | usage(); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use nix::mount::{mount, MsFlags}; 3 | use rand::Rng; 4 | use crane::image::*; 5 | use crate::image::*; 6 | use crate::utils::*; 7 | 8 | fn get_container_fs_home(container_id: &String) -> String { 9 | return get_rsdocker_containers_path() + container_id + "/fs"; 10 | } 11 | 12 | fn create_container_id() -> String { 13 | let mut rng = rand::thread_rng(); 14 | let random_bytes: Vec = (0..16) 15 | .map(|_| rng.gen::()) 16 | .collect(); 17 | 18 | let container_id = random_bytes.iter() 19 | .map(|byte| format!("{:02x}", byte)) 20 | .collect::(); 21 | 22 | container_id 23 | } 24 | 25 | fn create_container_directories(container_id: &String) { 26 | let cont_home = get_rsdocker_containers_path() + container_id; 27 | let cont_dirs = vec![ cont_home.clone() + "/fs", cont_home.clone() + "/fs/mnt/", cont_home.clone() + "/fs/upperdir/", cont_home.clone() + "/fs/workdir/"]; 28 | 29 | if let Err(e) = create_dirs_if_dont_exist(&cont_dirs) { 30 | log::error!("{}", e); 31 | } 32 | } 33 | 34 | 35 | fn mount_overlay_file_system(container_id: &String, image_sha_hex: &String) { 36 | let path_manifest = get_rsdocker_tmp_path() + "/manifest.json"; 37 | let mut mani = Manifest::new(); 38 | 39 | parse_manifest(path_manifest,&mut mani); 40 | 41 | let image_base_path = get_base_path_for_image(&image_sha_hex); 42 | // for layer in &mani[0].layers { 43 | let src_layers = image_base_path.clone() + mani.etag.unwrap().as_str(); 44 | // } 45 | 46 | let const_fs_home = get_container_fs_home(&container_id); 47 | let mnt_options = format!( 48 | "lowerdir={}:upperdir={}/upperdir,workdir={}/workdir", 49 | src_layers, 50 | const_fs_home, 51 | const_fs_home 52 | ); 53 | 54 | let fstype = Some(CString::new("overlay").expect("CString::new failed")); 55 | let flags = MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC; 56 | let data = Some(CString::new(mnt_options).expect("CString::new failed")); 57 | let source = None; 58 | let target = CString::new(const_fs_home + "/mnt").expect("CString::new failed"); 59 | if let Err(err) = mount(source.as_ref(), &target, fstype.as_ref(), flags, data.as_ref()) { 60 | eprintln!("Mount failed: {}", err); 61 | return; 62 | } 63 | } 64 | 65 | pub fn init_container(src: &str) { 66 | let container_id = create_container_id(); 67 | log::info!("container_id = {}", container_id); 68 | 69 | let image_sha_hex = down_load_image_if_required(src); 70 | log::info!("Image to overlay mount: {}", image_sha_hex); 71 | 72 | create_container_directories(&container_id); 73 | mount_overlay_file_system(&container_id, &image_sha_hex); 74 | 75 | } -------------------------------------------------------------------------------- /src/tarfile.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::Path; 3 | use tar::Archive; 4 | 5 | pub fn untar(tar_path: &String, dest_path: &String) -> Result<(), Box> { 6 | let file = File::open(tar_path)?; 7 | let mut tar = Archive::new(file); 8 | let output_path = Path::new(dest_path); 9 | 10 | for entry in tar.entries()? { 11 | let mut entry = entry?; 12 | let entry_path = entry.path()?; 13 | let output_entry_path = output_path.join(entry_path); 14 | 15 | if entry.header().entry_type().is_dir() { 16 | std::fs::create_dir_all(&output_entry_path)?; 17 | } else { 18 | let mut output_file = File::create(&output_entry_path)?; 19 | std::io::copy(&mut entry, &mut output_file)?; 20 | } 21 | } 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, File}; 2 | use std::io::prelude::*; 3 | use std::error::Error; 4 | use crane::image::Manifest; 5 | 6 | static RSDOCKER_HOME_PATH: &str = "/var/lib/rsdocker/"; 7 | static RSDOCKER_TEMP_PATH: &str = "/var/lib/rsdocker/tmp/"; 8 | static RSDOCKER_IMAGES_PATH: &str = "/var/lib/rsdocker/images/"; 9 | static RSDOCKER_CONTAINERS_PATH: &str = "/var/run/rsdocker/containers/"; 10 | static RSDOCKER_NET_NS_PATH: &str = "/var/run/rsdocker/net-ns/"; 11 | 12 | pub fn init_rsdocker_dirs() -> Result<(), std::io::Error> { 13 | let dirs = [RSDOCKER_HOME_PATH, RSDOCKER_TEMP_PATH, RSDOCKER_IMAGES_PATH, RSDOCKER_CONTAINERS_PATH, RSDOCKER_NET_NS_PATH]; 14 | 15 | for dir in dirs.iter() { 16 | if !fs::metadata(&dir).is_ok() { 17 | fs::create_dir_all(&dir)?; 18 | } 19 | } 20 | Ok(()) 21 | } 22 | 23 | pub fn do_or_die_with_msg(err: Option , msg: &str) { 24 | match err { 25 | Some(_err) => { 26 | println!("{}", msg); 27 | }, 28 | None => { 29 | 30 | } 31 | } 32 | } 33 | 34 | pub fn create_dirs_if_dont_exist(dirs: &[String]) -> Result<(), std::io::Error> { 35 | for dir in dirs { 36 | if !fs::metadata(dir).is_ok() { 37 | fs::create_dir_all(&dir)?; 38 | } 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn get_rsdocker_images_path() -> String { 44 | RSDOCKER_IMAGES_PATH.to_string() 45 | } 46 | 47 | pub fn get_rsdocker_tmp_path() -> String { 48 | RSDOCKER_TEMP_PATH.to_string() 49 | } 50 | 51 | pub fn get_rsdocker_containers_path() -> String { 52 | RSDOCKER_CONTAINERS_PATH.to_string() 53 | } 54 | 55 | pub fn parse_manifest(manifest_path: String, mani: &mut Manifest) { 56 | let mut file = File::open(manifest_path).unwrap(); 57 | let mut content = String::new(); 58 | 59 | file.read_to_string(&mut content); 60 | let serde_mani: Manifest = serde_json::from_str(content.as_str()).expect("Unable prase json"); 61 | *mani = serde_mani; 62 | } --------------------------------------------------------------------------------