istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/util/pluginutil.go (about) 1 // Copyright Istio 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 // http://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 util 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "os" 22 23 "github.com/fsnotify/fsnotify" 24 25 "istio.io/istio/pkg/file" 26 "istio.io/istio/pkg/log" 27 ) 28 29 type Watcher struct { 30 watcher *fsnotify.Watcher 31 Events chan struct{} 32 Errors chan error 33 } 34 35 // Waits until a file is modified (returns nil), the context is cancelled (returns context error), or returns error 36 func (w *Watcher) Wait(ctx context.Context) error { 37 select { 38 case <-w.Events: 39 return nil 40 case err := <-w.Errors: 41 return err 42 case <-ctx.Done(): 43 return ctx.Err() 44 } 45 } 46 47 func (w *Watcher) Close() { 48 _ = w.watcher.Close() 49 } 50 51 // Creates a file watcher that watches for any changes to the directory 52 func CreateFileWatcher(paths ...string) (*Watcher, error) { 53 watcher, err := fsnotify.NewWatcher() 54 if err != nil { 55 return nil, fmt.Errorf("watcher create: %v", err) 56 } 57 58 fileModified, errChan := make(chan struct{}), make(chan error) 59 go watchFiles(watcher, fileModified, errChan) 60 61 for _, path := range paths { 62 if !file.Exists(path) { 63 log.Infof("file watcher skipping watch on non-existent path: %v", path) 64 continue 65 } 66 if err := watcher.Add(path); err != nil { 67 if closeErr := watcher.Close(); closeErr != nil { 68 err = fmt.Errorf("%s: %w", closeErr.Error(), err) 69 } 70 return nil, err 71 } 72 } 73 74 return &Watcher{ 75 watcher: watcher, 76 Events: fileModified, 77 Errors: errChan, 78 }, nil 79 } 80 81 func watchFiles(watcher *fsnotify.Watcher, fileModified chan struct{}, errChan chan error) { 82 for { 83 select { 84 case event, ok := <-watcher.Events: 85 if !ok { 86 return 87 } 88 if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove) != 0 { 89 log.Infof("file modified: %v", event.Name) 90 fileModified <- struct{}{} 91 } 92 case err, ok := <-watcher.Errors: 93 if !ok { 94 return 95 } 96 errChan <- err 97 } 98 } 99 } 100 101 // Read CNI config from file and return the unmarshalled JSON as a map 102 func ReadCNIConfigMap(path string) (map[string]any, error) { 103 cniConfig, err := os.ReadFile(path) 104 if err != nil { 105 return nil, err 106 } 107 108 var cniConfigMap map[string]any 109 if err = json.Unmarshal(cniConfig, &cniConfigMap); err != nil { 110 return nil, fmt.Errorf("%s: %w", path, err) 111 } 112 113 return cniConfigMap, nil 114 } 115 116 // Given an unmarshalled CNI config JSON map, return the plugin list asserted as a []interface{} 117 func GetPlugins(cniConfigMap map[string]any) (plugins []any, err error) { 118 plugins, ok := cniConfigMap["plugins"].([]any) 119 if !ok { 120 err = fmt.Errorf("error reading plugin list from CNI config") 121 return 122 } 123 return 124 } 125 126 // Given the raw plugin interface, return the plugin asserted as a map[string]interface{} 127 func GetPlugin(rawPlugin any) (plugin map[string]any, err error) { 128 plugin, ok := rawPlugin.(map[string]any) 129 if !ok { 130 err = fmt.Errorf("error reading plugin from CNI config plugin list") 131 return 132 } 133 return 134 } 135 136 // Marshal the CNI config map and append a new line 137 func MarshalCNIConfig(cniConfigMap map[string]any) ([]byte, error) { 138 cniConfig, err := json.MarshalIndent(cniConfigMap, "", " ") 139 if err != nil { 140 return nil, err 141 } 142 cniConfig = append(cniConfig, "\n"...) 143 return cniConfig, nil 144 }