Skip to main content

xensec_internal_tools/
upstream.rs

1// Copyright 2021 Citrix
2// SPDX-License-Identifier: MIT OR Apache-2.0
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6//use forge::*;
7
8pub use gitforge::forge;
9pub use forge::IssueMrStatus as MrStatus;
10
11use forge::*;
12
13const DEFAULT_HOST: &str = "github.com";
14const DEFAULT_KIND: forge::Kind = forge::Kind::GitHub;
15
16#[derive(Debug,Clone)]
17#[derive(Serialize,Deserialize)]
18pub struct MrState {
19  pub state: Option<MrInfo>, // None: MR does not exist
20}
21
22#[derive(Debug,Clone)]
23#[derive(Serialize,Deserialize)]
24pub struct MrInfo {
25  pub num: String,
26  pub status: MrStatus,
27}
28
29#[derive(Debug)]
30pub struct Upstream {
31  forge: Box<dyn forge::Forge>,
32  cfg: Arc<config::Config>,
33  cvelist_repo_upstream: String,
34  cvelist_repo_xensec:   String,
35}
36
37impl Upstream {
38  #[throws(AE)]
39  pub fn config_resolve(config: &mut forge::Config) {
40    if config.host == "" { config.host = DEFAULT_HOST.into() }
41    config.kind.get_or_insert(DEFAULT_KIND);
42  }
43
44  #[throws(AE)]
45  pub fn new(cfg: &Arc<config::Config>, cvelist: &cvelist::Local) -> Self {
46    let forge = cfg.cvelist_upstream.forge()?;
47
48    let rrepo = |remote_name: &str| (||{
49      let remote_url = cvelist.remote_url(remote_name)?;
50      let repo = forge.remote_url_to_repo(&remote_url)?;
51      Ok::<_,AE>(repo)
52    })().with_context(|| remote_name.to_owned()).context("find remote repo");
53
54    Upstream {
55      cvelist_repo_upstream: rrepo(&cfg.cvelist_remote_upstream)?,
56      cvelist_repo_xensec:   rrepo(&cfg.cvelist_remote_xensec  )?,
57      forge,
58      cfg: cfg.clone(),
59    }
60  }
61
62  #[throws(AE)]
63  pub fn get_mr_state(&mut self, xsa: XsaNum) -> MrState {
64    let req = Req::MergeRequests(Req_MergeRequests {
65      target_repo:         self.cvelist_repo_upstream    .clone()  ,
66      source_repo:   Some( self.cvelist_repo_xensec      .clone() ),
67      source_branch: Some( xsa.cvelist_branch_name()              ),
68      target_branch: Some( cvelist::UPSTREAM_MAIN_BRANCH .into()  ),
69      ..default()
70    });
71    let resp = self.forge.request(&req)?;
72    let unprocessable = |s|
73      anyhow!("unprocessable response: {} ", s)
74      .context(format!("req={:?}", req))
75      .context(format!("resp={:?}", resp));
76
77    let mrs = match &resp {
78      Resp::MergeRequests { mrs,.. } => mrs,
79      _ => throw!(unprocessable("unexpected variant")),
80    };
81    match mrs.as_slice() {
82      [] => {
83        MrState { state: None }
84      },
85
86      [mr] => {
87        let status = match mr.state.status {
88          y@ MrStatus::Open |
89          y@ MrStatus::Closed |
90          y@ MrStatus::Merged => y,
91          _ => throw!(unprocessable("unexpected KR status")),
92        };
93        MrState { state: Some(MrInfo{
94          status,
95          num: mr.number.clone(),
96        }) }
97      },
98
99      _ => throw!(unprocessable("more than 1 MR")),
100    }
101  }
102}
103
104impl Ctx {
105  #[throws(AE)]
106  pub fn make_mr(&mut self, xsa: XsaNum, xstate: &analysis::XsaState)
107                 -> String
108  {
109    let title = xsa.title_with_cves(&mut xstate.cves.keys());
110    let description = (||{
111      let mut d = String::new();
112      write!(d, "{}\n\n", &title)?;
113      writeln!(d, "CVE data entry for:")?;
114      for cve in xstate.cves.keys() {
115        writeln!(d, "  {}", &cve)?;
116        write!(d, r#"
117(This MR was automatically generated by the Xen Project Security Team's
118xensec-internal-tools.  Please let us know if anything could be better.)
119"#
120        )?;
121      }
122      Ok::<_,AE>(d)
123    })().unwrap();
124
125    let req = Req::CreateMergeRequest(Req_CreateMergeRequest {
126      target: RepoBranch {
127        repo:              self.upstream.cvelist_repo_upstream   .clone()  ,
128        branch:            cvelist::UPSTREAM_MAIN_BRANCH         .into()   ,
129      },
130      source: RepoBranch {
131        repo:              self.upstream.cvelist_repo_xensec     .clone()  ,
132        branch:            xsa.cvelist_branch_name()                       ,
133      },
134      title,
135      description,
136      ..default()
137    });
138
139    if self.want_remote_actions(xsa,"make mr",&req)? {
140      self.cache.invalidate::<upstream::MrState>(&xsa)
141        .context("clear going-to-be-stale cache")?;
142
143      let resp = self.upstream.forge
144        .request(&req).context("make merge request")?;
145
146      let number = match &resp {
147        Resp::CreateMergeRequest { number,.. } => number,
148        _ => throw!(anyhow!("unexpected variant: {:?}", &resp)),
149      };
150
151      number.clone()
152    } else {
153      "<dry run, MR not created>".into()
154    }
155  }
156}