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 }