github.com/webmeshproj/webmesh-cni@v0.0.27/internal/config/provider.go (about)

     1  /*
     2  Copyright 2023 Avi Zimmerman <avi.zimmerman@gmail.com>.
     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      http://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 config
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/knadh/koanf/v2"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    31  	"k8s.io/client-go/rest"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  )
    34  
    35  // ConfigMapProvider is a provider for koanf that reads in configurations
    36  // from the given config map.
    37  type ConfigMapProvider struct {
    38  	cfg *rest.Config
    39  	obj client.ObjectKey
    40  }
    41  
    42  // NewConfigMapProvider returns a new configmap provider.
    43  func NewConfigMapProvider(cfg *rest.Config, obj client.ObjectKey) koanf.Provider {
    44  	return &ConfigMapProvider{
    45  		cfg: cfg,
    46  		obj: obj,
    47  	}
    48  }
    49  
    50  // Read returns the entire configuration as raw []bytes to be parsed.
    51  // with a Parser.
    52  func (c *ConfigMapProvider) ReadBytes() ([]byte, error) {
    53  	data, err := c.Read()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return json.Marshal(data)
    58  }
    59  
    60  // Read returns the parsed configuration as a nested map[string]interface{}.
    61  // It is important to note that the string keys should not be flat delimited
    62  // keys like `parent.child.key`, but nested like `{parent: {child: {key: 1}}}`.
    63  func (c *ConfigMapProvider) Read() (map[string]any, error) {
    64  	cm, err := c.getConfigMap()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	out := make(map[string]any)
    69  	for k, v := range cm.Data {
    70  		var val any
    71  		switch {
    72  		case v == "true":
    73  			val = true
    74  		case v == "false":
    75  			val = false
    76  		case v == "null":
    77  			val = nil
    78  		default:
    79  			// Check if its valid JSON
    80  			if err := json.Unmarshal([]byte(v), &val); err == nil {
    81  				goto Set
    82  			}
    83  			// Check if it can be parsed as a number
    84  			if n, err := strconv.Atoi(v); err == nil {
    85  				val = n
    86  				goto Set
    87  			}
    88  			// Check if it can be parsed as a duration
    89  			if d, err := time.ParseDuration(v); err == nil {
    90  				val = d
    91  				goto Set
    92  			}
    93  			val = v
    94  		}
    95  	Set:
    96  		fields := strings.Split(k, ".")
    97  		if len(fields) == 1 {
    98  			out[k] = val
    99  			continue
   100  		}
   101  		toSet := out
   102  		for i, f := range fields {
   103  			if i == len(fields)-1 {
   104  				break
   105  			}
   106  			if _, ok := toSet[f]; !ok {
   107  				toSet[f] = make(map[string]any)
   108  			}
   109  			toSet = toSet[f].(map[string]any)
   110  		}
   111  		toSet[fields[len(fields)-1]] = val
   112  	}
   113  	return out, nil
   114  }
   115  
   116  func (c *ConfigMapProvider) getConfigMap() (*corev1.ConfigMap, error) {
   117  	cli, err := c.newClient()
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	var cm corev1.ConfigMap
   122  	err = cli.Get(context.Background(), c.obj, &cm)
   123  	if err != nil {
   124  		return nil, fmt.Errorf("failed to get configmap: %w", err)
   125  	}
   126  	return &cm, nil
   127  }
   128  
   129  func (c *ConfigMapProvider) newClient() (client.Client, error) {
   130  	scheme := runtime.NewScheme()
   131  	err := clientgoscheme.AddToScheme(scheme)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("failed to add client-go scheme: %w", err)
   134  	}
   135  	cli, err := client.New(c.cfg, client.Options{
   136  		Scheme: scheme,
   137  	})
   138  	if err != nil {
   139  		return nil, fmt.Errorf("failed to create client: %w", err)
   140  	}
   141  	return client.NewNamespacedClient(cli, c.obj.Namespace), nil
   142  }