github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/runtimevar.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 runtimevar provides an easy and portable way to watch runtime
    16  // configuration variables. Subpackages contain driver implementations of
    17  // runtimevar for supported services.
    18  //
    19  // See https://gocloud.dev/howto/runtimevar/ for a detailed how-to guide.
    20  //
    21  //
    22  // OpenCensus Integration
    23  //
    24  // OpenCensus supports tracing and metric collection for multiple languages and
    25  // backend providers. See https://opencensus.io.
    26  //
    27  // This API collects an OpenCensus metric "gocloud.dev/runtimevar/value_changes",
    28  // a count of the number of times all variables have changed values, by driver.
    29  //
    30  // To enable metric collection in your application, see "Exporting stats" at
    31  // https://opencensus.io/quickstart/go/metrics.
    32  package runtimevar // import "gocloud.dev/runtimevar"
    33  
    34  import (
    35  	"bytes"
    36  	"context"
    37  	"encoding/gob"
    38  	"encoding/json"
    39  	"errors"
    40  	"fmt"
    41  	"net/url"
    42  	"os"
    43  	"reflect"
    44  	"strings"
    45  	"sync"
    46  	"time"
    47  
    48  	"go.opencensus.io/stats"
    49  	"go.opencensus.io/stats/view"
    50  	"go.opencensus.io/tag"
    51  	"gocloud.dev/internal/gcerr"
    52  	"gocloud.dev/internal/oc"
    53  	"gocloud.dev/internal/openurl"
    54  	"gocloud.dev/runtimevar/driver"
    55  	"gocloud.dev/secrets"
    56  )
    57  
    58  // Snapshot contains a snapshot of a variable's value and metadata about it.
    59  // It is intended to be read-only for users.
    60  type Snapshot struct {
    61  	// Value contains the value of the variable.
    62  	// The type for Value depends on the decoder used when creating the Variable.
    63  	Value interface{}
    64  
    65  	// UpdateTime is the time when the last change was detected.
    66  	UpdateTime time.Time
    67  
    68  	asFunc func(interface{}) bool
    69  }
    70  
    71  // As converts i to driver-specific types.
    72  // See https://gocloud.dev/concepts/as/ for background information, the "As"
    73  // examples in this package for examples, and the driver package
    74  // documentation for the specific types supported for that driver.
    75  func (s *Snapshot) As(i interface{}) bool {
    76  	if s.asFunc == nil {
    77  		return false
    78  	}
    79  	return s.asFunc(i)
    80  }
    81  
    82  const pkgName = "gocloud.dev/runtimevar"
    83  
    84  var (
    85  	changeMeasure = stats.Int64(pkgName+"/value_changes", "Count of variable value changes",
    86  		stats.UnitDimensionless)
    87  	// OpenCensusViews are predefined views for OpenCensus metrics.
    88  	OpenCensusViews = []*view.View{
    89  		{
    90  			Name:        pkgName + "/value_changes",
    91  			Measure:     changeMeasure,
    92  			Description: "Count of variable value changes by driver.",
    93  			TagKeys:     []tag.Key{oc.ProviderKey},
    94  			Aggregation: view.Count(),
    95  		},
    96  	}
    97  )
    98  
    99  // Variable provides an easy and portable way to watch runtime configuration
   100  // variables. To create a Variable, use constructors found in driver subpackages.
   101  type Variable struct {
   102  	dw       driver.Watcher
   103  	provider string // for metric collection; refers to driver package name
   104  
   105  	// For cancelling the background goroutine, and noticing when it has exited.
   106  	backgroundCancel context.CancelFunc
   107  	backgroundDone   chan struct{}
   108  
   109  	// haveGoodCh is closed when we get the first good value for the variable.
   110  	haveGoodCh chan struct{}
   111  	// A reference to changed at the last time Watch was called.
   112  	// Not protected by mu because it's only referenced in Watch, which is not
   113  	// supposed to be called from multiple goroutines.
   114  	lastWatch <-chan struct{}
   115  
   116  	mu       sync.RWMutex
   117  	changed  chan struct{} // closed when changing any of the other variables and replaced with a new channel
   118  	last     Snapshot
   119  	lastErr  error
   120  	lastGood Snapshot
   121  }
   122  
   123  // New is intended for use by drivers only. Do not use in application code.
   124  var New = newVar
   125  
   126  // newVar creates a new *Variable based on a specific driver implementation.
   127  func newVar(w driver.Watcher) *Variable {
   128  	ctx, cancel := context.WithCancel(context.Background())
   129  	changed := make(chan struct{})
   130  	v := &Variable{
   131  		dw:               w,
   132  		provider:         oc.ProviderName(w),
   133  		backgroundCancel: cancel,
   134  		backgroundDone:   make(chan struct{}),
   135  		haveGoodCh:       make(chan struct{}),
   136  		changed:          changed,
   137  		lastWatch:        changed,
   138  		lastErr:          gcerr.Newf(gcerr.FailedPrecondition, nil, "no value yet"),
   139  	}
   140  	go v.background(ctx)
   141  	return v
   142  }
   143  
   144  // ErrClosed is returned from Watch when the Variable has been Closed.
   145  var ErrClosed = gcerr.Newf(gcerr.FailedPrecondition, nil, "Variable has been Closed")
   146  
   147  // Watch returns when there is a new Snapshot of the current value of the
   148  // variable.
   149  //
   150  // The first call to Watch will block while reading the variable from the
   151  // driver, and will return the resulting Snapshot or error. If an error is
   152  // returned, the returned Snapshot is a zero value and should be ignored.
   153  // Subsequent calls will block until the variable's value changes or a different
   154  // error occurs.
   155  //
   156  // Watch returns an ErrClosed error if the Variable has been closed.
   157  //
   158  // Watch should not be called on the same variable from multiple goroutines
   159  // concurrently. The typical use case is to call it in a single goroutine in a
   160  // loop.
   161  //
   162  // If the variable does not exist, Watch returns an error for which
   163  // gcerrors.Code will return gcerrors.NotFound.
   164  //
   165  // Alternatively, use Latest to retrieve the latest good value.
   166  func (c *Variable) Watch(ctx context.Context) (Snapshot, error) {
   167  	// Block until there's a change since the last Watch call, signaled
   168  	// by lastWatch being closed by the background goroutine.
   169  	var ctxErr error
   170  	select {
   171  	case <-c.lastWatch:
   172  	case <-ctx.Done():
   173  		ctxErr = ctx.Err()
   174  	}
   175  	c.mu.Lock()
   176  	defer c.mu.Unlock()
   177  	if c.lastErr == ErrClosed {
   178  		return Snapshot{}, ErrClosed
   179  	} else if ctxErr != nil {
   180  		return Snapshot{}, ctxErr
   181  	}
   182  	c.lastWatch = c.changed
   183  	return c.last, c.lastErr
   184  }
   185  
   186  func (c *Variable) background(ctx context.Context) {
   187  	var curState, prevState driver.State
   188  	var wait time.Duration
   189  	for {
   190  		select {
   191  		case <-ctx.Done():
   192  			// We're shutting down; exit the goroutine.
   193  			close(c.backgroundDone)
   194  			return
   195  		case <-time.After(wait):
   196  			// Continue.
   197  		}
   198  
   199  		curState, wait = c.dw.WatchVariable(ctx, prevState)
   200  		if curState == nil {
   201  			// No change.
   202  			continue
   203  		}
   204  
   205  		// There's something new to return!
   206  		prevState = curState
   207  		_ = stats.RecordWithTags(ctx, []tag.Mutator{tag.Upsert(oc.ProviderKey, c.provider)}, changeMeasure.M(1))
   208  		// Error from RecordWithTags is not possible.
   209  
   210  		// Updates under the lock.
   211  		c.mu.Lock()
   212  		if c.lastErr == ErrClosed {
   213  			close(c.backgroundDone)
   214  			c.mu.Unlock()
   215  			return
   216  		}
   217  		if val, err := curState.Value(); err == nil {
   218  			// We got a good value!
   219  			c.last = Snapshot{
   220  				Value:      val,
   221  				UpdateTime: curState.UpdateTime(),
   222  				asFunc:     curState.As,
   223  			}
   224  			c.lastErr = nil
   225  			c.lastGood = c.last
   226  			// Close c.haveGoodCh if it's not already closed.
   227  			select {
   228  			case <-c.haveGoodCh:
   229  			default:
   230  				close(c.haveGoodCh)
   231  			}
   232  		} else {
   233  			// We got an error value.
   234  			c.last = Snapshot{}
   235  			c.lastErr = wrapError(c.dw, err)
   236  		}
   237  		close(c.changed)
   238  		c.changed = make(chan struct{})
   239  		c.mu.Unlock()
   240  	}
   241  }
   242  
   243  func (c *Variable) haveGood() bool {
   244  	select {
   245  	case <-c.haveGoodCh:
   246  		return true
   247  	default:
   248  		return false
   249  	}
   250  }
   251  
   252  // Latest is intended to be called per request, with the request context.
   253  // It returns the latest good Snapshot of the variable value, blocking if no
   254  // good value has ever been received. If ctx is Done, it returns the latest
   255  // error indicating why no good value is available (not the ctx.Err()).
   256  // You can pass an already-Done ctx to make Latest not block.
   257  //
   258  // Latest returns ErrClosed if the Variable has been closed.
   259  func (c *Variable) Latest(ctx context.Context) (Snapshot, error) {
   260  	haveGood := c.haveGood()
   261  	if !haveGood {
   262  		select {
   263  		case <-c.haveGoodCh:
   264  			haveGood = true
   265  		case <-ctx.Done():
   266  			// We don't return ctx.Err().
   267  		}
   268  	}
   269  	c.mu.RLock()
   270  	defer c.mu.RUnlock()
   271  	if haveGood && c.lastErr != ErrClosed {
   272  		return c.lastGood, nil
   273  	}
   274  	return Snapshot{}, c.lastErr
   275  }
   276  
   277  // CheckHealth returns an error unless Latest will return a good value
   278  // without blocking.
   279  func (c *Variable) CheckHealth() error {
   280  	haveGood := c.haveGood()
   281  	c.mu.RLock()
   282  	defer c.mu.RUnlock()
   283  	if haveGood && c.lastErr != ErrClosed {
   284  		return nil
   285  	}
   286  	return c.lastErr
   287  }
   288  
   289  // Close closes the Variable. The Variable is unusable after Close returns.
   290  func (c *Variable) Close() error {
   291  	// Record that we're closing. Subsequent calls to Watch/Latest will return ErrClosed.
   292  	c.mu.Lock()
   293  	if c.lastErr == ErrClosed {
   294  		c.mu.Unlock()
   295  		return ErrClosed
   296  	}
   297  	c.last = Snapshot{}
   298  	c.lastErr = ErrClosed
   299  
   300  	// Close any remaining channels to wake up any callers that are waiting on them.
   301  	close(c.changed)
   302  	// If it's the first good value, close haveGoodCh so that Latest doesn't block.
   303  	select {
   304  	case <-c.haveGoodCh:
   305  	default:
   306  		close(c.haveGoodCh)
   307  	}
   308  	c.mu.Unlock()
   309  
   310  	// Shut down the background goroutine.
   311  	c.backgroundCancel()
   312  	<-c.backgroundDone
   313  
   314  	// Close the driver.
   315  	err := c.dw.Close()
   316  	return wrapError(c.dw, err)
   317  }
   318  
   319  func wrapError(w driver.Watcher, err error) error {
   320  	if err == nil {
   321  		return nil
   322  	}
   323  	if gcerr.DoNotWrap(err) {
   324  		return err
   325  	}
   326  	return gcerr.New(w.ErrorCode(err), err, 2, "runtimevar")
   327  }
   328  
   329  // ErrorAs converts err to driver-specific types.
   330  // ErrorAs panics if i is nil or not a pointer.
   331  // ErrorAs returns false if err == nil.
   332  // See https://gocloud.dev/concepts/as/ for background information.
   333  func (c *Variable) ErrorAs(err error, i interface{}) bool {
   334  	return gcerr.ErrorAs(err, i, c.dw.ErrorAs)
   335  }
   336  
   337  // VariableURLOpener represents types than can open Variables based on a URL.
   338  // The opener must not modify the URL argument. OpenVariableURL must be safe to
   339  // call from multiple goroutines.
   340  //
   341  // This interface is generally implemented by types in driver packages.
   342  type VariableURLOpener interface {
   343  	OpenVariableURL(ctx context.Context, u *url.URL) (*Variable, error)
   344  }
   345  
   346  // URLMux is a URL opener multiplexer. It matches the scheme of the URLs
   347  // against a set of registered schemes and calls the opener that matches the
   348  // URL's scheme.
   349  // See https://gocloud.dev/concepts/urls/ for more information.
   350  //
   351  // The zero value is a multiplexer with no registered schemes.
   352  type URLMux struct {
   353  	schemes openurl.SchemeMap
   354  }
   355  
   356  // VariableSchemes returns a sorted slice of the registered Variable schemes.
   357  func (mux *URLMux) VariableSchemes() []string { return mux.schemes.Schemes() }
   358  
   359  // ValidVariableScheme returns true iff scheme has been registered for Variables.
   360  func (mux *URLMux) ValidVariableScheme(scheme string) bool { return mux.schemes.ValidScheme(scheme) }
   361  
   362  // RegisterVariable registers the opener with the given scheme. If an opener
   363  // already exists for the scheme, RegisterVariable panics.
   364  func (mux *URLMux) RegisterVariable(scheme string, opener VariableURLOpener) {
   365  	mux.schemes.Register("runtimevar", "Variable", scheme, opener)
   366  }
   367  
   368  // OpenVariable calls OpenVariableURL with the URL parsed from urlstr.
   369  // OpenVariable is safe to call from multiple goroutines.
   370  func (mux *URLMux) OpenVariable(ctx context.Context, urlstr string) (*Variable, error) {
   371  	opener, u, err := mux.schemes.FromString("Variable", urlstr)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	return opener.(VariableURLOpener).OpenVariableURL(ctx, u)
   376  }
   377  
   378  // OpenVariableURL dispatches the URL to the opener that is registered with the
   379  // URL's scheme. OpenVariableURL is safe to call from multiple goroutines.
   380  func (mux *URLMux) OpenVariableURL(ctx context.Context, u *url.URL) (*Variable, error) {
   381  	opener, err := mux.schemes.FromURL("Variable", u)
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	return opener.(VariableURLOpener).OpenVariableURL(ctx, u)
   386  }
   387  
   388  var defaultURLMux = new(URLMux)
   389  
   390  // DefaultURLMux returns the URLMux used by OpenVariable.
   391  //
   392  // Driver packages can use this to register their VariableURLOpener on the mux.
   393  func DefaultURLMux() *URLMux {
   394  	return defaultURLMux
   395  }
   396  
   397  // OpenVariable opens the variable identified by the URL given.
   398  // See the URLOpener documentation in driver subpackages for
   399  // details on supported URL formats, and https://gocloud.dev/concepts/urls
   400  // for more information.
   401  func OpenVariable(ctx context.Context, urlstr string) (*Variable, error) {
   402  	return defaultURLMux.OpenVariable(ctx, urlstr)
   403  }
   404  
   405  // Decode is a function type for unmarshaling/decoding a slice of bytes into
   406  // an arbitrary type. Decode functions are used when creating a Decoder via
   407  // NewDecoder. This package provides common Decode functions including
   408  // GobDecode and JSONDecode.
   409  type Decode func(context.Context, []byte, interface{}) error
   410  
   411  // Decoder decodes a slice of bytes into a particular Go object.
   412  //
   413  // This package provides some common Decoders that you can use directly,
   414  // including StringDecoder and BytesDecoder. You can also NewDecoder to
   415  // construct other Decoders.
   416  type Decoder struct {
   417  	typ reflect.Type
   418  	fn  Decode
   419  }
   420  
   421  // NewDecoder returns a Decoder that uses fn to decode a slice of bytes into
   422  // an object of type obj.
   423  //
   424  // This package provides some common Decode functions, including JSONDecode
   425  // and GobDecode, which can be passed to this function to create Decoders for
   426  // JSON and gob values.
   427  func NewDecoder(obj interface{}, fn Decode) *Decoder {
   428  	return &Decoder{
   429  		typ: reflect.TypeOf(obj),
   430  		fn:  fn,
   431  	}
   432  }
   433  
   434  // Decode decodes b into a new instance of the target type.
   435  func (d *Decoder) Decode(ctx context.Context, b []byte) (interface{}, error) {
   436  	nv := reflect.New(d.typ).Interface()
   437  	if err := d.fn(ctx, b, nv); err != nil {
   438  		return nil, err
   439  	}
   440  	ptr := reflect.ValueOf(nv)
   441  	return ptr.Elem().Interface(), nil
   442  }
   443  
   444  var (
   445  	// StringDecoder decodes into strings.
   446  	StringDecoder = NewDecoder("", StringDecode)
   447  
   448  	// BytesDecoder copies the slice of bytes.
   449  	BytesDecoder = NewDecoder([]byte{}, BytesDecode)
   450  )
   451  
   452  // JSONDecode can be passed to NewDecoder when decoding JSON (https://golang.org/pkg/encoding/json/).
   453  func JSONDecode(ctx context.Context, data []byte, obj interface{}) error {
   454  	return json.Unmarshal(data, obj)
   455  }
   456  
   457  // GobDecode can be passed to NewDecoder when decoding gobs (https://golang.org/pkg/encoding/gob/).
   458  func GobDecode(ctx context.Context, data []byte, obj interface{}) error {
   459  	return gob.NewDecoder(bytes.NewBuffer(data)).Decode(obj)
   460  }
   461  
   462  // StringDecode decodes raw bytes b into a string.
   463  func StringDecode(ctx context.Context, b []byte, obj interface{}) error {
   464  	v := obj.(*string)
   465  	*v = string(b)
   466  	return nil
   467  }
   468  
   469  // BytesDecode copies the slice of bytes b into obj.
   470  func BytesDecode(ctx context.Context, b []byte, obj interface{}) error {
   471  	v := obj.(*[]byte)
   472  	*v = b[:]
   473  	return nil
   474  }
   475  
   476  // DecryptDecode returns a decode function that can be passed to NewDecoder when
   477  // decoding an encrypted message (https://godoc.org/gocloud.dev/secrets).
   478  //
   479  // post defaults to BytesDecode. An optional decoder can be passed in to do
   480  // further decode operation based on the decrypted message.
   481  func DecryptDecode(k *secrets.Keeper, post Decode) Decode {
   482  	return func(ctx context.Context, b []byte, obj interface{}) error {
   483  		decrypted, err := k.Decrypt(ctx, b)
   484  		if err != nil {
   485  			return err
   486  		}
   487  		if post == nil {
   488  			return BytesDecode(ctx, decrypted, obj)
   489  		}
   490  		return post(ctx, decrypted, obj)
   491  	}
   492  }
   493  
   494  // DecoderByName returns a *Decoder based on decoderName.
   495  //
   496  // It is intended to be used by URL openers in driver packages.
   497  //
   498  // Supported values include:
   499  //   - empty string: Returns the default from the URLOpener.Decoder, or
   500  //       BytesDecoder if URLOpener.Decoder is nil (which is true if you're
   501  //       using the default URLOpener).
   502  //   - "bytes": Returns a BytesDecoder; Snapshot.Value will be of type []byte.
   503  //   - "jsonmap": Returns a JSON decoder for a map[string]interface{};
   504  //       Snapshot.Value will be of type *map[string]interface{}.
   505  //   - "string": Returns StringDecoder; Snapshot.Value will be of type string.
   506  // It also supports using "decrypt+<decoderName>" (or "decrypt" for default
   507  // decoder) to decrypt the data before decoding. It uses the secrets package to
   508  // open a keeper by the URL string stored in a environment variable
   509  // "RUNTIMEVAR_KEEPER_URL". See https://godoc.org/gocloud.dev/secrets#OpenKeeper
   510  // for more details.
   511  func DecoderByName(ctx context.Context, decoderName string, dflt *Decoder) (*Decoder, error) {
   512  	// Open a *secrets.Keeper if the decoderName contains "decrypt".
   513  	k, decoderName, err := decryptByName(ctx, decoderName)
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  
   518  	if dflt == nil {
   519  		dflt = BytesDecoder
   520  	}
   521  	switch decoderName {
   522  	case "":
   523  		return maybeDecrypt(ctx, k, dflt), nil
   524  	case "bytes":
   525  		return maybeDecrypt(ctx, k, BytesDecoder), nil
   526  	case "jsonmap":
   527  		var m map[string]interface{}
   528  		return maybeDecrypt(ctx, k, NewDecoder(&m, JSONDecode)), nil
   529  	case "string":
   530  		return maybeDecrypt(ctx, k, StringDecoder), nil
   531  	default:
   532  		return nil, fmt.Errorf("unsupported decoder %q", decoderName)
   533  	}
   534  }
   535  
   536  // decryptByName returns a *secrets.Keeper for decryption when decoderName
   537  // contains "decrypt".
   538  func decryptByName(ctx context.Context, decoderName string) (*secrets.Keeper, string, error) {
   539  	if !strings.HasPrefix(decoderName, "decrypt") {
   540  		return nil, decoderName, nil
   541  	}
   542  	keeperURL := os.Getenv("RUNTIMEVAR_KEEPER_URL")
   543  	if keeperURL == "" {
   544  		return nil, "", errors.New("environment variable RUNTIMEVAR_KEEPER_URL needed to open a *secrets.Keeper for decryption")
   545  	}
   546  	k, err := secrets.OpenKeeper(ctx, keeperURL)
   547  	if err != nil {
   548  		return nil, "", err
   549  	}
   550  	decoderName = strings.TrimPrefix(decoderName, "decrypt")
   551  	if decoderName != "" {
   552  		decoderName = strings.TrimLeftFunc(decoderName, func(r rune) bool {
   553  			return r == ' ' || r == '+'
   554  		})
   555  	}
   556  	// The parsed value is "decrypt <decoderName>".
   557  	return k, decoderName, nil
   558  }
   559  
   560  func maybeDecrypt(ctx context.Context, k *secrets.Keeper, dec *Decoder) *Decoder {
   561  	if k == nil {
   562  		return dec
   563  	}
   564  	return NewDecoder(reflect.New(dec.typ).Elem().Interface(), DecryptDecode(k, dec.fn))
   565  }