github.com/webmeshproj/webmesh-cni@v0.0.27/internal/types/netconf.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 types 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "log/slog" 24 "os" 25 "path/filepath" 26 "strings" 27 "time" 28 29 "github.com/containernetworking/cni/pkg/skel" 30 cnitypes "github.com/containernetworking/cni/pkg/types" 31 meshsys "github.com/webmeshproj/webmesh/pkg/meshnet/system" 32 meshtypes "github.com/webmeshproj/webmesh/pkg/storage/types" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/client-go/rest" 35 "k8s.io/client-go/tools/clientcmd" 36 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 39 meshcniv1 "github.com/webmeshproj/webmesh-cni/api/v1" 40 ) 41 42 // NetConf is the configuration for the CNI plugin. 43 type NetConf struct { 44 // NetConf is the typed configuration for the CNI plugin. 45 cnitypes.NetConf `json:",inline"` 46 47 // Interface is the configuration for container interfaces. 48 Interface Interface `json:"interface,omitempty"` 49 // Kubernetes is the configuration for the Kubernetes API server and 50 // information about the node we are running on. 51 Kubernetes Kubernetes `json:"kubernetes,omitempty"` 52 // LogLevel is the log level for the plugin and managed interfaces. 53 LogLevel string `json:"logLevel,omitempty"` 54 // LogFile is the file to write logs to. 55 LogFile string `json:"logFile,omitempty"` 56 } 57 58 // SetDefaults sets the default values for the configuration. 59 // It returns the configuration for convenience. 60 func (n *NetConf) SetDefaults() *NetConf { 61 if n == nil { 62 n = &NetConf{} 63 } 64 n.Kubernetes.Default() 65 n.Interface.Default() 66 if n.LogLevel == "" { 67 n.LogLevel = "info" 68 } 69 return n 70 } 71 72 // DeepEqual returns whether the configuration is equal to the given configuration. 73 func (n *NetConf) DeepEqual(other *NetConf) bool { 74 if n == nil && other == nil { 75 return true 76 } 77 if n == nil || other == nil { 78 return false 79 } 80 return n.Kubernetes.DeepEqual(&other.Kubernetes) && 81 n.Interface.DeepEqual(&other.Interface) && 82 n.LogLevel == other.LogLevel 83 } 84 85 // Interface is the configuration for a single interface. 86 type Interface struct { 87 // MTU is the MTU to set on interfaces. 88 MTU int `json:"mtu,omitempty"` 89 // DisableIPv4 is whether to disable IPv4 on the interface. 90 DisableIPv4 bool `json:"disableIPv4,omitempty"` 91 // DisableIPv6 is whether to disable IPv6 on the interface. 92 DisableIPv6 bool `json:"disableIPv6,omitempty"` 93 } 94 95 // Default sets the default values for the interface configuration. 96 func (i *Interface) Default() { 97 if i.MTU <= 0 { 98 i.MTU = meshsys.DefaultMTU 99 } 100 } 101 102 // DeepEqual returns whether the interface is equal to the given interface. 103 func (i *Interface) DeepEqual(other *Interface) bool { 104 if i == nil && other == nil { 105 return true 106 } 107 if i == nil || other == nil { 108 return false 109 } 110 return i.MTU == other.MTU && 111 i.DisableIPv4 == other.DisableIPv4 && 112 i.DisableIPv6 == other.DisableIPv6 113 } 114 115 // Kubernetes is the configuration for the Kubernetes API server and 116 // information about the node we are running on. 117 type Kubernetes struct { 118 // Kubeconfig is the path to the kubeconfig file. 119 Kubeconfig string `json:"kubeconfig,omitempty"` 120 // NodeName is the name of the node we are running on. 121 NodeName string `json:"nodeName,omitempty"` 122 // K8sAPIRoot is the root URL of the Kubernetes API server. 123 K8sAPIRoot string `json:"k8sAPIRoot,omitempty"` 124 // Namespace is the namespace to use for the plugin. 125 Namespace string `json:"namespace,omitempty"` 126 } 127 128 // Default sets the default values for the Kubernetes configuration. 129 func (k *Kubernetes) Default() { 130 if k.Kubeconfig == "" { 131 k.Kubeconfig = DefaultKubeconfigPath 132 } 133 if k.Namespace == "" { 134 k.Namespace = DefaultNamespace 135 } 136 } 137 138 // DeepEqual returns whether the Kubernetes configuration is equal to the given configuration. 139 func (k *Kubernetes) DeepEqual(other *Kubernetes) bool { 140 if k == nil && other == nil { 141 return true 142 } 143 if k == nil || other == nil { 144 return false 145 } 146 return k.Kubeconfig == other.Kubeconfig && 147 k.NodeName == other.NodeName && 148 k.K8sAPIRoot == other.K8sAPIRoot && 149 k.Namespace == other.Namespace 150 } 151 152 // LoadDefaultNetConf attempts to load the configuration from the default file. 153 func LoadDefaultNetConf() (*NetConf, error) { 154 return LoadNetConfFromFile(DefaultNetConfPath) 155 } 156 157 // LoadNetConfFromFile loads the configuration from the given file. 158 func LoadNetConfFromFile(path string) (*NetConf, error) { 159 data, err := os.ReadFile(path) 160 if err != nil { 161 return nil, fmt.Errorf("failed to read config file: %w", err) 162 } 163 return DecodeNetConf(data) 164 } 165 166 // LoadConfigFromArgs loads the configuration from the given CNI arguments. 167 func LoadNetConfFromArgs(cmd *skel.CmdArgs) (*NetConf, error) { 168 return DecodeNetConf(cmd.StdinData) 169 } 170 171 // DecodeNetConf loads the configuration from the given JSON data. 172 func DecodeNetConf(data []byte) (*NetConf, error) { 173 var conf NetConf 174 err := json.Unmarshal(data, &conf) 175 if err != nil { 176 return nil, fmt.Errorf("failed to load netconf from data: %w", err) 177 } 178 return conf.SetDefaults(), nil 179 } 180 181 // NewLogger creates a new logger for the plugin. 182 func (n *NetConf) NewLogger(args *skel.CmdArgs) *slog.Logger { 183 return slog.New(slog.NewJSONHandler(n.LogWriter(), &slog.HandlerOptions{ 184 AddSource: true, 185 Level: n.SlogLevel(), 186 })). 187 With("container", n.ObjectKeyFromArgs(args)). 188 With("args", args). 189 With("config", n) 190 } 191 192 // SlogLevel returns the slog.Level for the given log level string. 193 func (n *NetConf) SlogLevel() slog.Level { 194 switch strings.ToLower(n.LogLevel) { 195 case "debug": 196 return slog.LevelDebug 197 case "info": 198 return slog.LevelInfo 199 case "warn": 200 return slog.LevelWarn 201 case "error": 202 return slog.LevelError 203 default: 204 return slog.LevelInfo 205 } 206 } 207 208 // LogWriter reteurns the io.Writer for the plugin logger. 209 func (n *NetConf) LogWriter() io.Writer { 210 switch strings.ToLower(n.LogLevel) { 211 case "silent", "off": 212 return io.Discard 213 } 214 if n.LogFile != "" { 215 err := os.MkdirAll(filepath.Dir(n.LogFile), 0755) 216 if err != nil { 217 fmt.Fprintf(os.Stderr, "failed to create log directory, falling back to stderr: %v", err) 218 return os.Stderr 219 } 220 f, err := os.OpenFile(n.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 221 if err != nil { 222 fmt.Fprintf(os.Stderr, "failed to open log file, falling back to stderr: %v", err) 223 return os.Stderr 224 } 225 return f 226 } 227 return os.Stderr 228 } 229 230 // ObjectKeyFromArgs creates a new object key for the given container ID. 231 func (n *NetConf) ObjectKeyFromArgs(args *skel.CmdArgs) client.ObjectKey { 232 return client.ObjectKey{ 233 Name: meshtypes.TruncateID(args.ContainerID), 234 Namespace: n.Kubernetes.Namespace, 235 } 236 } 237 238 // ContainerFromArgs creates a skeleton container object for the given container arguments. 239 func (n *NetConf) ContainerFromArgs(args *skel.CmdArgs) meshcniv1.PeerContainer { 240 return meshcniv1.PeerContainer{ 241 TypeMeta: metav1.TypeMeta{ 242 Kind: "PeerContainer", 243 APIVersion: meshcniv1.GroupVersion.String(), 244 }, 245 ObjectMeta: metav1.ObjectMeta{ 246 Name: meshtypes.TruncateID(args.ContainerID), 247 Namespace: n.Kubernetes.Namespace, 248 }, 249 Spec: meshcniv1.PeerContainerSpec{ 250 NodeID: meshtypes.TruncateID(args.ContainerID), 251 ContainerID: args.ContainerID, 252 Netns: args.Netns, 253 IfName: IfNameFromID(meshtypes.TruncateID(args.ContainerID)), 254 NodeName: n.Kubernetes.NodeName, 255 MTU: n.Interface.MTU, 256 DisableIPv4: n.Interface.DisableIPv4, 257 DisableIPv6: n.Interface.DisableIPv6, 258 LogLevel: n.LogLevel, 259 }, 260 } 261 } 262 263 // IfNameFromID returns a suitable interface name for the given identifier. 264 func IfNameFromID(id string) string { 265 id = strings.Replace(id, "-", "", -1) 266 id = strings.Replace(id, "_", "", -1) 267 return IfacePrefix + id[:min(9, len(id))] + "0" 268 } 269 270 // NewClient creates a new client for the Kubernetes API server. 271 func (n *NetConf) NewClient(pingTimeout time.Duration) (*Client, error) { 272 if n == nil { 273 return nil, fmt.Errorf("netconf is nil") 274 } 275 restCfg, err := n.RestConfig() 276 if err != nil { 277 err = fmt.Errorf("failed to create REST config: %w", err) 278 return nil, err 279 } 280 cli, err := NewClientForConfig(ClientConfig{ 281 RestConfig: restCfg, 282 NetConf: n, 283 }) 284 if err != nil { 285 err = fmt.Errorf("failed to create client: %w", err) 286 return nil, err 287 } 288 return cli, cli.Ping(pingTimeout) 289 } 290 291 // RestConfig returns the rest config for the Kubernetes API server. 292 func (n *NetConf) RestConfig() (*rest.Config, error) { 293 cfg, err := clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) { 294 conf, err := clientcmd.LoadFromFile(n.Kubernetes.Kubeconfig) 295 if err != nil { 296 return nil, fmt.Errorf("failed to load kubeconfig from file: %w", err) 297 } 298 return conf, nil 299 }) 300 return cfg, err 301 }