A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
3.6 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. use crate::async_utils::join;
  2. use crate::resources::Resource;
  3. use crate::to_artifact::ToArtifact;
  4. use async_trait::async_trait;
  5. use slog::{Drain, Filter, Logger, OwnedKVList, Record};
  6. use slog_async::AsyncRecord;
  7. use std::cell::RefCell;
  8. use std::error::Error;
  9. use std::fmt::Debug;
  10. use std::io::{self, Write};
  11. use std::rc::Rc;
  12. use std::sync::{Arc, Mutex};
  13. pub trait AddableResource: 'static + Resource + Debug {}
  14. impl<R> AddableResource for R where R: 'static + Resource + Debug {}
  15. pub type AddResult<R> = Result<(<R as ToArtifact>::Artifact, bool), Box<dyn Error>>;
  16. #[async_trait(?Send)]
  17. pub trait Add<R: AddableResource> {
  18. async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R>;
  19. }
  20. #[async_trait(?Send)]
  21. pub trait AddGeneric<X: ToArtifact> {
  22. async fn add_generic(&self, logger: &Rc<Logger>, x: X, force_run: bool) -> AddResult<X>;
  23. }
  24. macro_rules! add_generic {
  25. ( $($name:ident)* ) => (
  26. #[async_trait(?Send)]
  27. #[allow(non_snake_case)]
  28. impl<_S, $($name: AddableResource,)*>
  29. AddGeneric<($($name,)*)> for _S
  30. where
  31. $(
  32. _S: AddGeneric<$name>
  33. ),*
  34. {
  35. #[allow(unused, clippy::shadow_unrelated)]
  36. async fn add_generic(&self, logger: &Rc<Logger>, ($($name,)*): ($($name,)*), force_run: bool) -> AddResult<($($name,)*)>
  37. {
  38. let ($($name,)*) = join!($(self.add_generic(logger, $name, force_run),)*);
  39. let mut did_run_any = false;
  40. $(
  41. let (artifact, did_run) = $name?;
  42. did_run_any = did_run_any || did_run;
  43. let $name = artifact;
  44. )*
  45. Ok((($($name,)*), did_run_any))
  46. }
  47. }
  48. );
  49. }
  50. for_each_tuple!(add_generic);
  51. #[async_trait(?Send)]
  52. impl<R: AddableResource + Debug, S: Add<R>> AddGeneric<R> for S {
  53. async fn add_generic(&self, logger: &Rc<Logger>, r: R, force_run: bool) -> AddResult<R> {
  54. self.add(logger, Rc::new(r), force_run).await
  55. }
  56. }
  57. // From https://users.rust-lang.org/t/how-to-send-a-writer-into-a-thread/4965/10
  58. #[derive(Clone)]
  59. struct Output<W>(Rc<RefCell<W>>);
  60. impl<W: Write> Output<W> {
  61. pub fn new(w: W) -> Self {
  62. Self(Rc::new(RefCell::new(w)))
  63. }
  64. }
  65. impl<W: Write> Write for Output<W> {
  66. fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
  67. self.0.borrow_mut().write(buf)
  68. }
  69. fn flush(&mut self) -> io::Result<()> {
  70. self.0.borrow_mut().flush()
  71. }
  72. }
  73. #[derive(Clone, Default)]
  74. pub struct Recorder(Arc<Mutex<Vec<AsyncRecord>>>);
  75. impl Drain for Recorder {
  76. type Ok = ();
  77. type Err = slog::Never;
  78. fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
  79. self
  80. .0
  81. .lock()
  82. .unwrap()
  83. .push(AsyncRecord::from(record, logger_values));
  84. Ok(())
  85. }
  86. }
  87. impl Recorder {
  88. pub fn into_string(self, filter_level: slog::Level) -> String {
  89. let output = Output::new(vec![]);
  90. {
  91. let decorator = slog_term::PlainDecorator::new(output.clone());
  92. let drain = Filter::new(
  93. slog_term::CompactFormat::new(decorator).build(),
  94. move |record| record.level().is_at_least(filter_level),
  95. );
  96. let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap
  97. for record in mutex.into_inner().unwrap() {
  98. record.log_to(&drain).unwrap();
  99. }
  100. }
  101. String::from_utf8(Rc::try_unwrap(output.0).unwrap().into_inner())
  102. .expect("Record output should be valid UTF-8")
  103. }
  104. }
  105. #[cfg(test)]
  106. mod test {
  107. use super::Recorder;
  108. use slog::Level;
  109. #[test]
  110. fn records_no_output() {
  111. let recorder = Recorder::default();
  112. assert_eq!(recorder.into_string(Level::Trace), "");
  113. }
  114. }