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  }