github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/filevar/filevar.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 filevar provides a runtimevar implementation with variables 16 // backed by the filesystem. Use OpenVariable to construct a *runtimevar.Variable. 17 // 18 // Configuration files can be updated using any commands (cp, mv) or 19 // tools/editors. This package does not guarantee read consistency since 20 // it does not have control over the writes. For example, some kinds of 21 // updates might result in filevar temporarily receiving an error or an 22 // empty value. 23 // 24 // Known Issues: 25 // 26 // * On macOS, if an empty file is copied into a configuration file, 27 // filevar will not detect the change. 28 // 29 // URLs 30 // 31 // For runtimevar.OpenVariable, filevar registers for the scheme "file". 32 // To customize the URL opener, or for more details on the URL format, 33 // see URLOpener. 34 // See https://gocloud.dev/concepts/urls/ for background information. 35 // 36 // As 37 // 38 // filevar does not support any types for As. 39 package filevar // import "gocloud.dev/runtimevar/filevar" 40 41 import ( 42 "bytes" 43 "context" 44 "errors" 45 "fmt" 46 "io/ioutil" 47 "net/url" 48 "os" 49 "path/filepath" 50 "strings" 51 "time" 52 53 "github.com/fsnotify/fsnotify" 54 "gocloud.dev/gcerrors" 55 "gocloud.dev/runtimevar" 56 "gocloud.dev/runtimevar/driver" 57 ) 58 59 func init() { 60 runtimevar.DefaultURLMux().RegisterVariable(Scheme, &URLOpener{}) 61 } 62 63 // Scheme is the URL scheme filevar registers its URLOpener under on runtimevar.DefaultMux. 64 const Scheme = "file" 65 66 // URLOpener opens filevar URLs like "file:///path/to/config.json?decoder=json". 67 // 68 // The URL's host+path is used as the path to the file to watch. 69 // If os.PathSeparator != "/", any leading "/" from the path is dropped 70 // and remaining '/' characters are converted to os.PathSeparator. 71 // 72 // The following URL parameters are supported: 73 // - decoder: The decoder to use. Defaults to URLOpener.Decoder, or 74 // runtimevar.BytesDecoder if URLOpener.Decoder is nil. 75 // See runtimevar.DecoderByName for supported values. 76 type URLOpener struct { 77 // Decoder specifies the decoder to use if one is not specified in the URL. 78 // Defaults to runtimevar.BytesDecoder. 79 Decoder *runtimevar.Decoder 80 81 // Options specifies the options to pass to OpenVariable. 82 Options Options 83 } 84 85 // OpenVariableURL opens the variable at the URL's path. See the package doc 86 // for more details. 87 func (o *URLOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) { 88 q := u.Query() 89 90 decoderName := q.Get("decoder") 91 q.Del("decoder") 92 decoder, err := runtimevar.DecoderByName(ctx, decoderName, o.Decoder) 93 if err != nil { 94 return nil, fmt.Errorf("open variable %v: invalid decoder: %v", u, err) 95 } 96 97 for param := range q { 98 return nil, fmt.Errorf("open variable %v: invalid query parameter %q", u, param) 99 } 100 path := u.Path 101 if os.PathSeparator != '/' { 102 path = strings.TrimPrefix(path, "/") 103 } 104 return OpenVariable(filepath.FromSlash(path), decoder, &o.Options) 105 } 106 107 // Options sets options. 108 type Options struct { 109 // WaitDuration controls the frequency of retries after an error. For example, 110 // if the file does not exist. Defaults to 30 seconds. 111 WaitDuration time.Duration 112 } 113 114 // OpenVariable constructs a *runtimevar.Variable backed by the file at path. 115 // The file holds raw bytes; provide a decoder to decode the raw bytes into the 116 // appropriate type for runtimevar.Snapshot.Value. 117 // See the runtimevar package documentation for examples of decoders. 118 func OpenVariable(path string, decoder *runtimevar.Decoder, opts *Options) (*runtimevar.Variable, error) { 119 w, err := newWatcher(path, decoder, opts) 120 if err != nil { 121 return nil, err 122 } 123 return runtimevar.New(w), nil 124 } 125 126 func newWatcher(path string, decoder *runtimevar.Decoder, opts *Options) (*watcher, error) { 127 if opts == nil { 128 opts = &Options{} 129 } 130 if path == "" { 131 return nil, errors.New("path is required") 132 } 133 if decoder == nil { 134 return nil, errors.New("decoder is required") 135 } 136 137 // Use absolute file path. 138 abspath, err := filepath.Abs(path) 139 if err != nil { 140 return nil, err 141 } 142 143 // Construct a fsnotify.Watcher. 144 notifier, err := fsnotify.NewWatcher() 145 if err != nil { 146 return nil, err 147 } 148 149 // Create a ctx for the background goroutine that does all of the reading. 150 // The cancel function will be used to shut it down during Close, with the 151 // result being passed back via closeCh. 152 ctx, cancel := context.WithCancel(context.Background()) 153 w := &watcher{ 154 path: abspath, 155 // See struct comments for why it's buffered. 156 ch: make(chan *state, 1), 157 closeCh: make(chan error), 158 shutdown: cancel, 159 } 160 go w.watch(ctx, notifier, abspath, decoder, driver.WaitDuration(opts.WaitDuration)) 161 return w, nil 162 } 163 164 // errNotExist wraps an underlying error in cases where the file likely doesn't 165 // exist. 166 type errNotExist struct { 167 err error 168 } 169 170 func (e *errNotExist) Error() string { 171 return e.err.Error() 172 } 173 174 // state implements driver.State. 175 type state struct { 176 val interface{} 177 updateTime time.Time 178 raw []byte 179 err error 180 } 181 182 func (s *state) Value() (interface{}, error) { 183 return s.val, s.err 184 } 185 186 func (s *state) UpdateTime() time.Time { 187 return s.updateTime 188 } 189 190 func (s *state) As(i interface{}) bool { 191 return false 192 } 193 194 // watcher implements driver.Watcher for configurations stored in files. 195 type watcher struct { 196 // The path for the file we're watching. 197 path string 198 // The background goroutine writes new *state values to ch. 199 // It is buffered so that the background goroutine can write without 200 // blocking; it always drains the buffer before writing so that the latest 201 // write is buffered. If writes could block, the background goroutine could be 202 // blocked indefinitely from reading fsnotify events. 203 ch chan *state 204 // closeCh is used to return any errors from closing the notifier 205 // back to watcher.Close. 206 closeCh chan error 207 // shutdown tells the background goroutine to exit. 208 shutdown func() 209 } 210 211 // WatchVariable implements driver.WatchVariable. 212 func (w *watcher) WatchVariable(ctx context.Context, _ driver.State) (driver.State, time.Duration) { 213 select { 214 case <-ctx.Done(): 215 return &state{err: ctx.Err()}, 0 216 case cur := <-w.ch: 217 return cur, 0 218 } 219 } 220 221 // updateState checks to see if s and prev both represent the same error. 222 // If not, it drains any previous state buffered in w.ch, then writes s to it. 223 // It always return s. 224 func (w *watcher) updateState(s, prev *state) *state { 225 if s.err != nil && prev != nil && prev.err != nil && (s.err == prev.err || s.err.Error() == prev.err.Error() || (os.IsNotExist(s.err) && os.IsNotExist(prev.err))) { 226 // s represents the same error as prev. 227 return s 228 } 229 // Drain any buffered value on ch; it is now stale. 230 select { 231 case <-w.ch: 232 default: 233 } 234 // This write can't block, since we're the only writer, ch has a buffer 235 // size of 1, and we just read anything that was buffered. 236 w.ch <- s 237 return s 238 } 239 240 // watch is run by a background goroutine. 241 // It watches file using notifier, and writes new states to w.ch. 242 // If it can't read or watch the file, it re-checks every wait. 243 // It exits when ctx is canceled, and writes any shutdown errors (or 244 // nil if there weren't any) to w.closeCh. 245 func (w *watcher) watch(ctx context.Context, notifier *fsnotify.Watcher, file string, decoder *runtimevar.Decoder, wait time.Duration) { 246 var cur *state 247 248 for { 249 // If the current state is an error, pause between attempts 250 // to avoid spin loops. In particular, this happens when the file 251 // doesn't exist. 252 if cur != nil && cur.err != nil { 253 select { 254 case <-ctx.Done(): 255 w.closeCh <- notifier.Close() 256 return 257 case <-time.After(wait): 258 } 259 } 260 261 // Add the file to the notifier to be watched. It's fine to be 262 // added multiple times, and fsnotifier is a bit flaky about when 263 // it's needed during renames, so just always try. 264 if err := notifier.Add(file); err != nil { 265 // File probably does not exist. Try again later. 266 cur = w.updateState(&state{err: &errNotExist{err}}, cur) 267 continue 268 } 269 270 // Read the file. 271 b, err := ioutil.ReadFile(file) 272 if err != nil { 273 // File probably does not exist. Try again later. 274 cur = w.updateState(&state{err: &errNotExist{err}}, cur) 275 continue 276 } 277 278 // If it's a new value, decode and return it. 279 if cur == nil || cur.err != nil || !bytes.Equal(cur.raw, b) { 280 if val, err := decoder.Decode(ctx, b); err != nil { 281 cur = w.updateState(&state{err: err}, cur) 282 } else { 283 cur = w.updateState(&state{val: val, updateTime: time.Now(), raw: b}, cur) 284 } 285 } 286 287 // Block until notifier tells us something relevant changed. 288 wait := true 289 for wait { 290 select { 291 case <-ctx.Done(): 292 w.closeCh <- notifier.Close() 293 return 294 295 case event := <-notifier.Events: 296 if event.Name != file { 297 continue 298 } 299 // Ignore if not one of the following operations. 300 if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) == 0 { 301 continue 302 } 303 wait = false 304 305 case err := <-notifier.Errors: 306 cur = w.updateState(&state{err: err}, cur) 307 } 308 } 309 } 310 } 311 312 // Close implements driver.WatchVariable. 313 func (w *watcher) Close() error { 314 // Tell the background goroutine to shut down by canceling its ctx. 315 w.shutdown() 316 // Wait for it to return the result of closing the notifier. 317 err := <-w.closeCh 318 // Cleanup our channels. 319 close(w.ch) 320 close(w.closeCh) 321 return err 322 } 323 324 // ErrorAs implements driver.ErrorAs. 325 func (w *watcher) ErrorAs(err error, i interface{}) bool { return false } 326 327 // ErrorCode implements driver.ErrorCode. 328 func (*watcher) ErrorCode(err error) gcerrors.ErrorCode { 329 if _, ok := err.(*errNotExist); ok { 330 return gcerrors.NotFound 331 } 332 return gcerrors.Unknown 333 }