github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/blob/gcsblob/gcsblob.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package gcsblob provides a blob implementation that uses GCS. Use OpenBucket
    16  // to construct a *blob.Bucket.
    17  //
    18  // # URLs
    19  //
    20  // For blob.OpenBucket, gcsblob registers for the scheme "gs".
    21  // The default URL opener will set up a connection using default credentials
    22  // from the environment, as described in
    23  // https://cloud.google.com/docs/authentication/production.
    24  // Some environments, such as GCE, come without a private key. In such cases
    25  // the IAM Credentials API will be configured for use in Options.MakeSignBytes,
    26  // which will introduce latency to any and all calls to bucket.SignedURL
    27  // that you can avoid by installing a service account credentials file or
    28  // obtaining and configuring a private key:
    29  // https://cloud.google.com/iam/docs/creating-managing-service-account-keys
    30  //
    31  // To customize the URL opener, or for more details on the URL format,
    32  // see URLOpener.
    33  // See https://gocloud.dev/concepts/urls/ for background information.
    34  //
    35  // # Escaping
    36  //
    37  // Go CDK supports all UTF-8 strings; to make this work with services lacking
    38  // full UTF-8 support, strings must be escaped (during writes) and unescaped
    39  // (during reads). The following escapes are performed for gcsblob:
    40  //   - Blob keys: ASCII characters 10 and 13 are escaped to "__0x<hex>__".
    41  //     Additionally, the "/" in "../" is escaped in the same way.
    42  //
    43  // # As
    44  //
    45  // gcsblob exposes the following types for As:
    46  //   - Bucket: *storage.Client
    47  //   - Error: *googleapi.Error
    48  //   - ListObject: storage.ObjectAttrs
    49  //   - ListOptions.BeforeList: *storage.Query
    50  //   - Reader: *storage.Reader
    51  //   - ReaderOptions.BeforeRead: **storage.ObjectHandle, *storage.Reader (if accessing both, must be in that order)
    52  //   - Attributes: storage.ObjectAttrs
    53  //   - CopyOptions.BeforeCopy: *CopyObjectHandles, *storage.Copier (if accessing both, must be in that order)
    54  //   - WriterOptions.BeforeWrite: **storage.ObjectHandle, *storage.Writer (if accessing both, must be in that order)
    55  //   - SignedURLOptions.BeforeSign: *storage.SignedURLOptions
    56  package gcsblob // import "gocloud.dev/blob/gcsblob"
    57  
    58  import (
    59  	"context"
    60  	"encoding/json"
    61  	"errors"
    62  	"fmt"
    63  	"io"
    64  	"io/ioutil"
    65  	"net/http"
    66  	"net/url"
    67  	"os"
    68  	"sort"
    69  	"strings"
    70  	"sync"
    71  	"time"
    72  
    73  	"cloud.google.com/go/compute/metadata"
    74  	"cloud.google.com/go/storage"
    75  	"github.com/google/wire"
    76  	"golang.org/x/oauth2/google"
    77  	"google.golang.org/api/googleapi"
    78  	"google.golang.org/api/iterator"
    79  	"google.golang.org/api/option"
    80  
    81  	"gocloud.dev/blob"
    82  	"gocloud.dev/blob/driver"
    83  	"gocloud.dev/gcerrors"
    84  	"gocloud.dev/gcp"
    85  	"gocloud.dev/internal/escape"
    86  	"gocloud.dev/internal/gcerr"
    87  	"gocloud.dev/internal/useragent"
    88  )
    89  
    90  const defaultPageSize = 1000
    91  
    92  func init() {
    93  	blob.DefaultURLMux().RegisterBucket(Scheme, new(lazyCredsOpener))
    94  }
    95  
    96  // Set holds Wire providers for this package.
    97  var Set = wire.NewSet(
    98  	wire.Struct(new(URLOpener), "Client"),
    99  )
   100  
   101  // readDefaultCredentials gets the field values from the supplied JSON data.
   102  // For its possible formats please see
   103  // https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-go
   104  //
   105  // Use "golang.org/x/oauth2/google".DefaultCredentials.JSON to get
   106  // the contents of the preferred credential file.
   107  //
   108  // Returns null-values for fields that have not been obtained.
   109  func readDefaultCredentials(credFileAsJSON []byte) (AccessID string, PrivateKey []byte) {
   110  	// For example, a credentials file as generated for service accounts through the web console.
   111  	var contentVariantA struct {
   112  		ClientEmail string `json:"client_email"`
   113  		PrivateKey  string `json:"private_key"`
   114  	}
   115  	if err := json.Unmarshal(credFileAsJSON, &contentVariantA); err == nil {
   116  		AccessID = contentVariantA.ClientEmail
   117  		PrivateKey = []byte(contentVariantA.PrivateKey)
   118  	}
   119  	if AccessID != "" {
   120  		return
   121  	}
   122  
   123  	// If obtained through the REST API.
   124  	var contentVariantB struct {
   125  		Name           string `json:"name"`
   126  		PrivateKeyData string `json:"privateKeyData"`
   127  	}
   128  	if err := json.Unmarshal(credFileAsJSON, &contentVariantB); err == nil {
   129  		nextFieldIsAccessID := false
   130  		for _, s := range strings.Split(contentVariantB.Name, "/") {
   131  			if nextFieldIsAccessID {
   132  				AccessID = s
   133  				break
   134  			}
   135  			nextFieldIsAccessID = s == "serviceAccounts"
   136  		}
   137  		PrivateKey = []byte(contentVariantB.PrivateKeyData)
   138  	}
   139  
   140  	return
   141  }
   142  
   143  // lazyCredsOpener obtains Application Default Credentials on the first call
   144  // to OpenBucketURL.
   145  type lazyCredsOpener struct {
   146  	init   sync.Once
   147  	opener *URLOpener
   148  	err    error
   149  }
   150  
   151  func (o *lazyCredsOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) {
   152  	o.init.Do(func() {
   153  		var opts Options
   154  		var creds *google.Credentials
   155  		if os.Getenv("STORAGE_EMULATOR_HOST") != "" {
   156  			creds, _ = google.CredentialsFromJSON(ctx, []byte(`{"type": "service_account", "project_id": "my-project-id"}`))
   157  		} else {
   158  			var err error
   159  			creds, err = gcp.DefaultCredentials(ctx)
   160  			if err != nil {
   161  				o.err = err
   162  				return
   163  			}
   164  
   165  			// Populate default values from credentials files, where available.
   166  			opts.GoogleAccessID, opts.PrivateKey = readDefaultCredentials(creds.JSON)
   167  
   168  			// … else, on GCE, at least get the instance's main service account.
   169  			if opts.GoogleAccessID == "" && metadata.OnGCE() {
   170  				mc := metadata.NewClient(nil)
   171  				opts.GoogleAccessID, _ = mc.Email("")
   172  			}
   173  		}
   174  
   175  		// Provide a default factory for SignBytes for environments without a private key.
   176  		if len(opts.PrivateKey) <= 0 && opts.GoogleAccessID != "" {
   177  			iam := new(credentialsClient)
   178  			// We cannot hold onto the first context: it might've been cancelled already.
   179  			ctx := context.Background()
   180  			opts.MakeSignBytes = iam.CreateMakeSignBytesWith(ctx, opts.GoogleAccessID)
   181  		}
   182  
   183  		client, err := gcp.NewHTTPClient(gcp.DefaultTransport(), creds.TokenSource)
   184  		if err != nil {
   185  			o.err = err
   186  			return
   187  		}
   188  		o.opener = &URLOpener{Client: client, Options: opts}
   189  	})
   190  	if o.err != nil {
   191  		return nil, fmt.Errorf("open bucket %v: %v", u, o.err)
   192  	}
   193  	return o.opener.OpenBucketURL(ctx, u)
   194  }
   195  
   196  // Scheme is the URL scheme gcsblob registers its URLOpener under on
   197  // blob.DefaultMux.
   198  const Scheme = "gs"
   199  
   200  // URLOpener opens GCS URLs like "gs://mybucket".
   201  //
   202  // The URL host is used as the bucket name.
   203  //
   204  // The following query parameters are supported:
   205  //
   206  //   - access_id: sets Options.GoogleAccessID
   207  //   - private_key_path: path to read for Options.PrivateKey
   208  //
   209  // Currently their use is limited to SignedURL.
   210  type URLOpener struct {
   211  	// Client must be set to a non-nil HTTP client authenticated with
   212  	// Cloud Storage scope or equivalent.
   213  	Client *gcp.HTTPClient
   214  
   215  	// Options specifies the default options to pass to OpenBucket.
   216  	Options Options
   217  }
   218  
   219  // OpenBucketURL opens the GCS bucket with the same name as the URL's host.
   220  func (o *URLOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*blob.Bucket, error) {
   221  	opts, err := o.forParams(ctx, u.Query())
   222  	if err != nil {
   223  		return nil, fmt.Errorf("open bucket %v: %v", u, err)
   224  	}
   225  	return OpenBucket(ctx, o.Client, u.Host, opts)
   226  }
   227  
   228  func (o *URLOpener) forParams(ctx context.Context, q url.Values) (*Options, error) {
   229  	for k := range q {
   230  		if k != "access_id" && k != "private_key_path" {
   231  			return nil, fmt.Errorf("invalid query parameter %q", k)
   232  		}
   233  	}
   234  	opts := new(Options)
   235  	*opts = o.Options
   236  	if accessID := q.Get("access_id"); accessID != "" && accessID != opts.GoogleAccessID {
   237  		opts.GoogleAccessID = accessID
   238  		opts.PrivateKey = nil // Clear any previous key unrelated to the new accessID.
   239  
   240  		// Clear this as well to prevent calls with the old and mismatched accessID.
   241  		opts.MakeSignBytes = nil
   242  	}
   243  	if keyPath := q.Get("private_key_path"); keyPath != "" {
   244  		pk, err := ioutil.ReadFile(keyPath)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  		opts.PrivateKey = pk
   249  	} else if _, exists := q["private_key_path"]; exists {
   250  		// A possible default value has been cleared by setting this to an empty value:
   251  		// The private key might have expired, or falling back to SignBytes/MakeSignBytes
   252  		// is intentional such as for tests or involving a key stored in a HSM/TPM.
   253  		opts.PrivateKey = nil
   254  	}
   255  	return opts, nil
   256  }
   257  
   258  // Options sets options for constructing a *blob.Bucket backed by GCS.
   259  type Options struct {
   260  	// GoogleAccessID represents the authorizer for SignedURL.
   261  	// Required to use SignedURL.
   262  	// See https://godoc.org/cloud.google.com/go/storage#SignedURLOptions.
   263  	GoogleAccessID string
   264  
   265  	// PrivateKey is the Google service account private key.
   266  	// Exactly one of PrivateKey or SignBytes must be non-nil to use SignedURL.
   267  	// See https://godoc.org/cloud.google.com/go/storage#SignedURLOptions.
   268  	// Deprecated: Use MakeSignBytes instead.
   269  	PrivateKey []byte
   270  
   271  	// SignBytes is a function for implementing custom signing.
   272  	// Exactly one of PrivateKey, SignBytes, or MakeSignBytes must be non-nil to use SignedURL.
   273  	// See https://godoc.org/cloud.google.com/go/storage#SignedURLOptions.
   274  	// Deprecated: Use MakeSignBytes instead.
   275  	SignBytes func([]byte) ([]byte, error)
   276  
   277  	// MakeSignBytes is a factory for functions that are being used in place of an empty SignBytes.
   278  	// If your implementation of 'SignBytes' needs a request context, set this instead.
   279  	MakeSignBytes func(requestCtx context.Context) SignBytesFunc
   280  }
   281  
   282  // SignBytesFunc is shorthand for the signature of Options.SignBytes.
   283  type SignBytesFunc func([]byte) ([]byte, error)
   284  
   285  // openBucket returns a GCS Bucket that communicates using the given HTTP client.
   286  func openBucket(ctx context.Context, client *gcp.HTTPClient, bucketName string, opts *Options) (*bucket, error) {
   287  	if client == nil {
   288  		return nil, errors.New("gcsblob.OpenBucket: client is required")
   289  	}
   290  	if bucketName == "" {
   291  		return nil, errors.New("gcsblob.OpenBucket: bucketName is required")
   292  	}
   293  
   294  	clientOpts := []option.ClientOption{option.WithHTTPClient(useragent.HTTPClient(&client.Client, "blob"))}
   295  	if host := os.Getenv("STORAGE_EMULATOR_HOST"); host != "" {
   296  		clientOpts = []option.ClientOption{
   297  			option.WithoutAuthentication(),
   298  			option.WithEndpoint("http://" + host + "/storage/v1/"),
   299  			option.WithHTTPClient(http.DefaultClient),
   300  		}
   301  	}
   302  
   303  	// We wrap the provided http.Client to add a Go CDK User-Agent.
   304  	c, err := storage.NewClient(ctx, clientOpts...)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	if opts == nil {
   309  		opts = &Options{}
   310  	}
   311  	return &bucket{name: bucketName, client: c, opts: opts}, nil
   312  }
   313  
   314  // OpenBucket returns a *blob.Bucket backed by an existing GCS bucket. See the
   315  // package documentation for an example.
   316  func OpenBucket(ctx context.Context, client *gcp.HTTPClient, bucketName string, opts *Options) (*blob.Bucket, error) {
   317  	drv, err := openBucket(ctx, client, bucketName, opts)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	return blob.NewBucket(drv), nil
   322  }
   323  
   324  // bucket represents a GCS bucket, which handles read, write and delete operations
   325  // on objects within it.
   326  type bucket struct {
   327  	name   string
   328  	client *storage.Client
   329  	opts   *Options
   330  }
   331  
   332  var emptyBody = ioutil.NopCloser(strings.NewReader(""))
   333  
   334  // reader reads a GCS object. It implements driver.Reader.
   335  type reader struct {
   336  	body  io.ReadCloser
   337  	attrs driver.ReaderAttributes
   338  	raw   *storage.Reader
   339  }
   340  
   341  func (r *reader) Read(p []byte) (int, error) {
   342  	return r.body.Read(p)
   343  }
   344  
   345  // Close closes the reader itself. It must be called when done reading.
   346  func (r *reader) Close() error {
   347  	return r.body.Close()
   348  }
   349  
   350  func (r *reader) Attributes() *driver.ReaderAttributes {
   351  	return &r.attrs
   352  }
   353  
   354  func (r *reader) As(i interface{}) bool {
   355  	p, ok := i.(**storage.Reader)
   356  	if !ok {
   357  		return false
   358  	}
   359  	*p = r.raw
   360  	return true
   361  }
   362  
   363  func (b *bucket) ErrorCode(err error) gcerrors.ErrorCode {
   364  	if err == storage.ErrObjectNotExist || err == storage.ErrBucketNotExist {
   365  		return gcerrors.NotFound
   366  	}
   367  	if gerr, ok := err.(*googleapi.Error); ok {
   368  		switch gerr.Code {
   369  		case http.StatusForbidden:
   370  			return gcerrors.PermissionDenied
   371  		case http.StatusNotFound:
   372  			return gcerrors.NotFound
   373  		case http.StatusPreconditionFailed:
   374  			return gcerrors.FailedPrecondition
   375  		case http.StatusTooManyRequests:
   376  			return gcerrors.ResourceExhausted
   377  		}
   378  	}
   379  	return gcerrors.Unknown
   380  }
   381  
   382  func (b *bucket) Close() error {
   383  	return nil
   384  }
   385  
   386  // ListPaged implements driver.ListPaged.
   387  func (b *bucket) ListPaged(ctx context.Context, opts *driver.ListOptions) (*driver.ListPage, error) {
   388  	bkt := b.client.Bucket(b.name)
   389  	query := &storage.Query{
   390  		Prefix:    escapeKey(opts.Prefix),
   391  		Delimiter: escapeKey(opts.Delimiter),
   392  	}
   393  	if opts.BeforeList != nil {
   394  		asFunc := func(i interface{}) bool {
   395  			p, ok := i.(**storage.Query)
   396  			if !ok {
   397  				return false
   398  			}
   399  			*p = query
   400  			return true
   401  		}
   402  		if err := opts.BeforeList(asFunc); err != nil {
   403  			return nil, err
   404  		}
   405  	}
   406  	pageSize := opts.PageSize
   407  	if pageSize == 0 {
   408  		pageSize = defaultPageSize
   409  	}
   410  	iter := bkt.Objects(ctx, query)
   411  	pager := iterator.NewPager(iter, pageSize, string(opts.PageToken))
   412  	var objects []*storage.ObjectAttrs
   413  	nextPageToken, err := pager.NextPage(&objects)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	page := driver.ListPage{NextPageToken: []byte(nextPageToken)}
   418  	if len(objects) > 0 {
   419  		page.Objects = make([]*driver.ListObject, len(objects))
   420  		for i, obj := range objects {
   421  			toCopy := obj
   422  			asFunc := func(val interface{}) bool {
   423  				p, ok := val.(*storage.ObjectAttrs)
   424  				if !ok {
   425  					return false
   426  				}
   427  				*p = *toCopy
   428  				return true
   429  			}
   430  			if obj.Prefix == "" {
   431  				// Regular blob.
   432  				page.Objects[i] = &driver.ListObject{
   433  					Key:     unescapeKey(obj.Name),
   434  					ModTime: obj.Updated,
   435  					Size:    obj.Size,
   436  					MD5:     obj.MD5,
   437  					AsFunc:  asFunc,
   438  				}
   439  			} else {
   440  				// "Directory".
   441  				page.Objects[i] = &driver.ListObject{
   442  					Key:    unescapeKey(obj.Prefix),
   443  					IsDir:  true,
   444  					AsFunc: asFunc,
   445  				}
   446  			}
   447  		}
   448  		// GCS always returns "directories" at the end; sort them.
   449  		sort.Slice(page.Objects, func(i, j int) bool {
   450  			return page.Objects[i].Key < page.Objects[j].Key
   451  		})
   452  	}
   453  	return &page, nil
   454  }
   455  
   456  // As implements driver.As.
   457  func (b *bucket) As(i interface{}) bool {
   458  	p, ok := i.(**storage.Client)
   459  	if !ok {
   460  		return false
   461  	}
   462  	*p = b.client
   463  	return true
   464  }
   465  
   466  // As implements driver.ErrorAs.
   467  func (b *bucket) ErrorAs(err error, i interface{}) bool {
   468  	switch v := err.(type) {
   469  	case *googleapi.Error:
   470  		if p, ok := i.(**googleapi.Error); ok {
   471  			*p = v
   472  			return true
   473  		}
   474  	}
   475  	return false
   476  }
   477  
   478  // Attributes implements driver.Attributes.
   479  func (b *bucket) Attributes(ctx context.Context, key string) (*driver.Attributes, error) {
   480  	key = escapeKey(key)
   481  	bkt := b.client.Bucket(b.name)
   482  	obj := bkt.Object(key)
   483  	attrs, err := obj.Attrs(ctx)
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  	// GCS seems to unquote the ETag; restore them.
   488  	// It should be of the form "xxxx" or W/"xxxx".
   489  	eTag := attrs.Etag
   490  	if !strings.HasPrefix(eTag, "W/\"") && !strings.HasPrefix(eTag, "\"") && !strings.HasSuffix(eTag, "\"") {
   491  		eTag = fmt.Sprintf("%q", eTag)
   492  	}
   493  	return &driver.Attributes{
   494  		CacheControl:       attrs.CacheControl,
   495  		ContentDisposition: attrs.ContentDisposition,
   496  		ContentEncoding:    attrs.ContentEncoding,
   497  		ContentLanguage:    attrs.ContentLanguage,
   498  		ContentType:        attrs.ContentType,
   499  		Metadata:           attrs.Metadata,
   500  		CreateTime:         attrs.Created,
   501  		ModTime:            attrs.Updated,
   502  		Size:               attrs.Size,
   503  		MD5:                attrs.MD5,
   504  		ETag:               eTag,
   505  		AsFunc: func(i interface{}) bool {
   506  			p, ok := i.(*storage.ObjectAttrs)
   507  			if !ok {
   508  				return false
   509  			}
   510  			*p = *attrs
   511  			return true
   512  		},
   513  	}, nil
   514  }
   515  
   516  // NewRangeReader implements driver.NewRangeReader.
   517  func (b *bucket) NewRangeReader(ctx context.Context, key string, offset, length int64, opts *driver.ReaderOptions) (driver.Reader, error) {
   518  	key = escapeKey(key)
   519  	bkt := b.client.Bucket(b.name)
   520  	obj := bkt.Object(key)
   521  
   522  	// Add an extra level of indirection so that BeforeRead can replace obj
   523  	// if needed. For example, ObjectHandle.If returns a new ObjectHandle.
   524  	// Also, make the Reader lazily in case this replacement happens.
   525  	objp := &obj
   526  	makeReader := func() (*storage.Reader, error) {
   527  		return (*objp).NewRangeReader(ctx, offset, length)
   528  	}
   529  
   530  	var r *storage.Reader
   531  	var rerr error
   532  	madeReader := false
   533  	if opts.BeforeRead != nil {
   534  		asFunc := func(i interface{}) bool {
   535  			if p, ok := i.(***storage.ObjectHandle); ok && !madeReader {
   536  				*p = objp
   537  				return true
   538  			}
   539  			if p, ok := i.(**storage.Reader); ok {
   540  				if !madeReader {
   541  					r, rerr = makeReader()
   542  					madeReader = true
   543  					if r == nil {
   544  						return false
   545  					}
   546  				}
   547  				*p = r
   548  				return true
   549  			}
   550  			return false
   551  		}
   552  		if err := opts.BeforeRead(asFunc); err != nil {
   553  			return nil, err
   554  		}
   555  	}
   556  	if !madeReader {
   557  		r, rerr = makeReader()
   558  	}
   559  	if rerr != nil {
   560  		return nil, rerr
   561  	}
   562  	return &reader{
   563  		body: r,
   564  		attrs: driver.ReaderAttributes{
   565  			ContentType: r.Attrs.ContentType,
   566  			ModTime:     r.Attrs.LastModified,
   567  			Size:        r.Attrs.Size,
   568  		},
   569  		raw: r,
   570  	}, nil
   571  }
   572  
   573  // escapeKey does all required escaping for UTF-8 strings to work with GCS.
   574  func escapeKey(key string) string {
   575  	return escape.HexEscape(key, func(r []rune, i int) bool {
   576  		switch {
   577  		// GCS doesn't handle these characters (determined via experimentation).
   578  		case r[i] == 10 || r[i] == 13:
   579  			return true
   580  		// For "../", escape the trailing slash.
   581  		case i > 1 && r[i] == '/' && r[i-1] == '.' && r[i-2] == '.':
   582  			return true
   583  		}
   584  		return false
   585  	})
   586  }
   587  
   588  // unescapeKey reverses escapeKey.
   589  func unescapeKey(key string) string {
   590  	return escape.HexUnescape(key)
   591  }
   592  
   593  // NewTypedWriter implements driver.NewTypedWriter.
   594  func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType string, opts *driver.WriterOptions) (driver.Writer, error) {
   595  	key = escapeKey(key)
   596  	bkt := b.client.Bucket(b.name)
   597  	obj := bkt.Object(key)
   598  
   599  	// Add an extra level of indirection so that BeforeWrite can replace obj
   600  	// if needed. For example, ObjectHandle.If returns a new ObjectHandle.
   601  	// Also, make the Writer lazily in case this replacement happens.
   602  	objp := &obj
   603  	makeWriter := func() *storage.Writer {
   604  		w := (*objp).NewWriter(ctx)
   605  		w.CacheControl = opts.CacheControl
   606  		w.ContentDisposition = opts.ContentDisposition
   607  		w.ContentEncoding = opts.ContentEncoding
   608  		w.ContentLanguage = opts.ContentLanguage
   609  		w.ContentType = contentType
   610  		w.ChunkSize = bufferSize(opts.BufferSize)
   611  		w.Metadata = opts.Metadata
   612  		w.MD5 = opts.ContentMD5
   613  		return w
   614  	}
   615  
   616  	var w *storage.Writer
   617  	if opts.BeforeWrite != nil {
   618  		asFunc := func(i interface{}) bool {
   619  			if p, ok := i.(***storage.ObjectHandle); ok && w == nil {
   620  				*p = objp
   621  				return true
   622  			}
   623  			if p, ok := i.(**storage.Writer); ok {
   624  				if w == nil {
   625  					w = makeWriter()
   626  				}
   627  				*p = w
   628  				return true
   629  			}
   630  			return false
   631  		}
   632  		if err := opts.BeforeWrite(asFunc); err != nil {
   633  			return nil, err
   634  		}
   635  	}
   636  	if w == nil {
   637  		w = makeWriter()
   638  	}
   639  	return w, nil
   640  }
   641  
   642  // CopyObjectHandles holds the ObjectHandles for the destination and source
   643  // of a Copy. It is used by the BeforeCopy As hook.
   644  type CopyObjectHandles struct {
   645  	Dst, Src *storage.ObjectHandle
   646  }
   647  
   648  // Copy implements driver.Copy.
   649  func (b *bucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.CopyOptions) error {
   650  	dstKey = escapeKey(dstKey)
   651  	srcKey = escapeKey(srcKey)
   652  	bkt := b.client.Bucket(b.name)
   653  
   654  	// Add an extra level of indirection so that BeforeCopy can replace the
   655  	// dst or src ObjectHandles if needed.
   656  	// Also, make the Copier lazily in case this replacement happens.
   657  	handles := CopyObjectHandles{
   658  		Dst: bkt.Object(dstKey),
   659  		Src: bkt.Object(srcKey),
   660  	}
   661  	makeCopier := func() *storage.Copier {
   662  		return handles.Dst.CopierFrom(handles.Src)
   663  	}
   664  
   665  	var copier *storage.Copier
   666  	if opts.BeforeCopy != nil {
   667  		asFunc := func(i interface{}) bool {
   668  			if p, ok := i.(**CopyObjectHandles); ok && copier == nil {
   669  				*p = &handles
   670  				return true
   671  			}
   672  			if p, ok := i.(**storage.Copier); ok {
   673  				if copier == nil {
   674  					copier = makeCopier()
   675  				}
   676  				*p = copier
   677  				return true
   678  			}
   679  			return false
   680  		}
   681  		if err := opts.BeforeCopy(asFunc); err != nil {
   682  			return err
   683  		}
   684  	}
   685  	if copier == nil {
   686  		copier = makeCopier()
   687  	}
   688  	_, err := copier.Run(ctx)
   689  	return err
   690  }
   691  
   692  // Delete implements driver.Delete.
   693  func (b *bucket) Delete(ctx context.Context, key string) error {
   694  	key = escapeKey(key)
   695  	bkt := b.client.Bucket(b.name)
   696  	obj := bkt.Object(key)
   697  	return obj.Delete(ctx)
   698  }
   699  
   700  func (b *bucket) SignedURL(ctx context.Context, key string, dopts *driver.SignedURLOptions) (string, error) {
   701  	numSigners := 0
   702  	if b.opts.PrivateKey != nil {
   703  		numSigners++
   704  	}
   705  	if b.opts.SignBytes != nil {
   706  		numSigners++
   707  	}
   708  	if b.opts.MakeSignBytes != nil {
   709  		numSigners++
   710  	}
   711  	if b.opts.GoogleAccessID == "" || numSigners != 1 {
   712  		return "", gcerr.New(gcerr.Unimplemented, nil, 1, "gcsblob: to use SignedURL, you must call OpenBucket with a valid Options.GoogleAccessID and exactly one of Options.PrivateKey, Options.SignBytes, or Options.MakeSignBytes")
   713  	}
   714  
   715  	key = escapeKey(key)
   716  	opts := &storage.SignedURLOptions{
   717  		Expires:        time.Now().Add(dopts.Expiry),
   718  		Method:         dopts.Method,
   719  		ContentType:    dopts.ContentType,
   720  		GoogleAccessID: b.opts.GoogleAccessID,
   721  		PrivateKey:     b.opts.PrivateKey,
   722  		SignBytes:      b.opts.SignBytes,
   723  	}
   724  	if b.opts.MakeSignBytes != nil {
   725  		opts.SignBytes = b.opts.MakeSignBytes(ctx)
   726  	}
   727  	if dopts.BeforeSign != nil {
   728  		asFunc := func(i interface{}) bool {
   729  			v, ok := i.(**storage.SignedURLOptions)
   730  			if ok {
   731  				*v = opts
   732  			}
   733  			return ok
   734  		}
   735  		if err := dopts.BeforeSign(asFunc); err != nil {
   736  			return "", err
   737  		}
   738  	}
   739  	return storage.SignedURL(b.name, key, opts)
   740  }
   741  
   742  func bufferSize(size int) int {
   743  	if size == 0 {
   744  		return googleapi.DefaultUploadChunkSize
   745  	} else if size > 0 {
   746  		return size
   747  	}
   748  	return 0 // disable buffering
   749  }