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  }