istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/watcher.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 inject 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "time" 22 23 "github.com/fsnotify/fsnotify" 24 "github.com/hashicorp/go-multierror" 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 28 "istio.io/istio/pkg/kube" 29 "istio.io/istio/pkg/kube/watcher/configmapwatcher" 30 "istio.io/istio/pkg/log" 31 "istio.io/istio/pkg/util/istiomultierror" 32 ) 33 34 // Watcher watches for and reacts to injection config updates. 35 type Watcher interface { 36 // SetHandler sets the handler that is run when the config changes. 37 // Must call this before Run. 38 SetHandler(func(*Config, string) error) 39 40 // Run starts the Watcher. Must call this after SetHandler. 41 Run(<-chan struct{}) 42 43 // Get returns the sidecar and values configuration. 44 Get() (*Config, string, error) 45 } 46 47 var _ Watcher = &fileWatcher{} 48 49 var _ Watcher = &configMapWatcher{} 50 51 type fileWatcher struct { 52 watcher *fsnotify.Watcher 53 configFile string 54 valuesFile string 55 handler func(*Config, string) error 56 } 57 58 type configMapWatcher struct { 59 c *configmapwatcher.Controller 60 client kube.Client 61 namespace string 62 name string 63 configKey string 64 valuesKey string 65 handler func(*Config, string) error 66 } 67 68 // NewFileWatcher creates a Watcher for local config and values files. 69 func NewFileWatcher(configFile, valuesFile string) (Watcher, error) { 70 watcher, err := fsnotify.NewWatcher() 71 if err != nil { 72 return nil, err 73 } 74 // watch the parent directory of the target files so we can catch 75 // symlink updates of k8s ConfigMaps volumes. 76 watchDir, _ := filepath.Split(configFile) 77 if err := watcher.Add(watchDir); err != nil { 78 return nil, fmt.Errorf("could not watch %v: %v", watchDir, err) 79 } 80 return &fileWatcher{ 81 watcher: watcher, 82 configFile: configFile, 83 valuesFile: valuesFile, 84 }, nil 85 } 86 87 func (w *fileWatcher) Run(stop <-chan struct{}) { 88 defer w.watcher.Close() 89 var timerC <-chan time.Time 90 for { 91 select { 92 case <-timerC: 93 timerC = nil 94 sidecarConfig, valuesConfig, err := w.Get() 95 if err != nil { 96 log.Errorf("update error: %v", err) 97 break 98 } 99 if w.handler != nil { 100 if err := w.handler(sidecarConfig, valuesConfig); err != nil { 101 log.Errorf("update error: %v", err) 102 } 103 } 104 case event, ok := <-w.watcher.Events: 105 if !ok { 106 return 107 } 108 log.Debugf("Injector watch update: %+v", event) 109 // use a timer to debounce configuration updates 110 if (event.Has(fsnotify.Write) || event.Has(fsnotify.Create)) && timerC == nil { 111 timerC = time.After(watchDebounceDelay) 112 } 113 case err, ok := <-w.watcher.Errors: 114 if !ok { 115 return 116 } 117 log.Errorf("Watcher error: %v", err) 118 case <-stop: 119 return 120 } 121 } 122 } 123 124 func (w *fileWatcher) Get() (*Config, string, error) { 125 return loadConfig(w.configFile, w.valuesFile) 126 } 127 128 func (w *fileWatcher) SetHandler(handler func(*Config, string) error) { 129 w.handler = handler 130 } 131 132 // NewConfigMapWatcher creates a new Watcher for changes to the given ConfigMap. 133 func NewConfigMapWatcher(client kube.Client, namespace, name, configKey, valuesKey string) Watcher { 134 w := &configMapWatcher{ 135 client: client, 136 namespace: namespace, 137 name: name, 138 configKey: configKey, 139 valuesKey: valuesKey, 140 } 141 w.c = configmapwatcher.NewController(client, namespace, name, func(cm *v1.ConfigMap) { 142 sidecarConfig, valuesConfig, err := readConfigMap(cm, configKey, valuesKey) 143 if err != nil { 144 log.Warnf("failed to read injection config from ConfigMap: %v", err) 145 return 146 } 147 if w.handler != nil { 148 if err := w.handler(sidecarConfig, valuesConfig); err != nil { 149 log.Errorf("update error: %v", err) 150 } 151 } 152 }) 153 return w 154 } 155 156 func (w *configMapWatcher) Run(stop <-chan struct{}) { 157 w.c.Run(stop) 158 } 159 160 func (w *configMapWatcher) Get() (*Config, string, error) { 161 cms := w.client.Kube().CoreV1().ConfigMaps(w.namespace) 162 cm, err := cms.Get(context.TODO(), w.name, metav1.GetOptions{}) 163 if err != nil { 164 return nil, "", err 165 } 166 return readConfigMap(cm, w.configKey, w.valuesKey) 167 } 168 169 func (w *configMapWatcher) SetHandler(handler func(*Config, string) error) { 170 w.handler = handler 171 } 172 173 func readConfigMap(cm *v1.ConfigMap, configKey, valuesKey string) (*Config, string, error) { 174 if cm == nil { 175 return nil, "", fmt.Errorf("no ConfigMap found") 176 } 177 178 configYaml, exists := cm.Data[configKey] 179 if !exists { 180 return nil, "", fmt.Errorf("missing ConfigMap config key %q", configKey) 181 } 182 c, err := unmarshalConfig([]byte(configYaml)) 183 if err != nil { 184 return nil, "", fmt.Errorf("failed reading config: %v. YAML:\n%s", err, configYaml) 185 } 186 187 valuesConfig, exists := cm.Data[valuesKey] 188 if !exists { 189 return nil, "", fmt.Errorf("missing ConfigMap values key %q", valuesKey) 190 } 191 return c, valuesConfig, nil 192 } 193 194 // WatcherMulticast allows multiple event handlers to register for the same watcher, 195 // simplifying injector based controllers. 196 type WatcherMulticast struct { 197 handlers []func(*Config, string) error 198 impl Watcher 199 Get func() WebhookConfig 200 } 201 202 func NewMulticast(impl Watcher, getter func() WebhookConfig) *WatcherMulticast { 203 res := &WatcherMulticast{ 204 impl: impl, 205 Get: getter, 206 } 207 impl.SetHandler(func(c *Config, s string) error { 208 err := istiomultierror.New() 209 for _, h := range res.handlers { 210 err = multierror.Append(err, h(c, s)) 211 } 212 return err.ErrorOrNil() 213 }) 214 return res 215 } 216 217 // SetHandler sets the handler that is run when the config changes. 218 // Must call this before Run. 219 func (wm *WatcherMulticast) AddHandler(handler func(*Config, string) error) { 220 wm.handlers = append(wm.handlers, handler) 221 }