diff options
Diffstat (limited to 'libpod/config')
-rw-r--r-- | libpod/config/config.go | 549 | ||||
-rw-r--r-- | libpod/config/config_test.go | 64 | ||||
-rw-r--r-- | libpod/config/default.go | 137 | ||||
-rw-r--r-- | libpod/config/merge.go | 183 | ||||
-rw-r--r-- | libpod/config/merge_test.go | 157 | ||||
-rw-r--r-- | libpod/config/testdata/empty.conf | 0 | ||||
l--------- | libpod/config/testdata/libpod.conf | 1 |
7 files changed, 1091 insertions, 0 deletions
diff --git a/libpod/config/config.go b/libpod/config/config.go new file mode 100644 index 000000000..5b4b57f3a --- /dev/null +++ b/libpod/config/config.go @@ -0,0 +1,549 @@ +package config + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/BurntSushi/toml" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // _defaultTransport is a prefix that we apply to an image name to check + // docker hub first for the image. + _defaultTransport = "docker://" + + // _rootlessConfigPath is the path to the rootless libpod.conf in $HOME. + _rootlessConfigPath = ".config/containers/libpod.conf" + + // _conmonMinMajorVersion is the major version required for conmon. + _conmonMinMajorVersion = 2 + + // _conmonMinMinorVersion is the minor version required for conmon. + _conmonMinMinorVersion = 0 + + // _conmonMinPatchVersion is the sub-minor version required for conmon. + _conmonMinPatchVersion = 1 + + // _conmonVersionFormatErr is used when the expected versio-format of conmon + // has changed. + _conmonVersionFormatErr = "conmon version changed format" + + // InstallPrefix is the prefix where podman will be installed. + // It can be overridden at build time. + _installPrefix = "/usr" + + // EtcDir is the sysconfdir where podman should look for system config files. + // It can be overridden at build time. + _etcDir = "/etc" + + // SeccompDefaultPath defines the default seccomp path. + SeccompDefaultPath = _installPrefix + "/share/containers/seccomp.json" + + // SeccompOverridePath if this exists it overrides the default seccomp path. + SeccompOverridePath = _etcDir + "/crio/seccomp.json" + + // _rootConfigPath is the path to the libpod configuration file + // This file is loaded to replace the builtin default config before + // runtime options (e.g. WithStorageConfig) are applied. + // If it is not present, the builtin default config is used instead + // This path can be overridden when the runtime is created by using + // NewRuntimeFromConfig() instead of NewRuntime(). + _rootConfigPath = _installPrefix + "/share/containers/libpod.conf" + + // _rootOverrideConfigPath is the path to an override for the default libpod + // configuration file. If OverrideConfigPath exists, it will be used in + // place of the configuration file pointed to by ConfigPath. + _rootOverrideConfigPath = _etcDir + "/containers/libpod.conf" +) + +// SetOptions contains a subset of options in a Config. It's used to indicate if +// a given option has either been set by the user or by a parsed libpod +// configuration file. If not, the corresponding option might be overwritten by +// values from the database. This behavior guarantess backwards compat with +// older version of libpod and Podman. +type SetOptions struct { + // StorageConfigRunRootSet indicates if the RunRoot has been explicitly set + // by the config or by the user. It's required to guarantee backwards + // compatibility with older versions of libpod for which we must query the + // database configuration. Not included in the on-disk config. + StorageConfigRunRootSet bool `toml:"-"` + + // StorageConfigGraphRootSet indicates if the RunRoot has been explicitly + // set by the config or by the user. It's required to guarantee backwards + // compatibility with older versions of libpod for which we must query the + // database configuration. Not included in the on-disk config. + StorageConfigGraphRootSet bool `toml:"-"` + + // StorageConfigGraphDriverNameSet indicates if the GraphDriverName has been + // explicitly set by the config or by the user. It's required to guarantee + // backwards compatibility with older versions of libpod for which we must + // query the database configuration. Not included in the on-disk config. + StorageConfigGraphDriverNameSet bool `toml:"-"` + + // VolumePathSet indicates if the VolumePath has been explicitly set by the + // config or by the user. It's required to guarantee backwards compatibility + // with older versions of libpod for which we must query the database + // configuration. Not included in the on-disk config. + VolumePathSet bool `toml:"-"` + + // StaticDirSet indicates if the StaticDir has been explicitly set by the + // config or by the user. It's required to guarantee backwards compatibility + // with older versions of libpod for which we must query the database + // configuration. Not included in the on-disk config. + StaticDirSet bool `toml:"-"` + + // TmpDirSet indicates if the TmpDir has been explicitly set by the config + // or by the user. It's required to guarantee backwards compatibility with + // older versions of libpod for which we must query the database + // configuration. Not included in the on-disk config. + TmpDirSet bool `toml:"-"` +} + +// Config contains configuration options used to set up a libpod runtime +type Config struct { + // NOTE: when changing this struct, make sure to update (*Config).Merge(). + + // SetOptions contains a subset of config options. It's used to indicate if + // a given option has either been set by the user or by a parsed libpod + // configuration file. If not, the corresponding option might be + // overwritten by values from the database. This behavior guarantess + // backwards compat with older version of libpod and Podman. + SetOptions + + // StateType is the type of the backing state store. Avoid using multiple + // values for this with the same containers/storage configuration on the + // same system. Different state types do not interact, and each will see a + // separate set of containers, which may cause conflicts in + // containers/storage. As such this is not exposed via the config file. + StateType define.RuntimeStateStore `toml:"-"` + + // StorageConfig is the configuration used by containers/storage Not + // included in the on-disk config, use the dedicated containers/storage + // configuration file instead. + StorageConfig storage.StoreOptions `toml:"-"` + + // VolumePath is the default location that named volumes will be created + // under. This convention is followed by the default volume driver, but + // may not be by other drivers. + VolumePath string `toml:"volume_path"` + + // ImageDefaultTransport is the default transport method used to fetch + // images. + ImageDefaultTransport string `toml:"image_default_transport"` + + // SignaturePolicyPath is the path to a signature policy to use for + // validating images. If left empty, the containers/image default signature + // policy will be used. + SignaturePolicyPath string `toml:"signature_policy_path,omitempty"` + + // OCIRuntime is the OCI runtime to use. + OCIRuntime string `toml:"runtime"` + + // OCIRuntimes are the set of configured OCI runtimes (default is runc). + OCIRuntimes map[string][]string `toml:"runtimes"` + + // RuntimeSupportsJSON is the list of the OCI runtimes that support + // --format=json. + RuntimeSupportsJSON []string `toml:"runtime_supports_json"` + + // RuntimeSupportsNoCgroups is a list of OCI runtimes that support + // running containers without CGroups. + RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroups"` + + // RuntimePath is the path to OCI runtime binary for launching containers. + // The first path pointing to a valid file will be used This is used only + // when there are no OCIRuntime/OCIRuntimes defined. It is used only to be + // backward compatible with older versions of Podman. + RuntimePath []string `toml:"runtime_path"` + + // ConmonPath is the path to the Conmon binary used for managing containers. + // The first path pointing to a valid file will be used. + ConmonPath []string `toml:"conmon_path"` + + // ConmonEnvVars are environment variables to pass to the Conmon binary + // when it is launched. + ConmonEnvVars []string `toml:"conmon_env_vars"` + + // CGroupManager is the CGroup Manager to use Valid values are "cgroupfs" + // and "systemd". + CgroupManager string `toml:"cgroup_manager"` + + // InitPath is the path to the container-init binary. + InitPath string `toml:"init_path"` + + // StaticDir is the path to a persistent directory to store container + // files. + StaticDir string `toml:"static_dir"` + + // TmpDir is the path to a temporary directory to store per-boot container + // files. Must be stored in a tmpfs. + TmpDir string `toml:"tmp_dir"` + + // MaxLogSize is the maximum size of container logfiles. + MaxLogSize int64 `toml:"max_log_size,omitempty"` + + // NoPivotRoot sets whether to set no-pivot-root in the OCI runtime. + NoPivotRoot bool `toml:"no_pivot_root"` + + // CNIConfigDir sets the directory where CNI configuration files are + // stored. + CNIConfigDir string `toml:"cni_config_dir"` + + // CNIPluginDir sets a number of directories where the CNI network + // plugins can be located. + CNIPluginDir []string `toml:"cni_plugin_dir"` + + // CNIDefaultNetwork is the network name of the default CNI network + // to attach pods to. + CNIDefaultNetwork string `toml:"cni_default_network,omitempty"` + + // HooksDir holds paths to the directories containing hooks + // configuration files. When the same filename is present in in + // multiple directories, the file in the directory listed last in + // this slice takes precedence. + HooksDir []string `toml:"hooks_dir"` + + // DefaultMountsFile is the path to the default mounts file for testing + // purposes only. + DefaultMountsFile string `toml:"-"` + + // Namespace is the libpod namespace to use. Namespaces are used to create + // scopes to separate containers and pods in the state. When namespace is + // set, libpod will only view containers and pods in the same namespace. All + // containers and pods created will default to the namespace set here. A + // namespace of "", the empty string, is equivalent to no namespace, and all + // containers and pods will be visible. The default namespace is "". + Namespace string `toml:"namespace,omitempty"` + + // InfraImage is the image a pod infra container will use to manage + // namespaces. + InfraImage string `toml:"infra_image"` + + // InfraCommand is the command run to start up a pod infra container. + InfraCommand string `toml:"infra_command"` + + // EnablePortReservation determines whether libpod will reserve ports on the + // host when they are forwarded to containers. When enabled, when ports are + // forwarded to containers, they are held open by conmon as long as the + // container is running, ensuring that they cannot be reused by other + // programs on the host. However, this can cause significant memory usage if + // a container has many ports forwarded to it. Disabling this can save + // memory. + EnablePortReservation bool `toml:"enable_port_reservation"` + + // EnableLabeling indicates whether libpod will support container labeling. + EnableLabeling bool `toml:"label"` + + // NetworkCmdPath is the path to the slirp4netns binary. + NetworkCmdPath string `toml:"network_cmd_path"` + + // NumLocks is the number of locks to make available for containers and + // pods. + NumLocks uint32 `toml:"num_locks,omitempty"` + + // LockType is the type of locking to use. + LockType string `toml:"lock_type,omitempty"` + + // EventsLogger determines where events should be logged. + EventsLogger string `toml:"events_logger"` + + // EventsLogFilePath is where the events log is stored. + EventsLogFilePath string `toml:"events_logfile_path"` + + //DetachKeys is the sequence of keys used to detach a container. + DetachKeys string `toml:"detach_keys"` + + // SDNotify tells Libpod to allow containers to notify the host systemd of + // readiness using the SD_NOTIFY mechanism. + SDNotify bool +} + +// DBConfig is a set of Libpod runtime configuration settings that are saved in +// a State when it is first created, and can subsequently be retrieved. +type DBConfig struct { + LibpodRoot string + LibpodTmp string + StorageRoot string + StorageTmp string + GraphDriver string + VolumePath string +} + +// readConfigFromFile reads the specified config file at `path` and attempts to +// unmarshal its content into a Config. +func readConfigFromFile(path string) (*Config, error) { + var config Config + + configBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + logrus.Debugf("Reading configuration file %q", path) + err = toml.Unmarshal(configBytes, &config) + + // For the sake of backwards compat we need to check if the config fields + // with *Set suffix are set in the config. Note that the storage-related + // fields are NOT set in the config here but in the storage.conf OR directly + // by the user. + if config.VolumePath != "" { + config.VolumePathSet = true + } + if config.StaticDir != "" { + config.StaticDirSet = true + } + if config.TmpDir != "" { + config.TmpDirSet = true + } + + return &config, err +} + +// Write decodes the config as TOML and writes it to the specified path. +func (c *Config) Write(path string) error { + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return errors.Wrapf(err, "error opening config file %q", path) + } + + buffer := new(bytes.Buffer) + if err := toml.NewEncoder(buffer).Encode(c); err != nil { + return errors.Wrapf(err, "error encoding config") + } + + if _, err := f.WriteString(buffer.String()); err != nil { + return errors.Wrapf(err, "error writing config %q", path) + } + return err +} + +// FindConmon iterates over (*Config).ConmonPath and returns the path to first +// (version) matching conmon binary. If non is found, we try to do a path lookup +// of "conmon". +func (c *Config) FindConmon() (string, error) { + foundOutdatedConmon := false + for _, path := range c.ConmonPath { + stat, err := os.Stat(path) + if err != nil { + continue + } + if stat.IsDir() { + continue + } + if err := probeConmon(path); err != nil { + logrus.Warnf("Conmon at %s invalid: %v", path, err) + foundOutdatedConmon = true + continue + } + logrus.Debugf("Using conmon: %q", path) + return path, nil + } + + // Search the $PATH as last fallback + if path, err := exec.LookPath("conmon"); err == nil { + if err := probeConmon(path); err != nil { + logrus.Warnf("Conmon at %s is invalid: %v", path, err) + foundOutdatedConmon = true + } else { + logrus.Debugf("Using conmon from $PATH: %q", path) + return path, nil + } + } + + if foundOutdatedConmon { + return "", errors.Wrapf(define.ErrConmonOutdated, + "please update to v%d.%d.%d or later", + _conmonMinMajorVersion, _conmonMinMinorVersion, _conmonMinPatchVersion) + } + + return "", errors.Wrapf(define.ErrInvalidArg, + "could not find a working conmon binary (configured options: %v)", + c.ConmonPath) +} + +// probeConmon calls conmon --version and verifies it is a new enough version for +// the runtime expectations podman currently has. +func probeConmon(conmonBinary string) error { + cmd := exec.Command(conmonBinary, "--version") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return err + } + r := regexp.MustCompile(`^conmon version (?P<Major>\d+).(?P<Minor>\d+).(?P<Patch>\d+)`) + + matches := r.FindStringSubmatch(out.String()) + if len(matches) != 4 { + return errors.Wrap(err, _conmonVersionFormatErr) + } + major, err := strconv.Atoi(matches[1]) + if err != nil { + return errors.Wrap(err, _conmonVersionFormatErr) + } + if major < _conmonMinMajorVersion { + return define.ErrConmonOutdated + } + if major > _conmonMinMajorVersion { + return nil + } + + minor, err := strconv.Atoi(matches[2]) + if err != nil { + return errors.Wrap(err, _conmonVersionFormatErr) + } + if minor < _conmonMinMinorVersion { + return define.ErrConmonOutdated + } + if minor > _conmonMinMinorVersion { + return nil + } + + patch, err := strconv.Atoi(matches[3]) + if err != nil { + return errors.Wrap(err, _conmonVersionFormatErr) + } + if patch < _conmonMinPatchVersion { + return define.ErrConmonOutdated + } + if patch > _conmonMinPatchVersion { + return nil + } + + return nil +} + +// 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 cgroupsv2. Other OCI runtimes are not yet supporting cgroupsv2. This +// might change in the future. +func NewConfig(userConfigPath string) (*Config, error) { + config := &Config{} // start with an empty config + + // First, try to read the user-specified config + if userConfigPath != "" { + var err error + config, err = readConfigFromFile(userConfigPath) + if err != nil { + return nil, errors.Wrapf(err, "error reading user config %q", userConfigPath) + } + } + + // Now, check if the user can access system configs and merge them if needed. + if configs, err := systemConfigs(); err != nil { + return nil, errors.Wrapf(err, "error finding config on system") + } else { + for _, path := range configs { + systemConfig, err := readConfigFromFile(path) + if err != nil { + return nil, errors.Wrapf(err, "error reading system config %q", path) + } + // Merge the it into the config. Any unset field in config will be + // over-written by the systemConfig. + if err := config.mergeConfig(systemConfig); err != nil { + return nil, errors.Wrapf(err, "error merging system config") + } + logrus.Debugf("Merged system config %q: %v", path, config) + } + } + + // Finally, create a default config from memory and forcefully merge it into + // the config. This way we try to make sure that all fields are properly set + // and that user AND system config can partially set. + if defaultConfig, err := defaultConfigFromMemory(); err != nil { + return nil, errors.Wrapf(err, "error generating default config from memory") + } else { + if err := config.mergeConfig(defaultConfig); err != nil { + return nil, errors.Wrapf(err, "error merging default config from memory") + } + } + + // Relative paths can cause nasty bugs, because core paths we use could + // shift between runs (or even parts of the program - the OCI runtime + // uses a different working directory than we do, for example. + if !filepath.IsAbs(config.StaticDir) { + return nil, errors.Wrapf(define.ErrInvalidArg, "static directory must be an absolute path - instead got %q", config.StaticDir) + } + if !filepath.IsAbs(config.TmpDir) { + return nil, errors.Wrapf(define.ErrInvalidArg, "temporary directory must be an absolute path - instead got %q", config.TmpDir) + } + if !filepath.IsAbs(config.VolumePath) { + return nil, errors.Wrapf(define.ErrInvalidArg, "volume path must be an absolute path - instead got %q", config.VolumePath) + } + + // Check if we need to switch to cgroupfs on rootless. + config.checkCgroupsAndAdjustConfig() + + return config, nil +} + +func rootlessConfigPath() (string, error) { + home, err := util.HomeDir() + if err != nil { + return "", err + } + + return filepath.Join(home, _rootlessConfigPath), nil +} + +func systemConfigs() ([]string, error) { + if rootless.IsRootless() { + path, err := rootlessConfigPath() + if err != nil { + return nil, err + } + if _, err := os.Stat(path); err == nil { + return []string{path}, nil + } + return nil, err + } + + configs := []string{} + if _, err := os.Stat(_rootOverrideConfigPath); err == nil { + configs = append(configs, _rootOverrideConfigPath) + } + if _, err := os.Stat(_rootConfigPath); err == nil { + configs = append(configs, _rootConfigPath) + } + 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. +func (c *Config) checkCgroupsAndAdjustConfig() { + if !rootless.IsRootless() || c.CgroupManager != define.SystemdCgroupsManager { + return + } + + session := os.Getenv("DBUS_SESSION_BUS_ADDRESS") + hasSession := session != "" + if hasSession && strings.HasPrefix(session, "unix:path=") { + _, err := os.Stat(strings.TrimPrefix(session, "unix:path=")) + hasSession = err == nil + } + + if !hasSession { + logrus.Warningf("The cgroups manager is set to systemd but there is no systemd user session available") + logrus.Warningf("For using systemd, you may need to login using an user session") + logrus.Warningf("Alternatively, you can enable lingering with: `loginctl enable-linger %d` (possibly as root)", rootless.GetRootlessUID()) + logrus.Warningf("Falling back to --cgroup-manager=cgroupfs") + c.CgroupManager = define.CgroupfsCgroupsManager + } +} diff --git a/libpod/config/config_test.go b/libpod/config/config_test.go new file mode 100644 index 000000000..47c092440 --- /dev/null +++ b/libpod/config/config_test.go @@ -0,0 +1,64 @@ +package config + +import ( + "reflect" + "testing" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/storage" + "github.com/stretchr/testify/assert" +) + +func TestEmptyConfig(t *testing.T) { + // Make sure that we can read empty configs + config, err := readConfigFromFile("testdata/empty.conf") + assert.NotNil(t, config) + assert.Nil(t, err) +} + +func TestDefaultLibpodConf(t *testing.T) { + // Make sure that we can read the default libpod.conf + config, err := readConfigFromFile("testdata/libpod.conf") + assert.NotNil(t, config) + assert.Nil(t, err) +} + +func TestMergeEmptyAndDefaultMemoryConfig(t *testing.T) { + // Make sure that when we merge the default config into an empty one that we + // effectively get the default config. + defaultConfig, err := defaultConfigFromMemory() + assert.NotNil(t, defaultConfig) + assert.Nil(t, err) + defaultConfig.StateType = define.InvalidStateStore + defaultConfig.StorageConfig = storage.StoreOptions{} + + emptyConfig, err := readConfigFromFile("testdata/empty.conf") + assert.NotNil(t, emptyConfig) + assert.Nil(t, err) + + err = emptyConfig.mergeConfig(defaultConfig) + assert.Nil(t, err) + + equal := reflect.DeepEqual(emptyConfig, defaultConfig) + assert.True(t, equal) +} + +func TestMergeEmptyAndLibpodConfig(t *testing.T) { + // Make sure that when we merge the default config into an empty one that we + // effectively get the default config. + libpodConfig, err := readConfigFromFile("testdata/libpod.conf") + assert.NotNil(t, libpodConfig) + assert.Nil(t, err) + libpodConfig.StateType = define.InvalidStateStore + libpodConfig.StorageConfig = storage.StoreOptions{} + + emptyConfig, err := readConfigFromFile("testdata/empty.conf") + assert.NotNil(t, emptyConfig) + assert.Nil(t, err) + + err = emptyConfig.mergeConfig(libpodConfig) + assert.Nil(t, err) + + equal := reflect.DeepEqual(emptyConfig, libpodConfig) + assert.True(t, equal) +} diff --git a/libpod/config/default.go b/libpod/config/default.go new file mode 100644 index 000000000..17574c059 --- /dev/null +++ b/libpod/config/default.go @@ -0,0 +1,137 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // _defaultGraphRoot points to the default path of the graph root. + _defaultGraphRoot = "/var/lib/containers/storage" + // _defaultRootlessSignaturePolicyPath points to the default path of the + // rootless policy.json file. + _defaultRootlessSignaturePolicyPath = ".config/containers/policy.json" +) + +// defaultConfigFromMemory returns a default libpod configuration. Note that the +// config is different for root and rootless. It also parses the storage.conf. +func defaultConfigFromMemory() (*Config, error) { + c := new(Config) + if tmp, err := defaultTmpDir(); err != nil { + return nil, err + } else { + c.TmpDir = tmp + } + c.EventsLogFilePath = filepath.Join(c.TmpDir, "events", "events.log") + + storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID()) + if err != nil { + return nil, err + } + if storeOpts.GraphRoot == "" { + logrus.Warnf("Storage configuration is unset - using hardcoded default graph root %q", _defaultGraphRoot) + storeOpts.GraphRoot = _defaultGraphRoot + } + c.StaticDir = filepath.Join(storeOpts.GraphRoot, "libpod") + c.VolumePath = filepath.Join(storeOpts.GraphRoot, "volumes") + c.StorageConfig = storeOpts + + c.ImageDefaultTransport = _defaultTransport + c.StateType = define.BoltDBStateStore + c.OCIRuntime = "runc" + c.OCIRuntimes = map[string][]string{ + "runc": { + "/usr/bin/runc", + "/usr/sbin/runc", + "/usr/local/bin/runc", + "/usr/local/sbin/runc", + "/sbin/runc", + "/bin/runc", + "/usr/lib/cri-o-runc/sbin/runc", + "/run/current-system/sw/bin/runc", + }, + // TODO - should we add "crun" defaults here as well? + } + c.ConmonPath = []string{ + "/usr/libexec/podman/conmon", + "/usr/local/libexec/podman/conmon", + "/usr/local/lib/podman/conmon", + "/usr/bin/conmon", + "/usr/sbin/conmon", + "/usr/local/bin/conmon", + "/usr/local/sbin/conmon", + "/run/current-system/sw/bin/conmon", + } + c.ConmonEnvVars = []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + } + c.RuntimeSupportsJSON = []string{ + "crun", + "runc", + } + c.RuntimeSupportsNoCgroups = []string{"crun"} + c.InitPath = define.DefaultInitPath + c.CgroupManager = define.SystemdCgroupsManager + c.MaxLogSize = -1 + c.NoPivotRoot = false + c.CNIConfigDir = _etcDir + "/cni/net.d/" + c.CNIPluginDir = []string{ + "/usr/libexec/cni", + "/usr/lib/cni", + "/usr/local/lib/cni", + "/opt/cni/bin", + } + c.CNIDefaultNetwork = "podman" + c.InfraCommand = define.DefaultInfraCommand + c.InfraImage = define.DefaultInfraImage + c.EnablePortReservation = true + c.EnableLabeling = true + c.NumLocks = 2048 + c.EventsLogger = events.DefaultEventerType.String() + c.DetachKeys = define.DefaultDetachKeys + // TODO - ideally we should expose a `type LockType string` along with + // constants. + c.LockType = "shm" + + if rootless.IsRootless() { + home, err := util.HomeDir() + if err != nil { + return nil, err + } + sigPath := filepath.Join(home, _defaultRootlessSignaturePolicyPath) + if _, err := os.Stat(sigPath); err == nil { + c.SignaturePolicyPath = sigPath + } + } + return c, nil +} + +func defaultTmpDir() (string, error) { + if !rootless.IsRootless() { + return "/var/run/libpod", nil + } + + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + libpodRuntimeDir := filepath.Join(runtimeDir, "libpod") + + if err := os.Mkdir(libpodRuntimeDir, 0700|os.ModeSticky); err != nil { + if !os.IsExist(err) { + return "", errors.Wrapf(err, "cannot mkdir %s", libpodRuntimeDir) + } else if err := os.Chmod(libpodRuntimeDir, 0700|os.ModeSticky); err != nil { + // The directory already exist, just set the sticky bit + return "", errors.Wrapf(err, "could not set sticky bit on %s", libpodRuntimeDir) + } + } + return filepath.Join(libpodRuntimeDir, "tmp"), nil +} diff --git a/libpod/config/merge.go b/libpod/config/merge.go new file mode 100644 index 000000000..798a63da7 --- /dev/null +++ b/libpod/config/merge.go @@ -0,0 +1,183 @@ +package config + +import ( + "path/filepath" + + "github.com/containers/libpod/libpod/define" + "github.com/sirupsen/logrus" +) + +// Merge merges the other config into the current one. Note that a field of the +// other config is only merged when it's not already set in the current one. +// +// Note that the StateType and the StorageConfig will NOT be changed. +func (c *Config) mergeConfig(other *Config) error { + // strings + c.CgroupManager = mergeStrings(c.CgroupManager, other.CgroupManager) + c.CNIConfigDir = mergeStrings(c.CNIConfigDir, other.CNIConfigDir) + c.CNIDefaultNetwork = mergeStrings(c.CNIDefaultNetwork, other.CNIDefaultNetwork) + c.DefaultMountsFile = mergeStrings(c.DefaultMountsFile, other.DefaultMountsFile) + c.DetachKeys = mergeStrings(c.DetachKeys, other.DetachKeys) + c.EventsLogFilePath = mergeStrings(c.EventsLogFilePath, other.EventsLogFilePath) + c.EventsLogger = mergeStrings(c.EventsLogger, other.EventsLogger) + c.ImageDefaultTransport = mergeStrings(c.ImageDefaultTransport, other.ImageDefaultTransport) + c.InfraCommand = mergeStrings(c.InfraCommand, other.InfraCommand) + c.InfraImage = mergeStrings(c.InfraImage, other.InfraImage) + c.InitPath = mergeStrings(c.InitPath, other.InitPath) + c.LockType = mergeStrings(c.LockType, other.LockType) + c.Namespace = mergeStrings(c.Namespace, other.Namespace) + c.NetworkCmdPath = mergeStrings(c.NetworkCmdPath, other.NetworkCmdPath) + c.OCIRuntime = mergeStrings(c.OCIRuntime, other.OCIRuntime) + c.SignaturePolicyPath = mergeStrings(c.SignaturePolicyPath, other.SignaturePolicyPath) + c.StaticDir = mergeStrings(c.StaticDir, other.StaticDir) + c.TmpDir = mergeStrings(c.TmpDir, other.TmpDir) + c.VolumePath = mergeStrings(c.VolumePath, other.VolumePath) + + // string map of slices + c.OCIRuntimes = mergeStringMaps(c.OCIRuntimes, other.OCIRuntimes) + + // string slices + c.CNIPluginDir = mergeStringSlices(c.CNIPluginDir, other.CNIPluginDir) + c.ConmonEnvVars = mergeStringSlices(c.ConmonEnvVars, other.ConmonEnvVars) + c.ConmonPath = mergeStringSlices(c.ConmonPath, other.ConmonPath) + c.HooksDir = mergeStringSlices(c.HooksDir, other.HooksDir) + c.RuntimePath = mergeStringSlices(c.RuntimePath, other.RuntimePath) + c.RuntimeSupportsJSON = mergeStringSlices(c.RuntimeSupportsJSON, other.RuntimeSupportsJSON) + c.RuntimeSupportsNoCgroups = mergeStringSlices(c.RuntimeSupportsNoCgroups, other.RuntimeSupportsNoCgroups) + + // int64s + c.MaxLogSize = mergeInt64s(c.MaxLogSize, other.MaxLogSize) + + // uint32s + c.NumLocks = mergeUint32s(c.NumLocks, other.NumLocks) + + // bools + c.EnableLabeling = mergeBools(c.EnableLabeling, other.EnableLabeling) + c.EnablePortReservation = mergeBools(c.EnablePortReservation, other.EnablePortReservation) + c.NoPivotRoot = mergeBools(c.NoPivotRoot, other.NoPivotRoot) + c.SDNotify = mergeBools(c.SDNotify, other.SDNotify) + + // state type + if c.StateType == define.InvalidStateStore { + c.StateType = other.StateType + } + + // store options - need to check all fields since some configs might only + // set it partially + c.StorageConfig.RunRoot = mergeStrings(c.StorageConfig.RunRoot, other.StorageConfig.RunRoot) + c.StorageConfig.GraphRoot = mergeStrings(c.StorageConfig.GraphRoot, other.StorageConfig.GraphRoot) + c.StorageConfig.GraphDriverName = mergeStrings(c.StorageConfig.GraphDriverName, other.StorageConfig.GraphDriverName) + c.StorageConfig.GraphDriverOptions = mergeStringSlices(c.StorageConfig.GraphDriverOptions, other.StorageConfig.GraphDriverOptions) + if c.StorageConfig.UIDMap == nil { + c.StorageConfig.UIDMap = other.StorageConfig.UIDMap + } + if c.StorageConfig.GIDMap == nil { + c.StorageConfig.GIDMap = other.StorageConfig.GIDMap + } + + // backwards compat *Set fields + c.StorageConfigRunRootSet = mergeBools(c.StorageConfigRunRootSet, other.StorageConfigRunRootSet) + c.StorageConfigGraphRootSet = mergeBools(c.StorageConfigGraphRootSet, other.StorageConfigGraphRootSet) + c.StorageConfigGraphDriverNameSet = mergeBools(c.StorageConfigGraphDriverNameSet, other.StorageConfigGraphDriverNameSet) + c.VolumePathSet = mergeBools(c.VolumePathSet, other.VolumePathSet) + c.StaticDirSet = mergeBools(c.StaticDirSet, other.StaticDirSet) + c.TmpDirSet = mergeBools(c.TmpDirSet, other.TmpDirSet) + + return nil +} + +// MergeDBConfig merges the configuration from the database. +func (c *Config) MergeDBConfig(dbConfig *DBConfig) error { + + if !c.StorageConfigRunRootSet && dbConfig.StorageTmp != "" { + if c.StorageConfig.RunRoot != dbConfig.StorageTmp && + c.StorageConfig.RunRoot != "" { + logrus.Debugf("Overriding run root %q with %q from database", + c.StorageConfig.RunRoot, dbConfig.StorageTmp) + } + c.StorageConfig.RunRoot = dbConfig.StorageTmp + } + + if !c.StorageConfigGraphRootSet && dbConfig.StorageRoot != "" { + if c.StorageConfig.GraphRoot != dbConfig.StorageRoot && + c.StorageConfig.GraphRoot != "" { + logrus.Debugf("Overriding graph root %q with %q from database", + c.StorageConfig.GraphRoot, dbConfig.StorageRoot) + } + c.StorageConfig.GraphRoot = dbConfig.StorageRoot + } + + if !c.StorageConfigGraphDriverNameSet && dbConfig.GraphDriver != "" { + if c.StorageConfig.GraphDriverName != dbConfig.GraphDriver && + c.StorageConfig.GraphDriverName != "" { + logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve", + c.StorageConfig.GraphDriverName, dbConfig.GraphDriver) + } + c.StorageConfig.GraphDriverName = dbConfig.GraphDriver + } + + if !c.StaticDirSet && dbConfig.LibpodRoot != "" { + if c.StaticDir != dbConfig.LibpodRoot && c.StaticDir != "" { + logrus.Debugf("Overriding static dir %q with %q from database", c.StaticDir, dbConfig.LibpodRoot) + } + c.StaticDir = dbConfig.LibpodRoot + } + + if !c.TmpDirSet && dbConfig.LibpodTmp != "" { + if c.TmpDir != dbConfig.LibpodTmp && c.TmpDir != "" { + logrus.Debugf("Overriding tmp dir %q with %q from database", c.TmpDir, dbConfig.LibpodTmp) + } + c.TmpDir = dbConfig.LibpodTmp + c.EventsLogFilePath = filepath.Join(dbConfig.LibpodTmp, "events", "events.log") + } + + if !c.VolumePathSet && dbConfig.VolumePath != "" { + if c.VolumePath != dbConfig.VolumePath && c.VolumePath != "" { + logrus.Debugf("Overriding volume path %q with %q from database", c.VolumePath, dbConfig.VolumePath) + } + c.VolumePath = dbConfig.VolumePath + } + return nil +} + +func mergeStrings(a, b string) string { + if a == "" { + return b + } + return a +} + +func mergeStringSlices(a, b []string) []string { + if len(a) == 0 && b != nil { + return b + } + return a +} + +func mergeStringMaps(a, b map[string][]string) map[string][]string { + if len(a) == 0 && b != nil { + return b + } + return a +} + +func mergeInt64s(a, b int64) int64 { + if a == 0 { + return b + } + return a +} + +func mergeUint32s(a, b uint32) uint32 { + if a == 0 { + return b + } + return a +} + +func mergeBools(a, b bool) bool { + if !a { + return b + } + return a +} diff --git a/libpod/config/merge_test.go b/libpod/config/merge_test.go new file mode 100644 index 000000000..eb450b273 --- /dev/null +++ b/libpod/config/merge_test.go @@ -0,0 +1,157 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMergeStrings(t *testing.T) { + testData := []struct { + a string + b string + res string + }{ + {"", "", ""}, + {"a", "", "a"}, + {"a", "b", "a"}, + {"", "b", "b"}, + } + for _, data := range testData { + res := mergeStrings(data.a, data.b) + assert.Equal(t, data.res, res) + } +} + +func TestMergeStringSlices(t *testing.T) { + testData := []struct { + a []string + b []string + res []string + }{ + { + nil, nil, nil, + }, + { + nil, + []string{}, + []string{}, + }, + { + []string{}, + nil, + []string{}, + }, + { + []string{}, + []string{}, + []string{}, + }, + { + []string{"a"}, + []string{}, + []string{"a"}, + }, + { + []string{"a"}, + []string{"b"}, + []string{"a"}, + }, + { + []string{}, + []string{"b"}, + []string{"b"}, + }, + } + for _, data := range testData { + res := mergeStringSlices(data.a, data.b) + assert.Equal(t, data.res, res) + } +} + +func TestMergeStringMaps(t *testing.T) { + testData := []struct { + a map[string][]string + b map[string][]string + res map[string][]string + }{ + { + nil, nil, nil, + }, + { + nil, + map[string][]string{}, + map[string][]string{}}, + { + map[string][]string{"a": {"a"}}, + nil, + map[string][]string{"a": {"a"}}, + }, + { + nil, + map[string][]string{"b": {"b"}}, + map[string][]string{"b": {"b"}}, + }, + { + map[string][]string{"a": {"a"}}, + map[string][]string{"b": {"b"}}, + map[string][]string{"a": {"a"}}, + }, + } + for _, data := range testData { + res := mergeStringMaps(data.a, data.b) + assert.Equal(t, data.res, res) + } +} + +func TestMergeInts64(t *testing.T) { + testData := []struct { + a int64 + b int64 + res int64 + }{ + {int64(0), int64(0), int64(0)}, + {int64(1), int64(0), int64(1)}, + {int64(0), int64(1), int64(1)}, + {int64(2), int64(1), int64(2)}, + {int64(-1), int64(1), int64(-1)}, + {int64(0), int64(-1), int64(-1)}, + } + for _, data := range testData { + res := mergeInt64s(data.a, data.b) + assert.Equal(t, data.res, res) + } +} +func TestMergeUint32(t *testing.T) { + testData := []struct { + a uint32 + b uint32 + res uint32 + }{ + {uint32(0), uint32(0), uint32(0)}, + {uint32(1), uint32(0), uint32(1)}, + {uint32(0), uint32(1), uint32(1)}, + {uint32(2), uint32(1), uint32(2)}, + } + for _, data := range testData { + res := mergeUint32s(data.a, data.b) + assert.Equal(t, data.res, res) + } +} + +func TestMergeBools(t *testing.T) { + testData := []struct { + a bool + b bool + res bool + }{ + {false, false, false}, + {true, false, true}, + {false, true, true}, + {true, true, true}, + } + for _, data := range testData { + res := mergeBools(data.a, data.b) + assert.Equal(t, data.res, res) + } +} diff --git a/libpod/config/testdata/empty.conf b/libpod/config/testdata/empty.conf new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libpod/config/testdata/empty.conf diff --git a/libpod/config/testdata/libpod.conf b/libpod/config/testdata/libpod.conf new file mode 120000 index 000000000..17d09fe4a --- /dev/null +++ b/libpod/config/testdata/libpod.conf @@ -0,0 +1 @@ +../../../libpod.conf
\ No newline at end of file |