github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/gcs/real_gcs.go (about)

     1  /*
     2  Copyright 2021 The TestGrid Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package gcs
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"io"
    23  	"net/http"
    24  	"strings"
    25  
    26  	"cloud.google.com/go/storage"
    27  	"google.golang.org/api/googleapi"
    28  )
    29  
    30  var (
    31  	_ Client = &realGCSClient{} // Ensure this implements interface
    32  )
    33  
    34  // NewGCSClient returns a GCSUploadClient for the storage.Client.
    35  func NewGCSClient(client *storage.Client) ConditionalClient {
    36  	return realGCSClient{client, nil, nil}
    37  }
    38  
    39  type realGCSClient struct {
    40  	client    *storage.Client
    41  	readCond  *storage.Conditions
    42  	writeCond *storage.Conditions
    43  }
    44  
    45  func (rgc realGCSClient) If(read, write *storage.Conditions) ConditionalClient {
    46  	return realGCSClient{
    47  		client:    rgc.client,
    48  		readCond:  read,
    49  		writeCond: write,
    50  	}
    51  }
    52  
    53  func (rgc realGCSClient) handle(path Path, cond *storage.Conditions) *storage.ObjectHandle {
    54  	oh := rgc.client.Bucket(path.Bucket()).Object(path.Object())
    55  	if cond == nil {
    56  		return oh
    57  	}
    58  	return oh.If(*cond)
    59  }
    60  
    61  func (rgc realGCSClient) Copy(ctx context.Context, from, to Path) (*storage.ObjectAttrs, error) {
    62  	fromH := rgc.handle(from, rgc.readCond)
    63  	a, err := rgc.handle(to, rgc.writeCond).CopierFrom(fromH).Run(ctx)
    64  	err = wrapGoogleAPIError(err) // so errors.Is(err, storage.ErrObjectNotExist) works
    65  	return a, err
    66  }
    67  
    68  func wrapGoogleAPIError(err error) error {
    69  	var e *googleapi.Error
    70  	if !errors.As(err, &e) || e.Code != http.StatusNotFound {
    71  		return err
    72  	}
    73  	return wrappedStorageError{
    74  		storageErr: storage.ErrObjectNotExist,
    75  		err:        e,
    76  	}
    77  }
    78  
    79  type wrappedStorageError struct {
    80  	storageErr error
    81  	err        error
    82  }
    83  
    84  func (e wrappedStorageError) Error() string {
    85  	return e.err.Error()
    86  }
    87  
    88  func (e wrappedStorageError) Unwrap() error {
    89  	return e.storageErr
    90  }
    91  
    92  func (rgc realGCSClient) Open(ctx context.Context, path Path) (io.ReadCloser, *storage.ReaderObjectAttrs, error) {
    93  	r, err := rgc.handle(path, rgc.readCond).NewReader(ctx)
    94  	if r == nil {
    95  		return nil, nil, err
    96  	}
    97  	if err == nil && rgc.readCond != nil {
    98  		err = checkPreconditions(r.Attrs, rgc.readCond)
    99  	}
   100  	return r, &r.Attrs, err
   101  }
   102  
   103  var (
   104  	errPreconditions = googleapi.Error{
   105  		Code: http.StatusPreconditionFailed,
   106  	}
   107  )
   108  
   109  func checkPreconditions(attrs storage.ReaderObjectAttrs, cond *storage.Conditions) error {
   110  	if g := cond.GenerationMatch; g > 0 && g != attrs.Generation {
   111  		return &errPreconditions
   112  	}
   113  	if g := cond.GenerationNotMatch; g > 0 && g == attrs.Generation {
   114  		return &errPreconditions
   115  	}
   116  	return nil
   117  }
   118  
   119  func (rgc realGCSClient) Objects(ctx context.Context, path Path, delimiter, startOffset string) Iterator {
   120  	p := path.Object()
   121  	if !strings.HasSuffix(p, "/") {
   122  		p += "/"
   123  	}
   124  	return rgc.client.Bucket(path.Bucket()).Objects(ctx, &storage.Query{
   125  		Delimiter:   delimiter,
   126  		Prefix:      p,
   127  		StartOffset: startOffset,
   128  	})
   129  }
   130  
   131  func (rgc realGCSClient) Upload(ctx context.Context, path Path, buf []byte, worldReadable bool, cacheControl string) (*storage.ObjectAttrs, error) {
   132  	return UploadHandle(ctx, rgc.handle(path, rgc.writeCond), buf, worldReadable, cacheControl)
   133  }
   134  
   135  func (rgc realGCSClient) Stat(ctx context.Context, path Path) (*storage.ObjectAttrs, error) {
   136  	return rgc.handle(path, rgc.readCond).Attrs(ctx)
   137  }