utils.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. use crate::{
  2. instance_manager::{InstanceInfo, InstanceManager},
  3. types::*,
  4. };
  5. use futures_util::StreamExt;
  6. use helix_db::{
  7. helix_engine::graph_core::config::Config,
  8. helixc::{
  9. analyzer::analyzer::analyze,
  10. generator::{generator_types::Source as GeneratedSource, tsdisplay::ToTypeScript},
  11. parser::helix_parser::{Content, HelixParser, HxFile, Source},
  12. },
  13. utils::styled_string::StyledString,
  14. };
  15. use reqwest::Client;
  16. use serde::Deserialize;
  17. use serde_json::{Value as JsonValue, json};
  18. use spinners::{Spinner, Spinners};
  19. use std::{
  20. error::Error,
  21. fmt::Write,
  22. fs::{self, DirEntry, File},
  23. io::{ErrorKind, Write as iWrite},
  24. net::{SocketAddr, TcpListener},
  25. path::{Path, PathBuf},
  26. process::{Command, Stdio},
  27. };
  28. use tokio_tungstenite::{
  29. connect_async,
  30. tungstenite::{
  31. Message,
  32. protocol::{CloseFrame, frame::coding::CloseCode},
  33. },
  34. };
  35. use toml::Value;
  36. pub const DB_DIR: &str = "helixdb-cfg/";
  37. pub const DEFAULT_SCHEMA: &str = r#"// Start building your schema here.
  38. //
  39. // The schema is used to to ensure a level of type safety in your queries.
  40. //
  41. // The schema is made up of Node types, denoted by N::,
  42. // and Edge types, denoted by E::
  43. //
  44. // Under the Node types you can define fields that
  45. // will be stored in the database.
  46. //
  47. // Under the Edge types you can define what type of node
  48. // the edge will connect to and from, and also the
  49. // properties that you want to store on the edge.
  50. //
  51. // Example:
  52. //
  53. // N::User {
  54. // Name: String,
  55. // Label: String,
  56. // Age: Integer,
  57. // IsAdmin: Boolean,
  58. // }
  59. //
  60. // E::Knows {
  61. // From: User,
  62. // To: User,
  63. // Properties: {
  64. // Since: Integer,
  65. // }
  66. // }
  67. //
  68. // For more information on how to write queries,
  69. // see the documentation at https://docs.helix-db.com
  70. // or checkout our GitHub at https://github.com/HelixDB/helix-db
  71. "#;
  72. pub const DEFAULT_QUERIES: &str = r#"// Start writing your queries here.
  73. //
  74. // You can use the schema to help you write your queries.
  75. //
  76. // Queries take the form:
  77. // QUERY {query name}({input name}: {input type}) =>
  78. // {variable} <- {traversal}
  79. // RETURN {variable}
  80. //
  81. // Example:
  82. // QUERY GetUserFriends(user_id: String) =>
  83. // friends <- N<User>(user_id)::Out<Knows>
  84. // RETURN friends
  85. //
  86. //
  87. // For more information on how to write queries,
  88. // see the documentation at https://docs.helix-db.com
  89. // or checkout our GitHub at https://github.com/HelixDB/helix-db
  90. "#;
  91. pub fn check_helix_installation() -> Option<PathBuf> {
  92. let home_dir = dirs::home_dir()?;
  93. let repo_path = home_dir.join(".helix/repo/helix-db");
  94. let container_path = repo_path.join("helix-container");
  95. let cargo_path = container_path.join("Cargo.toml");
  96. if !repo_path.exists()
  97. || !repo_path.is_dir()
  98. || !container_path.exists()
  99. || !container_path.is_dir()
  100. || !cargo_path.exists()
  101. {
  102. return None;
  103. }
  104. Some(container_path)
  105. }
  106. pub fn print_instance(instance: &InstanceInfo) {
  107. let rg: bool = instance.running;
  108. println!(
  109. "{} {} {}{}",
  110. if rg {
  111. format!(
  112. "{}{}{}",
  113. "(".green().bold(),
  114. instance.short_id.to_string().green().bold(),
  115. ")".green().bold(),
  116. )
  117. } else {
  118. format!(
  119. "{}{}{}",
  120. "(".yellow().bold(),
  121. instance.short_id.to_string().green().bold(),
  122. ")".yellow().bold(),
  123. )
  124. },
  125. if rg {
  126. "Instance ID:".green().bold()
  127. } else {
  128. "Instance ID:".yellow().bold()
  129. },
  130. if rg {
  131. instance.id.green().bold()
  132. } else {
  133. instance.id.yellow().bold()
  134. },
  135. if rg {
  136. " (running)".to_string().green().bold()
  137. } else {
  138. " (not running)".to_string().yellow().bold()
  139. },
  140. );
  141. println!(
  142. "└── Short ID: {}",
  143. instance.short_id.to_string().underline()
  144. );
  145. println!("└── Port: {}", instance.port);
  146. println!("└── Available endpoints:");
  147. instance
  148. .available_endpoints
  149. .iter()
  150. .for_each(|ep| println!(" └── /{ep}"));
  151. }
  152. pub fn get_cli_version() -> Result<Version, String> {
  153. Version::parse(&format!("v{}", env!("CARGO_PKG_VERSION")))
  154. }
  155. pub fn get_crate_version<P: AsRef<Path>>(path: P) -> Result<Version, String> {
  156. let cargo_toml_path = path.as_ref().join("Cargo.toml");
  157. if !cargo_toml_path.exists() {
  158. return Err("Not a Rust crate: Cargo.toml not found".to_string());
  159. }
  160. let contents = fs::read_to_string(&cargo_toml_path)
  161. .map_err(|e| format!("Failed to read Cargo.toml: {e}"))?;
  162. let parsed_toml = contents
  163. .parse::<Value>()
  164. .map_err(|e| format!("Failed to parse Cargo.toml: {e}"))?;
  165. let version = parsed_toml
  166. .get("package")
  167. .and_then(|pkg| pkg.get("version"))
  168. .and_then(|v| v.as_str())
  169. .ok_or("Version field not found in [package] section")?;
  170. let vers = Version::parse(version)?;
  171. Ok(vers)
  172. }
  173. pub async fn get_remote_helix_version() -> Result<Version, Box<dyn Error>> {
  174. let client = Client::new();
  175. let url = "https://api.github.com/repos/HelixDB/helix-db/releases/latest";
  176. let response = client
  177. .get(url)
  178. .header("User-Agent", "rust")
  179. .header("Accept", "application/vnd.github+json")
  180. .send()
  181. .await?
  182. .text()
  183. .await?;
  184. let json: JsonValue = serde_json::from_str(&response)?;
  185. let tag_name = json
  186. .get("tag_name")
  187. .and_then(|v| v.as_str())
  188. .ok_or("Failed to find tag_name in response")?
  189. .to_string();
  190. Ok(Version::parse(&tag_name)?)
  191. }
  192. pub async fn github_login() -> Result<(String, String), Box<dyn Error>> {
  193. let url = "ws://ec2-184-72-27-116.us-west-1.compute.amazonaws.com:3000/login";
  194. let (mut ws_stream, _) = connect_async(url).await?;
  195. let init_msg: UserCodeMsg = match ws_stream.next().await {
  196. Some(Ok(Message::Text(payload))) => sonic_rs::from_str(&payload)?,
  197. Some(Ok(m)) => return Err(format!("Unexpected message: {m:?}").into()),
  198. Some(Err(e)) => return Err(e.into()),
  199. None => return Err("Connection Closed Unexpectedly".into()),
  200. };
  201. println!(
  202. "To Login please go \x1b]8;;{}\x1b\\here\x1b]8;;\x1b\\({}),\nand enter the code: {}",
  203. init_msg.verification_uri,
  204. init_msg.verification_uri,
  205. init_msg.user_code.bold()
  206. );
  207. let msg: ApiKeyMsg = match ws_stream.next().await {
  208. Some(Ok(Message::Text(payload))) => sonic_rs::from_str(&payload)?,
  209. Some(Ok(Message::Close(Some(CloseFrame {
  210. code: CloseCode::Error,
  211. reason,
  212. })))) => return Err(format!("Error: {reason}").into()),
  213. Some(Ok(m)) => return Err(format!("Unexpected message: {m:?}").into()),
  214. Some(Err(e)) => return Err(e.into()),
  215. None => return Err("Connection Closed Unexpectedly".into()),
  216. };
  217. Ok((msg.key, msg.user_id))
  218. }
  219. #[derive(Deserialize)]
  220. struct UserCodeMsg {
  221. user_code: String,
  222. verification_uri: String,
  223. }
  224. #[derive(Deserialize)]
  225. struct ApiKeyMsg {
  226. user_id: String,
  227. key: String,
  228. }
  229. /// tries to parse a credential file, returning the key, if any
  230. pub fn parse_credentials(creds: &str) -> Option<&str> {
  231. for line in creds.lines() {
  232. if let Some((key, value)) = line.split_once("=")
  233. && key.to_lowercase() == "helix_user_key"
  234. {
  235. return Some(value);
  236. }
  237. }
  238. None
  239. }
  240. pub async fn check_helix_version() {
  241. match check_helix_installation() {
  242. Some(_) => {}
  243. None => return,
  244. }
  245. let repo_path = {
  246. let home_dir = match dirs::home_dir() {
  247. Some(dir) => dir,
  248. None => return,
  249. };
  250. home_dir.join(".helix/repo/helix-db/helix-db")
  251. };
  252. let local_cli_version = match Version::parse(&format!("v{}", env!("CARGO_PKG_VERSION"))) {
  253. Ok(value) => value,
  254. Err(_) => return,
  255. };
  256. let crate_version = match get_crate_version(&repo_path) {
  257. Ok(value) => value,
  258. Err(_) => return,
  259. };
  260. let local_db_version = match Version::parse(&format!("v{crate_version}")) {
  261. Ok(value) => value,
  262. Err(_) => return,
  263. };
  264. let remote_helix_version = match get_remote_helix_version().await {
  265. Ok(value) => value,
  266. Err(_) => return,
  267. };
  268. if local_db_version < remote_helix_version || local_cli_version < remote_helix_version {
  269. println!(
  270. "{} {} {} {}",
  271. "New HelixDB version is available!".yellow().bold(),
  272. "Run".yellow().bold(),
  273. "helix update".white().bold(),
  274. "to install the newest version!".yellow().bold(),
  275. );
  276. }
  277. }
  278. pub fn check_cargo_version() -> bool {
  279. match Command::new("cargo").arg("--version").output() {
  280. Ok(output) => {
  281. let version_str = String::from_utf8_lossy(&output.stdout);
  282. let version = version_str
  283. .split_whitespace()
  284. .nth(1)
  285. .unwrap_or("0.0.0")
  286. .split('-')
  287. .next()
  288. .unwrap_or("0.0.0");
  289. let parts: Vec<u32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
  290. if parts.len() >= 2 {
  291. parts[0] >= 1 && parts[1] >= 88
  292. } else {
  293. false
  294. }
  295. }
  296. Err(_) => false,
  297. }
  298. }
  299. pub fn get_n_helix_cli() -> Result<(), Box<dyn Error>> {
  300. // TODO: running this through rust doesn't identify GLIBC so has to compile from source
  301. let status = Command::new("sh")
  302. .arg("-c")
  303. .arg("curl -sSL 'https://install.helix-db.com' | bash")
  304. .env(
  305. "PATH",
  306. format!(
  307. "{}:{}",
  308. std::env::var("HOME")
  309. .map(|h| format!("{h}/.cargo/bin"))
  310. .unwrap_or_default(),
  311. std::env::var("PATH").unwrap_or_default()
  312. ),
  313. )
  314. .stdout(Stdio::inherit())
  315. .stderr(Stdio::inherit())
  316. .status()?;
  317. if !status.success() {
  318. return Err(format!("Command failed with status: {status}").into());
  319. }
  320. Ok(())
  321. }
  322. /// Checks if the path contains a schema.hx and config.hx.json file
  323. /// Returns a vector of DirEntry objects for all .hx files in the path
  324. pub fn check_and_read_files(path: &str) -> Result<Vec<DirEntry>, String> {
  325. if !fs::read_dir(path)
  326. .map_err(|e| format!("IO Error: {e}"))?
  327. .any(|file| file.ok().is_some_and(|f| f.file_name() == "schema.hx"))
  328. {
  329. return Err("No schema file found".to_string());
  330. }
  331. if !fs::read_dir(path)
  332. .map_err(|e| format!("IO Error: {e}"))?
  333. .any(|file| file.ok().is_some_and(|f| f.file_name() == "config.hx.json"))
  334. {
  335. return Err("No config.hx.json file found".to_string());
  336. }
  337. let files: Vec<DirEntry> = fs::read_dir(path)
  338. .unwrap()
  339. .filter_map(|entry| entry.ok())
  340. .filter(|file| file.file_name().to_string_lossy().ends_with(".hx"))
  341. .collect();
  342. let has_queries = files.iter().any(|file| file.file_name() != "schema.hx");
  343. if !has_queries {
  344. return Err("No query files (.hx) found".to_string());
  345. }
  346. Ok(files)
  347. }
  348. /// Generates a Content object from a vector of DirEntry objects
  349. /// Returns a Content object with the files and source
  350. ///
  351. /// This essentially makes a full string of all of the files while having a separate vector of the individual files
  352. ///
  353. /// This could be changed in the future but keeps the option open for being able to access the files separately or all at once
  354. pub fn generate_content(files: &[DirEntry]) -> Result<Content, String> {
  355. let files: Vec<HxFile> = files
  356. .iter()
  357. .map(|file| {
  358. let name = file.path().to_string_lossy().into_owned();
  359. let content = fs::read_to_string(file.path()).unwrap();
  360. HxFile { name, content }
  361. })
  362. .collect();
  363. let content = files
  364. .clone()
  365. .iter()
  366. .map(|file| file.content.clone())
  367. .collect::<Vec<String>>()
  368. .join("\n");
  369. Ok(Content {
  370. content,
  371. files,
  372. source: Source::default(),
  373. })
  374. }
  375. /// Uses the helix parser to parse the content into a Source object
  376. fn parse_content(content: &Content) -> Result<Source, String> {
  377. let source = match HelixParser::parse_source(content) {
  378. Ok(source) => source,
  379. Err(e) => {
  380. return Err(e.to_string());
  381. }
  382. };
  383. Ok(source)
  384. }
  385. /// Runs the static analyzer on the parsed source to catch errors and generate diagnostics if any.
  386. /// Otherwise returns the generated source object which is an IR used to transpile the queries to rust.
  387. fn analyze_source(source: Source) -> Result<GeneratedSource, String> {
  388. let (diagnostics, source) = analyze(&source);
  389. if !diagnostics.is_empty() {
  390. for diag in diagnostics {
  391. let filepath = diag.filepath.clone().unwrap_or("queries.hx".to_string());
  392. println!("{}", diag.render(&source.src, &filepath));
  393. }
  394. return Err("compilation failed!".to_string());
  395. }
  396. Ok(source)
  397. }
  398. pub fn generate(files: &[DirEntry], path: &str) -> Result<(Content, GeneratedSource), String> {
  399. let mut content = generate_content(files)?;
  400. content.source = parse_content(&content)?;
  401. let mut analyzed_source = analyze_source(content.source.clone())?;
  402. analyzed_source.config = read_config(path)?;
  403. Ok((content, analyzed_source))
  404. }
  405. pub fn read_config(path: &str) -> Result<Config, String> {
  406. let config_path = PathBuf::from(path).join("config.hx.json");
  407. let schema_path = PathBuf::from(path).join("schema.hx");
  408. let config = Config::from_files(config_path, schema_path).map_err(|e| e.to_string())?;
  409. Ok(config)
  410. }
  411. pub fn gen_typescript(source: &GeneratedSource, output_path: &str) -> Result<(), String> {
  412. let mut file = match File::create(PathBuf::from(output_path).join("interface.d.ts")) {
  413. Ok(file) => file,
  414. Err(e) => return Err(e.to_string()),
  415. };
  416. for node in &source.nodes {
  417. match write!(file, "{}", node.to_typescript()) {
  418. Ok(_) => {}
  419. Err(e) => return Err(e.to_string()),
  420. }
  421. }
  422. for edge in &source.edges {
  423. match write!(file, "{}", edge.to_typescript()) {
  424. Ok(_) => {}
  425. Err(e) => return Err(e.to_string()),
  426. }
  427. }
  428. for vector in &source.vectors {
  429. match write!(file, "{}", vector.to_typescript()) {
  430. Ok(_) => {}
  431. Err(e) => return Err(e.to_string()),
  432. }
  433. }
  434. Ok(())
  435. }
  436. pub fn find_available_port(start_port: u16) -> Option<u16> {
  437. let mut port = start_port;
  438. while port < 65535 {
  439. let addr = format!("0.0.0.0:{port}").parse::<SocketAddr>().unwrap();
  440. match TcpListener::bind(addr) {
  441. Ok(listener) => {
  442. drop(listener);
  443. let localhost = format!("127.0.0.1:{port}").parse::<SocketAddr>().unwrap();
  444. match TcpListener::bind(localhost) {
  445. Ok(local_listener) => {
  446. drop(local_listener);
  447. return Some(port);
  448. }
  449. Err(e) => {
  450. if e.kind() != ErrorKind::AddrInUse {
  451. return None;
  452. }
  453. port += 1;
  454. continue;
  455. }
  456. }
  457. }
  458. Err(e) => {
  459. if e.kind() != ErrorKind::AddrInUse {
  460. return None;
  461. }
  462. port += 1;
  463. continue;
  464. }
  465. }
  466. }
  467. None
  468. }
  469. pub fn get_cfg_deploy_path(cmd_path: Option<String>) -> String {
  470. if let Some(path) = cmd_path {
  471. return path;
  472. }
  473. let cwd = ".";
  474. let files = match check_and_read_files(cwd) {
  475. Ok(files) => files,
  476. Err(_) => {
  477. return DB_DIR.to_string();
  478. }
  479. };
  480. if !files.is_empty() {
  481. return cwd.to_string();
  482. }
  483. DB_DIR.to_string()
  484. }
  485. pub fn compile_and_build_helix(
  486. path: String,
  487. output: &PathBuf,
  488. files: Vec<DirEntry>,
  489. ) -> Result<Content, String> {
  490. let mut sp = Spinner::new(Spinners::Dots9, "Compiling Helix queries".into());
  491. let num_files = files.len();
  492. let (code, analyzed_source) = match generate(&files, &path) {
  493. Ok((code, analyzer_source)) => (code, analyzer_source),
  494. Err(e) => {
  495. sp.stop_with_message("Error compiling queries".red().bold().to_string());
  496. println!("└── {e}");
  497. return Err("Error compiling queries".to_string());
  498. }
  499. };
  500. sp.stop_with_message(format!(
  501. "{} {} {}",
  502. "Successfully compiled".green().bold(),
  503. num_files,
  504. "query files".green().bold()
  505. ));
  506. let cache_dir = PathBuf::from(&output);
  507. fs::create_dir_all(&cache_dir).unwrap();
  508. let file_path = PathBuf::from(&output).join("src/queries.rs");
  509. let mut generated_rust_code = String::new();
  510. match write!(&mut generated_rust_code, "{analyzed_source}") {
  511. Ok(_) => println!("{}", "Successfully transpiled queries".green().bold()),
  512. Err(e) => {
  513. println!("{}", "Failed to transpile queries".red().bold());
  514. println!("└── {} {}", "Error:".red().bold(), e);
  515. return Err("Failed to transpile queries".to_string());
  516. }
  517. }
  518. match fs::write(file_path, generated_rust_code) {
  519. Ok(_) => println!("{}", "Successfully wrote queries file".green().bold()),
  520. Err(e) => {
  521. println!("{}", "Failed to write queries file".red().bold());
  522. println!("└── {} {}", "Error:".red().bold(), e);
  523. return Err("Failed to write queries file".to_string());
  524. }
  525. }
  526. let mut sp = Spinner::new(Spinners::Dots9, "Building Helix".into());
  527. let config_path = PathBuf::from(&output).join("src/config.hx.json");
  528. fs::copy(
  529. PathBuf::from(path.to_string() + "/config.hx.json"),
  530. config_path,
  531. )
  532. .unwrap();
  533. let schema_path = PathBuf::from(&output).join("src/schema.hx");
  534. fs::copy(PathBuf::from(path.to_string() + "/schema.hx"), schema_path).unwrap();
  535. let mut runner = Command::new("cargo");
  536. runner
  537. .arg("check")
  538. .stdout(Stdio::null())
  539. .stderr(Stdio::null())
  540. .current_dir(PathBuf::from(&output));
  541. match runner.output() {
  542. Ok(_) => {}
  543. Err(e) => {
  544. sp.stop_with_message("Failed to check Rust code".red().bold().to_string());
  545. println!("└── {} {}", "Error:".red().bold(), e);
  546. return Err("Error checking rust code".to_string());
  547. }
  548. }
  549. let mut runner = Command::new("cargo");
  550. runner
  551. .arg("build")
  552. .arg("--release")
  553. .current_dir(PathBuf::from(&output))
  554. .env("RUSTFLAGS", "-Awarnings");
  555. match runner.output() {
  556. Ok(output) => {
  557. if output.status.success() {
  558. sp.stop_with_message("Successfully built Helix".green().bold().to_string());
  559. Ok(code)
  560. } else {
  561. sp.stop_with_message("Failed to build Helix".red().bold().to_string());
  562. let stderr = String::from_utf8_lossy(&output.stderr);
  563. if !stderr.is_empty() {
  564. println!("└── {} {}", "Error:\n".red().bold(), stderr);
  565. }
  566. Err("Error building helix".to_string())
  567. }
  568. }
  569. Err(e) => {
  570. sp.stop_with_message("Failed to build Helix".red().bold().to_string());
  571. println!("└── {} {}", "Error:".red().bold(), e);
  572. Err("Error building helix".to_string())
  573. }
  574. }
  575. }
  576. pub fn deploy_helix(port: u16, code: Content, instance_id: Option<String>) -> Result<(), String> {
  577. let mut sp = Spinner::new(Spinners::Dots9, "Starting Helix instance".into());
  578. let instance_manager = InstanceManager::new().unwrap();
  579. let binary_path = dirs::home_dir()
  580. .map(|path| path.join(".helix/repo/helix-db/target/release/helix-container"))
  581. .unwrap();
  582. let endpoints: Vec<String> = code.source.queries.iter().map(|q| q.name.clone()).collect();
  583. if let Some(iid) = instance_id {
  584. let cached_binary = instance_manager.cache_dir.join(&iid);
  585. match fs::copy(binary_path, &cached_binary) {
  586. Ok(_) => {}
  587. Err(e) => {
  588. println!("{} {}", "Error while copying binary:".red().bold(), e);
  589. return Err("".to_string());
  590. }
  591. }
  592. match instance_manager.start_instance(&iid, Some(endpoints)) {
  593. Ok(instance) => {
  594. sp.stop_with_message(
  595. "Successfully started Helix instance"
  596. .green()
  597. .bold()
  598. .to_string(),
  599. );
  600. print_instance(&instance);
  601. Ok(())
  602. }
  603. Err(e) => {
  604. sp.stop_with_message("Failed to start Helix instance".red().bold().to_string());
  605. println!("└── {} {}", "Error:".red().bold(), e);
  606. Err("".to_string())
  607. }
  608. }
  609. } else {
  610. match instance_manager.init_start_instance(&binary_path, port, endpoints) {
  611. Ok(instance) => {
  612. sp.stop_with_message(
  613. "Successfully started Helix instance"
  614. .green()
  615. .bold()
  616. .to_string(),
  617. );
  618. print_instance(&instance);
  619. Ok(())
  620. }
  621. Err(e) => {
  622. sp.stop_with_message("Failed to start Helix instance".red().bold().to_string());
  623. println!("└── {} {}", "Error:".red().bold(), e);
  624. Err("Failed to start Helix instance".to_string())
  625. }
  626. }
  627. }
  628. }
  629. pub fn redeploy_helix(instance: String, code: Content) -> Result<(), String> {
  630. let instance_manager = InstanceManager::new().unwrap();
  631. let iid = instance;
  632. match instance_manager.get_instance(&iid) {
  633. Ok(Some(_)) => println!("{}", "Helix instance found!".green().bold()),
  634. Ok(None) => {
  635. println!(
  636. "{} {}",
  637. "No Helix instance found with id".red().bold(),
  638. iid.red().bold()
  639. );
  640. return Err("Error".to_string());
  641. }
  642. Err(e) => {
  643. println!("{} {}", "Error:".red().bold(), e);
  644. return Err("".to_string());
  645. }
  646. };
  647. match instance_manager.stop_instance(&iid) {
  648. Ok(_) => {}
  649. Err(e) => {
  650. println!("{} {}", "Error while stopping instance:".red().bold(), e);
  651. return Err("".to_string());
  652. }
  653. }
  654. match deploy_helix(0, code, Some(iid)) {
  655. Ok(_) => Ok(()),
  656. Err(e) => Err(e.to_string()),
  657. }
  658. }
  659. pub async fn redeploy_helix_remote(
  660. cluster: String,
  661. path: String,
  662. files: Vec<DirEntry>,
  663. ) -> Result<(), String> {
  664. let mut sp = Spinner::new(Spinners::Dots9, "Uploading queries to remote db".into());
  665. let content = match generate_content(&files) {
  666. Ok(content) => content,
  667. Err(e) => {
  668. sp.stop_with_message("Error generating content".red().bold().to_string());
  669. println!("└── {e}");
  670. return Err("".to_string());
  671. }
  672. };
  673. // get config from ~/.helix/credentials
  674. let home_dir = std::env::var("HOME").unwrap_or("~/".to_string());
  675. let config_path = &format!("{home_dir}/.helix");
  676. let config_path = Path::new(config_path);
  677. let config_path = config_path.join("credentials");
  678. if !config_path.exists() {
  679. sp.stop_with_message("No credentials found".yellow().bold().to_string());
  680. println!(
  681. "{}",
  682. "Please run `helix config` to set your credentials"
  683. .yellow()
  684. .bold()
  685. );
  686. return Err("".to_string());
  687. }
  688. // TODO: probable could make this more secure
  689. // reads credentials from ~/.helix/credentials
  690. let config = fs::read_to_string(config_path).unwrap();
  691. let user_id = config
  692. .split("helix_user_id=")
  693. .nth(1)
  694. .unwrap()
  695. .split("\n")
  696. .next()
  697. .unwrap();
  698. let user_key = config
  699. .split("helix_user_key=")
  700. .nth(1)
  701. .unwrap()
  702. .split("\n")
  703. .next()
  704. .unwrap();
  705. // read config.hx.json
  706. let config = match Config::from_files(
  707. PathBuf::from(path.clone()).join("config.hx.json"),
  708. PathBuf::from(path.clone()).join("schema.hx"),
  709. ) {
  710. Ok(config) => config,
  711. Err(e) => {
  712. println!("Error loading config: {e}");
  713. sp.stop_with_message("Error loading config".red().bold().to_string());
  714. return Err("".to_string());
  715. }
  716. };
  717. // upload queries to central server
  718. let payload = json!({
  719. "user_id": user_id,
  720. "queries": content.files,
  721. "cluster_id": cluster,
  722. "version": "0.1.0",
  723. "helix_config": config.to_json()
  724. });
  725. println!("{payload:#?}");
  726. let client = reqwest::Client::new();
  727. println!("{user_key}");
  728. println!("{}", &cluster);
  729. match client
  730. .post(
  731. "http://ec2-184-72-27-116.us-west-1.compute.amazonaws.com:3000/clusters/deploy-queries",
  732. )
  733. .header("x-api-key", user_key) // used to verify user
  734. .header("x-cluster-id", &cluster) // used to verify instance with user
  735. .header("Content-Type", "application/json")
  736. .body(sonic_rs::to_string(&payload).unwrap())
  737. .send()
  738. .await
  739. {
  740. Ok(response) => {
  741. if response.status().is_success() {
  742. sp.stop_with_message("Queries uploaded to remote db".green().bold().to_string());
  743. } else {
  744. sp.stop_with_message(
  745. "Error uploading queries to remote db"
  746. .red()
  747. .bold()
  748. .to_string(),
  749. );
  750. println!("└── {}", response.text().await.unwrap());
  751. return Err("".to_string());
  752. }
  753. }
  754. Err(e) => {
  755. sp.stop_with_message(
  756. "Error uploading queries to remote db"
  757. .red()
  758. .bold()
  759. .to_string(),
  760. );
  761. println!("└── {e}");
  762. return Err("".to_string());
  763. }
  764. };
  765. Ok(())
  766. }