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  }