micromegas_telemetry/
blob_storage.rs1use anyhow::Result;
2use futures::StreamExt;
3use futures::stream;
4use object_store::prefix::PrefixStore;
5use object_store::{ObjectStore, ObjectStoreExt, PutMode, PutOptions, path::Path};
6use std::sync::Arc;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PutIfAbsent {
11 Created,
13 AlreadyExists,
15}
16
17pub fn parse_object_store_url(uri: &str) -> Result<(Arc<dyn ObjectStore>, Path)> {
21 parse_object_store_url_parsed(&url::Url::parse(uri)?)
22}
23
24pub fn parse_object_store_url_parsed(url: &url::Url) -> Result<(Arc<dyn ObjectStore>, Path)> {
27 let (store, prefix) = object_store::parse_url_opts(url, std::env::vars())?;
28 Ok((Arc::new(store), prefix))
29}
30
31#[derive(Debug)]
36pub struct BlobStorage {
37 blob_store: Arc<dyn ObjectStore>,
38}
39
40impl BlobStorage {
41 pub fn new(blob_store: Arc<dyn ObjectStore>, blob_store_root: Path) -> Self {
43 Self {
44 blob_store: Arc::new(PrefixStore::new(blob_store, blob_store_root)),
45 }
46 }
47
48 pub fn connect(object_store_url: &str) -> Result<Self> {
50 Self::connect_with_layer(object_store_url, |s| s)
51 }
52
53 pub fn parse_url_opts(object_store_url: &str) -> Result<(Arc<dyn ObjectStore>, Path)> {
60 parse_object_store_url(object_store_url)
61 }
62
63 pub fn connect_with_layer(
67 object_store_url: &str,
68 layer: impl FnOnce(Arc<dyn ObjectStore>) -> Arc<dyn ObjectStore>,
69 ) -> Result<Self> {
70 let (blob_store, blob_store_root) = Self::parse_url_opts(object_store_url)?;
71 let layered = layer(blob_store);
72 Ok(Self {
73 blob_store: Arc::new(PrefixStore::new(layered, blob_store_root)),
74 })
75 }
76
77 pub fn inner(&self) -> Arc<dyn ObjectStore> {
79 self.blob_store.clone()
80 }
81
82 pub async fn put(&self, obj_path: &str, buffer: bytes::Bytes) -> Result<()> {
84 self.blob_store
85 .put(&Path::from(obj_path), buffer.into())
86 .await?;
87 Ok(())
88 }
89
90 pub async fn put_if_absent(&self, obj_path: &str, buffer: bytes::Bytes) -> Result<PutIfAbsent> {
96 match self
97 .blob_store
98 .put_opts(
99 &Path::from(obj_path),
100 buffer.into(),
101 PutOptions::from(PutMode::Create),
102 )
103 .await
104 {
105 Ok(_) => Ok(PutIfAbsent::Created),
106 Err(object_store::Error::AlreadyExists { .. }) => Ok(PutIfAbsent::AlreadyExists),
107 Err(object_store::Error::NotImplemented { .. }) => Err(anyhow::anyhow!(
108 "object store does not support conditional put (PutMode::Create), which \
109 create-only block writes require; if this is an S3-compatible store, check \
110 that it is not configured with aws_conditional_put=disabled"
111 )),
112 Err(e) => Err(e.into()),
113 }
114 }
115
116 pub async fn read_blob(&self, obj_path: &str) -> Result<bytes::Bytes> {
118 let get_result = self.blob_store.get(&Path::from(obj_path)).await?;
119 Ok(get_result.bytes().await?)
120 }
121
122 pub async fn delete(&self, obj_path: &str) -> Result<()> {
124 self.blob_store.delete(&Path::from(obj_path)).await?;
125 Ok(())
126 }
127
128 pub async fn probe(&self) -> anyhow::Result<()> {
131 match self.blob_store.list(None).next().await {
132 Some(Ok(_)) | None => Ok(()),
133 Some(Err(e)) => Err(e.into()),
134 }
135 }
136
137 pub async fn delete_batch(&self, objects: &[String]) -> Result<()> {
139 let paths: Vec<_> = objects
140 .iter()
141 .map(|obj_path| Ok(Path::from(obj_path.as_str())))
142 .collect();
143 let path_stream = stream::iter(paths);
144 let mut stream = self.blob_store.delete_stream(Box::pin(path_stream));
145 while let Some(res) = stream.next().await {
146 if let Err(e) = res {
147 match e {
148 object_store::Error::NotFound { path: _, source: _ } => Ok(()),
149 ref _other_error => Err(e),
150 }?
151 }
152 }
153 Ok(())
154 }
155}