#![feature(proc_macro_hygiene, decl_macro)] extern crate rocket; extern crate serde; #[macro_use] extern crate serde_derive; extern crate nixideserver_lib; extern crate serde_json; use nixideserver_lib::*; use rocket::http::Status; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use std::thread; // podman create --name test -p 3000:3000 -v $PWD:/nixide -w /nixide docker.io/nixos/nix nix-shell --pure start-ide.nix --run run_ide.sh #[derive(Serialize, Deserialize, Clone)] pub enum PodmanState { Unkown, FetchingSource, StartingContainer, RunningContainer, StoppingContainer, StoppedContainer, DeletingContainer, DeletingSources, } #[derive(Serialize, Deserialize, Clone)] pub struct PodmanContainer { ide_id: String, state: PodmanState, ide_state: IdeState, ide_param: NixIdeServerParam, } #[derive(Serialize, Deserialize, Clone)] pub struct PodmanContainerList { container: Vec, } struct ThreadRunningParam { ide_folder: String, container: PodmanContainer, param: OpenGitParam, working_folder: PathBuf, } pub struct PodmanEngine { working_folder: PathBuf, list: PodmanContainerList, } impl PodmanEngine { pub fn new(working_folder: PathBuf) -> PodmanEngineTraitImpl { PodmanEngineTraitImpl { working_folder } } fn _new(working_folder: PathBuf) -> Self { let list_path = format!("{}/.podman_containers", &working_folder.display()); let list = match read_from_list_file(&list_path) { Ok(p) => p, _ => PodmanContainerList { container: Vec::new(), }, }; Self { working_folder, list, } } pub fn save(&self) -> Result<(), std::io::Error> { let json_string = serde_json::to_string_pretty(&self.list).unwrap(); // .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; fs::write( &format!("{}/.podman_containers", &self.working_folder.display()), json_string, ) } pub fn reload(trait_impl: &PodmanEngineTraitImpl) -> Result { Ok(Self::_new(trait_impl.working_folder.clone())) } fn find_mut_first_container(&mut self, ide_id: &str) -> Option<&mut PodmanContainer> { self.list.container.iter_mut().find(|i| i.ide_id.eq(ide_id)) } } #[derive(Serialize, Deserialize)] struct PodmanIdeStatus { ide_state: IdeState, } #[derive(Clone)] pub struct PodmanEngineTraitImpl { working_folder: PathBuf, } impl NixIdeManageServiceEngine for PodmanEngineTraitImpl { fn fetch_current_ide_state(&self, ide_id: &str) -> Result { let mut eng = PodmanEngine::reload(self) .map_err(|_| Status::new(500, "internal error : reload engine"))?; match eng.find_mut_first_container(ide_id) { Some(i) => Ok(i.ide_state.clone()), _ => Err(Status::NotFound), } } fn start_open(&self, ide_id: &str, param: &OpenGitParam) -> Result { let mut eng = PodmanEngine::reload(self) .map_err(|_| Status::new(500, "internal error : reload engine"))?; match eng.find_mut_first_container(ide_id) { Some(i) => Ok(i.ide_state.clone()), _ => { let ide_folder = create_ide_folder_path(&self.working_folder, ide_id); fs::create_dir_all(&ide_folder) .map_err(|_| Status::new(500, "internal error : can't create ide folder"))?; let listen_port = eng .list .container .iter() .max_by_key(|i| i.ide_param.listen_port) .and_then(|i| Some(i.ide_param.listen_port + 1)) .or(Some(3000)) .unwrap(); let ide_param = NixIdeServerParam { listen_address: default_listen_address(), listen_port, app_folder: default_app_folder(), project_folder: default_project_folder(), working_folder: default_working_folder(), }; let container = PodmanContainer { ide_id: ide_id.to_owned(), ide_state: IdeState::OPENING, state: PodmanState::FetchingSource, ide_param, }; eng.list.container.push(container.clone()); eng.save() .map_err(|_| Status::new(500, "internal error: can't save curent state"))?; let thread_param = ThreadRunningParam { ide_folder, container, param: param.clone(), working_folder: eng.working_folder, }; thread::spawn(move || { match working_thread(&thread_param) { Err(_) => clean_thread(&thread_param), _ => {} }; }); Ok(IdeState::OPENING) } } } } #[derive(Serialize, Deserialize, Clone)] pub struct NixIdeServerParam { #[serde(default = "default_listen_address")] listen_address: String, #[serde(default = "default_listen_port")] listen_port: u32, #[serde(default = "default_app_folder")] app_folder: String, #[serde(default = "default_project_folder")] project_folder: String, #[serde(default = "default_working_folder")] working_folder: String, } impl Default for NixIdeServerParam { fn default() -> Self { Self { listen_address: default_listen_address(), listen_port: default_listen_port(), app_folder: default_app_folder(), project_folder: default_project_folder(), working_folder: default_working_folder(), } } } fn default_listen_address() -> String { "0.0.0.0".to_owned() } fn default_listen_port() -> u32 { 3000 } fn default_app_folder() -> String { "_theiaideApp".to_owned() } fn default_project_folder() -> String { "$PWD".to_owned() } fn default_working_folder() -> String { "$PWD".to_owned() } fn create_ide_folder_path(working_folder_path: &Path, ide_id: &str) -> String { format!("{}/{}", working_folder_path.display(), ide_id) } fn read_from_list_file(path: &str) -> Result { match fs::read_to_string(path) { Ok(json_string) => serde_json::from_str::(&json_string) .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData)), Err(e) => Err(e), } } fn fetch_sources(repo_path: &str, param: &OpenGitParam) -> Result<(), std::io::Error> { let cmd_result = match Command::new("git") .arg("clone") .arg("-n") .arg(¶m.clone_url) .arg(repo_path) .status() { Ok(s) if s.success() => Command::new("git") .current_dir(repo_path) .arg("checkout") .arg(¶m.ref_name) .status(), e => e, }; match cmd_result { Ok(s) if s.success() => Ok(()), Err(e) => Err(e), _ => Err(std::io::Error::from(std::io::ErrorKind::InvalidData)), } } fn clean_thread(thread_param: &ThreadRunningParam) { let mut eng = PodmanEngine::_new(thread_param.working_folder.clone()); eng.list .container .iter() .position(|i| i.ide_id.eq(&thread_param.container.ide_id)) .and_then(|ix| { eng.list.container.remove(ix); Some(0) }) .unwrap_or_default(); eng.save().unwrap_or_default(); // todo delete container fs::remove_dir_all(eng.working_folder).unwrap_or_default(); } fn working_thread(thread_param: &ThreadRunningParam) -> Result<(), Error> { let ide_folder = &thread_param.ide_folder; let param = &thread_param.param; fetch_sources(&format!("{}/repo", ide_folder), ¶m).map_err(|_| Error {})?; let mut eng = PodmanEngine::_new(thread_param.working_folder.clone()); let ide_id = &thread_param.container.ide_id; match eng.find_mut_first_container(&ide_id) { Some(i) => { i.state = PodmanState::StartingContainer; eng.save().map_err(|_| Error {})?; } _ => {} }; let port = thread_param.container.ide_param.listen_port; let nix_expression = format!("with import {{}}; callPackage /nixide/repo/.nixide/start-ide.nix {{listenPort = {}; projectFolder = \"/nixide/repo\"; }}", port); let out = Command::new("podman") .current_dir(ide_folder) .arg("create") .arg("--name") .arg(ide_id) .arg("-p") .arg(format!("{}:{}", port, port)) .arg("-v") .arg(format!("{}:/nixide", ide_folder)) .arg("-w") .arg("/nixide") .arg("docker.io/nixos/nix") .arg("nix-shell") .arg("--expr") .arg(nix_expression) .arg("--pure") .arg("--run") .arg("run_ide.sh") .status().map_err(|_| Error{})?; // let nix_expression = format!("with import {{}}; callPackage {} {}", file, args); // let cmd = Command::new("nix-shell") // .stderr(Stdio::null()) // //.stdout(Stdio::null()) // .stdout(Stdio::piped()) // .arg("--expr") // .arg(nix_expression) // .arg("--pure") // .arg("--run") // .arg(run_script) // .spawn() // .expect("start ide command failed"); // podman create --name test -p 3000:3000 -v $PWD:/nixide -w /nixide docker.io/nixos/nix nix-shell --pure start-ide.nix --run run_ide.sh // .arg("create") // .arg("--name") // .arg(format!("{:x}", hash)) // .arg("-p") // .arg("3000:3000") // .arg("docker.io/nixos/nix") // // .arg(format!("git clone {}", param.clone_url)) // .output() // // String::from_utf8(out.stderr).unwrap() Ok(()) } #[test] fn test_fetch_sources() { let now = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap(); let temp_dir = std::env::temp_dir(); let repo_path = format!("{}/{}", temp_dir.display(), now.as_nanos()); let git_param = OpenGitParam { clone_url: "https://github.com/jmesmon/rust-hello-world.git".to_owned(), inquirer: "stubbfel".to_owned(), ref_name: "master".to_owned(), }; assert_eq!(fetch_sources(&repo_path, &git_param).unwrap(), ()); } #[test] fn test_fetch_sources_wrong_ref() { let now = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap(); let temp_dir = std::env::temp_dir(); let repo_path = format!("{}/{}", temp_dir.display(), now.as_nanos()); let git_param = OpenGitParam { clone_url: "https://github.com/jmesmon/rust-hello-world.git".to_owned(), inquirer: "stubbfel".to_owned(), ref_name: "master2".to_owned(), }; assert_eq!(fetch_sources(&repo_path, &git_param).is_err(), true); } #[test] fn test_fetch_sources_wrong_url() { let now = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap(); let temp_dir = std::env::temp_dir(); let repo_path = format!("{}/{}", temp_dir.display(), now.as_nanos()); let git_param = OpenGitParam { clone_url: "https://bar.com/foo.git".to_owned(), inquirer: "stubbfel".to_owned(), ref_name: "master".to_owned(), }; assert_eq!(fetch_sources(&repo_path, &git_param).is_err(), true); }