Files
mixer-tools/builder/config.go
T
Rodrigo Chiossi 1c7a08410d cmd: add mixer config convert command
Add 'convert' as a 'mixer config' subcommand. This command allows the
conversion from old config files to the new TOML config file.

Signed-off-by: Rodrigo Chiossi <rodrigo.chiossi@intel.com>
2018-03-22 11:02:49 -07:00

359 lines
9.6 KiB
Go

// 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 builder
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"github.com/BurntSushi/toml"
"github.com/clearlinux/mixer-tools/helpers"
"github.com/pkg/errors"
)
// UseNewConfig controls whether to use the new TOML config format.
// This is an experimental feature.
var UseNewConfig = false
// MixConfig represents the config parameters found in the builder config file.
type MixConfig struct {
Builder builderConf
Swupd swupdConf
Server serverConf
Mixer mixerConf
}
type builderConf struct {
BundleDir string `required:"true" toml:"BUNDLE_DIR"`
Cert string `required:"true" toml:"CERT"`
ServerStateDir string `required:"true" toml:"SERVER_STATE_DIR"`
VersionPath string `required:"true" toml:"VERSIONS_PATH"`
DNFConf string `required:"true" toml:"YUM_CONF"`
}
type swupdConf struct {
Bundle string `required:"false" toml:"BUNDLE"`
ContentURL string `required:"false" toml:"CONTENTURL"`
Format string `required:"true" toml:"FORMAT"`
VersionURL string `required:"false" toml:"VERSIONURL"`
}
type serverConf struct {
DebugInfoBanned string `required:"false" toml:"DEBUG_INFO_BANNED"`
DebugInfoLib string `required:"false" toml:"DEBUG_INFO_LIB"`
DebugInfoSrc string `required:"false" toml:"DEBUG_INFO_SRC"`
}
type mixerConf struct {
LocalBundleDir string `required:"false" toml:"LOCAL_BUNDLE_DIR"`
LocalRepoDir string `required:"false" toml:"LOCAL_REPO_DIR"`
LocalRPMDir string `required:"false" toml:"LOCAL_RPM_DIR"`
}
// LoadDefaults sets sane values for the config properties
func (config *MixConfig) LoadDefaults() error {
if !UseNewConfig {
return nil
}
pwd, err := os.Getwd()
if err != nil {
return err
}
// [Builder]
config.Builder.BundleDir = filepath.Join(pwd, "mix-bundles")
config.Builder.Cert = filepath.Join(pwd, "Swupd_Root.pem")
config.Builder.ServerStateDir = filepath.Join(pwd, "update")
config.Builder.VersionPath = pwd
config.Builder.DNFConf = filepath.Join(pwd, ".yum-mix.conf")
// [Swupd]
config.Swupd.Bundle = "os-core-update"
config.Swupd.ContentURL = "<URL where the content will be hosted>"
config.Swupd.Format = "1"
config.Swupd.VersionURL = "<URL where the version of the mix will be hosted>"
// [Server]
config.Server.DebugInfoBanned = "true"
config.Server.DebugInfoLib = "/usr/lib/debug"
config.Server.DebugInfoSrc = "/usr/src/debug"
// [Mixer]
config.Mixer.LocalBundleDir = filepath.Join(pwd, "local-bundles")
config.Mixer.LocalRPMDir = ""
config.Mixer.LocalRepoDir = ""
return nil
}
// CreateDefaultConfig creates a default builder.conf using the active
// directory as base path for the variables values.
func (config *MixConfig) CreateDefaultConfig(localrpms bool) error {
if !UseNewConfig {
return config.createLegacyConfig(localrpms)
}
if err := config.LoadDefaults(); err != nil {
return err
}
if localrpms {
pwd, err := os.Getwd()
if err != nil {
return err
}
config.Mixer.LocalRPMDir = filepath.Join(pwd, "local-rpms")
config.Mixer.LocalRepoDir = filepath.Join(pwd, "local-yum")
}
filename, err := GetConfigPath("")
if err != nil {
return err
}
return config.SaveConfig(filename)
}
// SaveConfig saves the properties in MixConfig to a TOML config file
func (config *MixConfig) SaveConfig(filename string) error {
if !UseNewConfig {
return errors.Errorf("SaveConfig can only be used with --new-config flag")
}
w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer func() {
_ = w.Close()
}()
enc := toml.NewEncoder(w)
return enc.Encode(config)
}
func (config *MixConfig) createLegacyConfig(localrpms bool) error {
if UseNewConfig {
return errors.Errorf("createLegacyConfig is not compatible with --new-config flag")
}
pwd, err := os.Getwd()
if err != nil {
return err
}
builderconf := filepath.Join(pwd, "builder.conf")
err = helpers.CopyFileNoOverwrite(builderconf, "/usr/share/defaults/bundle-chroot-builder/builder.conf")
if os.IsExist(err) {
// builder.conf already exists. Skip creation.
return nil
} else if err != nil {
return err
}
fmt.Println("Creating new builder.conf configuration file...")
raw, err := ioutil.ReadFile(builderconf)
if err != nil {
return err
}
// Patch all default path prefixes to PWD
data := strings.Replace(string(raw), "/home/clr/mix", pwd, -1)
// Add [Mixer] section
data += "\n[Mixer]\n"
data += "LOCAL_BUNDLE_DIR=" + filepath.Join(pwd, "local-bundles") + "\n"
if localrpms {
data += "LOCAL_RPM_DIR=" + filepath.Join(pwd, "local-rpms") + "\n"
data += "LOCAL_REPO_DIR=" + filepath.Join(pwd, "local-yum") + "\n"
}
return ioutil.WriteFile(builderconf, []byte(data), 0666)
}
// LoadConfig loads a configuration file from a provided path or from local directory
// is none is provided
func (config *MixConfig) LoadConfig(filename string) error {
if err := config.Parse(filename); err != nil {
return err
}
if err := config.expandEnv(); err != nil {
return err
}
return config.validate()
}
// Parse reads the values from a config file without performing validation or env expansion
func (config *MixConfig) Parse(filename string) error {
if !UseNewConfig {
if err := config.legacyParse(filename); err != nil {
return err
}
} else {
if _, err := toml.DecodeFile(filename, &config); err != nil {
return err
}
}
return nil
}
func (config *MixConfig) legacyParse(filename string) error {
if UseNewConfig {
return errors.Errorf("legacyParse is not compatible with --new-config flag")
}
lines, err := helpers.ReadFileAndSplit(filename)
if err != nil {
return errors.Wrap(err, "Failed to read buildconf")
}
// Map the builder values to the regex here to make it easier to assign
fields := []struct {
re string
dest *string
required bool
}{
// [Builder]
{`^BUNDLE_DIR\s*=\s*`, &config.Builder.BundleDir, true}, //Note: Can be removed once UseNewChrootBuilder is obsolete
{`^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*`, &config.Swupd.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},
}
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]:]
}
}
}
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) expandEnv() error {
re := regexp.MustCompile(`\$\{?([[:word:]]+)\}?`)
rv := reflect.ValueOf(config).Elem()
for i := 0; i < rv.NumField(); i++ {
sectionV := rv.Field(i)
for j := 0; j < sectionV.NumField(); j++ {
val := sectionV.Field(j).String()
matches := re.FindAllStringSubmatch(val, -1)
for _, s := range matches {
if _, ok := os.LookupEnv(s[1]); !ok {
return errors.Errorf("buildconf contains an undefined environment variable: %s\n", s[1])
}
}
sectionV.Field(j).SetString(os.ExpandEnv(val))
}
}
return nil
}
func (config *MixConfig) validate() error {
rv := reflect.ValueOf(config).Elem()
for i := 0; i < rv.NumField(); i++ {
sectionT := reflect.TypeOf(rv.Field(i).Interface())
sectionV := rv.Field(i)
for j := 0; j < sectionT.NumField(); j++ {
tag, ok := sectionT.Field(j).Tag.Lookup("required")
if ok && tag == "true" && sectionV.Field(j).String() == "" {
return errors.Errorf("Missing required field in config file: %s", sectionT.Field(j).Name)
}
}
}
return nil
}
// Convert parses an old config file and converts it to TOML format
func (config *MixConfig) Convert(filename string) error {
// Force UseNewConfig to false
UseNewConfig = false
if err := config.Parse(filename); err != nil {
return err
}
if err := helpers.CopyFile(filename+".bkp", filename); err != nil {
return err
}
// Force UseNewConfig to true
UseNewConfig = true
return config.SaveConfig(filename)
}
// Print print variables and values of a MixConfig struct
func (config *MixConfig) Print() error {
sb := bytes.NewBufferString("")
enc := toml.NewEncoder(sb)
if err := enc.Encode(config); err != nil {
return err
}
fmt.Println(sb.String())
return nil
}