Skip to content

Commit

Permalink
Merge pull request #1599 from vrothberg/RUN-1873
Browse files Browse the repository at this point in the history
containers.conf: implement modules
  • Loading branch information
openshift-merge-robot authored Aug 14, 2023
2 parents 6585c6e + 671ae57 commit b70b0c4
Show file tree
Hide file tree
Showing 26 changed files with 794 additions and 320 deletions.
29 changes: 20 additions & 9 deletions docs/containers.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ Note, container engines also use other configuration files for configuring the e
container images.
* `policy.conf` for controlling which images can be pulled to the system.

## ENVIRONMENT VARIABLES
If the `CONTAINERS_CONF` environment variable is set, all system and user
config files are ignored and only the specified config file will be loaded.

If the `CONTAINERS_CONF_OVERRIDE` path environment variable is set, the config
file will be loaded last even when `CONTAINERS_CONF` is set.

The values of both environment variables may be absolute or relative paths, for
instance, `CONTAINERS_CONF=/tmp/my_containers.conf`.

## MODULES
A module is a containers.conf file located directly in or a sub-directory of the following three directories:
- __$HOME/.config/containers/containers.conf.modules__
- __/etc/containers/containers.conf.modules__
- __/usr/share/containers/containers.conf.modules__

Files in those locations are not loaded by default but only on-demand. They are loaded after all system and user configuration files but before `CONTAINERS_CONF_OVERRIDE` hence allowing for overriding system and user configs.

Modules are currently supported by podman(1). The `podman --module` flag allows for loading a module and can be specified multiple times. If the specified value is an absolute path, the config file will be loaded directly. Relative paths are resolved relative to the three module directories mentioned above and in the specified order such that modules in `$HOME` allow for overriding those in `/etc` and `/usr/share`. Modules in `$HOME` (or `$XDG_CONFIG_HOME` if specified) are only used for rootless users.

# FORMAT
The [TOML format][toml] is used as the encoding of the configuration file.
Every option is nested under its table. No bare options are used. The format of
Expand Down Expand Up @@ -871,15 +891,6 @@ __/etc/containers/containers.conf.d__ which will be loaded in alphanumeric order
Rootless users can further override fields in the config by creating a config
file stored in the __$HOME/.config/containers/containers.conf__ file or __.conf__ files in __$HOME/.config/containers/containers.conf.d__.

If the `CONTAINERS_CONF` environment variable is set, all system and user
config files are ignored and only the specified config file will be loaded.

If the `CONTAINERS_CONF_OVERRIDE` path environment variable is set, the config
file will be loaded last even when `CONTAINERS_CONF` is set.

The values of both environment variables may be absolute or relative paths, for
instance, `CONTAINERS_CONF=/tmp/my_containers.conf`.

Fields specified in a containers.conf file override the default options, as
well as options in previously loaded containers.conf files.

Expand Down
200 changes: 3 additions & 197 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ package config
import (
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"

"github.com/BurntSushi/toml"
"github.com/containers/common/libnetwork/types"
Expand Down Expand Up @@ -81,6 +78,8 @@ type Config struct {
ConfigMaps ConfigMapConfig `toml:"configmaps"`
// Farms defines configurations for the buildfarm farms
Farms FarmConfig `toml:"farms"`

loadedModules []string // only used at runtime to store which modules were loaded
}

// ContainersConfig represents the "containers" TOML config table
Expand Down Expand Up @@ -708,166 +707,6 @@ func (c *EngineConfig) ImagePlatformToRuntime(os string, arch string) string {
return c.OCIRuntime
}

// NewConfig creates a new Config. It starts with an empty config and, if
// specified, merges the config at `userConfigPath` path. Depending if we're
// running as root or rootless, we then merge the system configuration followed
// by merging the default config (hard-coded default in memory).
// Note that the OCI runtime is hard-set to `crun` if we're running on a system
// with cgroupv2v2. Other OCI runtimes are not yet supporting cgroupv2v2. This
// might change in the future.
func NewConfig(userConfigPath string) (*Config, error) {
// Generate the default config for the system
config, err := DefaultConfig()
if err != nil {
return nil, err
}

// Now, gather the system configs and merge them as needed.
configs, err := systemConfigs()
if err != nil {
return nil, fmt.Errorf("finding config on system: %w", err)
}
for _, path := range configs {
// Merge changes in later configs with the previous configs.
// Each config file that specified fields, will override the
// previous fields.
if err = readConfigFromFile(path, config); err != nil {
return nil, fmt.Errorf("reading system config %q: %w", path, err)
}
logrus.Debugf("Merged system config %q", path)
logrus.Tracef("%+v", config)
}

// If the caller specified a config path to use, then we read it to
// override the system defaults.
if userConfigPath != "" {
var err error
// readConfigFromFile reads in container config in the specified
// file and then merge changes with the current default.
if err = readConfigFromFile(userConfigPath, config); err != nil {
return nil, fmt.Errorf("reading user config %q: %w", userConfigPath, err)
}
logrus.Debugf("Merged user config %q", userConfigPath)
logrus.Tracef("%+v", config)
}
config.addCAPPrefix()

if err := config.Validate(); err != nil {
return nil, err
}

if err := config.setupEnv(); err != nil {
return nil, err
}

return config, nil
}

// 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_OVERRIDE"); path != "" {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("CONTAINERS_CONF_OVERRIDE file: %w", err)
}
// Add the override config last to make sure it can override any
// previous settings.
defer func() {
if finalErr == nil {
configs = append(configs, path)
}
}()
}

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.
Expand Down Expand Up @@ -1190,37 +1029,6 @@ func rootlessConfigPath() (string, error) {
return filepath.Join(home, UserOverrideContainersConfig), nil
}

var (
configErr error
configMutex sync.Mutex
config *Config
)

// Default returns the default container config.
// Configuration files will be read in the following files:
// * /usr/share/containers/containers.conf
// * /etc/containers/containers.conf
// * $HOME/.config/containers/containers.conf # When run in rootless mode
// Fields in latter files override defaults set in previous files and the
// default config.
// None of these files are required, and not all fields need to be specified
// in each file, only the fields you want to override.
// The system defaults container config files can be overwritten using the
// CONTAINERS_CONF environment variable. This is usually done for testing.
func Default() (*Config, error) {
configMutex.Lock()
defer configMutex.Unlock()
if config != nil || configErr != nil {
return config, configErr
}
return defConfig()
}

func defConfig() (*Config, error) {
config, configErr = NewConfig("")
return config, configErr
}

func Path() string {
if path := os.Getenv("CONTAINERS_CONF"); path != "" {
return path
Expand Down Expand Up @@ -1289,9 +1097,7 @@ func (c *Config) Write() error {
// This function is meant to be used for long-running processes that need to reload potential changes made to
// the cached containers.conf files.
func Reload() (*Config, error) {
configMutex.Lock()
defer configMutex.Unlock()
return defConfig()
return New(&Options{SetDefault: true})
}

func (c *Config) ActiveDestination() (uri, identity string, machine bool, err error) {
Expand Down
Loading

0 comments on commit b70b0c4

Please sign in to comment.