github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/services/server/config/config.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/BurntSushi/toml"
    24  	"github.com/imdario/mergo"
    25  	"github.com/pkg/errors"
    26  
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/plugin"
    29  )
    30  
    31  // NOTE: Any new map fields added also need to be handled in mergeConfig.
    32  
    33  // Config provides containerd configuration data for the server
    34  type Config struct {
    35  	// Version of the config file
    36  	Version int `toml:"version"`
    37  	// Root is the path to a directory where containerd will store persistent data
    38  	Root string `toml:"root"`
    39  	// State is the path to a directory where containerd will store transient data
    40  	State string `toml:"state"`
    41  	// PluginDir is the directory for dynamic plugins to be stored
    42  	PluginDir string `toml:"plugin_dir"`
    43  	// GRPC configuration settings
    44  	GRPC GRPCConfig `toml:"grpc"`
    45  	// TTRPC configuration settings
    46  	TTRPC TTRPCConfig `toml:"ttrpc"`
    47  	// Debug and profiling settings
    48  	Debug Debug `toml:"debug"`
    49  	// Metrics and monitoring settings
    50  	Metrics MetricsConfig `toml:"metrics"`
    51  	// DisabledPlugins are IDs of plugins to disable. Disabled plugins won't be
    52  	// initialized and started.
    53  	DisabledPlugins []string `toml:"disabled_plugins"`
    54  	// RequiredPlugins are IDs of required plugins. Containerd exits if any
    55  	// required plugin doesn't exist or fails to be initialized or started.
    56  	RequiredPlugins []string `toml:"required_plugins"`
    57  	// Plugins provides plugin specific configuration for the initialization of a plugin
    58  	Plugins map[string]toml.Primitive `toml:"plugins"`
    59  	// OOMScore adjust the containerd's oom score
    60  	OOMScore int `toml:"oom_score"`
    61  	// Cgroup specifies cgroup information for the containerd daemon process
    62  	Cgroup CgroupConfig `toml:"cgroup"`
    63  	// ProxyPlugins configures plugins which are communicated to over GRPC
    64  	ProxyPlugins map[string]ProxyPlugin `toml:"proxy_plugins"`
    65  	// Timeouts specified as a duration
    66  	Timeouts map[string]string `toml:"timeouts"`
    67  	// Imports are additional file path list to config files that can overwrite main config file fields
    68  	Imports []string `toml:"imports"`
    69  
    70  	StreamProcessors map[string]StreamProcessor `toml:"stream_processors"`
    71  }
    72  
    73  // StreamProcessor provides configuration for diff content processors
    74  type StreamProcessor struct {
    75  	// Accepts specific media-types
    76  	Accepts []string `toml:"accepts"`
    77  	// Returns the media-type
    78  	Returns string `toml:"returns"`
    79  	// Path or name of the binary
    80  	Path string `toml:"path"`
    81  	// Args to the binary
    82  	Args []string `toml:"args"`
    83  }
    84  
    85  // GetVersion returns the config file's version
    86  func (c *Config) GetVersion() int {
    87  	if c.Version == 0 {
    88  		return 1
    89  	}
    90  	return c.Version
    91  }
    92  
    93  // ValidateV2 validates the config for a v2 file
    94  func (c *Config) ValidateV2() error {
    95  	if c.GetVersion() != 2 {
    96  		return nil
    97  	}
    98  	for _, p := range c.DisabledPlugins {
    99  		if len(strings.Split(p, ".")) < 4 {
   100  			return errors.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
   101  		}
   102  	}
   103  	for _, p := range c.RequiredPlugins {
   104  		if len(strings.Split(p, ".")) < 4 {
   105  			return errors.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
   106  		}
   107  	}
   108  	for p := range c.Plugins {
   109  		if len(strings.Split(p, ".")) < 4 {
   110  			return errors.Errorf("invalid plugin key URI %q expect io.containerd.x.vx", p)
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // GRPCConfig provides GRPC configuration for the socket
   117  type GRPCConfig struct {
   118  	Address        string `toml:"address"`
   119  	TCPAddress     string `toml:"tcp_address"`
   120  	TCPTLSCert     string `toml:"tcp_tls_cert"`
   121  	TCPTLSKey      string `toml:"tcp_tls_key"`
   122  	UID            int    `toml:"uid"`
   123  	GID            int    `toml:"gid"`
   124  	MaxRecvMsgSize int    `toml:"max_recv_message_size"`
   125  	MaxSendMsgSize int    `toml:"max_send_message_size"`
   126  }
   127  
   128  // TTRPCConfig provides TTRPC configuration for the socket
   129  type TTRPCConfig struct {
   130  	Address string `toml:"address"`
   131  	UID     int    `toml:"uid"`
   132  	GID     int    `toml:"gid"`
   133  }
   134  
   135  // Debug provides debug configuration
   136  type Debug struct {
   137  	Address string `toml:"address"`
   138  	UID     int    `toml:"uid"`
   139  	GID     int    `toml:"gid"`
   140  	Level   string `toml:"level"`
   141  }
   142  
   143  // MetricsConfig provides metrics configuration
   144  type MetricsConfig struct {
   145  	Address       string `toml:"address"`
   146  	GRPCHistogram bool   `toml:"grpc_histogram"`
   147  }
   148  
   149  // CgroupConfig provides cgroup configuration
   150  type CgroupConfig struct {
   151  	Path string `toml:"path"`
   152  }
   153  
   154  // ProxyPlugin provides a proxy plugin configuration
   155  type ProxyPlugin struct {
   156  	Type    string `toml:"type"`
   157  	Address string `toml:"address"`
   158  }
   159  
   160  // BoltConfig defines the configuration values for the bolt plugin, which is
   161  // loaded here, rather than back registered in the metadata package.
   162  type BoltConfig struct {
   163  	// ContentSharingPolicy sets the sharing policy for content between
   164  	// namespaces.
   165  	//
   166  	// The default mode "shared" will make blobs available in all
   167  	// namespaces once it is pulled into any namespace. The blob will be pulled
   168  	// into the namespace if a writer is opened with the "Expected" digest that
   169  	// is already present in the backend.
   170  	//
   171  	// The alternative mode, "isolated" requires that clients prove they have
   172  	// access to the content by providing all of the content to the ingest
   173  	// before the blob is added to the namespace.
   174  	//
   175  	// Both modes share backing data, while "shared" will reduce total
   176  	// bandwidth across namespaces, at the cost of allowing access to any blob
   177  	// just by knowing its digest.
   178  	ContentSharingPolicy string `toml:"content_sharing_policy"`
   179  }
   180  
   181  const (
   182  	// SharingPolicyShared represents the "shared" sharing policy
   183  	SharingPolicyShared = "shared"
   184  	// SharingPolicyIsolated represents the "isolated" sharing policy
   185  	SharingPolicyIsolated = "isolated"
   186  )
   187  
   188  // Validate validates if BoltConfig is valid
   189  func (bc *BoltConfig) Validate() error {
   190  	switch bc.ContentSharingPolicy {
   191  	case SharingPolicyShared, SharingPolicyIsolated:
   192  		return nil
   193  	default:
   194  		return errors.Wrapf(errdefs.ErrInvalidArgument, "unknown policy: %s", bc.ContentSharingPolicy)
   195  	}
   196  }
   197  
   198  // Decode unmarshals a plugin specific configuration by plugin id
   199  func (c *Config) Decode(p *plugin.Registration) (interface{}, error) {
   200  	id := p.URI()
   201  	if c.GetVersion() == 1 {
   202  		id = p.ID
   203  	}
   204  	data, ok := c.Plugins[id]
   205  	if !ok {
   206  		return p.Config, nil
   207  	}
   208  	if err := toml.PrimitiveDecode(data, p.Config); err != nil {
   209  		return nil, err
   210  	}
   211  	return p.Config, nil
   212  }
   213  
   214  // LoadConfig loads the containerd server config from the provided path
   215  func LoadConfig(path string, out *Config) error {
   216  	if out == nil {
   217  		return errors.Wrapf(errdefs.ErrInvalidArgument, "argument out must not be nil")
   218  	}
   219  
   220  	var (
   221  		loaded  = map[string]bool{}
   222  		pending = []string{path}
   223  	)
   224  
   225  	for len(pending) > 0 {
   226  		path, pending = pending[0], pending[1:]
   227  
   228  		// Check if a file at the given path already loaded to prevent circular imports
   229  		if _, ok := loaded[path]; ok {
   230  			continue
   231  		}
   232  
   233  		config, err := loadConfigFile(path)
   234  		if err != nil {
   235  			return err
   236  		}
   237  
   238  		if err := mergeConfig(out, config); err != nil {
   239  			return err
   240  		}
   241  
   242  		imports, err := resolveImports(path, config.Imports)
   243  		if err != nil {
   244  			return err
   245  		}
   246  
   247  		loaded[path] = true
   248  		pending = append(pending, imports...)
   249  	}
   250  
   251  	// Fix up the list of config files loaded
   252  	out.Imports = []string{}
   253  	for path := range loaded {
   254  		out.Imports = append(out.Imports, path)
   255  	}
   256  
   257  	return out.ValidateV2()
   258  }
   259  
   260  // loadConfigFile decodes a TOML file at the given path
   261  func loadConfigFile(path string) (*Config, error) {
   262  	config := &Config{}
   263  	_, err := toml.DecodeFile(path, &config)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	return config, nil
   268  }
   269  
   270  // resolveImports resolves import strings list to absolute paths list:
   271  // - If path contains *, glob pattern matching applied
   272  // - Non abs path is relative to parent config file directory
   273  // - Abs paths returned as is
   274  func resolveImports(parent string, imports []string) ([]string, error) {
   275  	var out []string
   276  
   277  	for _, path := range imports {
   278  		if strings.Contains(path, "*") {
   279  			matches, err := filepath.Glob(path)
   280  			if err != nil {
   281  				return nil, err
   282  			}
   283  
   284  			out = append(out, matches...)
   285  		} else {
   286  			path = filepath.Clean(path)
   287  			if !filepath.IsAbs(path) {
   288  				path = filepath.Join(filepath.Dir(parent), path)
   289  			}
   290  
   291  			out = append(out, path)
   292  		}
   293  	}
   294  
   295  	return out, nil
   296  }
   297  
   298  // mergeConfig merges Config structs with the following rules:
   299  // 'to'         'from'      'result'
   300  // ""           "value"     "value"
   301  // "value"      ""          "value"
   302  // 1            0           1
   303  // 0            1           1
   304  // []{"1"}      []{"2"}     []{"1","2"}
   305  // []{"1"}      []{}        []{"1"}
   306  // Maps merged by keys, but values are replaced entirely.
   307  func mergeConfig(to, from *Config) error {
   308  	err := mergo.Merge(to, from, mergo.WithOverride, mergo.WithAppendSlice)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	// Replace entire sections instead of merging map's values.
   314  	for k, v := range from.Plugins {
   315  		to.Plugins[k] = v
   316  	}
   317  
   318  	for k, v := range from.StreamProcessors {
   319  		to.StreamProcessors[k] = v
   320  	}
   321  
   322  	for k, v := range from.ProxyPlugins {
   323  		to.ProxyPlugins[k] = v
   324  	}
   325  
   326  	for k, v := range from.Timeouts {
   327  		to.Timeouts[k] = v
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  // V1DisabledFilter matches based on ID
   334  func V1DisabledFilter(list []string) plugin.DisableFilter {
   335  	set := make(map[string]struct{}, len(list))
   336  	for _, l := range list {
   337  		set[l] = struct{}{}
   338  	}
   339  	return func(r *plugin.Registration) bool {
   340  		_, ok := set[r.ID]
   341  		return ok
   342  	}
   343  }
   344  
   345  // V2DisabledFilter matches based on URI
   346  func V2DisabledFilter(list []string) plugin.DisableFilter {
   347  	set := make(map[string]struct{}, len(list))
   348  	for _, l := range list {
   349  		set[l] = struct{}{}
   350  	}
   351  	return func(r *plugin.Registration) bool {
   352  		_, ok := set[r.URI()]
   353  		return ok
   354  	}
   355  }