config: Add automatic conversion

This patch introduces automatic conversion of config files to the latest
format. This will seamlessly transition all users to the latest format
without the need for manually converting the config with the `mixer
config convert` command.

This patch also introduces the config_conver.go file, which centralizes
all conversion operations and also the parsing for the legacy config in
order to keep config.go clean and focused on current config features.

Signed-off-by: Rodrigo Chiossi <rodrigo.chiossi@intel.com>
This commit is contained in:
Rodrigo Chiossi
2018-09-08 00:11:44 +02:00
committed by Rodrigo Chiossi
parent 41f60493bc
commit 7f1e3f33a3
2 changed files with 224 additions and 141 deletions
+9 -141
View File
@@ -15,7 +15,6 @@
package config
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
@@ -34,9 +33,6 @@ import (
// This is an experimental feature.
var UseNewConfig = true
// CurrentConfigVersion holds the current version of the config file
const CurrentConfigVersion = "1.0"
// MixConfig represents the config parameters found in the builder config file.
type MixConfig struct {
Builder builderConf
@@ -256,7 +252,10 @@ func (config *MixConfig) LoadConfig(filename string) error {
if err := config.initConfigPath(filename); err != nil {
return err
}
if err := config.Parse(); err != nil {
if err := config.parseVersionAndConvert(); err != nil {
return err
}
if err := config.parse(); err != nil {
return err
}
if err := config.expandEnv(); err != nil {
@@ -266,118 +265,9 @@ func (config *MixConfig) LoadConfig(filename string) error {
return config.validate()
}
func (config *MixConfig) parseVersion(reader *bufio.Reader) (bool, error) {
verBytes, err := reader.ReadString('\n')
if err != nil {
return false, err
}
r := regexp.MustCompile("^#VERSION ([0-9]+.[0-9])+\n")
match := r.FindStringSubmatch(string(verBytes))
if len(match) != 2 {
return false, nil
}
config.version = match[1]
return true, nil
}
// Parse reads the values from a config file without performing validation or env expansion
func (config *MixConfig) Parse() error {
f, err := os.Open(config.filename)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
// Read config version
reader := bufio.NewReader(f)
found, err := config.parseVersion(reader)
if err != nil {
return err
}
if found {
// Version only exists in New Config, so parse builder.conf as TOML
if _, err := toml.DecodeReader(reader, &config); err != nil {
return err
}
} else {
// Assume missing version and try to parse as TOML
if _, err := toml.DecodeFile(config.filename, &config); err != nil {
// Try parsing as INI
if err := config.legacyParse(); err != nil {
return err
}
}
}
return nil
}
func (config *MixConfig) legacyParse() error {
UseNewConfig = false
lines, err := helpers.ReadFileAndSplit(config.filename)
if err != nil {
return errors.Wrap(err, "Failed to read buildconf")
}
var format string
// Map the builder values to the regex here to make it easier to assign
fields := []struct {
re string
dest *string
required bool
}{
// [Builder]
{`^CERT\s*=\s*`, &config.Builder.Cert, true},
{`^SERVER_STATE_DIR\s*=\s*`, &config.Builder.ServerStateDir, true},
{`^VERSIONS_PATH\s*=\s*`, &config.Builder.VersionPath, true},
{`^YUM_CONF\s*=\s*`, &config.Builder.DNFConf, true},
// [Swupd]
{`^BUNDLE\s*=\s*`, &config.Swupd.Bundle, false},
{`^CONTENTURL\s*=\s*`, &config.Swupd.ContentURL, false},
{`^FORMAT\s*=\s*`, &format, true},
{`^VERSIONURL\s*=\s*`, &config.Swupd.VersionURL, false},
// [Server]
{`^debuginfo_banned\s*=\s*`, &config.Server.DebugInfoBanned, false},
{`^debuginfo_lib\s*=\s*`, &config.Server.DebugInfoLib, false},
{`^debuginfo_src\s*=\s*`, &config.Server.DebugInfoSrc, false},
// [Mixer]
{`^LOCAL_BUNDLE_DIR\s*=\s*`, &config.Mixer.LocalBundleDir, false},
{`^LOCAL_REPO_DIR\s*=\s*`, &config.Mixer.LocalRepoDir, false},
{`^LOCAL_RPM_DIR\s*=\s*`, &config.Mixer.LocalRPMDir, false},
{`^DOCKER_IMAGE_PATH\s*=\s*`, &config.Mixer.DockerImgPath, false},
}
for _, h := range fields {
r := regexp.MustCompile(h.re)
for _, i := range lines {
if m := r.FindIndex([]byte(i)); m != nil {
*h.dest = i[m[1]:]
}
}
}
config.hasFormatField = format != ""
if config.Mixer.LocalBundleDir == "" {
pwd, err := os.Getwd()
if err != nil {
return err
}
config.Mixer.LocalBundleDir = filepath.Join(pwd, "local-bundles")
fmt.Printf("WARNING: LOCAL_BUNDLE_DIR not found in builder.conf. Falling back to %q.\n", config.Mixer.LocalBundleDir)
fmt.Println("Please set this value to the location you want local bundle definition files to be stored.")
}
return nil
func (config *MixConfig) parse() error {
_, err := toml.DecodeFile(config.filename, &config)
return err
}
func (config *MixConfig) expandEnv() error {
@@ -438,7 +328,7 @@ func (config *MixConfig) validate() error {
}
if config.hasFormatField {
fmt.Println("WARNING: Format value in builder.conf ignored. Using the value in mixer.state file")
fmt.Println("WARNING: FORMAT value was transferred to mixer.state file")
}
return nil
@@ -454,29 +344,7 @@ func (config *MixConfig) Convert(filename string) error {
return err
}
// Reset version for files without versioning
config.version = "0.0"
if err := config.Parse(); err != nil {
return err
}
// Config is already in the current format
if config.version == CurrentConfigVersion {
return nil
}
if err := helpers.CopyFile(config.filename+".bkp", config.filename); err != nil {
return err
}
// Make sure the converted config is in the New Format
UseNewConfig = true
// Set config to the current format
config.version = CurrentConfigVersion
return config.SaveConfig()
return config.parseVersionAndConvert()
}
// Print print variables and values of a MixConfig struct
+215
View File
@@ -0,0 +1,215 @@
// Copyright © 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"github.com/BurntSushi/toml"
"github.com/clearlinux/mixer-tools/helpers"
"github.com/pkg/errors"
)
// CurrentConfigVersion holds the current version of the config file
const CurrentConfigVersion = "1.0"
func (config *MixConfig) parseVersion(reader *bufio.Reader) (bool, error) {
verBytes, err := reader.ReadString('\n')
if err != nil {
return false, err
}
r := regexp.MustCompile("^#VERSION ([0-9]+.[0-9])+\n")
match := r.FindStringSubmatch(string(verBytes))
if len(match) != 2 {
return false, nil
}
config.version = match[1]
return true, nil
}
func (config *MixConfig) parseVersionAndConvert() error {
// Reset version for files without versioning
config.version = "0.0"
f, err := os.Open(config.filename)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
// Read config version
reader := bufio.NewReader(f)
found, err := config.parseVersion(reader)
if err != nil {
return err
}
//Already on latest version
if found && config.version == CurrentConfigVersion {
return nil
}
fmt.Printf("Converting config to version %s\n", CurrentConfigVersion)
if err := helpers.CopyFile(config.filename+".bkp", config.filename); err != nil {
return err
}
fmt.Printf("Old config saved as %s\n", config.filename+".bkp")
if !found {
return config.convertLegacy()
}
return config.convertCurrent()
}
func (config *MixConfig) convertCurrent() error {
// Version only exists in New Config, so parse builder.conf as TOML
if err := config.parse(); err != nil {
return err
}
// Make sure the converted config is in the New Format
UseNewConfig = true
// Set config to the current format
config.version = CurrentConfigVersion
return config.SaveConfig()
}
func (config *MixConfig) convertLegacy() error {
// Assume missing version and try to parse as TOML
if _, err := toml.DecodeFile(config.filename, &config); err != nil {
// Try parsing as INI
if err := config.parseLegacy(); err != nil {
return err
}
} else {
config.hasFormatField = true
}
// If the config still has the FORMAT value in it, check if
// it was already transferred to mixer.state
if config.hasFormatField {
if err := config.convertFormat(); err != nil {
return err
}
}
// Make sure the converted config is in the New Format
UseNewConfig = true
// Set config to the current format
config.version = CurrentConfigVersion
return config.SaveConfig()
}
func (config *MixConfig) convertFormat() error {
confBytes, err := ioutil.ReadFile(config.filename)
if err != nil {
return err
}
r := regexp.MustCompile(`FORMAT[\s"=]*([0-9]+)[\s"]*\n`)
match := r.FindStringSubmatch(string(confBytes))
if len(match) != 2 {
return nil
}
var state MixState
state.LoadDefaults()
_, err = os.Stat(state.filename)
if err == nil {
// mixer.state already exist, so don't overwrite it
return nil
}
state.Mix.Format = match[1]
return state.Save()
}
func (config *MixConfig) parseLegacy() error {
lines, err := helpers.ReadFileAndSplit(config.filename)
if err != nil {
return errors.Wrap(err, "Failed to read buildconf")
}
var format string
// Map the builder values to the regex here to make it easier to assign
fields := []struct {
re string
dest *string
required bool
}{
// [Builder]
{`^CERT\s*=\s*`, &config.Builder.Cert, true},
{`^SERVER_STATE_DIR\s*=\s*`, &config.Builder.ServerStateDir, true},
{`^VERSIONS_PATH\s*=\s*`, &config.Builder.VersionPath, true},
{`^YUM_CONF\s*=\s*`, &config.Builder.DNFConf, true},
// [Swupd]
{`^BUNDLE\s*=\s*`, &config.Swupd.Bundle, false},
{`^CONTENTURL\s*=\s*`, &config.Swupd.ContentURL, false},
{`^FORMAT\s*=\s*`, &format, true},
{`^VERSIONURL\s*=\s*`, &config.Swupd.VersionURL, false},
// [Server]
{`^debuginfo_banned\s*=\s*`, &config.Server.DebugInfoBanned, false},
{`^debuginfo_lib\s*=\s*`, &config.Server.DebugInfoLib, false},
{`^debuginfo_src\s*=\s*`, &config.Server.DebugInfoSrc, false},
// [Mixer]
{`^LOCAL_BUNDLE_DIR\s*=\s*`, &config.Mixer.LocalBundleDir, false},
{`^LOCAL_REPO_DIR\s*=\s*`, &config.Mixer.LocalRepoDir, false},
{`^LOCAL_RPM_DIR\s*=\s*`, &config.Mixer.LocalRPMDir, false},
{`^DOCKER_IMAGE_PATH\s*=\s*`, &config.Mixer.DockerImgPath, false},
}
for _, h := range fields {
r := regexp.MustCompile(h.re)
for _, i := range lines {
if m := r.FindIndex([]byte(i)); m != nil {
*h.dest = i[m[1]:]
}
}
}
config.hasFormatField = format != ""
if config.Mixer.LocalBundleDir == "" {
pwd, err := os.Getwd()
if err != nil {
return err
}
config.Mixer.LocalBundleDir = filepath.Join(pwd, "local-bundles")
fmt.Printf("WARNING: LOCAL_BUNDLE_DIR not found in builder.conf. Falling back to %q.\n", config.Mixer.LocalBundleDir)
fmt.Println("Please set this value to the location you want local bundle definition files to be stored.")
}
return nil
}