coven_storage/providers/
s3_empty.rs1#[cfg(feature = "dev-tools")]
7use super::own_bucket::RootEntry;
8use super::*;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct EmptiedS3Bucket {
13 pub objects: usize,
15 pub uploads: usize,
17}
18
19#[cfg(feature = "dev-tools")]
21#[derive(Debug, thiserror::Error)]
22pub enum EmptyS3BucketError {
23 #[error("the S3 bucket holds files no coven store wrote")]
27 OtherFiles,
28 #[error("emptying the S3 bucket: {0}")]
31 Storage(#[source] StorageError),
32}
33
34#[cfg(feature = "dev-tools")]
38pub(crate) async fn empty_bucket(
39 config: StorageConfig,
40 credentials: S3Credentials,
41 clock: ClockRef,
42 ids: IdSourceRef,
43 http: HttpClient,
44) -> Result<EmptiedS3Bucket, EmptyS3BucketError> {
45 crate::ConnectionCredentials {
46 location: config.clone(),
47 credentials: StorageCredentials::S3(credentials.clone()),
48 }
49 .validate()
50 .map_err(EmptyS3BucketError::Storage)?;
51 let storage = S3Storage::new(
52 config,
53 credentials,
54 clock,
55 ids,
56 std::sync::Arc::new(crate::NoCooldown),
57 http,
58 )
59 .map_err(EmptyS3BucketError::Storage)?;
60 let stores_only = storage
61 .root_admits(|entry| match entry {
62 RootEntry::Object(key) => key == crate::location_claim::LOCATION_MARKER,
63 RootEntry::Folder(folder) => folder
64 .strip_suffix('/')
65 .is_some_and(|name| coven_foundation::id_source::canonical_uuid(name).is_ok()),
66 })
67 .await
68 .map_err(EmptyS3BucketError::Storage)?;
69 if !stores_only {
70 return Err(EmptyS3BucketError::OtherFiles);
71 }
72 storage.empty_under("", storage.settings.listing).await.map_err(EmptyS3BucketError::Storage)
73}
74
75impl S3Storage {
76 pub(super) async fn empty_under(
83 &self,
84 prefix: &str,
85 listing: S3Listing,
86 ) -> Result<EmptiedS3Bucket, StorageError> {
87 let uploads = self.unfinished_uploads(prefix, None).await?;
88 for (key, id, _) in &uploads {
89 self.abort_multipart(key, id, None).await?;
90 }
91 let keys = self.keys_under(prefix, listing).await?;
92 for key in &keys {
93 self.delete_key(key).await?;
94 }
95 let objects = self.list_page(listing, prefix, None, None, Some(1)).await?;
98 if !objects.contents.is_empty() || !self.unfinished_uploads(prefix, None).await?.is_empty()
99 {
100 return Err(StorageFailure::Conflict
101 .with_source("objects or uploads arrived under the prefix while it was emptied"));
102 }
103 Ok(EmptiedS3Bucket { objects: keys.len(), uploads: uploads.len() })
104 }
105}