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 }