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