@@ -17,12 +17,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Config provides containerd configuration data for the server
|
||||
@@ -59,16 +62,14 @@ type Config struct {
|
||||
ProxyPlugins map[string]ProxyPlugin `toml:"proxy_plugins"`
|
||||
// Timeouts specified as a duration
|
||||
Timeouts map[string]string `toml:"timeouts"`
|
||||
// Imports are additional file path list to config files that can overwrite main config file fields
|
||||
Imports []string `toml:"imports"`
|
||||
|
||||
StreamProcessors []StreamProcessor `toml:"stream_processors"`
|
||||
|
||||
md toml.MetaData
|
||||
StreamProcessors map[string]StreamProcessor `toml:"stream_processors"`
|
||||
}
|
||||
|
||||
// StreamProcessor provides configuration for diff content processors
|
||||
type StreamProcessor struct {
|
||||
// ID of the processor, also used to fetch the specific payload
|
||||
ID string `toml:"id"`
|
||||
// Accepts specific media-types
|
||||
Accepts []string `toml:"accepts"`
|
||||
// Returns the media-type
|
||||
@@ -202,23 +203,125 @@ func (c *Config) Decode(p *plugin.Registration) (interface{}, error) {
|
||||
if !ok {
|
||||
return p.Config, nil
|
||||
}
|
||||
if err := c.md.PrimitiveDecode(data, p.Config); err != nil {
|
||||
if err := toml.PrimitiveDecode(data, p.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Config, nil
|
||||
}
|
||||
|
||||
// LoadConfig loads the containerd server config from the provided path
|
||||
func LoadConfig(path string, v *Config) error {
|
||||
if v == nil {
|
||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "argument v must not be nil")
|
||||
func LoadConfig(path string, out *Config) error {
|
||||
if out == nil {
|
||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "argument out must not be nil")
|
||||
}
|
||||
md, err := toml.DecodeFile(path, v)
|
||||
|
||||
var (
|
||||
loaded = map[string]bool{}
|
||||
pending = []string{path}
|
||||
)
|
||||
|
||||
for len(pending) > 0 {
|
||||
path, pending = pending[0], pending[1:]
|
||||
|
||||
// Check if a file at the given path already loaded to prevent circular imports
|
||||
if _, ok := loaded[path]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
config, err := loadConfigFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mergeConfig(out, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imports, err := resolveImports(path, config.Imports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loaded[path] = true
|
||||
pending = append(pending, imports...)
|
||||
}
|
||||
|
||||
// Fix up the list of config files loaded
|
||||
out.Imports = []string{}
|
||||
for path := range loaded {
|
||||
out.Imports = append(out.Imports, path)
|
||||
}
|
||||
|
||||
return out.ValidateV2()
|
||||
}
|
||||
|
||||
// loadConfigFile decodes a TOML file at the given path
|
||||
func loadConfigFile(path string) (*Config, error) {
|
||||
config := &Config{}
|
||||
_, err := toml.DecodeFile(path, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// resolveImports resolves import strings list to absolute paths list:
|
||||
// - If path contains *, glob pattern matching applied
|
||||
// - Non abs path is relative to parent config file directory
|
||||
// - Abs paths returned as is
|
||||
func resolveImports(parent string, imports []string) ([]string, error) {
|
||||
var out []string
|
||||
|
||||
for _, path := range imports {
|
||||
if strings.Contains(path, "*") {
|
||||
matches, err := filepath.Glob(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, matches...)
|
||||
} else {
|
||||
path = filepath.Clean(path)
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(filepath.Dir(parent), path)
|
||||
}
|
||||
|
||||
out = append(out, path)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// mergeConfig merges Config structs with the following rules:
|
||||
// 'to' 'from' 'result'
|
||||
// "" "value" "value"
|
||||
// "value" "" "value"
|
||||
// 1 0 1
|
||||
// 0 1 1
|
||||
// []{"1"} []{"2"} []{"1","2"}
|
||||
// []{"1"} []{} []{"1"}
|
||||
// Maps merged by keys, but values are replaced entirely.
|
||||
func mergeConfig(to, from *Config) error {
|
||||
err := mergo.Merge(to, from, mergo.WithOverride, mergo.WithAppendSlice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.md = md
|
||||
return v.ValidateV2()
|
||||
|
||||
// Replace entire sections instead of merging map's values.
|
||||
for k, v := range from.Plugins {
|
||||
to.Plugins[k] = v
|
||||
}
|
||||
|
||||
for k, v := range from.StreamProcessors {
|
||||
to.StreamProcessors[k] = v
|
||||
}
|
||||
|
||||
for k, v := range from.ProxyPlugins {
|
||||
to.ProxyPlugins[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// V1DisabledFilter matches based on ID
|
||||
|
||||
207
services/server/config/config_test.go
Normal file
207
services/server/config/config_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/containerd/containerd/plugin"
|
||||
)
|
||||
|
||||
func TestMergeConfigs(t *testing.T) {
|
||||
a := &Config{
|
||||
Version: 2,
|
||||
Root: "old_root",
|
||||
RequiredPlugins: []string{"old_plugin"},
|
||||
DisabledPlugins: []string{"old_plugin"},
|
||||
State: "old_state",
|
||||
OOMScore: 1,
|
||||
Timeouts: map[string]string{"a": "1"},
|
||||
StreamProcessors: map[string]StreamProcessor{"1": {Path: "2", Returns: "4"}, "2": {Path: "5"}},
|
||||
}
|
||||
|
||||
b := &Config{
|
||||
Root: "new_root",
|
||||
RequiredPlugins: []string{"new_plugin1", "new_plugin2"},
|
||||
OOMScore: 2,
|
||||
Timeouts: map[string]string{"b": "2"},
|
||||
StreamProcessors: map[string]StreamProcessor{"1": {Path: "3"}},
|
||||
}
|
||||
|
||||
err := mergeConfig(a, b)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, a.Version, 2)
|
||||
assert.Equal(t, a.Root, "new_root")
|
||||
assert.Equal(t, a.State, "old_state")
|
||||
assert.Equal(t, a.OOMScore, 2)
|
||||
assert.DeepEqual(t, a.RequiredPlugins, []string{"old_plugin", "new_plugin1", "new_plugin2"})
|
||||
assert.DeepEqual(t, a.DisabledPlugins, []string{"old_plugin"})
|
||||
assert.DeepEqual(t, a.Timeouts, map[string]string{"a": "1", "b": "2"})
|
||||
assert.DeepEqual(t, a.StreamProcessors, map[string]StreamProcessor{"1": {Path: "3"}, "2": {Path: "5"}})
|
||||
}
|
||||
|
||||
func TestResolveImports(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "containerd_")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
for _, filename := range []string{"config_1.toml", "config_2.toml", "test.toml"} {
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, filename), []byte(""), 0600)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
imports, err := resolveImports(filepath.Join(tempDir, "root.toml"), []string{
|
||||
filepath.Join(tempDir, "config_*.toml"), // Glob
|
||||
filepath.Join(tempDir, "./test.toml"), // Path clean up
|
||||
"current.toml", // Resolve current working dir
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.DeepEqual(t, imports, []string{
|
||||
filepath.Join(tempDir, "config_1.toml"),
|
||||
filepath.Join(tempDir, "config_2.toml"),
|
||||
filepath.Join(tempDir, "test.toml"),
|
||||
filepath.Join(tempDir, "current.toml"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSingleConfig(t *testing.T) {
|
||||
data := `
|
||||
version = 2
|
||||
root = "/var/lib/containerd"
|
||||
|
||||
[stream_processors]
|
||||
[stream_processors."io.containerd.processor.v1.pigz"]
|
||||
accepts = ["application/vnd.docker.image.rootfs.diff.tar.gzip"]
|
||||
path = "unpigz"
|
||||
`
|
||||
tempDir, err := ioutil.TempDir("", "containerd_")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
path := filepath.Join(tempDir, "config.toml")
|
||||
err = ioutil.WriteFile(path, []byte(data), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var out Config
|
||||
err = LoadConfig(path, &out)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 2, out.Version)
|
||||
assert.Equal(t, "/var/lib/containerd", out.Root)
|
||||
assert.DeepEqual(t, map[string]StreamProcessor{
|
||||
"io.containerd.processor.v1.pigz": {
|
||||
Accepts: []string{"application/vnd.docker.image.rootfs.diff.tar.gzip"},
|
||||
Path: "unpigz",
|
||||
},
|
||||
}, out.StreamProcessors)
|
||||
}
|
||||
|
||||
func TestLoadConfigWithImports(t *testing.T) {
|
||||
data1 := `
|
||||
version = 2
|
||||
root = "/var/lib/containerd"
|
||||
imports = ["data2.toml"]
|
||||
`
|
||||
|
||||
data2 := `
|
||||
disabled_plugins = ["io.containerd.v1.xyz"]
|
||||
`
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "containerd_")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, "data1.toml"), []byte(data1), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, "data2.toml"), []byte(data2), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var out Config
|
||||
err = LoadConfig(filepath.Join(tempDir, "data1.toml"), &out)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, out.Version)
|
||||
assert.Equal(t, "/var/lib/containerd", out.Root)
|
||||
assert.DeepEqual(t, []string{"io.containerd.v1.xyz"}, out.DisabledPlugins)
|
||||
}
|
||||
|
||||
func TestLoadConfigWithCircularImports(t *testing.T) {
|
||||
data1 := `
|
||||
version = 2
|
||||
root = "/var/lib/containerd"
|
||||
imports = ["data2.toml", "data1.toml"]
|
||||
`
|
||||
|
||||
data2 := `
|
||||
disabled_plugins = ["io.containerd.v1.xyz"]
|
||||
imports = ["data1.toml", "data2.toml"]
|
||||
`
|
||||
tempDir, err := ioutil.TempDir("", "containerd_")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, "data1.toml"), []byte(data1), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, "data2.toml"), []byte(data2), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var out Config
|
||||
err = LoadConfig(filepath.Join(tempDir, "data1.toml"), &out)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, out.Version)
|
||||
assert.Equal(t, "/var/lib/containerd", out.Root)
|
||||
assert.DeepEqual(t, []string{"io.containerd.v1.xyz"}, out.DisabledPlugins)
|
||||
|
||||
assert.DeepEqual(t, []string{
|
||||
filepath.Join(tempDir, "data1.toml"),
|
||||
filepath.Join(tempDir, "data2.toml"),
|
||||
}, out.Imports)
|
||||
}
|
||||
|
||||
func TestDecodePlugin(t *testing.T) {
|
||||
data := `
|
||||
version = 1
|
||||
[plugins.linux]
|
||||
shim_debug = true
|
||||
`
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "containerd_")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
path := filepath.Join(tempDir, "config.toml")
|
||||
err = ioutil.WriteFile(path, []byte(data), 0600)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var out Config
|
||||
err = LoadConfig(path, &out)
|
||||
assert.NilError(t, err)
|
||||
|
||||
pluginConfig := map[string]interface{}{}
|
||||
_, err = out.Decode(&plugin.Registration{ID: "linux", Config: &pluginConfig})
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, true, pluginConfig["shim_debug"])
|
||||
}
|
||||
@@ -89,8 +89,8 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range config.StreamProcessors {
|
||||
diff.RegisterProcessor(diff.BinaryHandler(p.ID, p.Returns, p.Accepts, p.Path, p.Args))
|
||||
for id, p := range config.StreamProcessors {
|
||||
diff.RegisterProcessor(diff.BinaryHandler(id, p.Returns, p.Accepts, p.Path, p.Args))
|
||||
}
|
||||
|
||||
serverOpts := []grpc.ServerOption{
|
||||
|
||||
Reference in New Issue
Block a user