github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/blobvar/blobvar.go (about)

     1  // Copyright 2019 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 blobvar provides a runtimevar implementation with
    16  // variables read from a blob.Bucket.
    17  // Use OpenVariable to construct a *runtimevar.Variable.
    18  //
    19  // URLs
    20  //
    21  // For runtimevar.OpenVariable, blobvar registers for the scheme "blob".
    22  // The default URL opener will open a blob.Bucket based on the environment
    23  // variable "BLOBVAR_BUCKET_URL".
    24  // To customize the URL opener, or for more details on the URL format,
    25  // see URLOpener.
    26  // See https://gocloud.dev/concepts/urls/ for background information.
    27  //
    28  // As
    29  //
    30  // blobvar exposes the following types for As:
    31  //  - Snapshot: Not supported.
    32  //  - Error: error, which can be passed to blob.ErrorAs.
    33  package blobvar // import "gocloud.dev/runtimevar/blobvar"
    34  
    35  import (
    36  	"bytes"
    37  	"context"
    38  	"errors"
    39  	"fmt"
    40  	"net/url"
    41  	"os"
    42  	"path"
    43  	"sync"
    44  	"time"
    45  
    46  	"gocloud.dev/blob"
    47  	"gocloud.dev/gcerrors"
    48  	"gocloud.dev/runtimevar"
    49  	"gocloud.dev/runtimevar/driver"
    50  )
    51  
    52  func init() {
    53  	runtimevar.DefaultURLMux().RegisterVariable(Scheme, &defaultOpener{})
    54  }
    55  
    56  type defaultOpener struct {
    57  	init   sync.Once
    58  	opener *URLOpener
    59  	err    error
    60  }
    61  
    62  func (o *defaultOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) {
    63  	o.init.Do(func() {
    64  		bucketURL := os.Getenv("BLOBVAR_BUCKET_URL")
    65  		if bucketURL == "" {
    66  			o.err = errors.New("BLOBVAR_BUCKET_URL environment variable is not set")
    67  			return
    68  		}
    69  		bucket, err := blob.OpenBucket(ctx, bucketURL)
    70  		if err != nil {
    71  			o.err = fmt.Errorf("failed to open default bucket %q: %v", bucketURL, err)
    72  			return
    73  		}
    74  		o.opener = &URLOpener{Bucket: bucket}
    75  	})
    76  	if o.err != nil {
    77  		return nil, fmt.Errorf("open variable %v: %v", u, o.err)
    78  	}
    79  	return o.opener.OpenVariableURL(ctx, u)
    80  }
    81  
    82  // Scheme is the URL scheme blobvar registers its URLOpener under on runtimevar.DefaultMux.
    83  const Scheme = "blob"
    84  
    85  // URLOpener opens blob-backed URLs like "blob://myblobkey?decoder=string".
    86  // It supports the following URL parameters:
    87  //   - decoder: The decoder to use. Defaults to URLOpener.Decoder, or
    88  //       runtimevar.BytesDecoder if URLOpener.Decoder is nil.
    89  //       See runtimevar.DecoderByName for supported values.
    90  type URLOpener struct {
    91  	// Bucket is required.
    92  	Bucket *blob.Bucket
    93  
    94  	// Decoder specifies the decoder to use if one is not specified in the URL.
    95  	// Defaults to runtimevar.BytesDecoder.
    96  	Decoder *runtimevar.Decoder
    97  
    98  	// Options specifies the Options for OpenVariable.
    99  	Options Options
   100  }
   101  
   102  // OpenVariableURL opens the variable at the URL's path. See the package doc
   103  // for more details.
   104  func (o *URLOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) {
   105  	q := u.Query()
   106  
   107  	if o.Bucket == nil {
   108  		return nil, fmt.Errorf("open variable %v: bucket is required", u)
   109  	}
   110  
   111  	decoderName := q.Get("decoder")
   112  	q.Del("decoder")
   113  	decoder, err := runtimevar.DecoderByName(ctx, decoderName, o.Decoder)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("open variable %v: invalid decoder: %v", u, err)
   116  	}
   117  
   118  	for param := range q {
   119  		return nil, fmt.Errorf("open variable %v: invalid query parameter %q", u, param)
   120  	}
   121  	return OpenVariable(o.Bucket, path.Join(u.Host, u.Path), decoder, &o.Options)
   122  }
   123  
   124  // Options sets options.
   125  type Options struct {
   126  	// WaitDuration controls the rate at which the blob is polled.
   127  	// Defaults to 30 seconds.
   128  	WaitDuration time.Duration
   129  }
   130  
   131  // OpenVariable constructs a *runtimevar.Variable backed by the referenced blob.
   132  // Reads of the blob return raw bytes; provide a decoder to decode the raw bytes
   133  // into the appropriate type for runtimevar.Snapshot.Value.
   134  // See the runtimevar package documentation for examples of decoders.
   135  func OpenVariable(bucket *blob.Bucket, key string, decoder *runtimevar.Decoder, opts *Options) (*runtimevar.Variable, error) {
   136  	return runtimevar.New(newWatcher(bucket, key, decoder, nil, opts)), nil
   137  }
   138  
   139  func newWatcher(bucket *blob.Bucket, key string, decoder *runtimevar.Decoder, opener *URLOpener, opts *Options) driver.Watcher {
   140  	if opts == nil {
   141  		opts = &Options{}
   142  	}
   143  	return &watcher{
   144  		bucket:  bucket,
   145  		opener:  opener,
   146  		key:     key,
   147  		wait:    driver.WaitDuration(opts.WaitDuration),
   148  		decoder: decoder,
   149  	}
   150  }
   151  
   152  // state implements driver.State.
   153  type state struct {
   154  	val        interface{}
   155  	updateTime time.Time
   156  	rawBytes   []byte
   157  	err        error
   158  }
   159  
   160  // Value implements driver.State.Value.
   161  func (s *state) Value() (interface{}, error) {
   162  	return s.val, s.err
   163  }
   164  
   165  // UpdateTime implements driver.State.UpdateTime.
   166  func (s *state) UpdateTime() time.Time {
   167  	return s.updateTime
   168  }
   169  
   170  // As implements driver.State.As.
   171  func (s *state) As(i interface{}) bool {
   172  	return false
   173  }
   174  
   175  // errorState returns a new State with err, unless prevS also represents
   176  // the same error, in which case it returns nil.
   177  func errorState(err error, prevS driver.State) driver.State {
   178  	s := &state{err: err}
   179  	if prevS == nil {
   180  		return s
   181  	}
   182  	prev := prevS.(*state)
   183  	if prev.err == nil {
   184  		// New error.
   185  		return s
   186  	}
   187  	if err == prev.err || err.Error() == prev.err.Error() {
   188  		// Same error, return nil to indicate no change.
   189  		return nil
   190  	}
   191  	return s
   192  }
   193  
   194  // watcher implements driver.Watcher for configurations provided by the Runtime Configurator
   195  // service.
   196  type watcher struct {
   197  	bucket  *blob.Bucket
   198  	opener  *URLOpener
   199  	key     string
   200  	wait    time.Duration
   201  	decoder *runtimevar.Decoder
   202  }
   203  
   204  // WatchVariable implements driver.WatchVariable.
   205  func (w *watcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) {
   206  	// Read the blob.
   207  	b, err := w.bucket.ReadAll(ctx, w.key)
   208  	if err != nil {
   209  		return errorState(err, prev), w.wait
   210  	}
   211  	// See if it's the same raw bytes as before.
   212  	if prev != nil && bytes.Equal(b, prev.(*state).rawBytes) {
   213  		// No change!
   214  		return nil, w.wait
   215  	}
   216  
   217  	// Decode the value.
   218  	val, err := w.decoder.Decode(ctx, b)
   219  	if err != nil {
   220  		return errorState(err, prev), w.wait
   221  	}
   222  	return &state{val: val, updateTime: time.Now(), rawBytes: b}, w.wait
   223  }
   224  
   225  // Close implements driver.Close.
   226  func (w *watcher) Close() error {
   227  	return nil
   228  }
   229  
   230  // ErrorAs implements driver.ErrorAs.
   231  // Since blobvar uses the blob package, ErrorAs delegates
   232  // to the bucket's ErrorAs method.
   233  func (w *watcher) ErrorAs(err error, i interface{}) bool {
   234  	return w.bucket.ErrorAs(err, i)
   235  }
   236  
   237  // ErrorCode implements driver.ErrorCode.
   238  func (*watcher) ErrorCode(err error) gcerrors.ErrorCode {
   239  	// err might have come from blob, in which case use its code.
   240  	return gcerrors.Code(err)
   241  }