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