| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package gcsutil |
| |
| import ( |
| "context" |
| "errors" |
| "time" |
| |
| "cloud.google.com/go/storage" |
| |
| "go.fuchsia.dev/fuchsia/tools/lib/retry" |
| ) |
| |
| // Retry wraps a function that makes a GCS API call, adding retries for failures |
| // that might be transient. |
| func Retry(ctx context.Context, f func() error) error { |
| const ( |
| initialWait = time.Second |
| backoff = 2 |
| maxAttempts = 5 |
| ) |
| retryStrategy := retry.WithMaxAttempts( |
| retry.NewExponentialBackoff(initialWait, 0, backoff), |
| maxAttempts) |
| return retryWithStrategy(ctx, retryStrategy, f) |
| } |
| |
| // Extracted to allow dependency injection for testing. |
| func retryWithStrategy(ctx context.Context, strategy retry.Backoff, f func() error) error { |
| return retry.Retry(ctx, strategy, func() error { |
| if err := f(); err != nil { |
| if errors.Is(err, storage.ErrBucketNotExist) || errors.Is(err, storage.ErrObjectNotExist) { |
| return retry.Fatal(err) |
| } |
| return err |
| } |
| return nil |
| }, nil) |
| } |
| |
| // ObjectAttrs gets the attributes for the given object, with retries. |
| func ObjectAttrs(ctx context.Context, obj *storage.ObjectHandle) (*storage.ObjectAttrs, error) { |
| var objAttrs *storage.ObjectAttrs |
| err := Retry(ctx, func() error { |
| var err error |
| objAttrs, err = obj.Attrs(ctx) |
| return err |
| }) |
| return objAttrs, err |
| } |
| |
| // NewObjectReader gets a reader for the given object, with retries. |
| func NewObjectReader(ctx context.Context, obj *storage.ObjectHandle) (*storage.Reader, error) { |
| var reader *storage.Reader |
| err := Retry(ctx, func() error { |
| var err error |
| reader, err = obj.NewReader(ctx) |
| return err |
| }) |
| return reader, err |
| } |