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  }