rustsec_admin/commands/
sync.rs

1//! `rustsec-admin sync` subcommand
2
3use crate::{prelude::*, synchronizer::Synchronizer};
4use abscissa_core::{Command, Runnable};
5use clap::Parser;
6use std::{
7    path::{Path, PathBuf},
8    process::exit,
9};
10
11/// `rustsec-admin sync` subcommand
12#[derive(Command, Debug, Default, Parser)]
13pub struct SyncCmd {
14    /// Path to the advisory database
15    #[arg(
16        num_args = 1..,
17        help = "filesystem path to the RustSec advisory DB git repo"
18    )]
19    path: Vec<PathBuf>,
20
21    /// Path to the OSV export
22    //
23    // Downloaded with:
24    //
25    // gsutil cp gs://osv-vulnerabilities/crates.io/all.zip .
26    // or
27    // wget https://osv-vulnerabilities.storage.googleapis.com/crates.io/all.zip
28    #[clap(
29        long = "osv",
30        help = "filesystem path to the OSV crates.io data export"
31    )]
32    osv: PathBuf,
33}
34
35impl Runnable for SyncCmd {
36    fn run(&self) {
37        let repo_path = match self.path.len() {
38            0 => Path::new("."),
39            1 => self.path[0].as_path(),
40            _ => unreachable!(),
41        };
42
43        let mut synchronizer = Synchronizer::new(repo_path, &self.osv).unwrap_or_else(|e| {
44            status_err!(
45                "error loading advisory DB repo from {}: {}",
46                repo_path.display(),
47                e
48            );
49
50            exit(1);
51        });
52
53        let advisories = synchronizer.advisory_db().iter();
54
55        // Ensure we're parsing some advisories
56        if advisories.len() == 0 {
57            status_err!("no advisories found!");
58            exit(1);
59        }
60
61        status_ok!(
62            "Loaded",
63            "{} security advisories (from {})",
64            advisories.len(),
65            repo_path.display()
66        );
67
68        let (updated, mut new) = synchronizer.sync().unwrap_or_else(|e| {
69            status_err!(
70                "error synchronizing advisory DB {}: {}",
71                repo_path.display(),
72                e
73            );
74
75            exit(1);
76        });
77
78        if new.is_empty() {
79            status_ok!("Success", "no new advisories to import");
80        } else {
81            status_ok!("Success", "{} aliases are missing in RustSec", new.len());
82            // Only a message from now
83            // TODO: automate new advisory draft
84            new.sort_by(|a, b| a.published().partial_cmp(b.published()).unwrap());
85            for a in new {
86                println!(
87                    "{:.10}: https://github.com/advisories/{} for {:?}",
88                    a.published(),
89                    a.id(),
90                    a.crates()
91                );
92            }
93        }
94
95        if updated == 0 {
96            status_ok!("Success", "all advisories are up to date");
97        } else {
98            status_ok!("Success", "{} advisories have been updated", updated);
99        }
100    }
101}