github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/rkt/config/config.go (about) 1 // Copyright 2015 The rkt 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 config 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "net/http" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 27 "github.com/coreos/rkt/common" 28 "github.com/hashicorp/errwrap" 29 ) 30 31 // Headerer is an interface for getting additional HTTP headers to use 32 // when downloading data (images, signatures). 33 type Headerer interface { 34 Header() http.Header 35 } 36 37 // BasicCredentials holds typical credentials used for authentication 38 // (user and password). Used for fetching docker images. 39 type BasicCredentials struct { 40 User string 41 Password string 42 } 43 44 // ConfigurablePaths holds various paths defined in the configuration. 45 type ConfigurablePaths struct { 46 DataDir string 47 Stage1ImagesDir string 48 } 49 50 // Stage1 holds name, version and location of a default stage1 image 51 // if it was specified in configuration. 52 type Stage1Data struct { 53 Name string 54 Version string 55 Location string 56 } 57 58 // Config is a single place where configuration for rkt frontend needs 59 // resides. 60 type Config struct { 61 AuthPerHost map[string]Headerer 62 DockerCredentialsPerRegistry map[string]BasicCredentials 63 Paths ConfigurablePaths 64 Stage1 Stage1Data 65 } 66 67 type configParser interface { 68 parse(config *Config, raw []byte) error 69 } 70 71 var ( 72 // configSubDirs is a map saying what kinds of configuration 73 // (values) are acceptable in a config subdirectory (key) 74 configSubDirs = make(map[string][]string) 75 parsersForKind = make(map[string]map[string]configParser) 76 ) 77 78 // ResolveAuthPerHost takes a map of strings to Headerer and resolves the 79 // Headerers to http.Headers 80 func ResolveAuthPerHost(authPerHost map[string]Headerer) map[string]http.Header { 81 hostHeaders := make(map[string]http.Header, len(authPerHost)) 82 for k, v := range authPerHost { 83 hostHeaders[k] = v.Header() 84 } 85 return hostHeaders 86 } 87 88 func addParser(kind, version string, parser configParser) { 89 if len(kind) == 0 { 90 panic("empty kind string when registering a config parser") 91 } 92 if len(version) == 0 { 93 panic("empty version string when registering a config parser") 94 } 95 if parser == nil { 96 panic("trying to register a nil parser") 97 } 98 if _, err := getParser(kind, version); err == nil { 99 panic(fmt.Sprintf("A parser for kind %q and version %q already exist", kind, version)) 100 } 101 if _, ok := parsersForKind[kind]; !ok { 102 parsersForKind[kind] = make(map[string]configParser) 103 } 104 parsersForKind[kind][version] = parser 105 } 106 107 func registerSubDir(dir string, kinds []string) { 108 if len(dir) == 0 { 109 panic("trying to register empty config subdirectory") 110 } 111 if len(kinds) == 0 { 112 panic("kinds array cannot be empty when registering config subdir") 113 } 114 allKinds := toArray(toSet(append(configSubDirs[dir], kinds...))) 115 sort.Strings(allKinds) 116 configSubDirs[dir] = allKinds 117 } 118 119 func toSet(a []string) map[string]struct{} { 120 s := make(map[string]struct{}) 121 for _, v := range a { 122 s[v] = struct{}{} 123 } 124 return s 125 } 126 127 func toArray(s map[string]struct{}) []string { 128 a := make([]string, 0, len(s)) 129 for k := range s { 130 a = append(a, k) 131 } 132 return a 133 } 134 135 // GetConfig gets the Config instance with configuration taken from 136 // default system path (see common.DefaultSystemConfigDir) overridden 137 // with configuration from default local path (see 138 // common.DefaultLocalConfigDir). 139 func GetConfig() (*Config, error) { 140 return GetConfigFrom(common.DefaultSystemConfigDir, common.DefaultLocalConfigDir) 141 } 142 143 // GetConfigFrom gets the Config instance with configuration taken 144 // from given paths. Subsequent paths override settings from the 145 // previous paths. 146 func GetConfigFrom(dirs ...string) (*Config, error) { 147 cfg := newConfig() 148 for _, cd := range dirs { 149 subcfg, err := GetConfigFromDir(cd) 150 if err != nil { 151 return nil, err 152 } 153 mergeConfigs(cfg, subcfg) 154 } 155 return cfg, nil 156 } 157 158 // GetConfigFromDir gets the Config instance with configuration taken 159 // from given directory. 160 func GetConfigFromDir(dir string) (*Config, error) { 161 subcfg := newConfig() 162 if valid, err := validDir(dir); err != nil { 163 return nil, err 164 } else if !valid { 165 return subcfg, nil 166 } 167 if err := readConfigDir(subcfg, dir); err != nil { 168 return nil, err 169 } 170 return subcfg, nil 171 } 172 173 func newConfig() *Config { 174 return &Config{ 175 AuthPerHost: make(map[string]Headerer), 176 DockerCredentialsPerRegistry: make(map[string]BasicCredentials), 177 Paths: ConfigurablePaths{ 178 DataDir: "", 179 }, 180 } 181 } 182 183 func readConfigDir(config *Config, dir string) error { 184 for csd, kinds := range configSubDirs { 185 d := filepath.Join(dir, csd) 186 if valid, err := validDir(d); err != nil { 187 return err 188 } else if !valid { 189 continue 190 } 191 configWalker := getConfigWalker(config, kinds, d) 192 if err := filepath.Walk(d, configWalker); err != nil { 193 return err 194 } 195 } 196 return nil 197 } 198 199 func validDir(path string) (bool, error) { 200 fi, err := os.Stat(path) 201 if err != nil { 202 if os.IsNotExist(err) { 203 return false, nil 204 } 205 return false, err 206 } 207 if !fi.IsDir() { 208 return false, fmt.Errorf("expected %q to be a directory", path) 209 } 210 return true, nil 211 } 212 213 func getConfigWalker(config *Config, kinds []string, root string) filepath.WalkFunc { 214 return func(path string, info os.FileInfo, err error) error { 215 if err != nil { 216 return err 217 } 218 if path == root { 219 return nil 220 } 221 return readFile(config, info, path, kinds) 222 } 223 } 224 225 func readFile(config *Config, info os.FileInfo, path string, kinds []string) error { 226 if valid, err := validConfigFile(info); err != nil { 227 return err 228 } else if !valid { 229 return nil 230 } 231 if err := parseConfigFile(config, path, kinds); err != nil { 232 return err 233 } 234 return nil 235 } 236 237 func validConfigFile(info os.FileInfo) (bool, error) { 238 mode := info.Mode() 239 switch { 240 case mode.IsDir(): 241 return false, filepath.SkipDir 242 case mode.IsRegular(): 243 return filepath.Ext(info.Name()) == ".json", nil 244 case mode&os.ModeSymlink == os.ModeSymlink: 245 // TODO: support symlinks? 246 return false, nil 247 default: 248 return false, nil 249 } 250 } 251 252 type configHeader struct { 253 RktVersion string `json:"rktVersion"` 254 RktKind string `json:"rktKind"` 255 } 256 257 func parseConfigFile(config *Config, path string, kinds []string) error { 258 raw, err := ioutil.ReadFile(path) 259 if err != nil { 260 return err 261 } 262 var header configHeader 263 if err := json.Unmarshal(raw, &header); err != nil { 264 return err 265 } 266 if len(header.RktKind) == 0 { 267 return fmt.Errorf("no rktKind specified in %q", path) 268 } 269 if len(header.RktVersion) == 0 { 270 return fmt.Errorf("no rktVersion specified in %q", path) 271 } 272 kindOk := false 273 for _, kind := range kinds { 274 if header.RktKind == kind { 275 kindOk = true 276 break 277 } 278 } 279 if !kindOk { 280 dir := filepath.Dir(path) 281 base := filepath.Base(path) 282 kindsStr := strings.Join(kinds, `", "`) 283 return fmt.Errorf("the configuration directory %q expects to have configuration files of kinds %q, but %q has kind of %q", dir, kindsStr, base, header.RktKind) 284 } 285 parser, err := getParser(header.RktKind, header.RktVersion) 286 if err != nil { 287 return err 288 } 289 if err := parser.parse(config, raw); err != nil { 290 return errwrap.Wrap(fmt.Errorf("failed to parse %q", path), err) 291 } 292 return nil 293 } 294 295 func getParser(kind, version string) (configParser, error) { 296 parsers, ok := parsersForKind[kind] 297 if !ok { 298 return nil, fmt.Errorf("no parser available for configuration of kind %q", kind) 299 } 300 parser, ok := parsers[version] 301 if !ok { 302 return nil, fmt.Errorf("no parser available for configuration of kind %q and version %q", kind, version) 303 } 304 return parser, nil 305 } 306 307 func mergeConfigs(config *Config, subconfig *Config) { 308 for host, headerer := range subconfig.AuthPerHost { 309 config.AuthPerHost[host] = headerer 310 } 311 for registry, creds := range subconfig.DockerCredentialsPerRegistry { 312 config.DockerCredentialsPerRegistry[registry] = creds 313 } 314 if subconfig.Paths.DataDir != "" { 315 config.Paths.DataDir = subconfig.Paths.DataDir 316 } 317 if subconfig.Paths.Stage1ImagesDir != "" { 318 config.Paths.Stage1ImagesDir = subconfig.Paths.Stage1ImagesDir 319 } 320 if subconfig.Stage1.Name != "" { 321 config.Stage1.Name = subconfig.Stage1.Name 322 config.Stage1.Version = subconfig.Stage1.Version 323 } 324 if subconfig.Stage1.Location != "" { 325 config.Stage1.Location = subconfig.Stage1.Location 326 } 327 }