From ff4843b1845e7580a832f4e978d5ad670b6e0244 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 7 Aug 2023 10:30:51 +0200 Subject: [PATCH] containers.conf: implement modules Add a new concept to containers.conf called "modules". A "module" is a containers.conf file located at a specific directory. More than one modules can be loaded in the specified order, following existing override semantics. There are three directories to load modules from: - $CONFIG_HOME/containers/containers.conf.modules - /etc/containers/containers.conf.modules - /usr/share/containers/containers.conf.modules With CONFIG_HOME pointing to $HOME/.config or, if set, $XDG_CONFIG_HOME. Absolute paths will be loaded as is, relative paths will be resolved relative to the three directories above allowing for admin configs (/etc/) to override system configs (/usr/share/) and user configs ($CONFIG_HOME) to override admin configs. Also move some functions from config.go for locality. Signed-off-by: Valentin Rothberg --- pkg/config/config.go | 94 ---------- pkg/config/modules.go | 88 +++++++++ pkg/config/modules_test.go | 169 ++++++++++++++++++ pkg/config/new.go | 158 +++++++++++++--- .../containers.conf.modules/first.conf | 2 + .../containers.conf.modules/fourth.conf | 2 + .../containers.conf.modules/second.conf | 2 + .../containers.conf.modules/sub/etc-only.conf | 2 + .../containers.conf.modules/third.conf | 2 + .../containers.conf.modules/first.conf | 2 + .../containers.conf.modules/second.conf | 2 + .../containers.conf.modules/sub/first.conf | 2 + .../containers.conf.modules/third.conf | 2 + pkg/config/testdata/modules/override.conf | 2 + .../containers.conf.modules/fifth.conf | 2 + .../containers.conf.modules/first.conf | 2 + .../containers.conf.modules/second.conf | 2 + .../containers.conf.modules/sub/first.conf | 2 + .../sub/share-only.conf | 2 + .../containers.conf.modules/third.conf | 2 + 20 files changed, 425 insertions(+), 116 deletions(-) create mode 100644 pkg/config/modules.go create mode 100644 pkg/config/modules_test.go create mode 100644 pkg/config/testdata/modules/etc/containers/containers.conf.modules/first.conf create mode 100644 pkg/config/testdata/modules/etc/containers/containers.conf.modules/fourth.conf create mode 100644 pkg/config/testdata/modules/etc/containers/containers.conf.modules/second.conf create mode 100644 pkg/config/testdata/modules/etc/containers/containers.conf.modules/sub/etc-only.conf create mode 100644 pkg/config/testdata/modules/etc/containers/containers.conf.modules/third.conf create mode 100644 pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/first.conf create mode 100644 pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/second.conf create mode 100644 pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/sub/first.conf create mode 100644 pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/third.conf create mode 100644 pkg/config/testdata/modules/override.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/fifth.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/first.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/second.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/first.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/share-only.conf create mode 100644 pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/third.conf diff --git a/pkg/config/config.go b/pkg/config/config.go index 96d3859acb..d848937da6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,12 +3,10 @@ package config import ( "errors" "fmt" - "io/fs" "os" "os/exec" "path/filepath" "runtime" - "sort" "strings" "github.com/BurntSushi/toml" @@ -707,98 +705,6 @@ func (c *EngineConfig) ImagePlatformToRuntime(os string, arch string) string { return c.OCIRuntime } -// readConfigFromFile reads the specified config file at `path` and attempts to -// unmarshal its content into a Config. The config param specifies the previous -// default config. If the path, only specifies a few fields in the Toml file -// the defaults from the config parameter will be used for all other fields. -func readConfigFromFile(path string, config *Config) error { - logrus.Tracef("Reading configuration file %q", path) - meta, err := toml.DecodeFile(path, config) - if err != nil { - return fmt.Errorf("decode configuration %v: %w", path, err) - } - keys := meta.Undecoded() - if len(keys) > 0 { - logrus.Debugf("Failed to decode the keys %q from %q.", keys, path) - } - - return nil -} - -// addConfigs will search one level in the config dirPath for config files -// If the dirPath does not exist, addConfigs will return nil -func addConfigs(dirPath string, configs []string) ([]string, error) { - newConfigs := []string{} - - err := filepath.WalkDir(dirPath, - // WalkFunc to read additional configs - func(path string, d fs.DirEntry, err error) error { - switch { - case err != nil: - // return error (could be a permission problem) - return err - case d.IsDir(): - if path != dirPath { - // make sure to not recurse into sub-directories - return filepath.SkipDir - } - // ignore directories - return nil - default: - // only add *.conf files - if strings.HasSuffix(path, ".conf") { - newConfigs = append(newConfigs, path) - } - return nil - } - }, - ) - if errors.Is(err, os.ErrNotExist) { - err = nil - } - sort.Strings(newConfigs) - return append(configs, newConfigs...), err -} - -// Returns the list of configuration files, if they exist in order of hierarchy. -// The files are read in order and each new file can/will override previous -// file settings. -func systemConfigs() (configs []string, finalErr error) { - if path := os.Getenv("CONTAINERS_CONF"); path != "" { - if _, err := os.Stat(path); err != nil { - return nil, fmt.Errorf("CONTAINERS_CONF file: %w", err) - } - return append(configs, path), nil - } - if _, err := os.Stat(DefaultContainersConfig); err == nil { - configs = append(configs, DefaultContainersConfig) - } - if _, err := os.Stat(OverrideContainersConfig); err == nil { - configs = append(configs, OverrideContainersConfig) - } - - var err error - configs, err = addConfigs(OverrideContainersConfig+".d", configs) - if err != nil { - return nil, err - } - - path, err := ifRootlessConfigPath() - if err != nil { - return nil, err - } - if path != "" { - if _, err := os.Stat(path); err == nil { - configs = append(configs, path) - } - configs, err = addConfigs(path+".d", configs) - if err != nil { - return nil, err - } - } - return configs, nil -} - // CheckCgroupsAndAdjustConfig checks if we're running rootless with the systemd // cgroup manager. In case the user session isn't available, we're switching the // cgroup manager to cgroupfs. Note, this only applies to rootless. diff --git a/pkg/config/modules.go b/pkg/config/modules.go new file mode 100644 index 0000000000..e7127e56d2 --- /dev/null +++ b/pkg/config/modules.go @@ -0,0 +1,88 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containers/storage/pkg/homedir" + "github.com/containers/storage/pkg/unshare" + "github.com/hashicorp/go-multierror" +) + +// The subdirectory for looking up containers.conf modules. +const moduleSubdir = "containers/containers.conf.modules" + +// Moving the base paths into variables allows for overriding them in units +// tests. +var ( + moduleBaseEtc = "/etc/" + moduleBaseUsr = "/usr/share" +) + +// Find the specified modules in the options. Return an error if a specific +// module cannot be located on the host. +func (o *Options) modules() ([]string, error) { + if len(o.Modules) == 0 { + return nil, nil + } + + dirs, err := moduleDirectories() + if err != nil { + return nil, err + } + + configs := make([]string, 0, len(o.Modules)) + for _, path := range o.Modules { + resolved, err := resolveModule(path, dirs) + if err != nil { + return nil, fmt.Errorf("could not resolve module %q: %w", path, err) + } + configs = append(configs, resolved) + } + + return configs, nil +} + +// Return the directories to load modules from: +// 1) XDG_CONFIG_HOME/HOME if rootless +// 2) /etc/ +// 3) /usr/share +func moduleDirectories() ([]string, error) { + modules := []string{ + filepath.Join(moduleBaseEtc, moduleSubdir), + filepath.Join(moduleBaseUsr, moduleSubdir), + } + + if !unshare.IsRootless() { + return modules, nil + } + + // Prepend the user modules dir. + configHome, err := homedir.GetConfigHome() + if err != nil { + return nil, err + } + return append([]string{filepath.Join(configHome, moduleSubdir)}, modules...), nil +} + +// Resolve the specified path to a module. +func resolveModule(path string, dirs []string) (string, error) { + if filepath.IsAbs(path) { + _, err := os.Stat(path) + return path, err + } + + // Collect all errors to avoid suppressing important errors (e.g., + // permission errors). + var multiErr error + for _, d := range dirs { + candidate := filepath.Join(d, path) + _, err := os.Stat(candidate) + if err == nil { + return candidate, nil + } + multiErr = multierror.Append(multiErr, err) + } + return "", multiErr +} diff --git a/pkg/config/modules_test.go b/pkg/config/modules_test.go new file mode 100644 index 0000000000..8927e9fc03 --- /dev/null +++ b/pkg/config/modules_test.go @@ -0,0 +1,169 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/containers/storage/pkg/unshare" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +const ( + testBaseHome = "testdata/modules/home/.config" + testBaseEtc = "testdata/modules/etc" + testBaseUsr = "testdata/modules/usr/share" +) + +func testSetModulePaths() (func(), error) { + oldXDG := os.Getenv("XDG_CONFIG_HOME") + oldEtc := moduleBaseEtc + oldUsr := moduleBaseUsr + + wd, err := os.Getwd() + if err != nil { + return nil, err + } + + if err := os.Setenv("XDG_CONFIG_HOME", filepath.Join(wd, testBaseHome)); err != nil { + return nil, err + } + + moduleBaseEtc = filepath.Join(wd, testBaseEtc) + moduleBaseUsr = filepath.Join(wd, testBaseUsr) + + return func() { + os.Setenv("XDG_CONFIG_HOME", oldXDG) + moduleBaseEtc = oldEtc + moduleBaseUsr = oldUsr + }, nil +} + +var _ = Describe("Config Modules", func() { + It("module directories", func() { + dirs, err := moduleDirectories() + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(dirs).NotTo(gomega.BeNil()) + + if unshare.IsRootless() { + gomega.Expect(dirs).To(gomega.HaveLen(3)) + } else { + gomega.Expect(dirs).To(gomega.HaveLen(2)) + } + }) + + It("resolve modules", func() { + // This test makes sure that the correct module is being + // returned. + cleanUp, err := testSetModulePaths() + gomega.Expect(err).To(gomega.BeNil()) + defer cleanUp() + + dirs, err := moduleDirectories() + gomega.Expect(err).To(gomega.BeNil()) + + if unshare.IsRootless() { + gomega.Expect(dirs).To(gomega.HaveLen(3)) + gomega.Expect(dirs[0]).To(gomega.ContainSubstring(testBaseHome)) + gomega.Expect(dirs[1]).To(gomega.ContainSubstring(testBaseEtc)) + gomega.Expect(dirs[2]).To(gomega.ContainSubstring(testBaseUsr)) + } else { + gomega.Expect(dirs).To(gomega.HaveLen(2)) + gomega.Expect(dirs[0]).To(gomega.ContainSubstring(testBaseEtc)) + gomega.Expect(dirs[1]).To(gomega.ContainSubstring(testBaseUsr)) + } + + for _, test := range []struct { + input string + expectedDir string + mustFail bool + rootless bool + }{ + // Rootless + {"first.conf", testBaseHome, false, true}, + {"second.conf", testBaseHome, false, true}, + {"third.conf", testBaseHome, false, true}, + {"sub/first.conf", testBaseHome, false, true}, + + // Root + Rootless + {"fourth.conf", testBaseEtc, false, false}, + {"sub/etc-only.conf", testBaseEtc, false, false}, + {"fifth.conf", testBaseUsr, false, false}, + {"sub/share-only.conf", testBaseUsr, false, false}, + {"none.conf", "", true, false}, + } { + if test.rootless && !unshare.IsRootless() { + continue + } + result, err := resolveModule(test.input, dirs) + if test.mustFail { + gomega.Expect(err).NotTo(gomega.BeNil()) + continue + } + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(result).To(gomega.HaveSuffix(filepath.Join(test.expectedDir, moduleSubdir, test.input))) + } + }) + + It("new config with modules", func() { + cleanUp, err := testSetModulePaths() + gomega.Expect(err).To(gomega.BeNil()) + defer cleanUp() + + options := &Options{Modules: []string{"none.conf"}} + _, err = New(options) + gomega.Expect(err).NotTo(gomega.BeNil()) // must error out + + options = &Options{} + c, err := New(options) + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(options.additionalConfigs).To(gomega.HaveLen(0)) // no module is getting loaded! + gomega.Expect(c).NotTo(gomega.BeNil()) + + options = &Options{Modules: []string{"fourth.conf"}} + c, err = New(options) + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(options.additionalConfigs).To(gomega.HaveLen(1)) // 1 module is getting loaded! + gomega.Expect(c.Containers.InitPath).To(gomega.Equal("etc four")) + + options = &Options{Modules: []string{"fourth.conf"}} + c, err = New(options) + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(options.additionalConfigs).To(gomega.HaveLen(1)) // 1 module is getting loaded! + gomega.Expect(c.Containers.InitPath).To(gomega.Equal("etc four")) + + options = &Options{Modules: []string{"fourth.conf", "sub/share-only.conf"}} + c, err = New(options) + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(options.additionalConfigs).To(gomega.HaveLen(2)) // 2 modules are getting loaded! + gomega.Expect(c.Containers.InitPath).To(gomega.Equal("etc four")) + gomega.Expect(c.Containers.Env).To(gomega.Equal([]string{"usr share only"})) + }) + + It("new config with modules and env variables", func() { + cleanUp, err := testSetModulePaths() + gomega.Expect(err).To(gomega.BeNil()) + defer cleanUp() + + oldOverride := os.Getenv(containersConfOverrideEnv) + defer func() { + os.Setenv(containersConfOverrideEnv, oldOverride) + }() + + err = os.Setenv(containersConfOverrideEnv, "testdata/modules/override.conf") + gomega.Expect(err).To(gomega.BeNil()) + + // Also make sure that absolute paths are loaded as is. + wd, err := os.Getwd() + gomega.Expect(err).To(gomega.BeNil()) + absConf := filepath.Join(wd, "testdata/modules/home/.config/containers/containers.conf.modules/second.conf") + + options := &Options{Modules: []string{"fourth.conf", "sub/share-only.conf", absConf}} + c, err := New(options) + gomega.Expect(err).To(gomega.BeNil()) + gomega.Expect(options.additionalConfigs).To(gomega.HaveLen(4)) // 2 modules + abs path + override conf are getting loaded! + gomega.Expect(c.Containers.InitPath).To(gomega.Equal("etc four")) + gomega.Expect(c.Containers.Env).To(gomega.Equal([]string{"override conf always wins"})) + gomega.Expect(c.Containers.Volumes).To(gomega.Equal([]string{"home second"})) + }) +}) diff --git a/pkg/config/new.go b/pkg/config/new.go index 001802f439..f588520576 100644 --- a/pkg/config/new.go +++ b/pkg/config/new.go @@ -1,10 +1,16 @@ package config import ( + "errors" "fmt" + "io/fs" "os" + "path/filepath" + "sort" + "strings" "sync" + "github.com/BurntSushi/toml" "github.com/sirupsen/logrus" ) @@ -14,14 +20,23 @@ var ( cachedConfig *Config ) +const ( + // FIXME: update code base and tests to use the two constants below. + containersConfEnv = "CONTAINERS_CONF" + containersConfOverrideEnv = containersConfEnv + "_OVERRIDE" +) + // Options to use when loading a Config via New(). type Options struct { + // Attempt to load the following config modules. + Modules []string + // Set the loaded config as the default one which can later on be // accessed via Default(). SetDefault bool - // Additional configs that will be loaded after all system/user configs - // and environment variables but the _OVERRIDE one. + // Additional configs to load. An internal only field to make the + // behavior observable and testable in unit tests. additionalConfigs []string } @@ -35,18 +50,23 @@ func New(options *Options) (*Config, error) { return newLocked(options) } +// Default returns the default container config. If no default config has been +// set yet, a new config will be loaded by New() and set as the default one. +// All callers are expected to use the returned Config read only. Changing +// data may impact other call sites. +func Default() (*Config, error) { + cachedConfigMutex.Lock() + defer cachedConfigMutex.Unlock() + if cachedConfig != nil || cachedConfigError != nil { + return cachedConfig, cachedConfigError + } + cachedConfig, cachedConfigError = newLocked(&Options{SetDefault: true}) + return cachedConfig, cachedConfigError +} + // A helper function for New() expecting the caller to hold the // cachedConfigMutex. func newLocked(options *Options) (*Config, error) { - // The _OVERRIDE variable _must_ always win. That's a contract we need - // to honor (for the Podman CI). - if path := os.Getenv("CONTAINERS_CONF_OVERRIDE"); path != "" { - if _, err := os.Stat(path); err != nil { - return nil, fmt.Errorf("CONTAINERS_CONF_OVERRIDE file: %w", err) - } - options.additionalConfigs = append(options.additionalConfigs, path) - } - // Start with the built-in defaults config, err := defaultConfig() if err != nil { @@ -69,6 +89,22 @@ func newLocked(options *Options) (*Config, error) { logrus.Tracef("%+v", config) } + modules, err := options.modules() + if err != nil { + return nil, err + } + + options.additionalConfigs = append(options.additionalConfigs, modules...) + + // The _OVERRIDE variable _must_ always win. That's a contract we need + // to honor (for the Podman CI). + if path := os.Getenv(containersConfOverrideEnv); path != "" { + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf("%s file: %w", containersConfOverrideEnv, err) + } + options.additionalConfigs = append(options.additionalConfigs, path) + } + // If the caller specified a config path to use, then we read it to // override the system defaults. for _, add := range options.additionalConfigs { @@ -109,16 +145,94 @@ func NewConfig(userConfigPath string) (*Config, error) { return New(&Options{additionalConfigs: []string{userConfigPath}}) } -// Default returns the default container config. If no default config has been -// set yet, a new config will be loaded by New() and set as the default one. -// All callers are expected to use the returned Config read only. Changing -// data may impact other call sites. -func Default() (*Config, error) { - cachedConfigMutex.Lock() - defer cachedConfigMutex.Unlock() - if cachedConfig != nil || cachedConfigError != nil { - return cachedConfig, cachedConfigError +// Returns the list of configuration files, if they exist in order of hierarchy. +// The files are read in order and each new file can/will override previous +// file settings. +func systemConfigs() (configs []string, finalErr error) { + if path := os.Getenv(containersConfEnv); path != "" { + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf("%s file: %w", containersConfEnv, err) + } + return append(configs, path), nil } - cachedConfig, cachedConfigError = newLocked(&Options{SetDefault: true}) - return cachedConfig, cachedConfigError + if _, err := os.Stat(DefaultContainersConfig); err == nil { + configs = append(configs, DefaultContainersConfig) + } + if _, err := os.Stat(OverrideContainersConfig); err == nil { + configs = append(configs, OverrideContainersConfig) + } + + var err error + configs, err = addConfigs(OverrideContainersConfig+".d", configs) + if err != nil { + return nil, err + } + + path, err := ifRootlessConfigPath() + if err != nil { + return nil, err + } + if path != "" { + if _, err := os.Stat(path); err == nil { + configs = append(configs, path) + } + configs, err = addConfigs(path+".d", configs) + if err != nil { + return nil, err + } + } + return configs, nil +} + +// addConfigs will search one level in the config dirPath for config files +// If the dirPath does not exist, addConfigs will return nil +func addConfigs(dirPath string, configs []string) ([]string, error) { + newConfigs := []string{} + + err := filepath.WalkDir(dirPath, + // WalkFunc to read additional configs + func(path string, d fs.DirEntry, err error) error { + switch { + case err != nil: + // return error (could be a permission problem) + return err + case d.IsDir(): + if path != dirPath { + // make sure to not recurse into sub-directories + return filepath.SkipDir + } + // ignore directories + return nil + default: + // only add *.conf files + if strings.HasSuffix(path, ".conf") { + newConfigs = append(newConfigs, path) + } + return nil + } + }, + ) + if errors.Is(err, os.ErrNotExist) { + err = nil + } + sort.Strings(newConfigs) + return append(configs, newConfigs...), err +} + +// readConfigFromFile reads the specified config file at `path` and attempts to +// unmarshal its content into a Config. The config param specifies the previous +// default config. If the path, only specifies a few fields in the Toml file +// the defaults from the config parameter will be used for all other fields. +func readConfigFromFile(path string, config *Config) error { + logrus.Tracef("Reading configuration file %q", path) + meta, err := toml.DecodeFile(path, config) + if err != nil { + return fmt.Errorf("decode configuration %v: %w", path, err) + } + keys := meta.Undecoded() + if len(keys) > 0 { + logrus.Debugf("Failed to decode the keys %q from %q.", keys, path) + } + + return nil } diff --git a/pkg/config/testdata/modules/etc/containers/containers.conf.modules/first.conf b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/first.conf new file mode 100644 index 0000000000..c833a772f4 --- /dev/null +++ b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/first.conf @@ -0,0 +1,2 @@ +[containers] +annotations=["etc first"] diff --git a/pkg/config/testdata/modules/etc/containers/containers.conf.modules/fourth.conf b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/fourth.conf new file mode 100644 index 0000000000..a799ccef62 --- /dev/null +++ b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/fourth.conf @@ -0,0 +1,2 @@ +[containers] +init_path="etc four" diff --git a/pkg/config/testdata/modules/etc/containers/containers.conf.modules/second.conf b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/second.conf new file mode 100644 index 0000000000..7c9010218c --- /dev/null +++ b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/second.conf @@ -0,0 +1,2 @@ +[containers] +volumes=["etc second"] diff --git a/pkg/config/testdata/modules/etc/containers/containers.conf.modules/sub/etc-only.conf b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/sub/etc-only.conf new file mode 100644 index 0000000000..8ef8e40e41 --- /dev/null +++ b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/sub/etc-only.conf @@ -0,0 +1,2 @@ +[containers] +default_network="etc only conf" diff --git a/pkg/config/testdata/modules/etc/containers/containers.conf.modules/third.conf b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/third.conf new file mode 100644 index 0000000000..20400828f9 --- /dev/null +++ b/pkg/config/testdata/modules/etc/containers/containers.conf.modules/third.conf @@ -0,0 +1,2 @@ +[containers] +default_network="etc third" diff --git a/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/first.conf b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/first.conf new file mode 100644 index 0000000000..ef38600e43 --- /dev/null +++ b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/first.conf @@ -0,0 +1,2 @@ +[containers] +annotations=["home first"] diff --git a/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/second.conf b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/second.conf new file mode 100644 index 0000000000..0af348a0dd --- /dev/null +++ b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/second.conf @@ -0,0 +1,2 @@ +[containers] +volumes=["home second"] diff --git a/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/sub/first.conf b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/sub/first.conf new file mode 100644 index 0000000000..e864023259 --- /dev/null +++ b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/sub/first.conf @@ -0,0 +1,2 @@ +[containers] +annotations=["home sub first"] diff --git a/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/third.conf b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/third.conf new file mode 100644 index 0000000000..64e20f8e4f --- /dev/null +++ b/pkg/config/testdata/modules/home/.config/containers/containers.conf.modules/third.conf @@ -0,0 +1,2 @@ +[containers] +default_network="home third" diff --git a/pkg/config/testdata/modules/override.conf b/pkg/config/testdata/modules/override.conf new file mode 100644 index 0000000000..a85b7dd9c2 --- /dev/null +++ b/pkg/config/testdata/modules/override.conf @@ -0,0 +1,2 @@ +[containers] +env=["override conf always wins"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/fifth.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/fifth.conf new file mode 100644 index 0000000000..ee3dc15184 --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/fifth.conf @@ -0,0 +1,2 @@ +[containers] +env=["usr five"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/first.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/first.conf new file mode 100644 index 0000000000..3e6ba0210e --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/first.conf @@ -0,0 +1,2 @@ +[containers] +annotations=["usr first"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/second.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/second.conf new file mode 100644 index 0000000000..43c8ed16c9 --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/second.conf @@ -0,0 +1,2 @@ +[containers] +volumes=["usr second"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/first.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/first.conf new file mode 100644 index 0000000000..b0b88e2625 --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/first.conf @@ -0,0 +1,2 @@ +[containers] +annotations=["usr sub first"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/share-only.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/share-only.conf new file mode 100644 index 0000000000..83d0fa7681 --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/sub/share-only.conf @@ -0,0 +1,2 @@ +[containers] +env=["usr share only"] diff --git a/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/third.conf b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/third.conf new file mode 100644 index 0000000000..fa0474b3e2 --- /dev/null +++ b/pkg/config/testdata/modules/usr/share/containers/containers.conf.modules/third.conf @@ -0,0 +1,2 @@ +[containers] +default_network="usr third"