xensec_internal_tools/
upstream.rs1use crate::prelude::*;
6pub 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>, }
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}