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 15package libcni 16 17import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "sort" 24) 25 26type NotFoundError struct { 27 Dir string 28 Name string 29} 30 31func (e NotFoundError) Error() string { 32 return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir) 33} 34 35type NoConfigsFoundError struct { 36 Dir string 37} 38 39func (e NoConfigsFoundError) Error() string { 40 return fmt.Sprintf(`no net configurations found in %s`, e.Dir) 41} 42 43func 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 54func 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 62func 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 disableCheck := false 87 if rawDisableCheck, ok := rawList["disableCheck"]; ok { 88 disableCheck, ok = rawDisableCheck.(bool) 89 if !ok { 90 return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck) 91 } 92 } 93 94 list := &NetworkConfigList{ 95 Name: name, 96 DisableCheck: disableCheck, 97 CNIVersion: cniVersion, 98 Bytes: bytes, 99 } 100 101 var plugins []interface{} 102 plug, ok := rawList["plugins"] 103 if !ok { 104 return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key") 105 } 106 plugins, ok = plug.([]interface{}) 107 if !ok { 108 return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug) 109 } 110 if len(plugins) == 0 { 111 return nil, fmt.Errorf("error parsing configuration list: no plugins in list") 112 } 113 114 for i, conf := range plugins { 115 newBytes, err := json.Marshal(conf) 116 if err != nil { 117 return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err) 118 } 119 netConf, err := ConfFromBytes(newBytes) 120 if err != nil { 121 return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err) 122 } 123 list.Plugins = append(list.Plugins, netConf) 124 } 125 126 return list, nil 127} 128 129func ConfListFromFile(filename string) (*NetworkConfigList, error) { 130 bytes, err := ioutil.ReadFile(filename) 131 if err != nil { 132 return nil, fmt.Errorf("error reading %s: %s", filename, err) 133 } 134 return ConfListFromBytes(bytes) 135} 136 137func ConfFiles(dir string, extensions []string) ([]string, error) { 138 // In part, adapted from rkt/networking/podenv.go#listFiles 139 files, err := ioutil.ReadDir(dir) 140 switch { 141 case err == nil: // break 142 case os.IsNotExist(err): 143 return nil, nil 144 default: 145 return nil, err 146 } 147 148 confFiles := []string{} 149 for _, f := range files { 150 if f.IsDir() { 151 continue 152 } 153 fileExt := filepath.Ext(f.Name()) 154 for _, ext := range extensions { 155 if fileExt == ext { 156 confFiles = append(confFiles, filepath.Join(dir, f.Name())) 157 } 158 } 159 } 160 return confFiles, nil 161} 162 163func LoadConf(dir, name string) (*NetworkConfig, error) { 164 files, err := ConfFiles(dir, []string{".conf", ".json"}) 165 switch { 166 case err != nil: 167 return nil, err 168 case len(files) == 0: 169 return nil, NoConfigsFoundError{Dir: dir} 170 } 171 sort.Strings(files) 172 173 for _, confFile := range files { 174 conf, err := ConfFromFile(confFile) 175 if err != nil { 176 return nil, err 177 } 178 if conf.Network.Name == name { 179 return conf, nil 180 } 181 } 182 return nil, NotFoundError{dir, name} 183} 184 185func LoadConfList(dir, name string) (*NetworkConfigList, error) { 186 files, err := ConfFiles(dir, []string{".conflist"}) 187 if err != nil { 188 return nil, err 189 } 190 sort.Strings(files) 191 192 for _, confFile := range files { 193 conf, err := ConfListFromFile(confFile) 194 if err != nil { 195 return nil, err 196 } 197 if conf.Name == name { 198 return conf, nil 199 } 200 } 201 202 // Try and load a network configuration file (instead of list) 203 // from the same name, then upconvert. 204 singleConf, err := LoadConf(dir, name) 205 if err != nil { 206 // A little extra logic so the error makes sense 207 if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok { 208 // Config lists found but no config files found 209 return nil, NotFoundError{dir, name} 210 } 211 212 return nil, err 213 } 214 return ConfListFromConf(singleConf) 215} 216 217func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) { 218 config := make(map[string]interface{}) 219 err := json.Unmarshal(original.Bytes, &config) 220 if err != nil { 221 return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) 222 } 223 224 for key, value := range newValues { 225 if key == "" { 226 return nil, fmt.Errorf("keys cannot be empty") 227 } 228 229 if value == nil { 230 return nil, fmt.Errorf("key '%s' value must not be nil", key) 231 } 232 233 config[key] = value 234 } 235 236 newBytes, err := json.Marshal(config) 237 if err != nil { 238 return nil, err 239 } 240 241 return ConfFromBytes(newBytes) 242} 243 244// ConfListFromConf "upconverts" a network config in to a NetworkConfigList, 245// with the single network as the only entry in the list. 246func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) { 247 // Re-deserialize the config's json, then make a raw map configlist. 248 // This may seem a bit strange, but it's to make the Bytes fields 249 // actually make sense. Otherwise, the generated json is littered with 250 // golang default values. 251 252 rawConfig := make(map[string]interface{}) 253 if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil { 254 return nil, err 255 } 256 257 rawConfigList := map[string]interface{}{ 258 "name": original.Network.Name, 259 "cniVersion": original.Network.CNIVersion, 260 "plugins": []interface{}{rawConfig}, 261 } 262 263 b, err := json.Marshal(rawConfigList) 264 if err != nil { 265 return nil, err 266 } 267 return ConfListFromBytes(b) 268} 269