istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/watcher/configmapwatcher/configmapwatcher.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 configmapwatcher
    16  
    17  import (
    18  	"go.uber.org/atomic"
    19  	v1 "k8s.io/api/core/v1"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/fields"
    22  	"k8s.io/apimachinery/pkg/types"
    23  
    24  	"istio.io/istio/pkg/kube"
    25  	"istio.io/istio/pkg/kube/controllers"
    26  	"istio.io/istio/pkg/kube/kclient"
    27  )
    28  
    29  // Controller watches a ConfigMap and calls the given callback when the ConfigMap changes.
    30  // The ConfigMap is passed to the callback, or nil if it doesn't exist.
    31  type Controller struct {
    32  	configmaps kclient.Client[*v1.ConfigMap]
    33  	queue      controllers.Queue
    34  
    35  	configMapNamespace string
    36  	configMapName      string
    37  	callback           func(*v1.ConfigMap)
    38  
    39  	hasSynced atomic.Bool
    40  }
    41  
    42  // NewController returns a new ConfigMap watcher controller.
    43  func NewController(client kube.Client, namespace, name string, callback func(*v1.ConfigMap)) *Controller {
    44  	c := &Controller{
    45  		configMapNamespace: namespace,
    46  		configMapName:      name,
    47  		callback:           callback,
    48  	}
    49  
    50  	c.configmaps = kclient.NewFiltered[*v1.ConfigMap](client, kclient.Filter{
    51  		Namespace:     namespace,
    52  		FieldSelector: fields.OneTermEqualSelector(metav1.ObjectNameField, name).String(),
    53  	})
    54  
    55  	c.queue = controllers.NewQueue("configmap "+name, controllers.WithReconciler(c.processItem))
    56  	c.configmaps.AddEventHandler(controllers.FilteredObjectSpecHandler(c.queue.AddObject, func(o controllers.Object) bool {
    57  		// Filter out other configmaps
    58  		return o.GetName() == name && o.GetNamespace() == namespace
    59  	}))
    60  
    61  	return c
    62  }
    63  
    64  func (c *Controller) Run(stop <-chan struct{}) {
    65  	// Start informer immediately instead of with the rest. This is because we use configmapwatcher for
    66  	// single types (so its never shared), and for use cases where we need the results immediately
    67  	// during startup.
    68  	c.configmaps.Start(stop)
    69  	if !kube.WaitForCacheSync("configmap "+c.configMapName, stop, c.configmaps.HasSynced) {
    70  		return
    71  	}
    72  	c.queue.Run(stop)
    73  }
    74  
    75  // HasSynced returns whether the underlying cache has synced and the callback has been called at least once.
    76  func (c *Controller) HasSynced() bool {
    77  	return c.queue.HasSynced()
    78  }
    79  
    80  func (c *Controller) processItem(name types.NamespacedName) error {
    81  	cm := c.configmaps.Get(name.Name, name.Namespace)
    82  	c.callback(cm)
    83  
    84  	c.hasSynced.Store(true)
    85  	return nil
    86  }