-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpurge.rs
More file actions
138 lines (127 loc) · 4.61 KB
/
Copy pathpurge.rs
File metadata and controls
138 lines (127 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use clap::Args;
use std::{collections::HashSet, path::PathBuf};
use tracing::{error, info, warn};
use uts_core::{
codec::{
Decode, Encode, VersionedProof,
v1::{Attestation, DetachedTimestamp, PendingAttestation},
},
utils::Hexed,
};
use uts_sdk::Sdk;
#[derive(Debug, Args)]
pub struct Purge {
/// Files to purge pending attestations from. May be specified multiple times.
#[arg(value_name = "FILE", num_args = 1..)]
files: Vec<PathBuf>,
/// Skip the interactive confirmation prompt and purge all pending attestations.
#[arg(short = 'y', long = "yes", default_value_t = false)]
yes: bool,
/// Purge malformed pending attestations that fail to decode. By default, these are retained
/// to avoid data loss, but enabling this flag will attempt to purge them as well.
#[arg(long = "purge-malformed", default_value_t = false)]
purge_malformed: bool,
}
impl Purge {
pub async fn run(self) -> eyre::Result<()> {
for path in &self.files {
if let Err(e) = self.purge_one(path).await {
error!("[{}] failed to purge: {e}", path.display());
}
}
Ok(())
}
async fn purge_one(&self, path: &PathBuf) -> eyre::Result<()> {
let file = tokio::fs::read(path).await?;
let proof = VersionedProof::<DetachedTimestamp>::decode(&mut &*file)?;
let pending = proof
.attestations()
.filter(|att| att.tag == PendingAttestation::TAG)
.flat_map(|att| {
PendingAttestation::from_raw(att)
.map(|p| p.uri)
.inspect_err(|e| {
warn!(
"[{path}] skipped malformed PendingAttestation (value = {data}), error: {e}",
path = path.display(),
data = Hexed(&att.data)
)
})
.ok()
})
.collect::<Vec<_>>();
if pending.is_empty() {
info!(
"[{}] no pending attestations found, skipping",
path.display()
);
return Ok(());
}
info!(
"[{}] found {} pending attestation(s):",
path.display(),
pending.len()
);
for (i, uri) in pending.iter().enumerate() {
info!(" [{}] {uri}", i + 1);
}
let uris_to_purge = if self.yes {
// Purge all when --yes flag is used
pending.into_iter().collect()
} else {
// Interactive selection
print!("Enter numbers to purge (comma-separated), 'all', or 'none' to skip: ");
use std::io::Write;
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("none") || input.is_empty() {
info!("[{}] skipped", path.display());
return Ok(());
}
if input.eq_ignore_ascii_case("all") {
pending.into_iter().collect()
} else {
let mut selected = HashSet::new();
for part in input.split(',') {
let part = part.trim();
match part.parse::<usize>() {
Ok(n) if n >= 1 && n <= pending.len() => {
selected.insert(pending[n - 1].clone());
}
_ => {
warn!("ignoring invalid selection: {part}");
}
}
}
if selected.is_empty() {
info!("[{}] no valid selections, skipping", path.display());
return Ok(());
}
selected
}
};
let Some(result) = Sdk::filter_pending_by_uris(
&proof,
|uri| uris_to_purge.contains(uri),
self.purge_malformed,
) else {
error!("won't purge [{}], results in empty proof", path.display());
return Ok(());
};
if result.purged.is_empty() {
info!("[{}] nothing to purge", path.display());
return Ok(());
}
let mut buf = Vec::new();
VersionedProof::new(result.new_stamp).encode(&mut buf)?;
tokio::fs::write(path, buf).await?;
info!(
"purged {} pending attestation(s) from [{}]",
result.purged.len(),
path.display(),
);
Ok(())
}
}