Skip to content

Commit 75237ca

Browse files
authored
Merge pull request #28 from nvaatstra/s3-folders
S3: GlobalPrefix and Recursive
2 parents 8b71881 + 77fb204 commit 75237ca

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

backends/s3/s3.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ type Options struct {
5151
// CreateBucket tells us to try to create the bucket
5252
CreateBucket bool `yaml:"create_bucket"`
5353

54+
// GlobalPrefix is a prefix applied to all operations, allowing work within a prefix
55+
// seamlessly
56+
GlobalPrefix string `yaml:"global_prefix"`
57+
58+
// PrefixFolders can be enabled to make List operations show nested prefixes as folders
59+
// instead of recursively listing all contents of nested prefixes
60+
PrefixFolders bool `yaml:"prefix_folders"`
61+
5462
// EndpointURL can be set to something like "http://localhost:9000" when using Minio
5563
// or "https://s3.amazonaws.com" for AWS S3.
5664
EndpointURL string `yaml:"endpoint_url"`
@@ -110,6 +118,9 @@ type Backend struct {
110118
}
111119

112120
func (b *Backend) List(ctx context.Context, prefix string) (simpleblob.BlobList, error) {
121+
// Prepend global prefix
122+
prefix = b.prependGlobalPrefix(prefix)
123+
113124
if !b.opt.UseUpdateMarker {
114125
return b.doList(ctx, prefix)
115126
}
@@ -150,9 +161,12 @@ func (b *Backend) List(ctx context.Context, prefix string) (simpleblob.BlobList,
150161
func (b *Backend) doList(ctx context.Context, prefix string) (simpleblob.BlobList, error) {
151162
var blobs simpleblob.BlobList
152163

164+
// Runes to strip from blob names for GlobalPrefix
165+
gpEndRune := len(b.opt.GlobalPrefix)
166+
153167
objCh := b.client.ListObjects(ctx, b.opt.Bucket, minio.ListObjectsOptions{
154168
Prefix: prefix,
155-
Recursive: false,
169+
Recursive: !b.opt.PrefixFolders,
156170
})
157171
for obj := range objCh {
158172
// Handle error returned by MinIO client
@@ -166,7 +180,14 @@ func (b *Backend) doList(ctx context.Context, prefix string) (simpleblob.BlobLis
166180
if obj.Key == UpdateMarkerFilename {
167181
continue
168182
}
169-
blobs = append(blobs, simpleblob.Blob{Name: obj.Key, Size: obj.Size})
183+
184+
// Strip global prefix from blob
185+
blobName := obj.Key
186+
if gpEndRune > 0 {
187+
blobName = blobName[gpEndRune:]
188+
}
189+
190+
blobs = append(blobs, simpleblob.Blob{Name: blobName, Size: obj.Size})
170191
}
171192

172193
// Minio appears to return them sorted, but maybe not all implementations
@@ -179,6 +200,9 @@ func (b *Backend) doList(ctx context.Context, prefix string) (simpleblob.BlobLis
179200
// Load retrieves the content of the object identified by name from S3 Bucket
180201
// configured in b.
181202
func (b *Backend) Load(ctx context.Context, name string) ([]byte, error) {
203+
// Prepend global prefix
204+
name = b.prependGlobalPrefix(name)
205+
182206
metricCalls.WithLabelValues("load").Inc()
183207
metricLastCallTimestamp.WithLabelValues("load").SetToCurrentTime()
184208

@@ -199,6 +223,9 @@ func (b *Backend) Load(ctx context.Context, name string) ([]byte, error) {
199223
// Store sets the content of the object identified by name to the content
200224
// of data, in the S3 Bucket configured in b.
201225
func (b *Backend) Store(ctx context.Context, name string, data []byte) error {
226+
// Prepend global prefix
227+
name = b.prependGlobalPrefix(name)
228+
202229
info, err := b.doStore(ctx, name, data)
203230
if err != nil {
204231
return err
@@ -222,6 +249,9 @@ func (b *Backend) doStore(ctx context.Context, name string, data []byte) (minio.
222249
// Delete removes the object identified by name from the S3 Bucket
223250
// configured in b.
224251
func (b *Backend) Delete(ctx context.Context, name string) error {
252+
// Prepend global prefix
253+
name = b.prependGlobalPrefix(name)
254+
225255
if err := b.doDelete(ctx, name); err != nil {
226256
return err
227257
}
@@ -370,6 +400,12 @@ func convertMinioError(err error) error {
370400
return nil
371401
}
372402

403+
// prependGlobalPrefix prepends the GlobalPrefix to the name/prefix
404+
// passed as input
405+
func (b *Backend) prependGlobalPrefix(name string) string {
406+
return b.opt.GlobalPrefix + name
407+
}
408+
373409
func init() {
374410
simpleblob.RegisterBackend("s3", func(ctx context.Context, p simpleblob.InitParams) (simpleblob.Interface, error) {
375411
var opt Options

backends/s3/s3_test.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ func getBackend(ctx context.Context, t *testing.T) (b *Backend) {
5454
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
5555
defer cancel()
5656

57-
blobs, err := b.doList(ctx, "")
57+
blobs, err := b.List(ctx, "")
5858
if err != nil {
5959
t.Logf("Blobs list error: %s", err)
6060
return
6161
}
6262
for _, blob := range blobs {
63-
err := b.client.RemoveObject(ctx, b.opt.Bucket, blob.Name, minio.RemoveObjectOptions{})
63+
err := b.Delete(ctx, blob.Name)
6464
if err != nil {
6565
t.Logf("Object delete error: %s", err)
6666
}
@@ -101,3 +101,55 @@ func TestBackend_marker(t *testing.T) {
101101
assert.NoError(t, err)
102102
assert.EqualValues(t, b.lastMarker, markerFileContent)
103103
}
104+
105+
func TestBackend_globalprefix(t *testing.T) {
106+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
107+
defer cancel()
108+
109+
b := getBackend(ctx, t)
110+
b.opt.GlobalPrefix = "v5/"
111+
112+
tester.DoBackendTests(t, b)
113+
assert.Len(t, b.lastMarker, 0)
114+
}
115+
116+
func TestBackend_recursive(t *testing.T) {
117+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
118+
defer cancel()
119+
120+
b := getBackend(ctx, t)
121+
122+
// Starts empty
123+
ls, err := b.List(ctx, "")
124+
assert.NoError(t, err)
125+
assert.Len(t, ls, 0)
126+
127+
// Add items
128+
err = b.Store(ctx, "bar-1", []byte("bar1"))
129+
assert.NoError(t, err)
130+
err = b.Store(ctx, "bar-2", []byte("bar2"))
131+
assert.NoError(t, err)
132+
err = b.Store(ctx, "foo/bar-3", []byte("bar3"))
133+
assert.NoError(t, err)
134+
135+
// List all - PrefixFolders disabled (default)
136+
ls, err = b.List(ctx, "")
137+
assert.NoError(t, err)
138+
assert.Equal(t, ls.Names(), []string{"bar-1", "bar-2", "foo/bar-3"})
139+
140+
// List all - PrefixFolders enabled
141+
b.opt.PrefixFolders = true
142+
143+
ls, err = b.List(ctx, "")
144+
assert.NoError(t, err)
145+
assert.Equal(t, ls.Names(), []string{"bar-1", "bar-2", "foo/"})
146+
147+
// List all - PrefixFolders disabled
148+
b.opt.PrefixFolders = false
149+
150+
ls, err = b.List(ctx, "")
151+
assert.NoError(t, err)
152+
assert.Equal(t, ls.Names(), []string{"bar-1", "bar-2", "foo/bar-3"})
153+
154+
assert.Len(t, b.lastMarker, 0)
155+
}

0 commit comments

Comments
 (0)