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 }