github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/gcs/gcs.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // Package gcs provides wrappers around Google Cloud Storage (GCS) APIs. 5 // Package uses Application Default Credentials assuming that the program runs on GCE. 6 // 7 // See the following links for details and API reference: 8 // https://cloud.google.com/go/getting-started/using-cloud-storage 9 // https://godoc.org/cloud.google.com/go/storage 10 package gcs 11 12 import ( 13 "context" 14 "errors" 15 "fmt" 16 "io" 17 "os" 18 "strings" 19 "time" 20 21 "cloud.google.com/go/storage" 22 "google.golang.org/api/iterator" 23 ) 24 25 type Client struct { 26 client *storage.Client 27 ctx context.Context 28 } 29 30 type File struct { 31 Updated time.Time 32 33 ctx context.Context 34 handle *storage.ObjectHandle 35 } 36 37 func (file *File) Reader() (io.ReadCloser, error) { 38 return file.handle.NewReader(file.ctx) 39 } 40 41 func NewClient() (*Client, error) { 42 ctx := context.Background() 43 storageClient, err := storage.NewClient(ctx) 44 if err != nil { 45 return nil, err 46 } 47 client := &Client{ 48 client: storageClient, 49 ctx: ctx, 50 } 51 return client, nil 52 } 53 54 func (client *Client) Close() { 55 client.client.Close() 56 } 57 58 func (client *Client) Read(gcsFile string) (*File, error) { 59 bucket, filename, err := split(gcsFile) 60 if err != nil { 61 return nil, err 62 } 63 bkt := client.client.Bucket(bucket) 64 f := bkt.Object(filename) 65 attrs, err := f.Attrs(client.ctx) 66 if err != nil { 67 return nil, fmt.Errorf("failed to read %v attributes: %w", gcsFile, err) 68 } 69 if !attrs.Deleted.IsZero() { 70 return nil, fmt.Errorf("file %v is deleted", gcsFile) 71 } 72 handle := f.If(storage.Conditions{ 73 GenerationMatch: attrs.Generation, 74 MetagenerationMatch: attrs.Metageneration, 75 }) 76 file := &File{ 77 Updated: attrs.Updated, 78 ctx: client.ctx, 79 handle: handle, 80 } 81 return file, nil 82 } 83 84 func (client *Client) UploadFile(localFile, gcsFile, contentType, contentEncoding string) error { 85 local, err := os.Open(localFile) 86 if err != nil { 87 return err 88 } 89 defer local.Close() 90 91 w, err := client.FileWriterExt(gcsFile, contentType, contentEncoding) 92 if err != nil { 93 return err 94 } 95 defer w.Close() 96 97 _, err = io.Copy(w, local) 98 return err 99 } 100 101 func (client *Client) FileWriterExt(gcsFile, contentType, contentEncoding string) (io.WriteCloser, error) { 102 bucket, filename, err := split(gcsFile) 103 if err != nil { 104 return nil, err 105 } 106 bkt := client.client.Bucket(bucket) 107 f := bkt.Object(filename) 108 w := f.NewWriter(client.ctx) 109 if contentType != "" { 110 w.ContentType = contentType 111 } 112 if contentEncoding != "" { 113 w.ContentEncoding = contentEncoding 114 } 115 return w, nil 116 } 117 118 func (client *Client) FileWriter(gcsFile string) (io.WriteCloser, error) { 119 return client.FileWriterExt(gcsFile, "", "") 120 } 121 122 // Publish lets any user read gcsFile. 123 func (client *Client) Publish(gcsFile string) error { 124 bucket, filename, err := split(gcsFile) 125 if err != nil { 126 return err 127 } 128 obj := client.client.Bucket(bucket).Object(filename) 129 return obj.ACL().Set(client.ctx, storage.AllUsers, storage.RoleReader) 130 } 131 132 var ErrFileNotFound = errors.New("the requested files does not exist") 133 134 func (client *Client) DeleteFile(gcsFile string) error { 135 bucket, filename, err := split(gcsFile) 136 if err != nil { 137 return err 138 } 139 err = client.client.Bucket(bucket).Object(filename).Delete(client.ctx) 140 if err == storage.ErrObjectNotExist { 141 return ErrFileNotFound 142 } 143 return err 144 } 145 146 func (client *Client) FileExists(gcsFile string) (bool, error) { 147 bucket, filename, err := split(gcsFile) 148 if err != nil { 149 return false, err 150 } 151 _, err = client.client.Bucket(bucket).Object(filename).Attrs(client.ctx) 152 if err == storage.ErrObjectNotExist { 153 return false, nil 154 } else if err != nil { 155 return false, err 156 } 157 return true, nil 158 } 159 160 // Where things get published. 161 const ( 162 PublicPrefix = "https://storage.googleapis.com/" 163 AuthenticatedPrefix = "https://storage.cloud.google.com/" 164 ) 165 166 func (client *Client) GetDownloadURL(gcsFile string, publicURL bool) string { 167 gcsFile = strings.TrimPrefix(gcsFile, "/") 168 if publicURL { 169 return PublicPrefix + gcsFile 170 } 171 return AuthenticatedPrefix + gcsFile 172 } 173 174 type Object struct { 175 Path string 176 CreatedAt time.Time 177 } 178 179 func (client *Client) ListObjects(bucket string) ([]*Object, error) { 180 ctx := context.Background() 181 it := client.client.Bucket(bucket).Objects(ctx, nil) 182 ret := []*Object{} 183 for { 184 objAttrs, err := it.Next() 185 if err == iterator.Done { 186 break 187 } 188 if err != nil { 189 return nil, fmt.Errorf("failed to query GCS objects: %w", err) 190 } 191 ret = append(ret, &Object{ 192 Path: objAttrs.Name, 193 CreatedAt: objAttrs.Created, 194 }) 195 } 196 return ret, nil 197 } 198 199 func split(file string) (bucket, filename string, err error) { 200 pos := strings.IndexByte(file, '/') 201 if pos == -1 { 202 return "", "", fmt.Errorf("invalid GCS file name: %v", file) 203 } 204 return file[:pos], file[pos+1:], nil 205 }