sos_debug_snapshot/
lib.rs

1use futures::{pin_mut, StreamExt};
2use sos_archive::ZipWriter;
3use sos_client_storage::{
4    ClientBaseStorage, ClientFolderStorage, ClientStorage,
5};
6use sos_logs::LOG_FILE_NAME;
7use sos_sync::SyncStorage;
8use sos_vfs as vfs;
9use std::path::Path;
10
11mod error;
12pub use error::Error;
13
14/// Options for debug snapshots.
15#[derive(Debug)]
16pub struct DebugSnapshotOptions {
17    /// Include log files in the archive.
18    pub include_log_files: bool,
19    /// Include audit trail for the first configured
20    /// audit provider.
21    pub include_audit_trail: bool,
22}
23
24impl Default for DebugSnapshotOptions {
25    fn default() -> Self {
26        Self {
27            include_log_files: true,
28            include_audit_trail: false,
29        }
30    }
31}
32
33/// Export a ZIP archive containing a snapshot of an
34/// account state; if the file exists it is overwritten.
35///
36/// # Privacy
37///
38/// No secret information is included but it does include the
39/// account identifier and folder names.
40pub async fn export_debug_snapshot(
41    source: &ClientStorage,
42    file: impl AsRef<Path>,
43    options: DebugSnapshotOptions,
44) -> Result<(), Error> {
45    let zip_file = vfs::File::create(file.as_ref()).await?;
46    let mut zip_writer = ZipWriter::new(zip_file);
47
48    let account_id = *source.account_id();
49    let debug_tree = source.debug_account_tree(account_id).await?;
50
51    let buffer = serde_json::to_vec_pretty(&debug_tree)?;
52    zip_writer.add_file("account.json", &buffer).await?;
53
54    let login = source.read_login_vault().await?;
55    let buffer = serde_json::to_vec_pretty(login.summary())?;
56    zip_writer.add_file("login.json", &buffer).await?;
57
58    if let Some(device) = source.read_device_vault().await? {
59        let buffer = serde_json::to_vec_pretty(device.summary())?;
60        zip_writer.add_file("device.json", &buffer).await?;
61    }
62
63    let target = source.backend_target();
64    let paths = target.paths();
65
66    if options.include_log_files {
67        let logs = paths.logs_dir();
68        let mut dir = vfs::read_dir(logs).await?;
69        while let Some(entry) = dir.next_entry().await? {
70            let path = entry.path();
71            if let Some(name) = path.file_name() {
72                if name.to_string_lossy().starts_with(LOG_FILE_NAME) {
73                    let buffer = vfs::read(&path).await?;
74                    zip_writer
75                        .add_file(
76                            &format!("logs/{}.jsonl", name.to_string_lossy()),
77                            &buffer,
78                        )
79                        .await?;
80                }
81            }
82        }
83    }
84
85    #[cfg(feature = "audit")]
86    if options.include_audit_trail {
87        if let Some(providers) = sos_backend::audit::providers() {
88            for (index, provider) in providers.iter().enumerate() {
89                let stream = provider.audit_stream(false).await?;
90                pin_mut!(stream);
91
92                let events = stream
93                    .filter_map(|e| async move { e.ok() })
94                    .filter_map(|e| async move {
95                        if e.account_id() == &account_id {
96                            Some(e)
97                        } else {
98                            None
99                        }
100                    })
101                    .collect::<Vec<_>>()
102                    .await;
103
104                let buffer = serde_json::to_vec_pretty(&events)?;
105                zip_writer
106                    .add_file(&format!("audit/{}.json", index), &buffer)
107                    .await?;
108            }
109        }
110    }
111
112    zip_writer.finish().await?;
113
114    Ok(())
115}