549 lines
19 KiB
Go
549 lines
19 KiB
Go
package types
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
cfg "github.com/containers/storage/pkg/config"
|
|
"github.com/containers/storage/pkg/fileutils"
|
|
"github.com/containers/storage/pkg/homedir"
|
|
"github.com/containers/storage/pkg/idtools"
|
|
"github.com/containers/storage/pkg/unshare"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// TOML-friendly explicit tables used for conversions.
|
|
type TomlConfig struct {
|
|
Storage struct {
|
|
Driver string `toml:"driver,omitempty"`
|
|
DriverPriority []string `toml:"driver_priority,omitempty"`
|
|
RunRoot string `toml:"runroot,omitempty"`
|
|
ImageStore string `toml:"imagestore,omitempty"`
|
|
GraphRoot string `toml:"graphroot,omitempty"`
|
|
RootlessStoragePath string `toml:"rootless_storage_path,omitempty"`
|
|
TransientStore bool `toml:"transient_store,omitempty"`
|
|
Options cfg.OptionsConfig `toml:"options,omitempty"`
|
|
} `toml:"storage"`
|
|
}
|
|
|
|
const (
|
|
overlayDriver = "overlay"
|
|
overlay2 = "overlay2"
|
|
storageConfEnv = "CONTAINERS_STORAGE_CONF"
|
|
)
|
|
|
|
var (
|
|
defaultStoreOptionsOnce sync.Once
|
|
loadDefaultStoreOptionsErr error
|
|
once sync.Once
|
|
storeOptions StoreOptions
|
|
storeError error
|
|
defaultConfigFileSet bool
|
|
// defaultConfigFile path to the system wide storage.conf file
|
|
defaultConfigFile = SystemConfigFile
|
|
// DefaultStoreOptions is a reasonable default set of options.
|
|
defaultStoreOptions StoreOptions
|
|
)
|
|
|
|
func loadDefaultStoreOptions() {
|
|
defaultStoreOptions.GraphDriverName = ""
|
|
|
|
setDefaults := func() {
|
|
// reload could set values to empty for run and graph root if config does not contains anything
|
|
if defaultStoreOptions.RunRoot == "" {
|
|
defaultStoreOptions.RunRoot = defaultRunRoot
|
|
}
|
|
if defaultStoreOptions.GraphRoot == "" {
|
|
defaultStoreOptions.GraphRoot = defaultGraphRoot
|
|
}
|
|
}
|
|
setDefaults()
|
|
|
|
if path, ok := os.LookupEnv(storageConfEnv); ok {
|
|
defaultOverrideConfigFile = path
|
|
if err := ReloadConfigurationFileIfNeeded(path, &defaultStoreOptions); err != nil {
|
|
loadDefaultStoreOptionsErr = err
|
|
return
|
|
}
|
|
setDefaults()
|
|
return
|
|
}
|
|
|
|
if path, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok {
|
|
homeConfigFile := filepath.Join(path, "containers", "storage.conf")
|
|
if err := fileutils.Exists(homeConfigFile); err == nil {
|
|
// user storage.conf in XDG_CONFIG_HOME if it exists
|
|
defaultOverrideConfigFile = homeConfigFile
|
|
} else {
|
|
if !os.IsNotExist(err) {
|
|
loadDefaultStoreOptionsErr = err
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
err := fileutils.Exists(defaultOverrideConfigFile)
|
|
if err == nil {
|
|
// The DefaultConfigFile() function returns the path
|
|
// of the used storage.conf file, by returning defaultConfigFile
|
|
// If override exists containers/storage uses it by default.
|
|
defaultConfigFile = defaultOverrideConfigFile
|
|
if err := ReloadConfigurationFileIfNeeded(defaultOverrideConfigFile, &defaultStoreOptions); err != nil {
|
|
loadDefaultStoreOptionsErr = err
|
|
return
|
|
}
|
|
setDefaults()
|
|
return
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
|
logrus.Warningf("Attempting to use %s, %v", defaultConfigFile, err)
|
|
}
|
|
if err := ReloadConfigurationFileIfNeeded(defaultConfigFile, &defaultStoreOptions); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
loadDefaultStoreOptionsErr = err
|
|
return
|
|
}
|
|
setDefaults()
|
|
}
|
|
|
|
// loadStoreOptions returns the default storage ops for containers
|
|
func loadStoreOptions() (StoreOptions, error) {
|
|
storageConf, err := DefaultConfigFile()
|
|
if err != nil {
|
|
return defaultStoreOptions, err
|
|
}
|
|
return loadStoreOptionsFromConfFile(storageConf)
|
|
}
|
|
|
|
// usePerUserStorage returns whether the user private storage must be used.
|
|
// We cannot simply use the unshare.IsRootless() condition, because
|
|
// that checks only if the current process needs a user namespace to
|
|
// work and it would break cases where the process is already created
|
|
// in a user namespace (e.g. nested Podman/Buildah) and the desired
|
|
// behavior is to use system paths instead of user private paths.
|
|
func usePerUserStorage() bool {
|
|
return unshare.IsRootless() && unshare.GetRootlessUID() != 0
|
|
}
|
|
|
|
// loadStoreOptionsFromConfFile is an internal implementation detail of DefaultStoreOptions to allow testing.
|
|
// Everyone but the tests this is intended for should only call loadStoreOptions, never this function.
|
|
func loadStoreOptionsFromConfFile(storageConf string) (StoreOptions, error) {
|
|
var (
|
|
defaultRootlessRunRoot string
|
|
defaultRootlessGraphRoot string
|
|
err error
|
|
)
|
|
|
|
defaultStoreOptionsOnce.Do(loadDefaultStoreOptions)
|
|
if loadDefaultStoreOptionsErr != nil {
|
|
return StoreOptions{}, loadDefaultStoreOptionsErr
|
|
}
|
|
storageOpts := defaultStoreOptions
|
|
if usePerUserStorage() {
|
|
storageOpts, err = getRootlessStorageOpts(storageOpts)
|
|
if err != nil {
|
|
return storageOpts, err
|
|
}
|
|
}
|
|
err = fileutils.Exists(storageConf)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return storageOpts, err
|
|
}
|
|
if err == nil && !defaultConfigFileSet {
|
|
defaultRootlessRunRoot = storageOpts.RunRoot
|
|
defaultRootlessGraphRoot = storageOpts.GraphRoot
|
|
storageOpts = StoreOptions{}
|
|
reloadConfigurationFileIfNeeded(storageConf, &storageOpts)
|
|
if usePerUserStorage() {
|
|
// If the file did not specify a graphroot or runroot,
|
|
// set sane defaults so we don't try and use root-owned
|
|
// directories
|
|
if storageOpts.RunRoot == "" {
|
|
storageOpts.RunRoot = defaultRootlessRunRoot
|
|
}
|
|
if storageOpts.GraphRoot == "" {
|
|
if storageOpts.RootlessStoragePath != "" {
|
|
storageOpts.GraphRoot = storageOpts.RootlessStoragePath
|
|
} else {
|
|
storageOpts.GraphRoot = defaultRootlessGraphRoot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if storageOpts.RunRoot == "" {
|
|
return storageOpts, fmt.Errorf("runroot must be set")
|
|
}
|
|
rootlessUID := unshare.GetRootlessUID()
|
|
runRoot, err := expandEnvPath(storageOpts.RunRoot, rootlessUID)
|
|
if err != nil {
|
|
return storageOpts, err
|
|
}
|
|
storageOpts.RunRoot = runRoot
|
|
|
|
if storageOpts.GraphRoot == "" {
|
|
return storageOpts, fmt.Errorf("graphroot must be set")
|
|
}
|
|
graphRoot, err := expandEnvPath(storageOpts.GraphRoot, rootlessUID)
|
|
if err != nil {
|
|
return storageOpts, err
|
|
}
|
|
storageOpts.GraphRoot = graphRoot
|
|
|
|
if storageOpts.RootlessStoragePath != "" {
|
|
storagePath, err := expandEnvPath(storageOpts.RootlessStoragePath, rootlessUID)
|
|
if err != nil {
|
|
return storageOpts, err
|
|
}
|
|
storageOpts.RootlessStoragePath = storagePath
|
|
}
|
|
|
|
if storageOpts.ImageStore != "" && storageOpts.ImageStore == storageOpts.GraphRoot {
|
|
return storageOpts, fmt.Errorf("imagestore %s must either be not set or be a different than graphroot", storageOpts.ImageStore)
|
|
}
|
|
|
|
return storageOpts, nil
|
|
}
|
|
|
|
// UpdateOptions should be called iff container engine received a SIGHUP,
|
|
// otherwise use DefaultStoreOptions
|
|
func UpdateStoreOptions() (StoreOptions, error) {
|
|
storeOptions, storeError = loadStoreOptions()
|
|
return storeOptions, storeError
|
|
}
|
|
|
|
// DefaultStoreOptions returns the default storage ops for containers
|
|
func DefaultStoreOptions() (StoreOptions, error) {
|
|
once.Do(func() {
|
|
storeOptions, storeError = loadStoreOptions()
|
|
})
|
|
return storeOptions, storeError
|
|
}
|
|
|
|
// StoreOptions is used for passing initialization options to GetStore(), for
|
|
// initializing a Store object and the underlying storage that it controls.
|
|
type StoreOptions struct {
|
|
// RunRoot is the filesystem path under which we can store run-time
|
|
// information, such as the locations of active mount points, that we
|
|
// want to lose if the host is rebooted.
|
|
RunRoot string `json:"runroot,omitempty"`
|
|
// GraphRoot is the filesystem path under which we will store the
|
|
// contents of layers, images, and containers.
|
|
GraphRoot string `json:"root,omitempty"`
|
|
// Image Store is the alternate location of image store if a location
|
|
// separate from the container store is required.
|
|
ImageStore string `json:"imagestore,omitempty"`
|
|
// RootlessStoragePath is the storage path for rootless users
|
|
// default $HOME/.local/share/containers/storage
|
|
RootlessStoragePath string `toml:"rootless_storage_path"`
|
|
// If the driver is not specified, the best suited driver will be picked
|
|
// either from GraphDriverPriority, if specified, or from the platform
|
|
// dependent priority list (in that order).
|
|
GraphDriverName string `json:"driver,omitempty"`
|
|
// GraphDriverPriority is a list of storage drivers that will be tried
|
|
// to initialize the Store for a given RunRoot and GraphRoot unless a
|
|
// GraphDriverName is set.
|
|
// This list can be used to define a custom order in which the drivers
|
|
// will be tried.
|
|
GraphDriverPriority []string `json:"driver-priority,omitempty"`
|
|
// GraphDriverOptions are driver-specific options.
|
|
GraphDriverOptions []string `json:"driver-options,omitempty"`
|
|
// UIDMap and GIDMap are used for setting up a container's root filesystem
|
|
// for use inside of a user namespace where UID mapping is being used.
|
|
UIDMap []idtools.IDMap `json:"uidmap,omitempty"`
|
|
GIDMap []idtools.IDMap `json:"gidmap,omitempty"`
|
|
// RootAutoNsUser is the user used to pick a subrange when automatically setting
|
|
// a user namespace for the root user.
|
|
RootAutoNsUser string `json:"root_auto_ns_user,omitempty"`
|
|
// AutoNsMinSize is the minimum size for an automatic user namespace.
|
|
AutoNsMinSize uint32 `json:"auto_userns_min_size,omitempty"`
|
|
// AutoNsMaxSize is the maximum size for an automatic user namespace.
|
|
AutoNsMaxSize uint32 `json:"auto_userns_max_size,omitempty"`
|
|
// PullOptions specifies options to be handed to pull managers
|
|
// This API is experimental and can be changed without bumping the major version number.
|
|
PullOptions map[string]string `toml:"pull_options"`
|
|
// DisableVolatile doesn't allow volatile mounts when it is set.
|
|
DisableVolatile bool `json:"disable-volatile,omitempty"`
|
|
// If transient, don't persist containers over boot (stores db in runroot)
|
|
TransientStore bool `json:"transient_store,omitempty"`
|
|
}
|
|
|
|
// isRootlessDriver returns true if the given storage driver is valid for containers running as non root
|
|
func isRootlessDriver(driver string) bool {
|
|
validDrivers := map[string]bool{
|
|
"btrfs": true,
|
|
"overlay": true,
|
|
"overlay2": true,
|
|
"vfs": true,
|
|
}
|
|
return validDrivers[driver]
|
|
}
|
|
|
|
// getRootlessStorageOpts returns the storage opts for containers running as non root
|
|
func getRootlessStorageOpts(systemOpts StoreOptions) (StoreOptions, error) {
|
|
var opts StoreOptions
|
|
|
|
rootlessUID := unshare.GetRootlessUID()
|
|
|
|
dataDir, err := homedir.GetDataHome()
|
|
if err != nil {
|
|
return opts, err
|
|
}
|
|
|
|
rootlessRuntime, err := homedir.GetRuntimeDir()
|
|
if err != nil {
|
|
return opts, err
|
|
}
|
|
|
|
opts.RunRoot = filepath.Join(rootlessRuntime, "containers")
|
|
if err := os.MkdirAll(opts.RunRoot, 0o700); err != nil {
|
|
return opts, fmt.Errorf("unable to make rootless runtime: %w", err)
|
|
}
|
|
|
|
opts.PullOptions = systemOpts.PullOptions
|
|
if systemOpts.RootlessStoragePath != "" {
|
|
opts.GraphRoot, err = expandEnvPath(systemOpts.RootlessStoragePath, rootlessUID)
|
|
if err != nil {
|
|
return opts, err
|
|
}
|
|
} else {
|
|
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
|
|
}
|
|
|
|
if driver := systemOpts.GraphDriverName; isRootlessDriver(driver) {
|
|
opts.GraphDriverName = driver
|
|
}
|
|
if driver := os.Getenv("STORAGE_DRIVER"); driver != "" {
|
|
opts.GraphDriverName = driver
|
|
}
|
|
if opts.GraphDriverName == overlay2 {
|
|
logrus.Warnf("Switching default driver from overlay2 to the equivalent overlay driver")
|
|
opts.GraphDriverName = overlayDriver
|
|
}
|
|
|
|
// If the configuration file was explicitly set, then copy all the options
|
|
// present.
|
|
if defaultConfigFileSet {
|
|
opts.GraphDriverOptions = systemOpts.GraphDriverOptions
|
|
opts.ImageStore = systemOpts.ImageStore
|
|
} else if opts.GraphDriverName == overlayDriver {
|
|
for _, o := range systemOpts.GraphDriverOptions {
|
|
if strings.Contains(o, "ignore_chown_errors") {
|
|
opts.GraphDriverOptions = append(opts.GraphDriverOptions, o)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if opts.GraphDriverName == "" {
|
|
if len(systemOpts.GraphDriverPriority) == 0 {
|
|
dirEntries, err := os.ReadDir(opts.GraphRoot)
|
|
if err == nil {
|
|
for _, entry := range dirEntries {
|
|
if strings.HasSuffix(entry.Name(), "-images") {
|
|
opts.GraphDriverName = strings.TrimSuffix(entry.Name(), "-images")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if opts.GraphDriverName == "" {
|
|
if canUseRootlessOverlay(opts.GraphRoot, opts.RunRoot) {
|
|
opts.GraphDriverName = overlayDriver
|
|
} else {
|
|
opts.GraphDriverName = "vfs"
|
|
}
|
|
}
|
|
} else {
|
|
opts.GraphDriverPriority = systemOpts.GraphDriverPriority
|
|
}
|
|
}
|
|
|
|
if os.Getenv("STORAGE_OPTS") != "" {
|
|
opts.GraphDriverOptions = append(opts.GraphDriverOptions, strings.Split(os.Getenv("STORAGE_OPTS"), ",")...)
|
|
}
|
|
|
|
return opts, nil
|
|
}
|
|
|
|
var prevReloadConfig = struct {
|
|
storeOptions *StoreOptions
|
|
mod time.Time
|
|
mutex sync.Mutex
|
|
configFile string
|
|
}{}
|
|
|
|
// SetDefaultConfigFilePath sets the default configuration to the specified path
|
|
func SetDefaultConfigFilePath(path string) error {
|
|
defaultConfigFile = path
|
|
defaultConfigFileSet = true
|
|
return ReloadConfigurationFileIfNeeded(defaultConfigFile, &defaultStoreOptions)
|
|
}
|
|
|
|
func ReloadConfigurationFileIfNeeded(configFile string, storeOptions *StoreOptions) error {
|
|
prevReloadConfig.mutex.Lock()
|
|
defer prevReloadConfig.mutex.Unlock()
|
|
|
|
fi, err := os.Stat(configFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mtime := fi.ModTime()
|
|
if prevReloadConfig.storeOptions != nil && prevReloadConfig.mod == mtime && prevReloadConfig.configFile == configFile {
|
|
*storeOptions = *prevReloadConfig.storeOptions
|
|
return nil
|
|
}
|
|
|
|
if err := ReloadConfigurationFile(configFile, storeOptions); err != nil {
|
|
return err
|
|
}
|
|
|
|
cOptions := *storeOptions
|
|
prevReloadConfig.storeOptions = &cOptions
|
|
prevReloadConfig.mod = mtime
|
|
prevReloadConfig.configFile = configFile
|
|
return nil
|
|
}
|
|
|
|
// ReloadConfigurationFile parses the specified configuration file and overrides
|
|
// the configuration in storeOptions.
|
|
func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) error {
|
|
config := new(TomlConfig)
|
|
|
|
meta, err := toml.DecodeFile(configFile, &config)
|
|
if err == nil {
|
|
keys := meta.Undecoded()
|
|
if len(keys) > 0 {
|
|
logrus.Warningf("Failed to decode the keys %q from %q", keys, configFile)
|
|
}
|
|
} else {
|
|
if !os.IsNotExist(err) {
|
|
logrus.Warningf("Failed to read %s %v\n", configFile, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Clear storeOptions of previous settings
|
|
*storeOptions = StoreOptions{}
|
|
if config.Storage.Driver != "" {
|
|
storeOptions.GraphDriverName = config.Storage.Driver
|
|
}
|
|
if os.Getenv("STORAGE_DRIVER") != "" {
|
|
config.Storage.Driver = os.Getenv("STORAGE_DRIVER")
|
|
storeOptions.GraphDriverName = config.Storage.Driver
|
|
}
|
|
if storeOptions.GraphDriverName == overlay2 {
|
|
logrus.Warnf("Switching default driver from overlay2 to the equivalent overlay driver")
|
|
storeOptions.GraphDriverName = overlayDriver
|
|
}
|
|
storeOptions.GraphDriverPriority = config.Storage.DriverPriority
|
|
if storeOptions.GraphDriverName == "" && len(storeOptions.GraphDriverPriority) == 0 {
|
|
logrus.Warnf("The storage 'driver' option should be set in %s. A driver was picked automatically.", configFile)
|
|
}
|
|
if config.Storage.RunRoot != "" {
|
|
storeOptions.RunRoot = config.Storage.RunRoot
|
|
}
|
|
if config.Storage.GraphRoot != "" {
|
|
storeOptions.GraphRoot = config.Storage.GraphRoot
|
|
}
|
|
if config.Storage.ImageStore != "" {
|
|
storeOptions.ImageStore = config.Storage.ImageStore
|
|
}
|
|
if config.Storage.RootlessStoragePath != "" {
|
|
storeOptions.RootlessStoragePath = config.Storage.RootlessStoragePath
|
|
}
|
|
for _, s := range config.Storage.Options.AdditionalImageStores {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.imagestore=%s", config.Storage.Driver, s))
|
|
}
|
|
for _, s := range config.Storage.Options.AdditionalLayerStores {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.additionallayerstore=%s", config.Storage.Driver, s))
|
|
}
|
|
if config.Storage.Options.Size != "" {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.size=%s", config.Storage.Driver, config.Storage.Options.Size))
|
|
}
|
|
if config.Storage.Options.MountProgram != "" {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.mount_program=%s", config.Storage.Driver, config.Storage.Options.MountProgram))
|
|
}
|
|
if config.Storage.Options.SkipMountHome != "" {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.skip_mount_home=%s", config.Storage.Driver, config.Storage.Options.SkipMountHome))
|
|
}
|
|
if config.Storage.Options.IgnoreChownErrors != "" {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.ignore_chown_errors=%s", config.Storage.Driver, config.Storage.Options.IgnoreChownErrors))
|
|
}
|
|
if config.Storage.Options.ForceMask != 0 {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.force_mask=%o", config.Storage.Driver, config.Storage.Options.ForceMask))
|
|
}
|
|
if config.Storage.Options.MountOpt != "" {
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.mountopt=%s", config.Storage.Driver, config.Storage.Options.MountOpt))
|
|
}
|
|
storeOptions.RootAutoNsUser = config.Storage.Options.RootAutoUsernsUser
|
|
if config.Storage.Options.AutoUsernsMinSize > 0 {
|
|
storeOptions.AutoNsMinSize = config.Storage.Options.AutoUsernsMinSize
|
|
}
|
|
if config.Storage.Options.AutoUsernsMaxSize > 0 {
|
|
storeOptions.AutoNsMaxSize = config.Storage.Options.AutoUsernsMaxSize
|
|
}
|
|
if config.Storage.Options.PullOptions != nil {
|
|
storeOptions.PullOptions = config.Storage.Options.PullOptions
|
|
}
|
|
|
|
storeOptions.DisableVolatile = config.Storage.Options.DisableVolatile
|
|
storeOptions.TransientStore = config.Storage.TransientStore
|
|
|
|
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, cfg.GetGraphDriverOptions(storeOptions.GraphDriverName, config.Storage.Options)...)
|
|
|
|
if opts, ok := os.LookupEnv("STORAGE_OPTS"); ok {
|
|
storeOptions.GraphDriverOptions = strings.Split(opts, ",")
|
|
}
|
|
if len(storeOptions.GraphDriverOptions) == 1 && storeOptions.GraphDriverOptions[0] == "" {
|
|
storeOptions.GraphDriverOptions = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Options() (StoreOptions, error) {
|
|
defaultStoreOptionsOnce.Do(loadDefaultStoreOptions)
|
|
return defaultStoreOptions, loadDefaultStoreOptionsErr
|
|
}
|
|
|
|
// Save overwrites the tomlConfig in storage.conf with the given conf
|
|
func Save(conf TomlConfig) error {
|
|
configFile, err := DefaultConfigFile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = os.Remove(configFile); !os.IsNotExist(err) && err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.Create(configFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return toml.NewEncoder(f).Encode(conf)
|
|
}
|
|
|
|
// StorageConfig is used to retrieve the storage.conf toml in order to overwrite it
|
|
func StorageConfig() (*TomlConfig, error) {
|
|
config := new(TomlConfig)
|
|
|
|
configFile, err := DefaultConfigFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = toml.DecodeFile(configFile, &config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|