func fieldName(f reflect.StructField) string {
	val, ok := f.Tag.Lookup("json")
	if !ok {
		return f.Name
	}
	tags := strings.Split(val, ",")
	if len(tags) < 1 {
		return f.Name
	}
	name := tags[0]
	if name == "" {
		return f.Name
	}
	return name
}
fieldName returns the name used to refer to the struct field in JSON/YAML encoding.
package config
import (
	"reflect"
	"sigs.k8s.io/yaml"
	"strings"
)
const (
	MainModule = "main"
)
type GetConfigFn func() interface{}
type NotifyFn func(Event, Source) error
type Event string
const (
	UpdateEvent = "update"
	RevertEvent = "rollback"
)
type Source string
const (
	ConfigFile Source = "configuration file"
	ConfigExternal Source = "external configuration"
	ConfigBackup Source = "backed up configuration"
)
type Module struct {
	path        string              
	description string              
	help        string              
	ptr         interface{}         
	parent      *Module             
	name        string              
	children    map[string]*Module  
	getdefault  GetConfigFn         
	notifiers   []NotifyFn          
	noValidate  bool                
}
var main = &Module{
	path:     MainModule,
	name:     MainModule,
	children: make(map[string]*Module),
}
func GetConfig() (Data, error) {
	return main.getconfig()
}
func SetConfig(cfg map[string]string) error {
	data, err := DataFromStringMap(cfg)
	if err != nil {
		return configError("failed to update configuration: %v", err)
	}
	return setconfig(data, ConfigExternal)
}
func SetConfigFromFile(path string) error {
	data, err := DataFromFile(path)
	if err != nil {
		return configError("failed to apply configuration from file: %v", err)
	}
	return setconfig(data, ConfigFile)
}
func GetModule(path string) *Module {
	return lookup(path)
}
func (m *Module) AddNotify(fn NotifyFn) error {
	return WithNotify(fn).apply(m)
}
func Register(path, description string, ptr interface{}, getfn GetConfigFn, opts ...Option) *Module {
	m := lookup(path)
	if !m.isImplicit() {
		log.Fatal("module %s: conflicting module with same path already declared (%s)",
			path, m.description)
	}
	m.setDescription(description)
	m.ptr = ptr
	m.getdefault = getfn
	m.check()
	foreign := m.notifiers
	m.notifiers = nil
	for _, opt := range opts {
		opt.apply(m)
	}
	m.notifiers = append(m.notifiers, foreign...)
	return m
}
func setconfig(data Data, source Source) error {
	snapshot, err := main.getconfig()
	if err != nil {
		return configError("pre-update configuration snapshot failed: %v", err)
	}
	log.Info("validating configuration...")
	err = main.validate(data)
	if err != nil {
		return err
	}
	log.Info("applying configuration...")
	err = main.configure(data, false)
	if err != nil {
		revertconfig(snapshot, false)
		return err
	}
	log.Info("activating configuration...")
	err = main.notify(UpdateEvent, source)
	if err != nil {
		log.Error("configuration rejected: %v", err)
		revertconfig(snapshot, true)
		return err
	}
	return nil
}
func revertconfig(snapshot Data, notify bool) {
	err := main.configure(snapshot, true)
	if err != nil {
		log.Error("failed to revert configuration: %v", err)
	}
	if !notify {
		return
	}
	err = main.notify(RevertEvent, ConfigBackup)
	if err != nil {
		log.Error("reverted configuration rejected: %v", err)
	}
}
func (m *Module) getconfig() (Data, error) {
	var mcfg, ccfg Data
	var err error
	if m.isImplicit() {
		mcfg = make(Data)
	} else {
		mcfg, err = DataFromObject(m.ptr)
		if err != nil {
			return nil, configError("module %s: failed to get confguration: %v",
				m.path, err)
		}
	}
	for name, child := range m.children {
		ccfg, err = child.getconfig()
		if err != nil {
			return nil, configError("module %s: failed to get child configuration for %s: %v",
				m.path, child.path, err)
		}
		mcfg[name] = ccfg
	}
	return mcfg, nil
}
func (m *Module) isImplicit() bool {
	return m.description == ""
}
func (m *Module) hasChild(name string) bool {
	_, ok := m.children[name]
	return ok
}
func (m *Module) configure(data Data, force bool) error {
	log.Debug("module %s: reconfiguring...", m.path)
	modcfg, subcfg := data.split(m.hasChild)
	if err := m.apply(modcfg); err != nil {
		if !force {
			return err
		}
		log.Error("%v", err)
	}
	for name, child := range m.children {
		childcfg, err := subcfg.pick(name, true)
		if err != nil {
			err = configError("module %s: failed to pick configuration: %v", child.path, err)
			if !force {
				return err
			}
			log.Error("%v", err)
		}
		err = child.configure(childcfg, force)
		if err != nil {
			if !force {
				return err
			}
			log.Error("%v", err)
		}
	}
	return nil
}
func (m *Module) apply(cfg Data) error {
	if m.isImplicit() {
		return nil
	}
	log.Debug("module %s: applying module configuration...", m.path)
	defcfg, err := DataFromObject(m.getdefault())
	if err != nil {
		return configError("module %s: failed to retrieve default configuration: %v", m.path, err)
	}
	raw, err := yaml.Marshal(defcfg)
	if err != nil {
		return configError("module %s: failed to marshal default configuration: %v", m.path, err)
	}
	if err = yaml.Unmarshal(raw, m.ptr); err != nil {
		return configError("module %s: failed to pre-reset to default configuration: %v", m.path, err)
	}
	if len(cfg) > 0 {
		raw, err = yaml.Marshal(cfg)
		if err != nil {
			return configError("module %s: failed to marshal configuration: %v", m.path, err)
		}
		if err = yaml.Unmarshal(raw, m.ptr); err != nil {
			return configError("module %s: failed to apply configuration: %v", m.path, err)
		}
	}
	return nil
}
func (m *Module) notify(event Event, source Source) error {
	for _, child := range m.children {
		if err := child.notify(event, source); err != nil {
			return err
		}
	}
	for _, fn := range m.notifiers {
		if err := fn(event, source); err != nil {
			return configError("module %s rejected %v configuration: %v", m.path, event, err)
		}
	}
	return nil
}
func (m *Module) check() {
	ptrType := reflect.TypeOf(m.ptr)
	ptr := reflect.ValueOf(m.ptr).Elem()
	if ptrType.Kind() != reflect.Ptr || ptr.Kind() != reflect.Struct {
		log.Fatal("module %s: configuration data must be a pointer to a struct, not %T",
			m.path, m.ptr)
	}
	if m.parent == nil || m.parent.isImplicit() {
		return
	}
	ptr = reflect.ValueOf(m.parent.ptr).Elem()
	for i := 0; i < ptr.NumField(); i++ {
		field := ptr.Type().Field(i)
		if m.name == fieldName(field) {
			log.Fatal("module %s: parent has configuration data with conflicting field", m.name)
		}
	}
}
func getFields(typ reflect.Type) map[string]struct{} {
	fields := make(map[string]struct{})
	var get func(t reflect.Type)
	get = func(t reflect.Type) {
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			if f.Type.Kind() == reflect.Struct && f.Anonymous {
				get(f.Type)
			} else {
				fields[fieldName(f)] = struct{}{}
			}
		}
	}
	get(typ)
	return fields
}
func (m *Module) validate(data Data) error {
	log.Debug("validating data for module %s...", m.path)
	modcfg, subcfg := data.split(m.hasChild)
	fields := map[string]struct{}{}
	if m.isImplicit() {
		if len(modcfg) > 0 {
			names := []string{}
			for name := range modcfg {
				names = append(names, name)
			}
			if !m.noValidate {
				return configError("implicit module %s: given configuration data %s",
					m.path, strings.Join(names, ","))
			}
			log.Error("implicit module %s: given configuration date %s",
				m.path, strings.Join(names, ","))
		}
	} else {
		fields = getFields(reflect.TypeOf(m.ptr).Elem())
	}
	for field := range modcfg {
		if _, ok := fields[field]; !ok {
			if !m.noValidate {
				return configError("module %s: given unknown configuration data %s", m.path, field)
			}
			log.Error("module %s: given unknown configuration data %s", m.path, field)
		}
	}
	subcfg = subcfg.copy()
	for name, child := range m.children {
		childcfg, err := subcfg.pick(name, true)
		if err != nil {
			return configError("module %s: failed to pick configuration for child %s: %v",
				m.path, child.path, err)
		}
		err = child.validate(childcfg)
		if err != nil {
			return err
		}
	}
	if len(subcfg) > 0 {
		unconsumed := []string{}
		for name := range subcfg {
			unconsumed = append(unconsumed, name)
		}
		return configError("module %s: no child corresponding to data %s",
			m.path, strings.Join(unconsumed, ","))
	}
	return nil
}