github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/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  //   - wait: The poll interval, in time.ParseDuration formats.
    91  //     Defaults to 30s.
    92  type URLOpener struct {
    93  	// Bucket is required.
    94  	Bucket *blob.Bucket
    95  
    96  	// Decoder specifies the decoder to use if one is not specified in the URL.
    97  	// Defaults to runtimevar.BytesDecoder.
    98  	Decoder *runtimevar.Decoder
    99  
   100  	// Options specifies the Options for OpenVariable.
   101  	Options Options
   102  }
   103  
   104  // OpenVariableURL opens the variable at the URL's path. See the package doc
   105  // for more details.
   106  func (o *URLOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) {
   107  	q := u.Query()
   108  
   109  	if o.Bucket == nil {
   110  		return nil, fmt.Errorf("open variable %v: bucket is required", u)
   111  	}
   112  
   113  	decoderName := q.Get("decoder")
   114  	q.Del("decoder")
   115  	decoder, err := runtimevar.DecoderByName(ctx, decoderName, o.Decoder)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("open variable %v: invalid decoder: %v", u, err)
   118  	}
   119  
   120  	opts := o.Options
   121  	if s := q.Get("wait"); s != "" {
   122  		q.Del("wait")
   123  		d, err := time.ParseDuration(s)
   124  		if err != nil {
   125  			return nil, fmt.Errorf("open variable %v: invalid wait %q: %v", u, s, err)
   126  		}
   127  		opts.WaitDuration = d
   128  	}
   129  	for param := range q {
   130  		return nil, fmt.Errorf("open variable %v: invalid query parameter %q", u, param)
   131  	}
   132  	return OpenVariable(o.Bucket, path.Join(u.Host, u.Path), decoder, &opts)
   133  }
   134  
   135  // Options sets options.
   136  type Options struct {
   137  	// WaitDuration controls the rate at which the blob is polled.
   138  	// Defaults to 30 seconds.
   139  	WaitDuration time.Duration
   140  }
   141  
   142  // OpenVariable constructs a *runtimevar.Variable backed by the referenced blob.
   143  // Reads of the blob return raw bytes; provide a decoder to decode the raw bytes
   144  // into the appropriate type for runtimevar.Snapshot.Value.
   145  // See the runtimevar package documentation for examples of decoders.
   146  func OpenVariable(bucket *blob.Bucket, key string, decoder *runtimevar.Decoder, opts *Options) (*runtimevar.Variable, error) {
   147  	return runtimevar.New(newWatcher(bucket, key, decoder, nil, opts)), nil
   148  }
   149  
   150  func newWatcher(bucket *blob.Bucket, key string, decoder *runtimevar.Decoder, opener *URLOpener, opts *Options) driver.Watcher {
   151  	if opts == nil {
   152  		opts = &Options{}
   153  	}
   154  	return &watcher{
   155  		bucket:  bucket,
   156  		opener:  opener,
   157  		key:     key,
   158  		wait:    driver.WaitDuration(opts.WaitDuration),
   159  		decoder: decoder,
   160  	}
   161  }
   162  
   163  // state implements driver.State.
   164  type state struct {
   165  	val        interface{}
   166  	updateTime time.Time
   167  	rawBytes   []byte
   168  	err        error
   169  }
   170  
   171  // Value implements driver.State.Value.
   172  func (s *state) Value() (interface{}, error) {
   173  	return s.val, s.err
   174  }
   175  
   176  // UpdateTime implements driver.State.UpdateTime.
   177  func (s *state) UpdateTime() time.Time {
   178  	return s.updateTime
   179  }
   180  
   181  // As implements driver.State.As.
   182  func (s *state) As(i interface{}) bool {
   183  	return false
   184  }
   185  
   186  // errorState returns a new State with err, unless prevS also represents
   187  // the same error, in which case it returns nil.
   188  func errorState(err error, prevS driver.State) driver.State {
   189  	s := &state{err: err}
   190  	if prevS == nil {
   191  		return s
   192  	}
   193  	prev := prevS.(*state)
   194  	if prev.err == nil {
   195  		// New error.
   196  		return s
   197  	}
   198  	if err == prev.err || err.Error() == prev.err.Error() {
   199  		// Same error, return nil to indicate no change.
   200  		return nil
   201  	}
   202  	return s
   203  }
   204  
   205  // watcher implements driver.Watcher for configurations provided by the Runtime Configurator
   206  // service.
   207  type watcher struct {
   208  	bucket  *blob.Bucket
   209  	opener  *URLOpener
   210  	key     string
   211  	wait    time.Duration
   212  	decoder *runtimevar.Decoder
   213  }
   214  
   215  // WatchVariable implements driver.WatchVariable.
   216  func (w *watcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) {
   217  	// Read the blob.
   218  	b, err := w.bucket.ReadAll(ctx, w.key)
   219  	if err != nil {
   220  		return errorState(err, prev), w.wait
   221  	}
   222  	// See if it's the same raw bytes as before.
   223  	if prev != nil && bytes.Equal(b, prev.(*state).rawBytes) {
   224  		// No change!
   225  		return nil, w.wait
   226  	}
   227  
   228  	// Decode the value.
   229  	val, err := w.decoder.Decode(ctx, b)
   230  	if err != nil {
   231  		return errorState(err, prev), w.wait
   232  	}
   233  	return &state{val: val, updateTime: time.Now(), rawBytes: b}, w.wait
   234  }
   235  
   236  // Close implements driver.Close.
   237  func (w *watcher) Close() error {
   238  	return nil
   239  }
   240  
   241  // ErrorAs implements driver.ErrorAs.
   242  // Since blobvar uses the blob package, ErrorAs delegates
   243  // to the bucket's ErrorAs method.
   244  func (w *watcher) ErrorAs(err error, i interface{}) bool {
   245  	return w.bucket.ErrorAs(err, i)
   246  }
   247  
   248  // ErrorCode implements driver.ErrorCode.
   249  func (*watcher) ErrorCode(err error) gcerrors.ErrorCode {
   250  	// err might have come from blob, in which case use its code.
   251  	return gcerrors.Code(err)
   252  }