github.com/grafana/pyroscope@v1.18.0/pkg/objstore/objstore.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package objstore 5 6 import ( 7 "context" 8 "io" 9 "strings" 10 11 "github.com/thanos-io/objstore" 12 ) 13 14 type Bucket interface { 15 objstore.Bucket 16 ReaderAt(ctx context.Context, filename string) (ReaderAtCloser, error) 17 } 18 19 type BucketReader interface { 20 objstore.BucketReader 21 ReaderAt(ctx context.Context, filename string) (ReaderAtCloser, error) 22 } 23 24 // IsOpFailureExpectedFunc allows to mark certain errors as expected, so they will not increment objstore_bucket_operation_failures_total metric. 25 type IsOpFailureExpectedFunc func(error) bool 26 27 type InstrumentedBucket interface { 28 Bucket 29 // WithExpectedErrs allows to specify a filter that marks certain errors as expected, so it will not increment 30 // objstore_bucket_operation_failures_total metric. 31 WithExpectedErrs(IsOpFailureExpectedFunc) Bucket 32 33 // ReaderWithExpectedErrs allows to specify a filter that marks certain errors as expected, so it will not increment 34 // objstore_bucket_operation_failures_total metric. 35 // TODO(bwplotka): Remove this when moved to Go 1.14 and replace with InstrumentedBucketReader. 36 ReaderWithExpectedErrs(IsOpFailureExpectedFunc) BucketReader 37 } 38 39 type PrefixedBucket struct { 40 Bucket 41 prefix string 42 } 43 44 func NewPrefixedBucket(bkt Bucket, prefix string) Bucket { 45 if validPrefix(prefix) { 46 return &PrefixedBucket{Bucket: bkt, prefix: strings.Trim(prefix, objstore.DirDelim)} 47 } 48 49 return bkt 50 } 51 52 func validPrefix(prefix string) bool { 53 prefix = strings.ReplaceAll(prefix, "/", "") 54 return len(prefix) > 0 55 } 56 57 func conditionalPrefix(prefix, name string) string { 58 if len(name) > 0 { 59 return withPrefix(prefix, name) 60 } 61 62 return name 63 } 64 65 func withPrefix(prefix, name string) string { 66 return prefix + objstore.DirDelim + name 67 } 68 69 func (p *PrefixedBucket) Close() error { 70 return p.Bucket.Close() 71 } 72 73 func (p *PrefixedBucket) Prefix() string { 74 if prefixed, ok := p.Bucket.(interface{ Prefix() string }); ok && prefixed.Prefix() != "" { 75 return prefixed.Prefix() + objstore.DirDelim + p.prefix 76 } 77 return p.prefix 78 } 79 80 func (p *PrefixedBucket) ReaderAt(ctx context.Context, name string) (ReaderAtCloser, error) { 81 return p.Bucket.ReaderAt(ctx, conditionalPrefix(p.prefix, name)) 82 } 83 84 // Iter calls f for each entry in the given directory (not recursive.). The argument to f is the full 85 // object name including the prefix of the inspected directory. 86 // Entries are passed to function in sorted order. 87 func (p *PrefixedBucket) Iter(ctx context.Context, dir string, f func(string) error, options ...objstore.IterOption) error { 88 pdir := withPrefix(p.prefix, dir) 89 90 return p.Bucket.Iter(ctx, pdir, func(s string) error { 91 return f(strings.TrimPrefix(s, p.prefix+objstore.DirDelim)) 92 }, options...) 93 } 94 95 // Get returns a reader for the given object name. 96 func (p *PrefixedBucket) Get(ctx context.Context, name string) (io.ReadCloser, error) { 97 return p.Bucket.Get(ctx, conditionalPrefix(p.prefix, name)) 98 } 99 100 // GetRange returns a new range reader for the given object name and range. 101 func (p *PrefixedBucket) GetRange(ctx context.Context, name string, off int64, length int64) (io.ReadCloser, error) { 102 return p.Bucket.GetRange(ctx, conditionalPrefix(p.prefix, name), off, length) 103 } 104 105 // Exists checks if the given object exists in the bucket. 106 func (p *PrefixedBucket) Exists(ctx context.Context, name string) (bool, error) { 107 return p.Bucket.Exists(ctx, conditionalPrefix(p.prefix, name)) 108 } 109 110 // IsObjNotFoundErr returns true if error means that object is not found. Relevant to Get operations. 111 func (p *PrefixedBucket) IsObjNotFoundErr(err error) bool { 112 return p.Bucket.IsObjNotFoundErr(err) 113 } 114 115 // Attributes returns information about the specified object. 116 func (p PrefixedBucket) Attributes(ctx context.Context, name string) (objstore.ObjectAttributes, error) { 117 return p.Bucket.Attributes(ctx, conditionalPrefix(p.prefix, name)) 118 } 119 120 // Upload the contents of the reader as an object into the bucket. 121 // Upload should be idempotent. 122 func (p *PrefixedBucket) Upload(ctx context.Context, name string, r io.Reader, opts ...objstore.ObjectUploadOption) error { 123 return p.Bucket.Upload(ctx, conditionalPrefix(p.prefix, name), r, opts...) 124 } 125 126 // Delete removes the object with the given name. 127 // If object does not exists in the moment of deletion, Delete should throw error. 128 func (p *PrefixedBucket) Delete(ctx context.Context, name string) error { 129 return p.Bucket.Delete(ctx, conditionalPrefix(p.prefix, name)) 130 } 131 132 // Name returns the bucket name for the provider. 133 func (p *PrefixedBucket) Name() string { 134 return p.Bucket.Name() 135 } 136 137 // ReaderWithExpectedErrs implements objstore.Bucket. 138 func (p *PrefixedBucket) ReaderWithExpectedErrs(fn IsOpFailureExpectedFunc) BucketReader { 139 return p.WithExpectedErrs(fn) 140 } 141 142 // WithExpectedErrs implements objstore.Bucket. 143 func (p *PrefixedBucket) WithExpectedErrs(fn IsOpFailureExpectedFunc) Bucket { 144 if ib, ok := p.Bucket.(InstrumentedBucket); ok { 145 return &PrefixedBucket{ 146 Bucket: ib.WithExpectedErrs(fn), 147 prefix: p.prefix, 148 } 149 } 150 151 return p 152 }