knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/configmap/store.go (about)

     1  /*
     2  Copyright 2018 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package configmap
    18  
    19  import (
    20  	"reflect"
    21  	"sync/atomic"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  )
    25  
    26  // Logger is the interface that UntypedStore expects its logger to conform to.
    27  // UntypedStore will log when updates succeed or fail.
    28  type Logger interface {
    29  	Debugf(string, ...interface{})
    30  	Infof(string, ...interface{})
    31  	Fatalf(string, ...interface{})
    32  	Errorf(string, ...interface{})
    33  }
    34  
    35  // Constructors is a map for specifying configmap names to
    36  // their function constructors
    37  //
    38  // # The values of this map must be functions with the definition
    39  //
    40  // func(*k8s.io/api/core/v1.ConfigMap) (... , error)
    41  //
    42  // These functions can return any type along with an error
    43  type Constructors map[string]interface{}
    44  
    45  // An UntypedStore is a responsible for storing and
    46  // constructing configs from Kubernetes ConfigMaps
    47  //
    48  // WatchConfigs should be used with a configmap.Watcher
    49  // in order for this store to remain up to date
    50  type UntypedStore struct {
    51  	name   string
    52  	logger Logger
    53  
    54  	storages     map[string]*atomic.Value
    55  	constructors map[string]reflect.Value
    56  
    57  	onAfterStore []func(name string, value interface{})
    58  }
    59  
    60  // NewUntypedStore creates an UntypedStore with given name,
    61  // Logger and Constructors
    62  //
    63  // # The Logger must not be nil
    64  //
    65  // The values in the Constructors map must be functions with
    66  // the definition
    67  //
    68  // func(*k8s.io/api/core/v1.ConfigMap) (... , error)
    69  //
    70  // These functions can return any type along with an error.
    71  // If the function definition differs then NewUntypedStore
    72  // will panic.
    73  //
    74  // onAfterStore is a variadic list of callbacks to run
    75  // after the ConfigMap has been transformed (via the appropriate Constructor)
    76  // and stored. These callbacks run sequentially (in the argument order) in a
    77  // separate go-routine and are of type func(name string, value interface{})
    78  // where name is the config-map name and value is the object that has been
    79  // constructed from the config-map and stored.
    80  func NewUntypedStore(
    81  	name string,
    82  	logger Logger,
    83  	constructors Constructors,
    84  	onAfterStore ...func(name string, value interface{}),
    85  ) *UntypedStore {
    86  	store := &UntypedStore{
    87  		name:         name,
    88  		logger:       logger,
    89  		storages:     make(map[string]*atomic.Value),
    90  		constructors: make(map[string]reflect.Value),
    91  		onAfterStore: onAfterStore,
    92  	}
    93  
    94  	for configName, constructor := range constructors {
    95  		store.registerConfig(configName, constructor)
    96  	}
    97  
    98  	return store
    99  }
   100  
   101  func (s *UntypedStore) registerConfig(name string, constructor interface{}) {
   102  	if err := ValidateConstructor(constructor); err != nil {
   103  		panic(err)
   104  	}
   105  
   106  	s.storages[name] = &atomic.Value{}
   107  	s.constructors[name] = reflect.ValueOf(constructor)
   108  }
   109  
   110  // WatchConfigs uses the provided configmap.Watcher
   111  // to setup watches for the config names provided in the
   112  // Constructors map
   113  func (s *UntypedStore) WatchConfigs(w Watcher) {
   114  	for configMapName := range s.constructors {
   115  		w.Watch(configMapName, s.OnConfigChanged)
   116  	}
   117  }
   118  
   119  // UntypedLoad will return the constructed value for a given
   120  // ConfigMap name
   121  func (s *UntypedStore) UntypedLoad(name string) interface{} {
   122  	storage := s.storages[name]
   123  	return storage.Load()
   124  }
   125  
   126  // OnConfigChanged will invoke the mapped constructor against
   127  // a Kubernetes ConfigMap. If successful it will be stored.
   128  // If construction fails during the first appearance the store
   129  // will log a fatal error. If construction fails while updating
   130  // the store will log an error message.
   131  func (s *UntypedStore) OnConfigChanged(c *corev1.ConfigMap) {
   132  	name := c.ObjectMeta.Name
   133  
   134  	storage := s.storages[name]
   135  	constructor := s.constructors[name]
   136  
   137  	inputs := []reflect.Value{
   138  		reflect.ValueOf(c),
   139  	}
   140  
   141  	outputs := constructor.Call(inputs)
   142  	result := outputs[0].Interface()
   143  	errVal := outputs[1]
   144  
   145  	if !errVal.IsNil() {
   146  		err := errVal.Interface()
   147  		if storage.Load() != nil {
   148  			s.logger.Errorf("Error updating %s config %q: %q", s.name, name, err)
   149  		} else {
   150  			s.logger.Fatalf("Error initializing %s config %q: %q", s.name, name, err)
   151  		}
   152  		return
   153  	}
   154  
   155  	s.logger.Debugf("%s config %q config was added or updated: %#v", s.name, name, result)
   156  	storage.Store(result)
   157  
   158  	for _, f := range s.onAfterStore {
   159  		f(name, result)
   160  	}
   161  }