github.com/containerd/Containerd@v1.4.13/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 }