@@ -6,11 +6,13 @@ package objstore
6
6
import (
7
7
"bytes"
8
8
"context"
9
+ "fmt"
9
10
"io"
10
11
"io/fs"
11
12
"os"
12
13
"path"
13
14
"path/filepath"
15
+ "slices"
14
16
"strings"
15
17
"sync"
16
18
"time"
@@ -34,8 +36,6 @@ const (
34
36
OpAttributes = "attributes"
35
37
)
36
38
37
- var EmptyObjectAttributes = ObjectAttributes {}
38
-
39
39
// Bucket provides read and write access to an object storage bucket.
40
40
// NOTE: We assume strong consistency for write-read flow.
41
41
type Bucket interface {
@@ -70,12 +70,21 @@ type InstrumentedBucket interface {
70
70
71
71
// BucketReader provides read access to an object storage bucket.
72
72
type BucketReader interface {
73
- // Iter calls f for each entry in the given directory (not recursive.). The first argument to f is the full
74
- // object name including the prefix of the inspected directory. The second argument are the object attributes
75
- // returned by the underlying provider. Attributes can be requested using various IterOption's.
76
- //
73
+ // Iter calls f for each entry in the given directory (not recursive.). The argument to f is the full
74
+ // object name including the prefix of the inspected directory.
75
+
77
76
// Entries are passed to function in sorted order.
78
- Iter (ctx context.Context , dir string , f func (name string , attrs ObjectAttributes ) error , options ... IterOption ) error
77
+ Iter (ctx context.Context , dir string , f func (name string ) error , options ... IterOption ) error
78
+
79
+ // IterWithAttributes calls f for each entry in the given directory similar to Iter.
80
+ // In addition to Name, it also includes requested object attributes in the argument to f.
81
+ //
82
+ // Attributes can be requested using IterOption.
83
+ // Not all IterOptions are supported by all providers, requesting for an unsupported option will fail with ErrOptionNotSupported.
84
+ IterWithAttributes (ctx context.Context , dir string , f func (attrs IterObjectAttributes ) error , options ... IterOption ) error
85
+
86
+ // SupportedIterOptions returns a list of supported IterOptions by the underlying provider.
87
+ SupportedIterOptions () []IterOptionType
79
88
80
89
// Get returns a reader for the given object name.
81
90
Get (ctx context.Context , name string ) (io.ReadCloser , error )
@@ -105,32 +114,66 @@ type InstrumentedBucketReader interface {
105
114
ReaderWithExpectedErrs (IsOpFailureExpectedFunc ) BucketReader
106
115
}
107
116
117
+ var ErrOptionNotSupported = errors .New ("iter option is not supported" )
118
+
119
+ // IterOptionType is used for type-safe option support checking
120
+ type IterOptionType int
121
+
122
+ const (
123
+ Recursive IterOptionType = iota
124
+ UpdatedAt
125
+ )
126
+
108
127
// IterOption configures the provided params.
109
- type IterOption func (params * IterParams )
128
+ type IterOption struct {
129
+ Type IterOptionType
130
+ Apply func (params * IterParams )
131
+ }
110
132
111
133
// WithRecursiveIter is an option that can be applied to Iter() to recursively list objects
112
134
// in the bucket.
113
- func WithRecursiveIter (params * IterParams ) {
114
- params .Recursive = true
135
+ func WithRecursiveIter () IterOption {
136
+ return IterOption {
137
+ Type : Recursive ,
138
+ Apply : func (params * IterParams ) {
139
+ params .Recursive = true
140
+ },
141
+ }
115
142
}
116
143
117
144
// WithUpdatedAt is an option that can be applied to Iter() to
118
- // return the updated time attribute of each object.
119
- // This option is currently supported for the GCS and Filesystem providers.
120
- func WithUpdatedAt (params * IterParams ) {
121
- params .WithUpdatedAt = true
145
+ // include the last modified time in the attributes.
146
+ // NB: Prefixes may not report last modified time.
147
+ // This option is currently supported for the azure, aws, bos, gcs and filesystem providers.
148
+ func WithUpdatedAt () IterOption {
149
+ return IterOption {
150
+ Type : UpdatedAt ,
151
+ Apply : func (params * IterParams ) {
152
+ params .LastModified = true
153
+ },
154
+ }
122
155
}
123
156
124
157
// IterParams holds the Iter() parameters and is used by objstore clients implementations.
125
158
type IterParams struct {
126
- Recursive bool
127
- WithUpdatedAt bool
159
+ Recursive bool
160
+ LastModified bool
161
+ }
162
+
163
+ func ValidateIterOptions (supportedOptions []IterOptionType , options ... IterOption ) error {
164
+ for _ , opt := range options {
165
+ if ! slices .Contains (supportedOptions , opt .Type ) {
166
+ return fmt .Errorf ("%w: %v" , ErrOptionNotSupported , opt .Type )
167
+ }
168
+ }
169
+
170
+ return nil
128
171
}
129
172
130
173
func ApplyIterOptions (options ... IterOption ) IterParams {
131
174
out := IterParams {}
132
175
for _ , opt := range options {
133
- opt (& out )
176
+ opt . Apply (& out )
134
177
}
135
178
return out
136
179
}
@@ -201,6 +244,20 @@ type ObjectAttributes struct {
201
244
LastModified time.Time `json:"last_modified"`
202
245
}
203
246
247
+ type IterObjectAttributes struct {
248
+ Name string
249
+ lastModified time.Time
250
+ }
251
+
252
+ func (i * IterObjectAttributes ) SetLastModified (t time.Time ) {
253
+ i .lastModified = t
254
+ }
255
+
256
+ // LastModified returns the timestamp the object was last modified. Returns false if the timestamp is not available.
257
+ func (i * IterObjectAttributes ) LastModified () (time.Time , bool ) {
258
+ return i .lastModified , ! i .lastModified .IsZero ()
259
+ }
260
+
204
261
// TryToGetSize tries to get upfront size from reader.
205
262
// Some implementations may return only size of unread data in the reader, so it's best to call this method before
206
263
// doing any reading.
@@ -359,7 +416,7 @@ func DownloadDir(ctx context.Context, logger log.Logger, bkt BucketReader, origi
359
416
var downloadedFiles []string
360
417
var m sync.Mutex
361
418
362
- err := bkt .Iter (ctx , src , func (name string , _ ObjectAttributes ) error {
419
+ err := bkt .Iter (ctx , src , func (name string ) error {
363
420
g .Go (func () error {
364
421
dst := filepath .Join (dst , filepath .Base (name ))
365
422
if strings .HasSuffix (name , DirDelim ) {
@@ -495,7 +552,7 @@ func (b *metricBucket) ReaderWithExpectedErrs(fn IsOpFailureExpectedFunc) Bucket
495
552
return b .WithExpectedErrs (fn )
496
553
}
497
554
498
- func (b * metricBucket ) Iter (ctx context.Context , dir string , f func (name string , _ ObjectAttributes ) error , options ... IterOption ) error {
555
+ func (b * metricBucket ) Iter (ctx context.Context , dir string , f func (string ) error , options ... IterOption ) error {
499
556
const op = OpIter
500
557
b .ops .WithLabelValues (op ).Inc ()
501
558
@@ -508,6 +565,23 @@ func (b *metricBucket) Iter(ctx context.Context, dir string, f func(name string,
508
565
return err
509
566
}
510
567
568
+ func (b * metricBucket ) IterWithAttributes (ctx context.Context , dir string , f func (IterObjectAttributes ) error , options ... IterOption ) error {
569
+ const op = OpIter
570
+ b .ops .WithLabelValues (op ).Inc ()
571
+
572
+ err := b .bkt .IterWithAttributes (ctx , dir , f , options ... )
573
+ if err != nil {
574
+ if ! b .isOpFailureExpected (err ) && ctx .Err () != context .Canceled {
575
+ b .opsFailures .WithLabelValues (op ).Inc ()
576
+ }
577
+ }
578
+ return err
579
+ }
580
+
581
+ func (b * metricBucket ) SupportedIterOptions () []IterOptionType {
582
+ return b .SupportedIterOptions ()
583
+ }
584
+
511
585
func (b * metricBucket ) Attributes (ctx context.Context , name string ) (ObjectAttributes , error ) {
512
586
const op = OpAttributes
513
587
b .ops .WithLabelValues (op ).Inc ()
0 commit comments