github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/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 "errors" 20 "fmt" 21 "io/ioutil" 22 "net/http" 23 "os" 24 "path/filepath" 25 "sort" 26 "strings" 27 28 "github.com/hashicorp/errwrap" 29 "github.com/rkt/rkt/common" 30 ) 31 32 // Headerer is an interface for getting additional HTTP headers to use 33 // when downloading data (images, signatures). 34 type Headerer interface { 35 GetHeader() http.Header 36 SignRequest(r *http.Request) *http.Request 37 } 38 39 // BasicCredentials holds typical credentials used for authentication 40 // (user and password). Used for fetching docker images. 41 type BasicCredentials struct { 42 User string `json:"user"` 43 Password string `json:"password"` 44 } 45 46 // ConfigurablePaths holds various paths defined in the configuration. 47 type ConfigurablePaths struct { 48 DataDir string 49 Stage1ImagesDir string 50 } 51 52 // Stage1 holds name, version and location of a default stage1 image 53 // if it was specified in configuration. 54 type Stage1Data struct { 55 Name string 56 Version string 57 Location string 58 } 59 60 // Config is a single place where configuration for rkt frontend needs 61 // resides. 62 type Config struct { 63 AuthPerHost map[string]Headerer 64 DockerCredentialsPerRegistry map[string]BasicCredentials 65 Paths ConfigurablePaths 66 Stage1 Stage1Data 67 } 68 69 // MarshalJSON marshals the config for user output. 70 func (c *Config) MarshalJSON() ([]byte, error) { 71 stage0 := []interface{}{} 72 73 for host, auth := range c.AuthPerHost { 74 var typ string 75 var credentials interface{} 76 77 switch h := auth.(type) { 78 case *basicAuthHeaderer: 79 typ = "basic" 80 credentials = h.auth 81 case *oAuthBearerTokenHeaderer: 82 typ = "oauth" 83 credentials = h.auth 84 case *awsAuthHeaderer: 85 typ = "aws" 86 credentials = h.auth 87 default: 88 return nil, errors.New("unknown headerer type") 89 } 90 91 auth := struct { 92 RktVersion string `json:"rktVersion"` 93 RktKind string `json:"rktKind"` 94 Domains []string `json:"domains"` 95 Type string `json:"type"` 96 Credentials interface{} `json:"credentials"` 97 }{ 98 RktVersion: "v1", 99 RktKind: "auth", 100 Domains: []string{host}, 101 Type: typ, 102 Credentials: credentials, 103 } 104 105 stage0 = append(stage0, auth) 106 } 107 108 for registry, credentials := range c.DockerCredentialsPerRegistry { 109 dockerAuth := struct { 110 RktVersion string `json:"rktVersion"` 111 RktKind string `json:"rktKind"` 112 Registries []string `json:"registries"` 113 Credentials BasicCredentials `json:"credentials"` 114 }{ 115 RktVersion: "v1", 116 RktKind: "dockerAuth", 117 Registries: []string{registry}, 118 Credentials: credentials, 119 } 120 121 stage0 = append(stage0, dockerAuth) 122 } 123 124 paths := struct { 125 RktVersion string `json:"rktVersion"` 126 RktKind string `json:"rktKind"` 127 Data string `json:"data"` 128 Stage1Images string `json:"stage1-images"` 129 }{ 130 RktVersion: "v1", 131 RktKind: "paths", 132 Data: c.Paths.DataDir, 133 Stage1Images: c.Paths.Stage1ImagesDir, 134 } 135 136 stage1 := struct { 137 RktVersion string `json:"rktVersion"` 138 RktKind string `json:"rktKind"` 139 Name string `json:"name"` 140 Version string `json:"version"` 141 Location string `json:"location"` 142 }{ 143 RktVersion: "v1", 144 RktKind: "stage1", 145 Name: c.Stage1.Name, 146 Version: c.Stage1.Version, 147 Location: c.Stage1.Location, 148 } 149 150 stage0 = append(stage0, paths, stage1) 151 152 data := map[string]interface{}{"stage0": stage0} 153 return json.Marshal(data) 154 } 155 156 type configParser interface { 157 parse(config *Config, raw []byte) error 158 } 159 160 var ( 161 // configSubDirs is a map saying what kinds of configuration 162 // (values) are acceptable in a config subdirectory (key) 163 configSubDirs = make(map[string][]string) 164 parsersForKind = make(map[string]map[string]configParser) 165 ) 166 167 // ResolveAuthPerHost takes a map of strings to Headerer and resolves the 168 // Headerers to http.Headers 169 func ResolveAuthPerHost(authPerHost map[string]Headerer) map[string]http.Header { 170 hostHeaders := make(map[string]http.Header, len(authPerHost)) 171 for k, v := range authPerHost { 172 hostHeaders[k] = v.GetHeader() 173 } 174 return hostHeaders 175 } 176 177 func addParser(kind, version string, parser configParser) { 178 if len(kind) == 0 { 179 panic("empty kind string when registering a config parser") 180 } 181 if len(version) == 0 { 182 panic("empty version string when registering a config parser") 183 } 184 if parser == nil { 185 panic("trying to register a nil parser") 186 } 187 if _, err := getParser(kind, version); err == nil { 188 panic(fmt.Sprintf("A parser for kind %q and version %q already exist", kind, version)) 189 } 190 if _, ok := parsersForKind[kind]; !ok { 191 parsersForKind[kind] = make(map[string]configParser) 192 } 193 parsersForKind[kind][version] = parser 194 } 195 196 func registerSubDir(dir string, kinds []string) { 197 if len(dir) == 0 { 198 panic("trying to register empty config subdirectory") 199 } 200 if len(kinds) == 0 { 201 panic("kinds array cannot be empty when registering config subdir") 202 } 203 allKinds := toArray(toSet(append(configSubDirs[dir], kinds...))) 204 sort.Strings(allKinds) 205 configSubDirs[dir] = allKinds 206 } 207 208 func toSet(a []string) map[string]struct{} { 209 s := make(map[string]struct{}) 210 for _, v := range a { 211 s[v] = struct{}{} 212 } 213 return s 214 } 215 216 func toArray(s map[string]struct{}) []string { 217 a := make([]string, 0, len(s)) 218 for k := range s { 219 a = append(a, k) 220 } 221 return a 222 } 223 224 // GetConfig gets the Config instance with configuration taken from 225 // default system path (see common.DefaultSystemConfigDir) overridden 226 // with configuration from default local path (see 227 // common.DefaultLocalConfigDir). 228 func GetConfig() (*Config, error) { 229 return GetConfigFrom(common.DefaultSystemConfigDir, common.DefaultLocalConfigDir) 230 } 231 232 // GetConfigFrom gets the Config instance with configuration taken 233 // from given paths. Subsequent paths override settings from the 234 // previous paths. 235 func GetConfigFrom(dirs ...string) (*Config, error) { 236 cfg := newConfig() 237 for _, cd := range dirs { 238 subcfg, err := GetConfigFromDir(cd) 239 if err != nil { 240 return nil, err 241 } 242 mergeConfigs(cfg, subcfg) 243 } 244 return cfg, nil 245 } 246 247 // GetConfigFromDir gets the Config instance with configuration taken 248 // from given directory. 249 func GetConfigFromDir(dir string) (*Config, error) { 250 subcfg := newConfig() 251 if valid, err := validDir(dir); err != nil { 252 return nil, err 253 } else if !valid { 254 return subcfg, nil 255 } 256 if err := readConfigDir(subcfg, dir); err != nil { 257 return nil, err 258 } 259 return subcfg, nil 260 } 261 262 func newConfig() *Config { 263 return &Config{ 264 AuthPerHost: make(map[string]Headerer), 265 DockerCredentialsPerRegistry: make(map[string]BasicCredentials), 266 Paths: ConfigurablePaths{ 267 DataDir: "", 268 }, 269 } 270 } 271 272 func readConfigDir(config *Config, dir string) error { 273 for csd, kinds := range configSubDirs { 274 d := filepath.Join(dir, csd) 275 if valid, err := validDir(d); err != nil { 276 return err 277 } else if !valid { 278 continue 279 } 280 configWalker := getConfigWalker(config, kinds, d) 281 if err := filepath.Walk(d, configWalker); err != nil { 282 return err 283 } 284 } 285 return nil 286 } 287 288 func validDir(path string) (bool, error) { 289 fi, err := os.Stat(path) 290 if err != nil { 291 if os.IsNotExist(err) { 292 return false, nil 293 } 294 return false, err 295 } 296 if !fi.IsDir() { 297 return false, fmt.Errorf("expected %q to be a directory", path) 298 } 299 return true, nil 300 } 301 302 func getConfigWalker(config *Config, kinds []string, root string) filepath.WalkFunc { 303 return func(path string, info os.FileInfo, err error) error { 304 if err != nil { 305 return err 306 } 307 if path == root { 308 return nil 309 } 310 return readFile(config, info, path, kinds) 311 } 312 } 313 314 func readFile(config *Config, info os.FileInfo, path string, kinds []string) error { 315 if valid, err := validConfigFile(info); err != nil { 316 return err 317 } else if !valid { 318 return nil 319 } 320 if err := parseConfigFile(config, path, kinds); err != nil { 321 return err 322 } 323 return nil 324 } 325 326 func validConfigFile(info os.FileInfo) (bool, error) { 327 mode := info.Mode() 328 switch { 329 case mode.IsDir(): 330 return false, filepath.SkipDir 331 case mode.IsRegular(): 332 return filepath.Ext(info.Name()) == ".json", nil 333 case mode&os.ModeSymlink == os.ModeSymlink: 334 // TODO: support symlinks? 335 return false, nil 336 default: 337 return false, nil 338 } 339 } 340 341 type configHeader struct { 342 RktVersion string `json:"rktVersion"` 343 RktKind string `json:"rktKind"` 344 } 345 346 func parseConfigFile(config *Config, path string, kinds []string) error { 347 raw, err := ioutil.ReadFile(path) 348 if err != nil { 349 return err 350 } 351 var header configHeader 352 if err := json.Unmarshal(raw, &header); err != nil { 353 return err 354 } 355 if len(header.RktKind) == 0 { 356 return fmt.Errorf("no rktKind specified in %q", path) 357 } 358 if len(header.RktVersion) == 0 { 359 return fmt.Errorf("no rktVersion specified in %q", path) 360 } 361 kindOk := false 362 for _, kind := range kinds { 363 if header.RktKind == kind { 364 kindOk = true 365 break 366 } 367 } 368 if !kindOk { 369 dir := filepath.Dir(path) 370 base := filepath.Base(path) 371 kindsStr := strings.Join(kinds, `", "`) 372 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) 373 } 374 parser, err := getParser(header.RktKind, header.RktVersion) 375 if err != nil { 376 return err 377 } 378 if err := parser.parse(config, raw); err != nil { 379 return errwrap.Wrap(fmt.Errorf("failed to parse %q", path), err) 380 } 381 return nil 382 } 383 384 func getParser(kind, version string) (configParser, error) { 385 parsers, ok := parsersForKind[kind] 386 if !ok { 387 return nil, fmt.Errorf("no parser available for configuration of kind %q", kind) 388 } 389 parser, ok := parsers[version] 390 if !ok { 391 return nil, fmt.Errorf("no parser available for configuration of kind %q and version %q", kind, version) 392 } 393 return parser, nil 394 } 395 396 func mergeConfigs(config *Config, subconfig *Config) { 397 for host, headerer := range subconfig.AuthPerHost { 398 config.AuthPerHost[host] = headerer 399 } 400 for registry, creds := range subconfig.DockerCredentialsPerRegistry { 401 config.DockerCredentialsPerRegistry[registry] = creds 402 } 403 if subconfig.Paths.DataDir != "" { 404 config.Paths.DataDir = subconfig.Paths.DataDir 405 } 406 if subconfig.Paths.Stage1ImagesDir != "" { 407 config.Paths.Stage1ImagesDir = subconfig.Paths.Stage1ImagesDir 408 } 409 if subconfig.Stage1.Name != "" { 410 config.Stage1.Name = subconfig.Stage1.Name 411 config.Stage1.Version = subconfig.Stage1.Version 412 } 413 if subconfig.Stage1.Location != "" { 414 config.Stage1.Location = subconfig.Stage1.Location 415 } 416 }