mirror of
https://github.com/containers/podman.git
synced 2025-10-17 03:04:21 +08:00
Merge pull request #3385 from mheon/read_only_validate
Make configuration validation not require a DB commit
This commit is contained in:
@ -72,98 +72,160 @@ var (
|
|||||||
volPathKey = []byte(volPathName)
|
volPathKey = []byte(volPathName)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This represents a field in the runtime configuration that will be validated
|
||||||
|
// against the DB to ensure no configuration mismatches occur.
|
||||||
|
type dbConfigValidation struct {
|
||||||
|
name string // Only used for error messages
|
||||||
|
runtimeValue string
|
||||||
|
key []byte
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the configuration of the database is compatible with the
|
// Check if the configuration of the database is compatible with the
|
||||||
// configuration of the runtime opening it
|
// configuration of the runtime opening it
|
||||||
// If there is no runtime configuration loaded, load our own
|
// If there is no runtime configuration loaded, load our own
|
||||||
func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
|
func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to validate the following things
|
||||||
|
checks := []dbConfigValidation{
|
||||||
|
{
|
||||||
|
"OS",
|
||||||
|
runtime.GOOS,
|
||||||
|
osKey,
|
||||||
|
runtime.GOOS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"libpod root directory (staticdir)",
|
||||||
|
rt.config.StaticDir,
|
||||||
|
staticDirKey,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"libpod temporary files directory (tmpdir)",
|
||||||
|
rt.config.TmpDir,
|
||||||
|
tmpDirKey,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"storage temporary directory (runroot)",
|
||||||
|
rt.config.StorageConfig.RunRoot,
|
||||||
|
runRootKey,
|
||||||
|
storeOpts.RunRoot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"storage graph root directory (graphroot)",
|
||||||
|
rt.config.StorageConfig.GraphRoot,
|
||||||
|
graphRootKey,
|
||||||
|
storeOpts.GraphRoot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"storage graph driver",
|
||||||
|
rt.config.StorageConfig.GraphDriverName,
|
||||||
|
graphDriverKey,
|
||||||
|
storeOpts.GraphDriverName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"volume path",
|
||||||
|
rt.config.VolumePath,
|
||||||
|
volPathKey,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// These fields were missing and will have to be recreated.
|
||||||
|
missingFields := []dbConfigValidation{}
|
||||||
|
|
||||||
|
// Let's try and validate read-only first
|
||||||
|
err = db.View(func(tx *bolt.Tx) error {
|
||||||
configBkt, err := getRuntimeConfigBucket(tx)
|
configBkt, err := getRuntimeConfigBucket(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateDBAgainstConfig(configBkt, "OS", runtime.GOOS, osKey, runtime.GOOS); err != nil {
|
for _, check := range checks {
|
||||||
return err
|
exists, err := readOnlyValidateConfig(configBkt, check)
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateDBAgainstConfig(configBkt, "libpod root directory (staticdir)",
|
|
||||||
rt.config.StaticDir, staticDirKey, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateDBAgainstConfig(configBkt, "libpod temporary files directory (tmpdir)",
|
|
||||||
rt.config.TmpDir, tmpDirKey, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := validateDBAgainstConfig(configBkt, "storage temporary directory (runroot)",
|
if !exists {
|
||||||
rt.config.StorageConfig.RunRoot, runRootKey,
|
missingFields = append(missingFields, check)
|
||||||
storeOpts.RunRoot); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateDBAgainstConfig(configBkt, "storage graph root directory (graphroot)",
|
return nil
|
||||||
rt.config.StorageConfig.GraphRoot, graphRootKey,
|
|
||||||
storeOpts.GraphRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateDBAgainstConfig(configBkt, "storage graph driver",
|
|
||||||
rt.config.StorageConfig.GraphDriverName,
|
|
||||||
graphDriverKey,
|
|
||||||
storeOpts.GraphDriverName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return validateDBAgainstConfig(configBkt, "volume path",
|
|
||||||
rt.config.VolumePath, volPathKey, "")
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate a configuration entry in the DB against current runtime config
|
if len(missingFields) == 0 {
|
||||||
// If the given configuration key does not exist it will be created
|
return nil
|
||||||
// If the given runtimeValue or value retrieved from the database are the empty
|
}
|
||||||
// string and defaultValue is not, defaultValue will be checked instead. This
|
|
||||||
// ensures that we will not fail on configuration changes in configured c/storage.
|
// Populate missing fields
|
||||||
func validateDBAgainstConfig(bucket *bolt.Bucket, fieldName, runtimeValue string, keyName []byte, defaultValue string) error {
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
keyBytes := bucket.Get(keyName)
|
configBkt, err := getRuntimeConfigBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, missing := range missingFields {
|
||||||
|
dbValue := []byte(missing.runtimeValue)
|
||||||
|
if missing.runtimeValue == "" && missing.defaultValue != "" {
|
||||||
|
dbValue = []byte(missing.defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := configBkt.Put(missing.key, dbValue); err != nil {
|
||||||
|
return errors.Wrapf(err, "error updating %s in DB runtime config", missing.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt a read-only validation of a configuration entry in the DB against an
|
||||||
|
// element of the current runtime configuration.
|
||||||
|
// If the configuration key in question does not exist, (false, nil) will be
|
||||||
|
// returned.
|
||||||
|
// If the configuration key does exist, and matches the runtime configuration
|
||||||
|
// successfully, (true, nil) is returned.
|
||||||
|
// An error is only returned when validation fails.
|
||||||
|
// if the given runtimeValue or value retrieved from the database are empty,
|
||||||
|
// and defaultValue is not, defaultValue will be checked instead. This ensures
|
||||||
|
// that we will not fail on configuration changes in c/storage (where we may
|
||||||
|
// pass the empty string to use defaults).
|
||||||
|
func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) {
|
||||||
|
keyBytes := bucket.Get(toCheck.key)
|
||||||
if keyBytes == nil {
|
if keyBytes == nil {
|
||||||
dbValue := []byte(runtimeValue)
|
// False return indicates missing key
|
||||||
if runtimeValue == "" && defaultValue != "" {
|
return false, nil
|
||||||
dbValue = []byte(defaultValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bucket.Put(keyName, dbValue); err != nil {
|
dbValue := string(keyBytes)
|
||||||
return errors.Wrapf(err, "error updating %s in DB runtime config", fieldName)
|
|
||||||
}
|
if toCheck.runtimeValue != dbValue {
|
||||||
} else {
|
// If the runtime value is the empty string and default is not,
|
||||||
if runtimeValue != string(keyBytes) {
|
// check against default.
|
||||||
// If runtimeValue is the empty string, check against
|
if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
|
||||||
// the default
|
return true, nil
|
||||||
if runtimeValue == "" && defaultValue != "" &&
|
|
||||||
string(keyBytes) == defaultValue {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If DB value is the empty string, check that the
|
// If the DB value is the empty string, check that the runtime
|
||||||
// runtime value is the default
|
// value is the default.
|
||||||
if string(keyBytes) == "" && defaultValue != "" &&
|
if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue {
|
||||||
runtimeValue == defaultValue {
|
return true, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrapf(ErrDBBadConfig, "database %s %s does not match our %s %s",
|
return true, errors.Wrapf(ErrDBBadConfig, "database %s %q does not match our %s %q",
|
||||||
fieldName, string(keyBytes), fieldName, runtimeValue)
|
toCheck.name, dbValue, toCheck.name, toCheck.runtimeValue)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a connection to the database.
|
// Open a connection to the database.
|
||||||
|
Reference in New Issue
Block a user