summaryrefslogtreecommitdiff
path: root/ci-web/src/bin/cgi.rs
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@linux.dev>2023-01-26 12:21:17 -0500
committerKent Overstreet <kent.overstreet@linux.dev>2023-01-26 14:48:18 -0500
commit0ebafa9068f4eff792824bc089bebcc22eac01f3 (patch)
tree7ddf33e12e65d914e7553d0ad994d93f0c48dfaf /ci-web/src/bin/cgi.rs
parentf0b4bbb2b25d818f4327e87cde9ffa1774f88c0e (diff)
ci-web: Reorganize to fix build warnings
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Diffstat (limited to 'ci-web/src/bin/cgi.rs')
-rw-r--r--ci-web/src/bin/cgi.rs363
1 files changed, 363 insertions, 0 deletions
diff --git a/ci-web/src/bin/cgi.rs b/ci-web/src/bin/cgi.rs
new file mode 100644
index 0000000..e0776c5
--- /dev/null
+++ b/ci-web/src/bin/cgi.rs
@@ -0,0 +1,363 @@
+use std::collections::BTreeMap;
+use std::fmt::Write;
+use regex::Regex;
+extern crate cgi;
+extern crate querystring;
+
+use ci_cgi::{Ktestrc, ktestrc_read, TestResultsMap, TestStatus, read_lines, commitdir_get_results_toml, git_get_commit};
+
+const COMMIT_FILTER: &str = include_str!("../../commit-filter");
+const STYLESHEET: &str = "bootstrap.min.css";
+
+fn filter_results(r: TestResultsMap, tests_matching: &Regex) -> TestResultsMap {
+ r.iter()
+ .filter(|i| tests_matching.is_match(&i.0) )
+ .map(|(k, v)| (k.clone(), *v))
+ .collect()
+}
+
+struct Ci {
+ ktestrc: Ktestrc,
+ repo: git2::Repository,
+ stylesheet: String,
+ script_name: String,
+
+ branch: Option<String>,
+ commit: Option<String>,
+ tests_matching: Regex,
+}
+
+fn commitdir_get_results_filtered(ci: &Ci, commit_id: &String) -> TestResultsMap {
+ let results = commitdir_get_results_toml(&ci.ktestrc, commit_id).unwrap_or(BTreeMap::new());
+
+ filter_results(results, &ci.tests_matching)
+}
+
+struct CommitResults {
+ id: String,
+ message: String,
+ tests: TestResultsMap,
+}
+
+fn commit_get_results(ci: &Ci, commit: &git2::Commit) -> CommitResults {
+ let id = commit.id().to_string();
+ let tests = commitdir_get_results_filtered(ci, &id);
+
+ CommitResults {
+ id: id,
+ message: commit.message().unwrap().to_string(),
+ tests: tests,
+ }
+}
+
+fn branch_get_results(ci: &Ci) -> Result<Vec<CommitResults>, String> {
+ let mut nr_empty = 0;
+ let mut nr_commits = 0;
+ let mut ret: Vec<CommitResults> = Vec::new();
+
+ let branch = ci.branch.as_ref().unwrap();
+ let mut walk = ci.repo.revwalk().unwrap();
+
+ let reference = git_get_commit(&ci.repo, branch.clone());
+ if reference.is_err() {
+ /* XXX: return a 404 */
+ return Err(format!("commit not found"));
+ }
+ let reference = reference.unwrap();
+
+ if let Err(e) = walk.push(reference.id()) {
+ return Err(format!("Error walking {}: {}", branch, e));
+ }
+
+ for commit in walk
+ .filter_map(|i| i.ok())
+ .filter_map(|i| ci.repo.find_commit(i).ok()) {
+ let r = commit_get_results(ci, &commit);
+
+ if !r.tests.is_empty() {
+ nr_empty = 0;
+ } else {
+ nr_empty += 1;
+ if nr_empty > 100 {
+ break;
+ }
+ }
+
+ ret.push(r);
+
+ nr_commits += 1;
+ if nr_commits > 50 {
+ break;
+ }
+ }
+
+
+ while !ret.is_empty() && ret[ret.len() - 1].tests.is_empty() {
+ ret.pop();
+ }
+
+ Ok(ret)
+}
+
+fn ci_log(ci: &Ci) -> cgi::Response {
+ let mut out = String::new();
+ let branch = ci.branch.as_ref().unwrap();
+
+ let commits = branch_get_results(ci);
+ if let Err(e) = commits {
+ return error_response(e);
+ }
+
+ let commits = commits.unwrap();
+
+ let mut multiple_test_view = false;
+ for r in &commits {
+ if r.tests.len() > 1 {
+ multiple_test_view = true;
+ }
+ }
+
+ writeln!(&mut out, "<!DOCTYPE HTML>").unwrap();
+ writeln!(&mut out, "<html><head><title>{}</title></head>", branch).unwrap();
+ writeln!(&mut out, "<link href=\"{}\" rel=\"stylesheet\">", ci.stylesheet).unwrap();
+
+ writeln!(&mut out, "<body>").unwrap();
+ writeln!(&mut out, "<div class=\"container\">").unwrap();
+ writeln!(&mut out, "<table class=\"table\">").unwrap();
+
+
+ if multiple_test_view {
+ writeln!(&mut out, "<tr>").unwrap();
+ writeln!(&mut out, "<th> Commit </th>").unwrap();
+ writeln!(&mut out, "<th> Description </th>").unwrap();
+ writeln!(&mut out, "<th> Passed </th>").unwrap();
+ writeln!(&mut out, "<th> Failed </th>").unwrap();
+ writeln!(&mut out, "<th> Not started </th>").unwrap();
+ writeln!(&mut out, "<th> Not run </th>").unwrap();
+ writeln!(&mut out, "<th> In progress </th>").unwrap();
+ writeln!(&mut out, "<th> Unknown </th>").unwrap();
+ writeln!(&mut out, "<th> Total </th>").unwrap();
+ writeln!(&mut out, "<th> Duration </th>").unwrap();
+ writeln!(&mut out, "</tr>").unwrap();
+
+ let mut nr_empty = 0;
+ for r in &commits {
+ if !r.tests.is_empty() {
+ if nr_empty != 0 {
+ writeln!(&mut out, "<tr> <td> ({} untested commits) </td> </tr>", nr_empty).unwrap();
+ nr_empty = 0;
+ }
+
+ fn count(r: &TestResultsMap, t: TestStatus) -> usize {
+ r.iter().filter(|x| x.1.status == t).count()
+ }
+
+ let subject_len = r.message.find('\n').unwrap_or(r.message.len());
+
+ let duration: usize = r.tests.iter().map(|x| x.1.duration).sum();
+
+ writeln!(&mut out, "<tr>").unwrap();
+ writeln!(&mut out, "<td> <a href=\"{}?branch={}&commit={}\">{}</a> </td>",
+ ci.script_name, branch,
+ r.id, &r.id.as_str()[..14]).unwrap();
+ writeln!(&mut out, "<td> {} </td>", &r.message[..subject_len]).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::Passed)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::Failed)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::NotStarted)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::NotRun)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::InProgress)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", count(&r.tests, TestStatus::Unknown)).unwrap();
+ writeln!(&mut out, "<td> {} </td>", r.tests.len()).unwrap();
+ writeln!(&mut out, "<td> {}s </td>", duration).unwrap();
+ writeln!(&mut out, "</tr>").unwrap();
+ } else {
+ nr_empty += 1;
+ }
+ }
+ } else {
+ writeln!(&mut out, "<tr>").unwrap();
+ writeln!(&mut out, "<th> Commit </th>").unwrap();
+ writeln!(&mut out, "<th> Description </th>").unwrap();
+ writeln!(&mut out, "<th> Status </th>").unwrap();
+ writeln!(&mut out, "<th> Duration </th>").unwrap();
+ writeln!(&mut out, "</tr>").unwrap();
+
+ let mut nr_empty = 0;
+ for r in &commits {
+ if let Some(t) = r.tests.first_key_value() {
+ if nr_empty != 0 {
+ writeln!(&mut out, "<tr> <td> ({} untested commits) </td> </tr>", nr_empty).unwrap();
+ nr_empty = 0;
+ }
+
+ let subject_len = r.message.find('\n').unwrap_or(r.message.len());
+
+ writeln!(&mut out, "<tr class={}>", t.1.status.table_class()).unwrap();
+ writeln!(&mut out, "<td> <a href=\"{}?branch={}&commit={}\">{}</a> </td>",
+ ci.script_name, branch,
+ r.id, &r.id.as_str()[..14]).unwrap();
+ writeln!(&mut out, "<td> {} </td>", &r.message[..subject_len]).unwrap();
+ writeln!(&mut out, "<td> {} </td>", t.1.status.to_str()).unwrap();
+ writeln!(&mut out, "<td> {}s </td>", t.1.duration).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}/log.br> log </a> </td>", &r.id, t.0).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}/full_log.br> full log </a> </td>", &r.id, t.0).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}> output directory </a> </td>", &r.id, t.0).unwrap();
+ writeln!(&mut out, "</tr>").unwrap();
+ } else {
+ nr_empty += 1;
+ }
+ }
+ }
+
+ writeln!(&mut out, "</table>").unwrap();
+ writeln!(&mut out, "</div>").unwrap();
+ writeln!(&mut out, "</body>").unwrap();
+ writeln!(&mut out, "</html>").unwrap();
+ cgi::html_response(200, out)
+}
+
+fn ci_commit(ci: &Ci) -> cgi::Response {
+ let commit_id = ci.commit.as_ref().unwrap();
+ let mut out = String::new();
+ let commit = git_get_commit(&ci.repo, commit_id.clone());
+ if commit.is_err() {
+ /* XXX: return a 404 */
+ return error_response(format!("commit not found"));
+ }
+ let commit = commit.unwrap();
+
+ let message = commit.message().unwrap();
+ let subject_len = message.find('\n').unwrap_or(message.len());
+
+ writeln!(&mut out, "<!DOCTYPE HTML>").unwrap();
+ writeln!(&mut out, "<html><head><title>{}</title></head>", &message[..subject_len]).unwrap();
+ writeln!(&mut out, "<link href=\"{}\" rel=\"stylesheet\">", ci.stylesheet).unwrap();
+
+ writeln!(&mut out, "<body>").unwrap();
+ writeln!(&mut out, "<div class=\"container\">").unwrap();
+
+ writeln!(&mut out, "<h3><th>{}</th></h3>", &message[..subject_len]).unwrap();
+
+ out.push_str(COMMIT_FILTER);
+
+ writeln!(&mut out, "<table class=\"table\">").unwrap();
+
+ for (name, result) in commitdir_get_results_filtered(ci, &commit_id) {
+ writeln!(&mut out, "<tr class={}>", result.status.table_class()).unwrap();
+ writeln!(&mut out, "<td> {} </td>", name).unwrap();
+ writeln!(&mut out, "<td> {} </td>", result.status.to_str()).unwrap();
+ writeln!(&mut out, "<td> {}s </td>", result.duration).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}/log.br> log </a> </td>", &commit_id, name).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}/full_log.br> full log </a> </td>", &commit_id, name).unwrap();
+ writeln!(&mut out, "<td> <a href=c/{}/{}> output directory </a> </td>", &commit_id, name).unwrap();
+
+ if let Some(branch) = &ci.branch {
+ writeln!(&mut out, "<td> <a href={}?branch={}&test=^{}$> git log </a> </td>",
+ ci.script_name, &branch, name).unwrap();
+ }
+
+ writeln!(&mut out, "</tr>").unwrap();
+ }
+
+ writeln!(&mut out, "</table>").unwrap();
+ writeln!(&mut out, "</div>").unwrap();
+ writeln!(&mut out, "</body>").unwrap();
+ writeln!(&mut out, "</html>").unwrap();
+ cgi::html_response(200, out)
+}
+
+fn ci_list_branches(ci: &Ci) -> cgi::Response {
+ let mut out = String::new();
+
+ writeln!(&mut out, "<!DOCTYPE HTML>").unwrap();
+ writeln!(&mut out, "<html><head><title>CI branch list</title></head>").unwrap();
+ writeln!(&mut out, "<link href=\"{}\" rel=\"stylesheet\">", ci.stylesheet).unwrap();
+
+ writeln!(&mut out, "<body>").unwrap();
+ writeln!(&mut out, "<table class=\"table\">").unwrap();
+
+ let lines = read_lines(&ci.ktestrc.ci_branches_to_test);
+ if let Err(e) = lines {
+ return error_response(format!("error opening ci_branches_to_test {:?}: {}", ci.ktestrc.ci_branches_to_test, e));
+ }
+ let lines = lines.unwrap();
+
+ let branches: std::collections::HashSet<_> = lines
+ .filter_map(|i| i.ok())
+ .map(|i| if let Some(w) = i.split_whitespace().nth(0) { Some(String::from(w)) } else { None })
+ .filter_map(|i| i)
+ .collect();
+
+ let mut branches: Vec<_> = branches.iter().collect();
+ branches.sort();
+
+ for b in branches {
+ writeln!(&mut out, "<tr> <th> <a href={}?branch={}>{}</a> </th> </tr>", ci.script_name, b, b).unwrap();
+ }
+
+ writeln!(&mut out, "</table>").unwrap();
+ writeln!(&mut out, "</div>").unwrap();
+ writeln!(&mut out, "</body>").unwrap();
+ writeln!(&mut out, "</html>").unwrap();
+ cgi::html_response(200, out)
+}
+
+fn cgi_header_get(request: &cgi::Request, name: &str) -> String {
+ request.headers().get(name)
+ .map(|x| x.to_str())
+ .transpose().ok().flatten()
+ .map(|x| x.to_string())
+ .unwrap_or(String::new())
+}
+
+fn error_response(msg: String) -> cgi::Response {
+ let mut out = String::new();
+ writeln!(&mut out, "{}", msg).unwrap();
+ let env: Vec<_> = std::env::vars().collect();
+ writeln!(&mut out, "env: {:?}", env).unwrap();
+ cgi::text_response(200, out)
+}
+
+cgi::cgi_main! {|request: cgi::Request| -> cgi::Response {
+ let ktestrc = ktestrc_read();
+ if let Err(e) = ktestrc {
+ return error_response(format!("could not read config; {}", e));
+ }
+ let ktestrc = ktestrc.unwrap();
+
+ if !ktestrc.ci_output_dir.exists() {
+ return error_response(format!("required file missing: JOBSERVER_OUTPUT_DIR (got {:?})",
+ ktestrc.ci_output_dir));
+ }
+
+ let repo = git2::Repository::open(&ktestrc.ci_linux_repo);
+ if let Err(e) = repo {
+ return error_response(format!("error opening repository {:?}: {}", ktestrc.ci_linux_repo, e));
+ }
+ let repo = repo.unwrap();
+
+ let query = cgi_header_get(&request, "x-cgi-query-string");
+ let query: std::collections::HashMap<_, _> =
+ querystring::querify(&query).into_iter().collect();
+
+ let tests_matching = query.get("test").unwrap_or(&"");
+
+ let ci = Ci {
+ ktestrc: ktestrc,
+ repo: repo,
+ stylesheet: String::from(STYLESHEET),
+ script_name: cgi_header_get(&request, "x-cgi-script-name"),
+
+ branch: query.get("branch").map(|x| x.to_string()),
+ commit: query.get("commit").map(|x| x.to_string()),
+ tests_matching: Regex::new(tests_matching).unwrap_or(Regex::new("").unwrap()),
+ };
+
+ if ci.commit.is_some() {
+ ci_commit(&ci)
+ } else if ci.branch.is_some() {
+ ci_log(&ci)
+ } else {
+ ci_list_branches(&ci)
+ }
+} }