@@ -3,6 +3,7 @@ package s3
33import (
44 "bytes"
55 "context"
6+ "errors"
67 "fmt"
78 "io"
89 "net/url"
@@ -14,8 +15,8 @@ import (
1415 "time"
1516
1617 "github.com/PowerDNS/go-tlsconfig"
18+ "github.com/minio/minio-go/v7"
1719 "github.com/go-logr/logr"
18- minio "github.com/minio/minio-go/v7"
1920 "github.com/minio/minio-go/v7/pkg/credentials"
2021
2122 "github.com/PowerDNS/simpleblob"
@@ -24,7 +25,7 @@ import (
2425const (
2526 // DefaultEndpointURL is the default S3 endpoint to use if none is set.
2627 // Here, no custom endpoint assumes AWS endpoint.
27- DefaultEndpointURL = "s3.amazonaws.com"
28+ DefaultEndpointURL = "https:// s3.amazonaws.com"
2829 // DefaultRegion is the default S3 region to use, if none is configured
2930 DefaultRegion = "us-east-1"
3031 // DefaultInitTimeout is the time we allow for initialisation, like credential
@@ -51,7 +52,7 @@ type Options struct {
5152 CreateBucket bool `yaml:"create_bucket"`
5253
5354 // EndpointURL can be set to something like "http://localhost:9000" when using Minio
54- // or "s3.amazonaws.com" for AWS S3.
55+ // or "https:// s3.amazonaws.com" for AWS S3.
5556 EndpointURL string `yaml:"endpoint_url"`
5657
5758 // TLS allows customising the TLS configuration
@@ -65,7 +66,7 @@ type Options struct {
6566
6667 // UseUpdateMarker makes the backend write and read a file to determine if
6768 // it can cache the last List command. The file contains the name of the
68- // last file stored.
69+ // last file stored or deleted .
6970 // This can reduce the number of LIST commands sent to S3, replacing them
7071 // with GET commands that are about 12x cheaper.
7172 // If enabled, it MUST be enabled on all instances!
@@ -113,36 +114,36 @@ func (b *Backend) List(ctx context.Context, prefix string) (simpleblob.BlobList,
113114 return b .doList (ctx , prefix )
114115 }
115116
116- // Request and cache full list, and use marker file to invalidate the cache
117- now := time .Now ()
118- data , err := b .Load (ctx , UpdateMarkerFilename )
119- if err != nil && ! os .IsNotExist (err ) {
120- return nil , err
117+ m , err := b .Load (ctx , UpdateMarkerFilename )
118+ exists := ! errors .Is (err , os .ErrNotExist )
119+ if err != nil && exists {
120+ return nil , err
121121 }
122- current := string (data )
122+ upstreamMarker := string (m )
123123
124124 b .mu .Lock ()
125- lastMarker := b .lastMarker
126- age := now .Sub (b .lastTime )
125+ mustUpdate := b .lastList == nil ||
126+ upstreamMarker != b .lastMarker ||
127+ time .Since (b .lastTime ) >= b .opt .UpdateMarkerForceListInterval ||
128+ ! exists
129+ blobs := b .lastList
127130 b .mu .Unlock ()
128131
129- var blobs simpleblob.BlobList
130- if current != lastMarker || age >= b .opt .UpdateMarkerForceListInterval {
131- // Update cache
132- blobs , err = b .doList (ctx , "" ) // all, no prefix
133- if err != nil {
134- return nil , err
135- }
132+ if ! mustUpdate {
133+ return blobs .WithPrefix (prefix ), nil
134+ }
136135
137- b .mu .Lock ()
138- b .lastMarker = current
139- b .lastList = blobs
140- b .mu .Unlock ()
141- } else {
142- b .mu .Lock ()
143- blobs = b .lastList
144- b .mu .Unlock ()
136+ blobs , err = b .doList (ctx , "" ) // We want to cache all, so no prefix
137+ if err != nil {
138+ return nil , err
145139 }
140+
141+ b .mu .Lock ()
142+ b .lastMarker = upstreamMarker
143+ b .lastList = blobs
144+ b .lastTime = time .Now ()
145+ b .mu .Unlock ()
146+
146147 return blobs .WithPrefix (prefix ), nil
147148}
148149
@@ -169,57 +170,64 @@ func (b *Backend) doList(ctx context.Context, prefix string) (simpleblob.BlobLis
169170 return blobs , nil
170171}
171172
173+ // Load retrieves the content of the object identified by name from S3 Bucket
174+ // configured in b.
172175func (b * Backend ) Load (ctx context.Context , name string ) ([]byte , error ) {
173176 metricCalls .WithLabelValues ("load" ).Inc ()
174177 metricLastCallTimestamp .WithLabelValues ("load" ).SetToCurrentTime ()
175178
176179 obj , err := b .client .GetObject (ctx , b .opt .Bucket , name , minio.GetObjectOptions {})
177- if err != nil {
178- if err = handleErrorResponse (err ); err != nil {
179- return nil , err
180- }
180+ if err = convertMinioError (err ); err != nil {
181+ return nil , err
181182 } else if obj == nil {
182183 return nil , os .ErrNotExist
183184 }
184185
185186 p , err := io .ReadAll (obj )
186- if err = handleErrorResponse (err ); err != nil {
187+ if err = convertMinioError (err ); err != nil {
187188 return nil , err
188189 }
189190 return p , nil
190191}
191192
193+ // Store sets the content of the object identified by name to the content
194+ // of data, in the S3 Bucket configured in b.
192195func (b * Backend ) Store (ctx context.Context , name string , data []byte ) error {
193- if err := b .doStore (ctx , name , data ); err != nil {
196+ info , err := b .doStore (ctx , name , data )
197+ if err != nil {
194198 return err
195199 }
196- if b .opt .UseUpdateMarker {
197- if err := b .doStore (ctx , UpdateMarkerFilename , []byte (name )); err != nil {
198- return err
199- }
200- }
201- return nil
200+ return b .setMarker (ctx , name , info .ETag , false )
202201}
203202
204- func (b * Backend ) doStore (ctx context.Context , name string , data []byte ) error {
203+ func (b * Backend ) doStore (ctx context.Context , name string , data []byte ) (minio. UploadInfo , error ) {
205204 metricCalls .WithLabelValues ("store" ).Inc ()
206205 metricLastCallTimestamp .WithLabelValues ("store" ).SetToCurrentTime ()
207206
208- _ , err := b .client .PutObject (ctx , b .opt .Bucket , name , bytes .NewReader (data ), int64 (len (data )), minio.PutObjectOptions {
207+ info , err := b .client .PutObject (ctx , b .opt .Bucket , name , bytes .NewReader (data ), int64 (len (data )), minio.PutObjectOptions {
209208 NumThreads : 3 ,
210209 })
211210 if err != nil {
212211 metricCallErrors .WithLabelValues ("store" ).Inc ()
213212 }
214- return err
213+ return info , err
215214}
216215
216+ // Delete removes the object identified by name from the S3 Bucket
217+ // configured in b.
217218func (b * Backend ) Delete (ctx context.Context , name string ) error {
219+ if err := b .doDelete (ctx , name ); err != nil {
220+ return err
221+ }
222+ return b .setMarker (ctx , name , "" , true )
223+ }
224+
225+ func (b * Backend ) doDelete (ctx context.Context , name string ) error {
218226 metricCalls .WithLabelValues ("delete" ).Inc ()
219227 metricLastCallTimestamp .WithLabelValues ("delete" ).SetToCurrentTime ()
220228
221229 err := b .client .RemoveObject (ctx , b .opt .Bucket , name , minio.RemoveObjectOptions {})
222- if err = handleErrorResponse (err ); err != nil {
230+ if err = convertMinioError (err ); err != nil {
223231 metricCallErrors .WithLabelValues ("delete" ).Inc ()
224232 }
225233 return err
@@ -288,8 +296,10 @@ func New(ctx context.Context, opt Options) (*Backend, error) {
288296 // Ok, no SSL
289297 case "https" :
290298 useSSL = true
299+ case "" :
300+ return nil , fmt .Errorf ("no scheme provided for endpoint URL '%s', use http or https." , opt .EndpointURL )
291301 default :
292- return nil , fmt .Errorf ("unsupported scheme for S3: '%s'" , u .Scheme )
302+ return nil , fmt .Errorf ("unsupported scheme for S3: '%s', use http or https. " , u .Scheme )
293303 }
294304
295305 cfg := & minio.Options {
@@ -320,7 +330,7 @@ func New(ctx context.Context, opt Options) (*Backend, error) {
320330
321331 err := client .MakeBucket (ctx , opt .Bucket , minio.MakeBucketOptions {Region : opt .Region })
322332 if err != nil {
323- if err := handleErrorResponse (err ); err != nil {
333+ if err := convertMinioError (err ); err != nil {
324334 return nil , err
325335 }
326336 }
@@ -336,11 +346,14 @@ func New(ctx context.Context, opt Options) (*Backend, error) {
336346 return b , nil
337347}
338348
339- // handleErrorResponse takes an error, possibly a minio.ErrorResponse
349+ // convertMinioError takes an error, possibly a minio.ErrorResponse
340350// and turns it into a well known error when possible.
341351// If error is not well known, it is returned as is.
342352// If error is considered to be ignorable, nil is returned.
343- func handleErrorResponse (err error ) error {
353+ func convertMinioError (err error ) error {
354+ if err == nil {
355+ return nil
356+ }
344357 errResp := minio .ToErrorResponse (err )
345358 if errResp .StatusCode == 404 {
346359 return os .ErrNotExist
0 commit comments