github.com/noisysockets/noisysockets@v0.21.2-0.20240515114641-7f467e651c90/config/config.go (about)

     1  // SPDX-License-Identifier: MPL-2.0
     2  /*
     3   * Copyright (C) 2024 The Noisy Sockets Authors.
     4   *
     5   * This Source Code Form is subject to the terms of the Mozilla Public
     6   * License, v. 2.0. If a copy of the MPL was not distributed with this
     7   * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     8   */
     9  
    10  package config
    11  
    12  import (
    13  	"fmt"
    14  	"io"
    15  
    16  	"github.com/noisysockets/noisysockets/config/types"
    17  	"github.com/noisysockets/noisysockets/config/v1alpha1"
    18  	latestconfig "github.com/noisysockets/noisysockets/config/v1alpha2"
    19  	"github.com/noisysockets/noisysockets/internal/util"
    20  	"github.com/noisysockets/noisysockets/networkutil"
    21  	"gopkg.in/yaml.v3"
    22  )
    23  
    24  // FromYAML reads the given reader and returns a config object.
    25  func FromYAML(r io.Reader) (conf *latestconfig.Config, err error) {
    26  	confBytes, err := io.ReadAll(r)
    27  	if err != nil {
    28  		return nil, fmt.Errorf("failed to read config from reader: %w", err)
    29  	}
    30  
    31  	var typeMeta types.TypeMeta
    32  	if err := yaml.Unmarshal(confBytes, &typeMeta); err != nil {
    33  		return nil, fmt.Errorf("failed to unmarshal type meta from config file: %w", err)
    34  	}
    35  
    36  	var versionedConf types.Config
    37  	switch typeMeta.APIVersion {
    38  	case v1alpha1.APIVersion:
    39  		versionedConf, err = v1alpha1.GetConfigByKind(typeMeta.Kind)
    40  	case latestconfig.APIVersion:
    41  		versionedConf, err = latestconfig.GetConfigByKind(typeMeta.Kind)
    42  	default:
    43  		return nil, fmt.Errorf("unsupported api version: %s", typeMeta.APIVersion)
    44  	}
    45  	if err != nil {
    46  		return nil, fmt.Errorf("failed to get config by kind %q: %w", typeMeta.Kind, err)
    47  	}
    48  
    49  	if err := yaml.Unmarshal(confBytes, versionedConf); err != nil {
    50  		return nil, fmt.Errorf("failed to unmarshal config from config file: %w", err)
    51  	}
    52  
    53  	if versionedConf.GetAPIVersion() != latestconfig.APIVersion {
    54  		conf, err = migrate(versionedConf)
    55  		if err != nil {
    56  			return nil, fmt.Errorf("failed to migrate config: %w", err)
    57  		}
    58  	} else {
    59  		conf = versionedConf.(*latestconfig.Config)
    60  	}
    61  
    62  	// TODO: validate config?
    63  
    64  	return conf, nil
    65  }
    66  
    67  // ToYAML writes the given config object to the given writer.
    68  func ToYAML(w io.Writer, versionedConf types.Config) error {
    69  	var conf *latestconfig.Config
    70  	if versionedConf.GetAPIVersion() != latestconfig.APIVersion {
    71  		var err error
    72  		conf, err = migrate(versionedConf)
    73  		if err != nil {
    74  			return fmt.Errorf("failed to migrate config: %w", err)
    75  		}
    76  	} else {
    77  		conf = versionedConf.(*latestconfig.Config)
    78  	}
    79  
    80  	conf.PopulateTypeMeta()
    81  
    82  	if err := yaml.NewEncoder(w).Encode(conf); err != nil {
    83  		return fmt.Errorf("failed to marshal config: %w", err)
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func migrate(versionedConf types.Config) (*latestconfig.Config, error) {
    90  	switch conf := versionedConf.(type) {
    91  	case *v1alpha1.Config:
    92  		return migrateV1Alpha1ToV1Alpha2(conf)
    93  	default:
    94  		return nil, fmt.Errorf("unsupported config version: %s", versionedConf.GetAPIVersion())
    95  	}
    96  }
    97  
    98  func migrateV1Alpha1ToV1Alpha2(conf *v1alpha1.Config) (*latestconfig.Config, error) {
    99  	interfaceAddrs, err := util.ParseAddrList(conf.IPs)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("could not parse local addresses: %w", err)
   102  	}
   103  
   104  	migratedConf := &latestconfig.Config{}
   105  	migratedConf.PopulateTypeMeta()
   106  
   107  	migratedConf.Name = conf.Name
   108  	migratedConf.ListenPort = conf.ListenPort
   109  	migratedConf.PrivateKey = conf.PrivateKey
   110  	migratedConf.IPs = conf.IPs
   111  
   112  	migratedConf.Peers = make([]latestconfig.PeerConfig, len(conf.Peers))
   113  	for i, peerConf := range conf.Peers {
   114  		migratedConf.Peers[i] = latestconfig.PeerConfig{
   115  			Name:      peerConf.Name,
   116  			PublicKey: peerConf.PublicKey,
   117  			Endpoint:  peerConf.Endpoint,
   118  			IPs:       peerConf.IPs,
   119  		}
   120  	}
   121  
   122  	if conf.DNSServers != nil {
   123  		migratedConf.DNS = &latestconfig.DNSConfig{
   124  			Nameservers: conf.DNSServers,
   125  		}
   126  	}
   127  
   128  	for _, peerConf := range conf.Peers {
   129  		if peerConf.DefaultGateway {
   130  			if networkutil.HasIPv4(interfaceAddrs) {
   131  				peerConf.GatewayForCIDRs = append(peerConf.GatewayForCIDRs, "0.0.0.0/0")
   132  			}
   133  			if networkutil.HasIPv6(interfaceAddrs) {
   134  				peerConf.GatewayForCIDRs = append(peerConf.GatewayForCIDRs, "::/0")
   135  			}
   136  		}
   137  
   138  		for _, prefix := range peerConf.GatewayForCIDRs {
   139  			routeConf := latestconfig.RouteConfig{
   140  				Destination: prefix,
   141  				Via:         peerConf.PublicKey,
   142  			}
   143  
   144  			if peerConf.Name != "" {
   145  				routeConf.Via = peerConf.Name
   146  			}
   147  
   148  			migratedConf.Routes = append(migratedConf.Routes, routeConf)
   149  		}
   150  	}
   151  
   152  	return migratedConf, nil
   153  }