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  }