config.rs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. use crate::github::GithubClient;
  2. use failure::Error;
  3. use std::collections::HashMap;
  4. use std::fmt;
  5. use std::sync::{Arc, RwLock};
  6. use std::time::{Duration, Instant};
  7. static CONFIG_FILE_NAME: &str = "triagebot.toml";
  8. const REFRESH_EVERY: Duration = Duration::from_secs(2 * 60); // Every two minutes
  9. lazy_static::lazy_static! {
  10. static ref CONFIG_CACHE:
  11. RwLock<HashMap<String, (Result<Arc<Config>, ConfigurationError>, Instant)>> =
  12. RwLock::new(HashMap::new());
  13. }
  14. #[derive(Debug, serde::Deserialize)]
  15. pub(crate) struct Config {
  16. pub(crate) relabel: Option<RelabelConfig>,
  17. pub(crate) assign: Option<AssignConfig>,
  18. }
  19. #[derive(Debug, serde::Deserialize)]
  20. pub(crate) struct AssignConfig {
  21. #[serde(default)]
  22. _empty: (),
  23. }
  24. #[derive(Debug, serde::Deserialize)]
  25. #[serde(rename_all = "kebab-case")]
  26. pub(crate) struct RelabelConfig {
  27. #[serde(default)]
  28. pub(crate) allow_unauthenticated: Vec<String>,
  29. }
  30. pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, ConfigurationError> {
  31. if let Some(config) = get_cached_config(repo) {
  32. log::trace!("returning config for {} from cache", repo);
  33. config
  34. } else {
  35. log::trace!("fetching fresh config for {}", repo);
  36. let res = get_fresh_config(gh, repo).await;
  37. CONFIG_CACHE
  38. .write()
  39. .unwrap()
  40. .insert(repo.to_string(), (res.clone(), Instant::now()));
  41. res
  42. }
  43. }
  44. fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
  45. let cache = CONFIG_CACHE.read().unwrap();
  46. cache.get(repo).and_then(|(config, fetch_time)| {
  47. if fetch_time.elapsed() < REFRESH_EVERY {
  48. Some(config.clone())
  49. } else {
  50. None
  51. }
  52. })
  53. }
  54. async fn get_fresh_config(
  55. gh: &GithubClient,
  56. repo: &str,
  57. ) -> Result<Arc<Config>, ConfigurationError> {
  58. let contents = gh
  59. .raw_file(repo, "master", CONFIG_FILE_NAME)
  60. .await
  61. .map_err(|e| ConfigurationError::Http(Arc::new(e)))?
  62. .ok_or(ConfigurationError::Missing)?;
  63. let config = Arc::new(toml::from_slice::<Config>(&contents).map_err(ConfigurationError::Toml)?);
  64. log::debug!("fresh configuration for {}: {:?}", repo, config);
  65. Ok(config)
  66. }
  67. #[derive(Clone, Debug)]
  68. pub enum ConfigurationError {
  69. Missing,
  70. Toml(toml::de::Error),
  71. Http(Arc<Error>),
  72. }
  73. impl std::error::Error for ConfigurationError {}
  74. impl fmt::Display for ConfigurationError {
  75. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  76. match self {
  77. ConfigurationError::Missing => write!(
  78. f,
  79. "This repository is not enabled to use triagebot.\n\
  80. Add a `triagebot.toml` in the root of the master branch to enable it."
  81. ),
  82. ConfigurationError::Toml(e) => {
  83. write!(f, "Malformed `triagebot.toml` in master branch.\n{}", e)
  84. }
  85. ConfigurationError::Http(_) => {
  86. write!(f, "Failed to query configuration for this repository.")
  87. }
  88. }
  89. }
  90. }