Skip to content

Commit f0d063a

Browse files
implement list with deleted in azblob
1 parent 06f088d commit f0d063a

File tree

4 files changed

+134
-3
lines changed

4 files changed

+134
-3
lines changed

core/services/azblob/src/backend.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,15 @@ impl AzblobBuilder {
237237
self
238238
}
239239

240+
/// Set the soft delete feature for this backend.
241+
///
242+
/// If enabled, deleted blobs will be retained for the configured retention period
243+
/// and can be listed using list_with_deleted.
244+
pub fn enable_soft_deletes(mut self, enabled: bool) -> Self {
245+
self.config.enable_soft_deletes = enabled;
246+
self
247+
}
248+
240249
/// from_connection_string will make a builder from connection string
241250
///
242251
/// connection string looks like:
@@ -402,6 +411,12 @@ impl Builder for AzblobBuilder {
402411

403412
list: true,
404413
list_with_recursive: true,
414+
list_has_etag: true,
415+
list_has_content_length: true,
416+
list_has_content_md5: true,
417+
list_has_content_type: true,
418+
list_has_last_modified: true,
419+
list_with_deleted: self.config.enable_soft_deletes,
405420

406421
presign: self.config.sas_token.is_some(),
407422
presign_stat: self.config.sas_token.is_some(),

core/services/azblob/src/core.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ impl AzblobCore {
599599
next_marker: &str,
600600
delimiter: &str,
601601
limit: Option<usize>,
602+
include_deleted: bool,
602603
) -> Result<Response<Buffer>> {
603604
let p = build_abs_path(&self.root, path);
604605
let mut url = QueryPairsWriter::new(&format!("{}/{}", self.endpoint, self.container))
@@ -618,6 +619,10 @@ impl AzblobCore {
618619
url = url.push("marker", next_marker);
619620
}
620621

622+
if include_deleted {
623+
url = url.push("include", "deleted");
624+
}
625+
621626
let mut req = Request::get(url.finish())
622627
.extension(Operation::List)
623628
.body(Buffer::new())
@@ -685,6 +690,8 @@ pub struct BlobPrefix {
685690
pub struct Blob {
686691
pub properties: Properties,
687692
pub name: String,
693+
#[serde(rename = "Deleted")]
694+
pub deleted: Option<bool>,
688695
}
689696

690697
#[derive(Default, Debug, Deserialize)]

core/services/azblob/src/lister.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,25 @@ pub struct AzblobLister {
3232
path: String,
3333
delimiter: &'static str,
3434
limit: Option<usize>,
35+
deleted: bool,
3536
}
3637

3738
impl AzblobLister {
38-
pub fn new(core: Arc<AzblobCore>, path: String, recursive: bool, limit: Option<usize>) -> Self {
39+
pub fn new(
40+
core: Arc<AzblobCore>,
41+
path: String,
42+
recursive: bool,
43+
limit: Option<usize>,
44+
deleted: bool,
45+
) -> Self {
3946
let delimiter = if recursive { "" } else { "/" };
4047

4148
Self {
4249
core,
4350
path,
4451
delimiter,
4552
limit,
53+
deleted,
4654
}
4755
}
4856
}
@@ -51,7 +59,13 @@ impl oio::PageList for AzblobLister {
5159
async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
5260
let resp = self
5361
.core
54-
.azblob_list_blobs(&self.path, &ctx.token, self.delimiter, self.limit)
62+
.azblob_list_blobs(
63+
&self.path,
64+
&ctx.token,
65+
self.delimiter,
66+
self.limit,
67+
self.deleted,
68+
)
5569
.await?;
5670

5771
if resp.status() != http::StatusCode::OK {
@@ -88,7 +102,7 @@ impl oio::PageList for AzblobLister {
88102
path = "/".to_string();
89103
}
90104

91-
let meta = Metadata::new(EntryMode::from_path(&path))
105+
let mut meta = Metadata::new(EntryMode::from_path(&path))
92106
// Keep fit with ETag header.
93107
.with_etag(format!("\"{}\"", object.properties.etag.as_str()))
94108
.with_content_length(object.properties.content_length)
@@ -98,6 +112,11 @@ impl oio::PageList for AzblobLister {
98112
object.properties.last_modified.as_str(),
99113
)?);
100114

115+
// Mark as deleted if this is a delete marker
116+
if object.deleted.unwrap_or(false) {
117+
meta = meta.with_is_deleted(true);
118+
}
119+
101120
let de = oio::Entry::with(path, meta);
102121
ctx.entries.push_back(de);
103122
}

core/src/services/azblob/config.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::fmt::Debug;
19+
use std::fmt::Formatter;
20+
21+
use serde::Deserialize;
22+
use serde::Serialize;
23+
24+
/// Azure Storage Blob services support.
25+
#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
26+
pub struct AzblobConfig {
27+
/// The root of Azblob service backend.
28+
///
29+
/// All operations will happen under this root.
30+
pub root: Option<String>,
31+
32+
/// The container name of Azblob service backend.
33+
pub container: String,
34+
35+
/// The endpoint of Azblob service backend.
36+
///
37+
/// Endpoint must be full uri, e.g.
38+
///
39+
/// - Azblob: `https://accountname.blob.core.windows.net`
40+
/// - Azurite: `http://127.0.0.1:10000/devstoreaccount1`
41+
pub endpoint: Option<String>,
42+
43+
/// The account name of Azblob service backend.
44+
pub account_name: Option<String>,
45+
46+
/// The account key of Azblob service backend.
47+
pub account_key: Option<String>,
48+
49+
/// The encryption key of Azblob service backend.
50+
pub encryption_key: Option<String>,
51+
52+
/// The encryption key sha256 of Azblob service backend.
53+
pub encryption_key_sha256: Option<String>,
54+
55+
/// The encryption algorithm of Azblob service backend.
56+
pub encryption_algorithm: Option<String>,
57+
58+
/// The sas token of Azblob service backend.
59+
pub sas_token: Option<String>,
60+
61+
/// The maximum batch operations of Azblob service backend.
62+
pub batch_max_operations: Option<usize>,
63+
64+
/// Enable soft deletes for this storage account.
65+
#[serde(default)]
66+
pub enable_soft_deletes: bool,
67+
}
68+
69+
impl Debug for AzblobConfig {
70+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71+
let mut ds = f.debug_struct("AzblobConfig");
72+
73+
ds.field("root", &self.root);
74+
ds.field("container", &self.container);
75+
ds.field("endpoint", &self.endpoint);
76+
77+
if self.account_name.is_some() {
78+
ds.field("account_name", &"<redacted>");
79+
}
80+
if self.account_key.is_some() {
81+
ds.field("account_key", &"<redacted>");
82+
}
83+
if self.sas_token.is_some() {
84+
ds.field("sas_token", &"<redacted>");
85+
}
86+
ds.field("enable_soft_deletes", &self.enable_soft_deletes);
87+
88+
ds.finish()
89+
}
90+
}

0 commit comments

Comments
 (0)