Parcourir la source

add a per-repository configuration file

Pietro Albini il y a 6 ans
Parent
commit
035dac6320
6 fichiers modifiés avec 122 ajouts et 25 suppressions
  1. 17 0
      Cargo.lock
  2. 2 0
      Cargo.toml
  3. 61 0
      src/config.rs
  4. 19 1
      src/github.rs
  5. 16 24
      src/handlers/label.rs
  6. 7 0
      src/main.rs

+ 17 - 0
Cargo.lock

@@ -387,6 +387,11 @@ dependencies = [
  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "h2"
 version = "0.1.16"
@@ -1504,6 +1509,14 @@ dependencies = [
  "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "toml"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "traitobject"
 version = "0.1.0"
@@ -1516,6 +1529,7 @@ dependencies = [
  "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1528,6 +1542,7 @@ dependencies = [
  "rust_team_data 1.0.0 (git+https://github.com/rust-lang/team)",
  "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1771,6 +1786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
 "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
 "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
+"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 "checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e"
 "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
 "checksum http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1a10e5b573b9a0146545010f50772b9e8b1dd0a256564cc4307694c68832a2f5"
@@ -1887,6 +1903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c3fd86cb15547d02daa2b21aadaf4e37dee3368df38a526178a5afa3c034d2fb"
 "checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6"
 "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
+"checksum toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c5890a989fa47ecdc7bcb4c63a77a82c18f306714104b1decfd722db17b39e"
 "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
 "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
 "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"

+ 2 - 0
Cargo.toml

@@ -21,6 +21,8 @@ hex = "0.3.2"
 env_logger = "0.6"
 parser = { path = "parser" }
 rust_team_data = { git = "https://github.com/rust-lang/team" }
+glob = "0.3.0"
+toml = "0.5.0"
 
 [dependencies.serde]
 version = "1"

+ 61 - 0
src/config.rs

@@ -0,0 +1,61 @@
+use crate::github::GithubClient;
+use failure::Error;
+use std::collections::HashMap;
+use std::sync::{Arc, RwLock};
+use std::time::{Duration, Instant};
+
+static CONFIG_FILE_NAME: &str = "triagebot.toml";
+const REFRESH_EVERY: Duration = Duration::from_secs(2 * 60); // Every two minutes
+
+lazy_static::lazy_static! {
+    static ref CONFIG_CACHE: RwLock<HashMap<String, (Arc<Config>, Instant)>> =
+        RwLock::new(HashMap::new());
+}
+
+#[derive(serde::Deserialize)]
+pub(crate) struct Config {
+    pub(crate) label: Option<LabelConfig>,
+}
+
+#[derive(serde::Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub(crate) struct LabelConfig {
+    #[serde(default)]
+    pub(crate) allow_unauthenticated: Vec<String>,
+}
+
+pub(crate) fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
+    if let Some(config) = get_cached_config(repo) {
+        Ok(config)
+    } else {
+        get_fresh_config(gh, repo)
+    }
+}
+
+fn get_cached_config(repo: &str) -> Option<Arc<Config>> {
+    let cache = CONFIG_CACHE.read().unwrap();
+    cache.get(repo).and_then(|(config, fetch_time)| {
+        if fetch_time.elapsed() < REFRESH_EVERY {
+            Some(config.clone())
+        } else {
+            None
+        }
+    })
+}
+
+fn get_fresh_config(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, Error> {
+    let contents = gh
+        .raw_file(repo, "master", CONFIG_FILE_NAME)?
+        .ok_or_else(|| {
+            failure::err_msg(
+                "This repository is not enabled to use triagebot.\n\
+                 Add a `triagebot.toml` in the root of the master branch to enable it.",
+            )
+        })?;
+    let config = Arc::new(toml::from_slice::<Config>(&contents)?);
+    CONFIG_CACHE
+        .write()
+        .unwrap()
+        .insert(repo.to_string(), (config.clone(), Instant::now()));
+    Ok(config)
+}

+ 19 - 1
src/github.rs

@@ -1,6 +1,7 @@
 use failure::{Error, ResultExt};
 use reqwest::header::{AUTHORIZATION, USER_AGENT};
-use reqwest::{Client, Error as HttpError, RequestBuilder, Response};
+use reqwest::{Client, Error as HttpError, RequestBuilder, Response, StatusCode};
+use std::io::Read;
 
 #[derive(Debug, serde::Deserialize)]
 pub struct User {
@@ -183,6 +184,23 @@ impl GithubClient {
         &self.client
     }
 
+    pub fn raw_file(&self, repo: &str, branch: &str, path: &str) -> Result<Option<Vec<u8>>, Error> {
+        let url = format!(
+            "https://raw.githubusercontent.com/{}/{}/{}",
+            repo, branch, path
+        );
+        let mut resp = self.get(&url).send()?;
+        match resp.status() {
+            StatusCode::OK => {
+                let mut buf = Vec::with_capacity(resp.content_length().unwrap_or(4) as usize);
+                resp.read_to_end(&mut buf)?;
+                Ok(Some(buf))
+            }
+            StatusCode::NOT_FOUND => Ok(None),
+            status => failure::bail!("failed to GET {}: {}", url, status),
+        }
+    }
+
     fn get(&self, url: &str) -> RequestBuilder {
         log::trace!("get {:?}", url);
         self.client.get(url).configure(self)

+ 16 - 24
src/handlers/label.rs

@@ -33,6 +33,7 @@ impl Handler for LabelHandler {
             return Ok(());
         };
 
+        let repo = &event.repository.full_name;
         let mut issue_labels = event.issue.labels().to_owned();
 
         let mut input = Input::new(&event.comment.body, &self.username);
@@ -59,8 +60,8 @@ impl Handler for LabelHandler {
         let mut changed = false;
         for delta in &deltas {
             let name = delta.label().as_str();
-            if let Err(msg) = check_filter(name, &event.comment.user, &self.client) {
-                ErrorComment::new(&event.issue, msg).post(&self.client)?;
+            if let Err(msg) = check_filter(name, repo, &event.comment.user, &self.client) {
+                ErrorComment::new(&event.issue, msg.to_string()).post(&self.client)?;
                 return Ok(());
             }
             match delta {
@@ -89,7 +90,12 @@ impl Handler for LabelHandler {
     }
 }
 
-fn check_filter(label: &str, user: &github::User, client: &GithubClient) -> Result<(), String> {
+fn check_filter(
+    label: &str,
+    repo: &str,
+    user: &github::User,
+    client: &GithubClient,
+) -> Result<(), Error> {
     let is_team_member;
     match user.is_team_member(client) {
         Ok(true) => return Ok(()),
@@ -102,34 +108,20 @@ fn check_filter(label: &str, user: &github::User, client: &GithubClient) -> Resu
             // continue on; if we failed to check their membership assume that they are not members.
         }
     }
-    if label.starts_with("C-") // categories
-    || label.starts_with("A-") // areas
-    || label.starts_with("E-") // easy, mentor, etc.
-    || label.starts_with("NLL-")
-    || label.starts_with("O-") // operating systems
-    || label.starts_with("S-") // status labels
-    || label.starts_with("T-")
-    || label.starts_with("WG-")
-    {
-        return Ok(());
-    }
-    match label {
-        "I-compilemem" | "I-compiletime" | "I-crash" | "I-hang" | "I-ICE" | "I-slow" => {
+    let config = crate::config::get(client, repo)?;
+    for pattern in &config.label.as_ref().unwrap().allow_unauthenticated {
+        let pattern = glob::Pattern::new(pattern)?;
+        if pattern.matches(label) {
             return Ok(());
         }
-        _ => {}
     }
-
     if is_team_member.is_ok() {
-        Err(format!(
-            "Label {} can only be set by Rust team members",
-            label
-        ))
+        failure::bail!("Label {} can only be set by Rust team members", label);
     } else {
-        Err(format!(
+        failure::bail!(
             "Label {} can only be set by Rust team members;\
              we were unable to check if you are a team member.",
             label
-        ))
+        );
     }
 }

+ 7 - 0
src/main.rs

@@ -15,6 +15,7 @@ use std::sync::Arc;
 mod handlers;
 mod registry;
 
+mod config;
 mod github;
 mod interactions;
 mod payload;
@@ -37,6 +38,12 @@ pub struct IssueCommentEvent {
     action: IssueCommentAction,
     issue: Issue,
     comment: Comment,
+    repository: Repository,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Repository {
+    full_name: String,
 }
 
 enum Event {