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 }