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 }