knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/kvstore/kvstore_cm.go (about) 1 /* 2 Copyright 2020 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 // Simple abstraction for storing state on a k8s ConfigMap. Very very simple 18 // and uses a single entry in the ConfigMap.data for storing serialized 19 // JSON of the generic data that Load/Save uses. Handy for things like sources 20 // that need to persist some state (checkpointing for example). 21 package kvstore 22 23 import ( 24 "context" 25 "encoding/json" 26 "fmt" 27 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 v1 "k8s.io/client-go/kubernetes/typed/core/v1" 32 "knative.dev/pkg/logging" 33 ) 34 35 type configMapKVStore struct { 36 cmClient v1.ConfigMapInterface 37 name string 38 namespace string 39 data map[string]string 40 } 41 42 var _ Interface = (*configMapKVStore)(nil) 43 44 func NewConfigMapKVStore(ctx context.Context, name string, namespace string, clientset v1.CoreV1Interface) Interface { 45 return &configMapKVStore{name: name, namespace: namespace, cmClient: clientset.ConfigMaps(namespace)} 46 } 47 48 // Init initializes configMapKVStore either by loading or creating an empty one. 49 func (cs *configMapKVStore) Init(ctx context.Context) error { 50 l := logging.FromContext(ctx) 51 l.Info("Initializing configMapKVStore...") 52 53 err := cs.Load(ctx) 54 if apierrors.IsNotFound(err) { 55 l.Info("No config found, creating empty") 56 return cs.createConfigMap(ctx) 57 } 58 return err 59 } 60 61 // Load fetches the ConfigMap from k8s and unmarshals the data found 62 // in the configdatakey type as specified by value. 63 func (cs *configMapKVStore) Load(ctx context.Context) error { 64 cm, err := cs.cmClient.Get(ctx, cs.name, metav1.GetOptions{}) 65 if err != nil { 66 return err 67 } 68 cs.data = cm.Data 69 return nil 70 } 71 72 // Save takes the value given in, and marshals it into a string 73 // and saves it into the k8s ConfigMap under the configdatakey. 74 func (cs *configMapKVStore) Save(ctx context.Context) error { 75 cm, err := cs.cmClient.Get(ctx, cs.name, metav1.GetOptions{}) 76 if err != nil { 77 return err 78 } 79 cm.Data = cs.data 80 _, err = cs.cmClient.Update(ctx, cm, metav1.UpdateOptions{}) 81 return err 82 } 83 84 // Get retrieves and unmarshals the value from the map. 85 func (cs *configMapKVStore) Get(ctx context.Context, key string, value interface{}) error { 86 v, ok := cs.data[key] 87 if !ok { 88 return fmt.Errorf("key %s does not exist", key) 89 } 90 err := json.Unmarshal([]byte(v), value) 91 if err != nil { 92 return fmt.Errorf("failed to Unmarshal %q: %w", v, err) 93 } 94 return nil 95 } 96 97 // Set marshals and sets the value given under specified key. 98 func (cs *configMapKVStore) Set(ctx context.Context, key string, value interface{}) error { 99 bytes, err := json.Marshal(value) 100 if err != nil { 101 return fmt.Errorf("failed to Marshal: %w", err) 102 } 103 if cs.data == nil { 104 cs.data = map[string]string{} 105 } 106 cs.data[key] = string(bytes) 107 return nil 108 } 109 110 func (cs *configMapKVStore) createConfigMap(ctx context.Context) error { 111 cm := &corev1.ConfigMap{ 112 TypeMeta: metav1.TypeMeta{ 113 APIVersion: "v1", 114 Kind: "ConfigMap", 115 }, 116 ObjectMeta: metav1.ObjectMeta{ 117 Name: cs.name, 118 Namespace: cs.namespace, 119 }, 120 } 121 _, err := cs.cmClient.Create(ctx, cm, metav1.CreateOptions{}) 122 return err 123 }