github.com/mccv1r0/cni@v0.7.0-alpha1/libcni/conf.go (about)

     1  // Copyright 2015 CNI authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package libcni
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"sort"
    24  )
    25  
    26  type NotFoundError struct {
    27  	Dir  string
    28  	Name string
    29  }
    30  
    31  func (e NotFoundError) Error() string {
    32  	return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
    33  }
    34  
    35  type NoConfigsFoundError struct {
    36  	Dir string
    37  }
    38  
    39  func (e NoConfigsFoundError) Error() string {
    40  	return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
    41  }
    42  
    43  func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
    44  	conf := &NetworkConfig{Bytes: bytes}
    45  	if err := json.Unmarshal(bytes, &conf.Network); err != nil {
    46  		return nil, fmt.Errorf("error parsing configuration: %s", err)
    47  	}
    48  	if conf.Network.Type == "" {
    49  		return nil, fmt.Errorf("error parsing configuration: missing 'type'")
    50  	}
    51  	return conf, nil
    52  }
    53  
    54  func ConfFromFile(filename string) (*NetworkConfig, error) {
    55  	bytes, err := ioutil.ReadFile(filename)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("error reading %s: %s", filename, err)
    58  	}
    59  	return ConfFromBytes(bytes)
    60  }
    61  
    62  func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
    63  	rawList := make(map[string]interface{})
    64  	if err := json.Unmarshal(bytes, &rawList); err != nil {
    65  		return nil, fmt.Errorf("error parsing configuration list: %s", err)
    66  	}
    67  
    68  	rawName, ok := rawList["name"]
    69  	if !ok {
    70  		return nil, fmt.Errorf("error parsing configuration list: no name")
    71  	}
    72  	name, ok := rawName.(string)
    73  	if !ok {
    74  		return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName)
    75  	}
    76  
    77  	var cniVersion string
    78  	rawVersion, ok := rawList["cniVersion"]
    79  	if ok {
    80  		cniVersion, ok = rawVersion.(string)
    81  		if !ok {
    82  			return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion)
    83  		}
    84  	}
    85  
    86  	list := &NetworkConfigList{
    87  		Name:       name,
    88  		CNIVersion: cniVersion,
    89  		Bytes:      bytes,
    90  	}
    91  
    92  	var plugins []interface{}
    93  	plug, ok := rawList["plugins"]
    94  	if !ok {
    95  		return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
    96  	}
    97  	plugins, ok = plug.([]interface{})
    98  	if !ok {
    99  		return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
   100  	}
   101  	if len(plugins) == 0 {
   102  		return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
   103  	}
   104  
   105  	for i, conf := range plugins {
   106  		newBytes, err := json.Marshal(conf)
   107  		if err != nil {
   108  			return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
   109  		}
   110  		netConf, err := ConfFromBytes(newBytes)
   111  		if err != nil {
   112  			return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
   113  		}
   114  		list.Plugins = append(list.Plugins, netConf)
   115  	}
   116  
   117  	return list, nil
   118  }
   119  
   120  func ConfListFromFile(filename string) (*NetworkConfigList, error) {
   121  	bytes, err := ioutil.ReadFile(filename)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("error reading %s: %s", filename, err)
   124  	}
   125  	return ConfListFromBytes(bytes)
   126  }
   127  
   128  func ConfFiles(dir string, extensions []string) ([]string, error) {
   129  	// In part, adapted from rkt/networking/podenv.go#listFiles
   130  	files, err := ioutil.ReadDir(dir)
   131  	switch {
   132  	case err == nil: // break
   133  	case os.IsNotExist(err):
   134  		return nil, nil
   135  	default:
   136  		return nil, err
   137  	}
   138  
   139  	confFiles := []string{}
   140  	for _, f := range files {
   141  		if f.IsDir() {
   142  			continue
   143  		}
   144  		fileExt := filepath.Ext(f.Name())
   145  		for _, ext := range extensions {
   146  			if fileExt == ext {
   147  				confFiles = append(confFiles, filepath.Join(dir, f.Name()))
   148  			}
   149  		}
   150  	}
   151  	return confFiles, nil
   152  }
   153  
   154  func LoadConf(dir, name string) (*NetworkConfig, error) {
   155  	files, err := ConfFiles(dir, []string{".conf", ".json"})
   156  	switch {
   157  	case err != nil:
   158  		return nil, err
   159  	case len(files) == 0:
   160  		return nil, NoConfigsFoundError{Dir: dir}
   161  	}
   162  	sort.Strings(files)
   163  
   164  	for _, confFile := range files {
   165  		conf, err := ConfFromFile(confFile)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		if conf.Network.Name == name {
   170  			return conf, nil
   171  		}
   172  	}
   173  	return nil, NotFoundError{dir, name}
   174  }
   175  
   176  func LoadConfList(dir, name string) (*NetworkConfigList, error) {
   177  	files, err := ConfFiles(dir, []string{".conflist"})
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	sort.Strings(files)
   182  
   183  	for _, confFile := range files {
   184  		conf, err := ConfListFromFile(confFile)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		if conf.Name == name {
   189  			return conf, nil
   190  		}
   191  	}
   192  
   193  	// Try and load a network configuration file (instead of list)
   194  	// from the same name, then upconvert.
   195  	singleConf, err := LoadConf(dir, name)
   196  	if err != nil {
   197  		// A little extra logic so the error makes sense
   198  		if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
   199  			// Config lists found but no config files found
   200  			return nil, NotFoundError{dir, name}
   201  		}
   202  
   203  		return nil, err
   204  	}
   205  	return ConfListFromConf(singleConf)
   206  }
   207  
   208  func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
   209  	config := make(map[string]interface{})
   210  	err := json.Unmarshal(original.Bytes, &config)
   211  	if err != nil {
   212  		return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
   213  	}
   214  
   215  	for key, value := range newValues {
   216  		if key == "" {
   217  			return nil, fmt.Errorf("keys cannot be empty")
   218  		}
   219  
   220  		if value == nil {
   221  			return nil, fmt.Errorf("key '%s' value must not be nil", key)
   222  		}
   223  
   224  		config[key] = value
   225  	}
   226  
   227  	newBytes, err := json.Marshal(config)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	return ConfFromBytes(newBytes)
   233  }
   234  
   235  // ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
   236  // with the single network as the only entry in the list.
   237  func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
   238  	// Re-deserialize the config's json, then make a raw map configlist.
   239  	// This may seem a bit strange, but it's to make the Bytes fields
   240  	// actually make sense. Otherwise, the generated json is littered with
   241  	// golang default values.
   242  
   243  	rawConfig := make(map[string]interface{})
   244  	if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	rawConfigList := map[string]interface{}{
   249  		"name":       original.Network.Name,
   250  		"cniVersion": original.Network.CNIVersion,
   251  		"plugins":    []interface{}{rawConfig},
   252  	}
   253  
   254  	b, err := json.Marshal(rawConfigList)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	return ConfListFromBytes(b)
   259  }