Migrate enki from osbuilder
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
This commit is contained in:
303
pkg/config/config.go
Normal file
303
pkg/config/config.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/kairos-io/enki/internal/version"
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/cloudinit"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/http"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/sanity-io/litter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"io"
|
||||
"io/fs"
|
||||
"k8s.io/mount-utils"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var decodeHook = viper.DecodeHook(
|
||||
mapstructure.ComposeDecodeHookFunc(
|
||||
UnmarshalerHook(),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
),
|
||||
)
|
||||
|
||||
func WithFs(fs v1.FS) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Fs = fs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger v1.Logger) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Logger = logger
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithSyscall(syscall v1.SyscallInterface) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Syscall = syscall
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithMounter(mounter mount.Interface) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Mounter = mounter
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRunner(runner v1.Runner) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Runner = runner
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithClient(client v1.HTTPClient) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Client = client
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.CloudInitRunner = ci
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithArch(arch string) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Arch = arch
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithImageExtractor(extractor v1.ImageExtractor) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.ImageExtractor = extractor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type GenericOptions func(a *v1.Config) error
|
||||
|
||||
func ReadConfigBuild(configDir string, flags *pflag.FlagSet, mounter mount.Interface) (*v1.BuildConfig, error) {
|
||||
logger := v1.NewLogger()
|
||||
if configDir == "" {
|
||||
configDir = "."
|
||||
}
|
||||
|
||||
cfg := NewBuildConfig(
|
||||
WithLogger(logger),
|
||||
WithMounter(mounter),
|
||||
)
|
||||
|
||||
configLogger(cfg.Logger, cfg.Fs)
|
||||
|
||||
viper.AddConfigPath(configDir)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName("manifest.yaml")
|
||||
// If a config file is found, read it in.
|
||||
_ = viper.MergeInConfig()
|
||||
|
||||
// Bind buildconfig flags
|
||||
bindGivenFlags(viper.GetViper(), flags)
|
||||
|
||||
// unmarshal all the vars into the config object
|
||||
err := viper.Unmarshal(cfg, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
cfg.Logger.Warnf("error unmarshalling config: %s", err)
|
||||
}
|
||||
|
||||
err = cfg.Sanitize()
|
||||
cfg.Logger.Debugf("Full config loaded: %s", litter.Sdump(cfg))
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func ReadBuildISO(b *v1.BuildConfig, flags *pflag.FlagSet) (*v1.LiveISO, error) {
|
||||
iso := NewISO()
|
||||
vp := viper.Sub("iso")
|
||||
if vp == nil {
|
||||
vp = viper.New()
|
||||
}
|
||||
// Bind build-iso cmd flags
|
||||
bindGivenFlags(vp, flags)
|
||||
|
||||
err := vp.Unmarshal(iso, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
b.Logger.Warnf("error unmarshalling LiveISO: %s", err)
|
||||
}
|
||||
err = iso.Sanitize()
|
||||
b.Logger.Debugf("Loaded LiveISO: %s", litter.Sdump(iso))
|
||||
return iso, err
|
||||
}
|
||||
|
||||
func NewISO() *v1.LiveISO {
|
||||
return &v1.LiveISO{
|
||||
Label: constants.ISOLabel,
|
||||
GrubEntry: constants.GrubDefEntry,
|
||||
UEFI: []*v1.ImageSource{},
|
||||
Image: []*v1.ImageSource{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewBuildConfig(opts ...GenericOptions) *v1.BuildConfig {
|
||||
b := &v1.BuildConfig{
|
||||
Config: *NewConfig(opts...),
|
||||
Name: constants.BuildImgName,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func NewConfig(opts ...GenericOptions) *v1.Config {
|
||||
log := v1.NewLogger()
|
||||
arch, err := utils.GolangArchToArch(runtime.GOARCH)
|
||||
if err != nil {
|
||||
log.Errorf("invalid arch: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &v1.Config{
|
||||
Fs: vfs.OSFS,
|
||||
Logger: log,
|
||||
Syscall: &v1.RealSyscall{},
|
||||
Client: http.NewClient(),
|
||||
Repos: []v1.Repository{},
|
||||
Arch: arch,
|
||||
SquashFsNoCompression: true,
|
||||
}
|
||||
for _, o := range opts {
|
||||
err := o(c)
|
||||
if err != nil {
|
||||
log.Errorf("error applying config option: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// delay runner creation after we have run over the options in case we use WithRunner
|
||||
if c.Runner == nil {
|
||||
c.Runner = &v1.RealRunner{Logger: c.Logger}
|
||||
}
|
||||
|
||||
// Now check if the runner has a logger inside, otherwise point our logger into it
|
||||
// This can happen if we set the WithRunner option as that doesn't set a logger
|
||||
if c.Runner.GetLogger() == nil {
|
||||
c.Runner.SetLogger(c.Logger)
|
||||
}
|
||||
|
||||
// Delay the yip runner creation, so we set the proper logger instead of blindly setting it to the logger we create
|
||||
// at the start of NewRunConfig, as WithLogger can be passed on init, and that would result in 2 different logger
|
||||
// instances, on the config.Logger and the other on config.CloudInitRunner
|
||||
if c.CloudInitRunner == nil {
|
||||
c.CloudInitRunner = cloudinit.NewYipCloudInitRunner(c.Logger, c.Runner, vfs.OSFS)
|
||||
}
|
||||
|
||||
if c.Mounter == nil {
|
||||
c.Mounter = mount.New(constants.MountBinary)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func configLogger(log v1.Logger, vfs v1.FS) {
|
||||
// Set debug level
|
||||
if viper.GetBool("debug") {
|
||||
log.SetLevel(v1.DebugLevel())
|
||||
}
|
||||
|
||||
// Set formatter so both file and stdout format are equal
|
||||
log.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
DisableColors: false,
|
||||
DisableTimestamp: false,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
|
||||
// Logfile
|
||||
logfile := viper.GetString("logfile")
|
||||
if logfile != "" {
|
||||
o, err := vfs.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, fs.ModePerm)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Could not open %s for logging to file: %s", logfile, err.Error())
|
||||
}
|
||||
|
||||
// else set it to both stdout and the file
|
||||
mw := io.MultiWriter(os.Stdout, o)
|
||||
log.SetOutput(mw)
|
||||
} else { // no logfile
|
||||
if viper.GetBool("quiet") { // quiet is enabled so discard all logging
|
||||
log.SetOutput(io.Discard)
|
||||
} else { // default to stdout
|
||||
log.SetOutput(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Starting enki version %s", version.GetVersion())
|
||||
if log.GetLevel() == logrus.DebugLevel {
|
||||
log.Debugf("%+v\n", version.Get())
|
||||
}
|
||||
}
|
||||
|
||||
// BindGivenFlags binds to viper only passed flags, ignoring any non provided flag
|
||||
func bindGivenFlags(vp *viper.Viper, flagSet *pflag.FlagSet) {
|
||||
if flagSet != nil {
|
||||
flagSet.VisitAll(func(f *pflag.Flag) {
|
||||
if f.Changed {
|
||||
_ = vp.BindPFlag(f.Name, f)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// setDecoder sets ZeroFields mastructure attribute to true
|
||||
func setDecoder(config *mapstructure.DecoderConfig) {
|
||||
// Make sure we zero fields before applying them, this is relevant for slices
|
||||
// so we do not merge with any already present value and directly apply whatever
|
||||
// we got form configs.
|
||||
config.ZeroFields = true
|
||||
}
|
||||
|
||||
type Unmarshaler interface {
|
||||
CustomUnmarshal(interface{}) (bool, error)
|
||||
}
|
||||
|
||||
func UnmarshalerHook() mapstructure.DecodeHookFunc {
|
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||
// get the destination object address if it is not passed by reference
|
||||
if to.CanAddr() {
|
||||
to = to.Addr()
|
||||
}
|
||||
// If the destination implements the unmarshaling interface
|
||||
u, ok := to.Interface().(Unmarshaler)
|
||||
if !ok {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
// If it is nil and a pointer, create and assign the target value first
|
||||
if to.IsNil() && to.Type().Kind() == reflect.Ptr {
|
||||
to.Set(reflect.New(to.Type().Elem()))
|
||||
u = to.Interface().(Unmarshaler)
|
||||
}
|
||||
// Call the custom unmarshaling method
|
||||
cont, err := u.CustomUnmarshal(from.Interface())
|
||||
if cont {
|
||||
// Continue with the decoding stack
|
||||
return from.Interface(), err
|
||||
}
|
||||
// Decoding finalized
|
||||
return to.Interface(), err
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user