/meilies-inspect/src/main.rs

https://github.com/meilisearch/MeiliES · Rust · 119 lines · 95 code · 16 blank · 8 comment · 6 complexity · 6edd3cbccf5c86d9519a055f63cd58b7 MD5 · raw file

  1. use std::io::{Error, ErrorKind, Write};
  2. use std::net::ToSocketAddrs;
  3. use std::process::{Command, Stdio};
  4. use futures::future::{self, Future};
  5. use futures::stream::Stream;
  6. use meilies::reqresp::Response;
  7. use meilies::stream::Stream as EsStream;
  8. use meilies_client::sub_connect;
  9. use structopt::StructOpt;
  10. #[derive(Debug, StructOpt)]
  11. #[structopt(
  12. name = "meilies-inspect",
  13. about = "A cli to inspect MeiliES events by executing a command on each event.",
  14. author
  15. )]
  16. struct Opt {
  17. /// Server hostname.
  18. #[structopt(short = "h", long = "hostname", default_value = "127.0.0.1")]
  19. hostname: String,
  20. /// Server port.
  21. #[structopt(short = "p", long = "port", default_value = "6480")]
  22. port: u16,
  23. /// Stream to follow on which events need to be inspected.
  24. #[structopt(long = "stream", parse(try_from_str))]
  25. stream: EsStream,
  26. /// Command and arguments that will interpret the event data piped in stdin.
  27. ///
  28. /// `MEILIES_STREAM_NAME` contains the stream name.
  29. /// `MEILIES_EVENT_NAME` contains the event name.
  30. /// `MEILIES_EVENT_NUMBER` contains the event number.
  31. command: String,
  32. }
  33. fn future_io_err<T, E>(error: E) -> future::FutureResult<T, std::io::Error>
  34. where
  35. E: Into<Box<dyn std::error::Error + Send + Sync>>,
  36. {
  37. future::err(Error::new(ErrorKind::Other, error))
  38. }
  39. fn main() {
  40. let Opt {
  41. hostname,
  42. port,
  43. stream,
  44. command,
  45. } = Opt::from_args();
  46. let addr = (hostname.as_str(), port);
  47. let addr = match addr
  48. .to_socket_addrs()
  49. .map(|addrs| addrs.filter(|a| a.is_ipv4()).next())
  50. {
  51. Ok(Some(addr)) => addr,
  52. Ok(None) => panic!("impossible to dns resolve addr; {:?}", addr),
  53. Err(e) => panic!("error parsing addr; {}", e),
  54. };
  55. let fut = sub_connect(addr)
  56. .map_err(|e| eprintln!("{}", e))
  57. .and_then(move |(mut ctrl, msgs)| {
  58. ctrl.subscribe_to(stream);
  59. msgs.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
  60. .for_each(move |msg| match msg {
  61. Ok(Response::Event {
  62. stream,
  63. number,
  64. event_name,
  65. event_data,
  66. }) => {
  67. eprintln!("processing event number {}", number.0);
  68. let result = Command::new("/bin/bash")
  69. .arg("-c")
  70. .arg(&command)
  71. .stdin(Stdio::piped())
  72. .env("MEILIES_STREAM_NAME", stream.into_inner())
  73. .env("MEILIES_EVENT_NAME", event_name.into_inner())
  74. .env("MEILIES_EVENT_NUMBER", number.0.to_string())
  75. .spawn();
  76. let mut child = match result {
  77. Ok(child) => child,
  78. Err(e) => return future::err(e),
  79. };
  80. let data = event_data.0.as_slice();
  81. if let Err(e) = child.stdin.as_mut().unwrap().write_all(data) {
  82. return future::err(e);
  83. }
  84. let output = match child.wait_with_output() {
  85. Ok(output) => output,
  86. Err(e) => return future::err(e),
  87. };
  88. if !output.status.success() {
  89. return future_io_err("command execution was not successful");
  90. }
  91. future::ok(())
  92. }
  93. Ok(_response) => future::ok(()),
  94. Err(error) => future_io_err(format!("Error: {}", error)),
  95. })
  96. .map_err(|e| eprintln!("{}", e))
  97. })
  98. .and_then(|_| {
  99. println!("Connection closed by the server");
  100. Err(())
  101. });
  102. tokio::run(fut);
  103. }