github.com/aaronlehmann/figtree@v1.0.1/figtree.go (about)

     1  package figtree
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"reflect"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"unicode"
    18  
    19  	"github.com/fatih/camelcase"
    20  	"github.com/pkg/errors"
    21  	"gopkg.in/yaml.v3"
    22  )
    23  
    24  type Logger interface {
    25  	Debugf(format string, args ...interface{})
    26  }
    27  
    28  type nullLogger struct{}
    29  
    30  func (*nullLogger) Debugf(string, ...interface{}) {}
    31  
    32  var Log Logger = &nullLogger{}
    33  
    34  func defaultApplyChangeSet(changeSet map[string]*string) error {
    35  	for k, v := range changeSet {
    36  		if v != nil {
    37  			os.Setenv(k, *v)
    38  		} else {
    39  			os.Unsetenv(k)
    40  		}
    41  	}
    42  	return nil
    43  }
    44  
    45  type Option func(*FigTree)
    46  
    47  func WithHome(home string) Option {
    48  	return func(f *FigTree) {
    49  		f.home = home
    50  	}
    51  }
    52  
    53  func WithCwd(cwd string) Option {
    54  	return func(f *FigTree) {
    55  		f.workDir = cwd
    56  	}
    57  }
    58  
    59  func WithEnvPrefix(env string) Option {
    60  	return func(f *FigTree) {
    61  		f.envPrefix = env
    62  	}
    63  }
    64  
    65  func WithConfigDir(dir string) Option {
    66  	return func(f *FigTree) {
    67  		f.configDir = dir
    68  	}
    69  }
    70  
    71  type ChangeSetFunc func(map[string]*string) error
    72  
    73  func WithApplyChangeSet(apply ChangeSetFunc) Option {
    74  	return func(f *FigTree) {
    75  		f.applyChangeSet = apply
    76  	}
    77  }
    78  
    79  type PreProcessor func([]byte) ([]byte, error)
    80  
    81  func WithPreProcessor(pp PreProcessor) Option {
    82  	return func(f *FigTree) {
    83  		f.preProcessor = pp
    84  	}
    85  }
    86  
    87  type FilterOut func([]byte) bool
    88  
    89  func WithFilterOut(filt FilterOut) Option {
    90  	return func(f *FigTree) {
    91  		f.filterOut = filt
    92  	}
    93  }
    94  
    95  func defaultFilterOut(f *FigTree) FilterOut {
    96  	// looking for:
    97  	// ```
    98  	// config:
    99  	//   stop: true|false
   100  	// ```
   101  	configStop := struct {
   102  		Config struct {
   103  			Stop bool `json:"stop" yaml:"stop"`
   104  		} `json:"config" yaml:"config"`
   105  	}{}
   106  	return func(config []byte) bool {
   107  		// if previous parse found a stop we should abort here
   108  		if configStop.Config.Stop {
   109  			return true
   110  		}
   111  		// now check if current doc has a stop
   112  		f.unmarshal(config, &configStop)
   113  		// even if current doc has a stop, we should continue to
   114  		// process it, we dont want to process the "next" document
   115  		return false
   116  	}
   117  }
   118  
   119  func WithUnmarshaller(unmarshaller func(in []byte, out interface{}) error) Option {
   120  	return func(f *FigTree) {
   121  		f.unmarshal = unmarshaller
   122  	}
   123  }
   124  
   125  func WithoutExec() Option {
   126  	return func(f *FigTree) {
   127  		f.exec = false
   128  	}
   129  }
   130  
   131  type FigTree struct {
   132  	home           string
   133  	workDir        string
   134  	configDir      string
   135  	envPrefix      string
   136  	preProcessor   PreProcessor
   137  	applyChangeSet ChangeSetFunc
   138  	exec           bool
   139  	filterOut      FilterOut
   140  	unmarshal      func(in []byte, out interface{}) error
   141  }
   142  
   143  func NewFigTree(opts ...Option) *FigTree {
   144  	wd, _ := os.Getwd()
   145  	fig := &FigTree{
   146  		home:           os.Getenv("HOME"),
   147  		workDir:        wd,
   148  		envPrefix:      "FIGTREE",
   149  		applyChangeSet: defaultApplyChangeSet,
   150  		exec:           true,
   151  		unmarshal:      yaml.Unmarshal,
   152  	}
   153  	for _, opt := range opts {
   154  		opt(fig)
   155  	}
   156  	return fig
   157  }
   158  
   159  func (f *FigTree) WithHome(home string) {
   160  	WithHome(home)(f)
   161  }
   162  
   163  func (f *FigTree) WithCwd(cwd string) {
   164  	WithCwd(cwd)(f)
   165  }
   166  
   167  func (f *FigTree) WithEnvPrefix(env string) {
   168  	WithEnvPrefix(env)(f)
   169  }
   170  
   171  func (f *FigTree) WithConfigDir(dir string) {
   172  	WithConfigDir(dir)(f)
   173  }
   174  
   175  func (f *FigTree) WithPreProcessor(pp PreProcessor) {
   176  	WithPreProcessor(pp)(f)
   177  }
   178  
   179  func (f *FigTree) WithFilterOut(filt FilterOut) {
   180  	WithFilterOut(filt)(f)
   181  }
   182  
   183  func (f *FigTree) WithUnmarshaller(unmarshaller func(in []byte, out interface{}) error) {
   184  	WithUnmarshaller(unmarshaller)(f)
   185  }
   186  
   187  func (f *FigTree) WithApplyChangeSet(apply ChangeSetFunc) {
   188  	WithApplyChangeSet(apply)(f)
   189  }
   190  
   191  func (f *FigTree) WithIgnoreChangeSet() {
   192  	WithApplyChangeSet(func(_ map[string]*string) error {
   193  		return nil
   194  	})(f)
   195  }
   196  
   197  func (f *FigTree) WithoutExec() {
   198  	WithoutExec()(f)
   199  }
   200  
   201  func (f *FigTree) Copy() *FigTree {
   202  	cp := *f
   203  	return &cp
   204  }
   205  
   206  func (f *FigTree) LoadAllConfigs(configFile string, options interface{}) error {
   207  	if f.configDir != "" {
   208  		configFile = path.Join(f.configDir, configFile)
   209  	}
   210  
   211  	paths := FindParentPaths(f.home, f.workDir, configFile)
   212  	paths = append([]string{fmt.Sprintf("/etc/%s", configFile)}, paths...)
   213  
   214  	configSources := []ConfigSource{}
   215  	// iterate paths in reverse
   216  	for i := len(paths) - 1; i >= 0; i-- {
   217  		file := paths[i]
   218  		cs, err := f.ReadFile(file)
   219  		if err != nil {
   220  			return err
   221  		}
   222  		if cs == nil {
   223  			// no file contents to parse, file likely does not exist
   224  			continue
   225  		}
   226  		configSources = append(configSources, *cs)
   227  	}
   228  	return f.LoadAllConfigSources(configSources, options)
   229  }
   230  
   231  type ConfigSource struct {
   232  	Config   []byte
   233  	Filename string
   234  }
   235  
   236  func (f *FigTree) LoadAllConfigSources(sources []ConfigSource, options interface{}) error {
   237  	m := NewMerger()
   238  	filterOut := f.filterOut
   239  	if filterOut == nil {
   240  		filterOut = defaultFilterOut(f)
   241  	}
   242  
   243  	for _, source := range sources {
   244  		// automatically skip empty configs
   245  		if len(source.Config) == 0 {
   246  			continue
   247  		}
   248  		skip := filterOut(source.Config)
   249  		if skip {
   250  			continue
   251  		}
   252  
   253  		m.sourceFile = source.Filename
   254  		err := f.loadConfigBytes(m, source.Config, options)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		m.advance()
   259  	}
   260  	return nil
   261  }
   262  
   263  func (f *FigTree) LoadConfigBytes(config []byte, source string, options interface{}) error {
   264  	m := NewMerger(WithSourceFile(source))
   265  	return f.loadConfigBytes(m, config, options)
   266  }
   267  
   268  func (f *FigTree) loadConfigBytes(m *Merger, config []byte, options interface{}) error {
   269  	if !reflect.ValueOf(options).IsValid() {
   270  		return fmt.Errorf("options argument [%#v] is not valid", options)
   271  	}
   272  
   273  	var err error
   274  	if f.preProcessor != nil {
   275  		config, err = f.preProcessor(config)
   276  		if err != nil {
   277  			return errors.Wrapf(err, "Failed to process config file: %s", m.sourceFile)
   278  		}
   279  	}
   280  
   281  	tmp := reflect.New(reflect.ValueOf(options).Elem().Type()).Interface()
   282  	// look for config settings first
   283  	err = f.unmarshal(config, m)
   284  	if err != nil {
   285  		return errors.Wrapf(err, "Unable to parse %s", m.sourceFile)
   286  	}
   287  
   288  	// then parse document into requested struct
   289  	err = f.unmarshal(config, tmp)
   290  	if err != nil {
   291  		return errors.Wrapf(err, "Unable to parse %s", m.sourceFile)
   292  	}
   293  
   294  	m.setSource(reflect.ValueOf(tmp))
   295  	m.mergeStructs(
   296  		reflect.ValueOf(options),
   297  		reflect.ValueOf(tmp),
   298  	)
   299  	changeSet := f.PopulateEnv(options)
   300  	return f.applyChangeSet(changeSet)
   301  }
   302  
   303  func (f *FigTree) LoadConfig(file string, options interface{}) error {
   304  	cs, err := f.ReadFile(file)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	if cs == nil {
   309  		// no file contents to parse, file likely does not exist
   310  		return nil
   311  	}
   312  	return f.LoadConfigBytes(cs.Config, cs.Filename, options)
   313  }
   314  
   315  // ReadFile will return a ConfigSource for given file path.  If the
   316  // file is executable (and WithoutExec was not used), it will execute
   317  // the file and return the stdout otherwise it will return the file
   318  // contents directly.
   319  func (f *FigTree) ReadFile(file string) (*ConfigSource, error) {
   320  	rel, err := filepath.Rel(f.workDir, file)
   321  	if err != nil {
   322  		rel = file
   323  	}
   324  
   325  	if stat, err := os.Stat(file); err == nil {
   326  		if stat.Mode()&0111 == 0 || !f.exec {
   327  			Log.Debugf("Reading config %s", file)
   328  			data, err := ioutil.ReadFile(file)
   329  			if err != nil {
   330  				return nil, errors.Wrapf(err, "Failed to read %s", rel)
   331  			}
   332  			return &ConfigSource{
   333  				Config:   data,
   334  				Filename: rel,
   335  			}, nil
   336  		} else {
   337  			Log.Debugf("Found Executable Config file: %s", file)
   338  			// it is executable, so run it and try to parse the output
   339  			cmd := exec.Command(file)
   340  			stdout := bytes.NewBufferString("")
   341  			cmd.Stdout = stdout
   342  			cmd.Stderr = bytes.NewBufferString("")
   343  			if err := cmd.Run(); err != nil {
   344  				return nil, errors.Wrapf(err, "%s is exectuable, but it failed to execute:\n%s", file, cmd.Stderr)
   345  			}
   346  			return &ConfigSource{
   347  				Config:   stdout.Bytes(),
   348  				Filename: rel,
   349  			}, nil
   350  		}
   351  	}
   352  	return nil, nil
   353  }
   354  
   355  func FindParentPaths(homedir, cwd, fileName string) []string {
   356  	paths := make([]string, 0)
   357  	if filepath.IsAbs(fileName) {
   358  		// dont recursively look for files when fileName is an abspath
   359  		_, err := os.Stat(fileName)
   360  		if err == nil {
   361  			paths = append(paths, fileName)
   362  		}
   363  		return paths
   364  	}
   365  
   366  	// special case if homedir is not in current path then check there anyway
   367  	if !strings.HasPrefix(cwd, homedir) {
   368  		file := path.Join(homedir, fileName)
   369  		if _, err := os.Stat(file); err == nil {
   370  			paths = append(paths, filepath.FromSlash(file))
   371  		}
   372  	}
   373  
   374  	var dir string
   375  	for _, part := range strings.Split(cwd, string(os.PathSeparator)) {
   376  		if part == "" && dir == "" {
   377  			dir = "/"
   378  		} else {
   379  			dir = path.Join(dir, part)
   380  		}
   381  		file := path.Join(dir, fileName)
   382  		if _, err := os.Stat(file); err == nil {
   383  			paths = append(paths, filepath.FromSlash(file))
   384  		}
   385  	}
   386  	return paths
   387  }
   388  
   389  func (f *FigTree) FindParentPaths(fileName string) []string {
   390  	return FindParentPaths(f.home, f.workDir, fileName)
   391  }
   392  
   393  var camelCaseWords = regexp.MustCompile("[0-9A-Za-z]+")
   394  
   395  func camelCase(name string) string {
   396  	words := camelCaseWords.FindAllString(name, -1)
   397  	for i, word := range words {
   398  		words[i] = strings.Title(word)
   399  	}
   400  	return strings.Join(words, "")
   401  
   402  }
   403  
   404  type Merger struct {
   405  	sourceFile  string
   406  	preserveMap map[string]struct{}
   407  	Config      ConfigOptions `json:"config,omitempty" yaml:"config,omitempty"`
   408  	ignore      []string
   409  }
   410  
   411  type MergeOption func(*Merger)
   412  
   413  func WithSourceFile(source string) MergeOption {
   414  	return func(m *Merger) {
   415  		m.sourceFile = source
   416  	}
   417  }
   418  
   419  func PreserveMap(keys ...string) MergeOption {
   420  	return func(m *Merger) {
   421  		for _, key := range keys {
   422  			m.preserveMap[key] = struct{}{}
   423  		}
   424  	}
   425  }
   426  
   427  func NewMerger(options ...MergeOption) *Merger {
   428  	m := &Merger{
   429  		sourceFile:  "merge",
   430  		preserveMap: make(map[string]struct{}),
   431  	}
   432  	for _, opt := range options {
   433  		opt(m)
   434  	}
   435  	return m
   436  }
   437  
   438  // advance will move all the current overwrite properties to
   439  // the ignore properties, then reset the overwrite properties.
   440  // This is used after a document has be processed so the next
   441  // document does not modify overwritten fields.
   442  func (m *Merger) advance() {
   443  	for _, overwrite := range m.Config.Overwrite {
   444  		found := false
   445  		for _, ignore := range m.ignore {
   446  			if ignore == overwrite {
   447  				found = true
   448  				break
   449  			}
   450  		}
   451  		if !found {
   452  			m.ignore = append(m.ignore, overwrite)
   453  		}
   454  	}
   455  	m.Config.Overwrite = nil
   456  }
   457  
   458  // Merge will attempt to merge the data from src into dst.  They shoud be either both maps or both structs.
   459  // The structs do not need to have the same structure, but any field name that exists in both
   460  // structs will must be the same type.
   461  func Merge(dst, src interface{}) {
   462  	m := NewMerger()
   463  	m.mergeStructs(reflect.ValueOf(dst), reflect.ValueOf(src))
   464  }
   465  
   466  // MakeMergeStruct will take multiple structs and return a pointer to a zero value for the
   467  // anonymous struct that has all the public fields from all the structs merged into one struct.
   468  // If there are multiple structs with the same field names, the first appearance of that name
   469  // will be used.
   470  func MakeMergeStruct(structs ...interface{}) interface{} {
   471  	m := NewMerger()
   472  	return m.MakeMergeStruct(structs...)
   473  }
   474  
   475  func (m *Merger) MakeMergeStruct(structs ...interface{}) interface{} {
   476  	values := []reflect.Value{}
   477  	for _, data := range structs {
   478  		values = append(values, reflect.ValueOf(data))
   479  	}
   480  	return m.makeMergeStruct(values...).Interface()
   481  }
   482  
   483  func inlineField(field reflect.StructField) bool {
   484  	if tag := field.Tag.Get("figtree"); tag != "" {
   485  		return strings.HasSuffix(tag, ",inline")
   486  	}
   487  	if tag := field.Tag.Get("yaml"); tag != "" {
   488  		return strings.HasSuffix(tag, ",inline")
   489  	}
   490  	return false
   491  }
   492  
   493  func (m *Merger) makeMergeStruct(values ...reflect.Value) reflect.Value {
   494  	foundFields := map[string]reflect.StructField{}
   495  	for i := 0; i < len(values); i++ {
   496  		v := values[i]
   497  		if v.Kind() == reflect.Ptr {
   498  			v = v.Elem()
   499  		}
   500  		typ := v.Type()
   501  		var field reflect.StructField
   502  		if typ.Kind() == reflect.Struct {
   503  			for i := 0; i < typ.NumField(); i++ {
   504  				field = typ.Field(i)
   505  				if field.PkgPath != "" {
   506  					// unexported field, skip
   507  					continue
   508  				}
   509  				if f, ok := foundFields[field.Name]; ok {
   510  					if f.Type.Kind() == reflect.Struct && field.Type.Kind() == reflect.Struct {
   511  						if fName, fieldName := f.Type.Name(), field.Type.Name(); fName == "" || fieldName == "" || fName != fieldName {
   512  							// we have 2 fields with the same name and they are both structs, so we need
   513  							// to merge the existing struct with the new one in case they are different
   514  							newval := m.makeMergeStruct(reflect.New(f.Type).Elem(), reflect.New(field.Type).Elem()).Elem()
   515  							f.Type = newval.Type()
   516  							foundFields[field.Name] = f
   517  						}
   518  					}
   519  					// field already found, skip
   520  					continue
   521  				}
   522  				if inlineField(field) {
   523  					values = append(values, v.Field(i))
   524  					continue
   525  				}
   526  				foundFields[field.Name] = field
   527  			}
   528  		} else if typ.Kind() == reflect.Map {
   529  			for _, key := range v.MapKeys() {
   530  				keyval := reflect.ValueOf(v.MapIndex(key).Interface())
   531  				if _, ok := m.preserveMap[key.String()]; !ok {
   532  					if keyval.Kind() == reflect.Ptr && keyval.Elem().Kind() == reflect.Map {
   533  						keyval = m.makeMergeStruct(keyval.Elem())
   534  					} else if keyval.Kind() == reflect.Map {
   535  						keyval = m.makeMergeStruct(keyval).Elem()
   536  					}
   537  				}
   538  				var t reflect.Type
   539  				if !keyval.IsValid() {
   540  					// this nonsense is to create a generic `interface{}` type.  There is
   541  					// probably an easier to do this, but it eludes me at the moment.
   542  					var dummy interface{}
   543  					t = reflect.ValueOf(&dummy).Elem().Type()
   544  				} else {
   545  					t = reflect.ValueOf(keyval.Interface()).Type()
   546  				}
   547  				field = reflect.StructField{
   548  					Name: camelCase(key.String()),
   549  					Type: t,
   550  					Tag:  reflect.StructTag(fmt.Sprintf(`json:"%s" yaml:"%s"`, key.String(), key.String())),
   551  				}
   552  				if f, ok := foundFields[field.Name]; ok {
   553  					if f.Type.Kind() == reflect.Struct && t.Kind() == reflect.Struct {
   554  						if fName, tName := f.Type.Name(), t.Name(); fName == "" || tName == "" || fName != tName {
   555  							// we have 2 fields with the same name and they are both structs, so we need
   556  							// to merge the existig struct with the new one in case they are different
   557  							newval := m.makeMergeStruct(reflect.New(f.Type).Elem(), reflect.New(t).Elem()).Elem()
   558  							f.Type = newval.Type()
   559  							foundFields[field.Name] = f
   560  						}
   561  					}
   562  					// field already found, skip
   563  					continue
   564  				}
   565  				foundFields[field.Name] = field
   566  			}
   567  		}
   568  	}
   569  
   570  	fields := []reflect.StructField{}
   571  	for _, value := range foundFields {
   572  		fields = append(fields, value)
   573  	}
   574  	sort.Slice(fields, func(i, j int) bool {
   575  		return fields[i].Name < fields[j].Name
   576  	})
   577  	newType := reflect.StructOf(fields)
   578  	return reflect.New(newType)
   579  }
   580  
   581  func (m *Merger) mapToStruct(src reflect.Value) reflect.Value {
   582  	if src.Kind() != reflect.Map {
   583  		return reflect.Value{}
   584  	}
   585  
   586  	dest := m.makeMergeStruct(src)
   587  	if dest.Kind() == reflect.Ptr {
   588  		dest = dest.Elem()
   589  	}
   590  
   591  	for _, key := range src.MapKeys() {
   592  		structFieldName := camelCase(key.String())
   593  		keyval := reflect.ValueOf(src.MapIndex(key).Interface())
   594  		// skip invalid (ie nil) key values
   595  		if !keyval.IsValid() {
   596  			continue
   597  		}
   598  		if keyval.Kind() == reflect.Ptr && keyval.Elem().Kind() == reflect.Map {
   599  			keyval = m.mapToStruct(keyval.Elem()).Addr()
   600  			m.mergeStructs(dest.FieldByName(structFieldName), reflect.ValueOf(keyval.Interface()))
   601  		} else if keyval.Kind() == reflect.Map {
   602  			keyval = m.mapToStruct(keyval)
   603  			m.mergeStructs(dest.FieldByName(structFieldName), reflect.ValueOf(keyval.Interface()))
   604  		} else {
   605  			dest.FieldByName(structFieldName).Set(reflect.ValueOf(keyval.Interface()))
   606  		}
   607  	}
   608  	return dest
   609  }
   610  
   611  func structToMap(src reflect.Value) reflect.Value {
   612  	if src.Kind() != reflect.Struct {
   613  		return reflect.Value{}
   614  	}
   615  
   616  	dest := reflect.ValueOf(map[string]interface{}{})
   617  
   618  	typ := src.Type()
   619  
   620  	for i := 0; i < typ.NumField(); i++ {
   621  		structField := typ.Field(i)
   622  		if structField.PkgPath != "" {
   623  			// skip private fields
   624  			continue
   625  		}
   626  		name := yamlFieldName(structField)
   627  		dest.SetMapIndex(reflect.ValueOf(name), src.Field(i))
   628  	}
   629  
   630  	return dest
   631  }
   632  
   633  type ConfigOptions struct {
   634  	Overwrite []string `json:"overwrite,omitempty" yaml:"overwrite,omitempty"`
   635  }
   636  
   637  func yamlFieldName(sf reflect.StructField) string {
   638  	if tag, ok := sf.Tag.Lookup("yaml"); ok {
   639  		// with yaml:"foobar,omitempty"
   640  		// we just want to the "foobar" part
   641  		parts := strings.Split(tag, ",")
   642  		return parts[0]
   643  	}
   644  	// guess the field name from reversing camel case
   645  	// so "FooBar" becomes "foo-bar"
   646  	parts := camelcase.Split(sf.Name)
   647  	for i := range parts {
   648  		parts[i] = strings.ToLower(parts[i])
   649  	}
   650  	return strings.Join(parts, "-")
   651  }
   652  
   653  func (m *Merger) mustOverwrite(name string) bool {
   654  	for _, prop := range m.Config.Overwrite {
   655  		if name == prop {
   656  			return true
   657  		}
   658  	}
   659  	return false
   660  }
   661  
   662  func (m *Merger) mustIgnore(name string) bool {
   663  	for _, prop := range m.ignore {
   664  		if name == prop {
   665  			return true
   666  		}
   667  	}
   668  	return false
   669  }
   670  
   671  func isDefault(v reflect.Value) bool {
   672  	if v.CanAddr() {
   673  		if option, ok := v.Addr().Interface().(option); ok {
   674  			if option.GetSource() == "default" {
   675  				return true
   676  			}
   677  		}
   678  	}
   679  	return false
   680  }
   681  
   682  func isZero(v reflect.Value) bool {
   683  	if !v.IsValid() {
   684  		return true
   685  	}
   686  	return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
   687  }
   688  
   689  func isSame(v1, v2 reflect.Value) bool {
   690  	return reflect.DeepEqual(v1.Interface(), v2.Interface())
   691  }
   692  
   693  // recursively set the Source attribute of the Options
   694  func (m *Merger) setSource(v reflect.Value) {
   695  	if v.Kind() == reflect.Ptr {
   696  		v = v.Elem()
   697  	}
   698  	switch v.Kind() {
   699  	case reflect.Map:
   700  		for _, key := range v.MapKeys() {
   701  			keyval := v.MapIndex(key)
   702  			if keyval.Kind() == reflect.Struct && keyval.FieldByName("Source").IsValid() {
   703  				// map values are immutable, so we need to copy the value
   704  				// update the value, then re-insert the value to the map
   705  				newval := reflect.New(keyval.Type())
   706  				newval.Elem().Set(keyval)
   707  				m.setSource(newval)
   708  				v.SetMapIndex(key, newval.Elem())
   709  			}
   710  		}
   711  	case reflect.Struct:
   712  		if v.CanAddr() {
   713  			if option, ok := v.Addr().Interface().(option); ok {
   714  				if option.IsDefined() {
   715  					option.SetSource(m.sourceFile)
   716  				}
   717  				return
   718  			}
   719  		}
   720  		for i := 0; i < v.NumField(); i++ {
   721  			structField := v.Type().Field(i)
   722  			// PkgPath is empty for upper case (exported) field names.
   723  			if structField.PkgPath != "" {
   724  				// unexported field, skipping
   725  				continue
   726  			}
   727  			m.setSource(v.Field(i))
   728  		}
   729  	case reflect.Array:
   730  		fallthrough
   731  	case reflect.Slice:
   732  		for i := 0; i < v.Len(); i++ {
   733  			m.setSource(v.Index(i))
   734  		}
   735  	}
   736  }
   737  
   738  func (m *Merger) assignValue(dest, src reflect.Value, overwrite bool) {
   739  	if src.Type().AssignableTo(dest.Type()) {
   740  		shouldAssignDest := overwrite || isZero(dest) || (isDefault(dest) && !isDefault(src))
   741  		isValidSrc := !isZero(src)
   742  		if shouldAssignDest && isValidSrc {
   743  			if src.Kind() == reflect.Map {
   744  				// maps are mutable, so create a brand new shiny one
   745  				dup := reflect.New(src.Type()).Elem()
   746  				m.mergeMaps(dup, src)
   747  				dest.Set(dup)
   748  			} else {
   749  				dest.Set(src)
   750  			}
   751  			return
   752  		}
   753  		return
   754  	}
   755  	if dest.CanAddr() {
   756  		if option, ok := dest.Addr().Interface().(option); ok {
   757  			destOptionValue := reflect.ValueOf(option.GetValue())
   758  			// map interface type to real-ish type:
   759  			src = reflect.ValueOf(src.Interface())
   760  			if !src.IsValid() {
   761  				Log.Debugf("assignValue: src isValid: %t", src.IsValid())
   762  				return
   763  			}
   764  			if src.Type().AssignableTo(destOptionValue.Type()) {
   765  				option.SetValue(src.Interface())
   766  				option.SetSource(m.sourceFile)
   767  				Log.Debugf("assignValue: assigned %#v to %#v", destOptionValue, src)
   768  				return
   769  			}
   770  			if destOptionValue.Kind() == reflect.Bool && src.Kind() == reflect.String {
   771  				b, err := strconv.ParseBool(src.Interface().(string))
   772  				if err != nil {
   773  					panic(fmt.Errorf("%s is not assignable to %s, invalid bool value: %s", src.Type(), destOptionValue.Type(), err))
   774  				}
   775  				option.SetValue(b)
   776  				option.SetSource(m.sourceFile)
   777  				Log.Debugf("assignValue: assigned %#v to %#v", destOptionValue, b)
   778  				return
   779  			}
   780  			if destOptionValue.Kind() == reflect.String && src.Kind() != reflect.String {
   781  				option.SetValue(fmt.Sprintf("%v", src.Interface()))
   782  				option.SetSource(m.sourceFile)
   783  				Log.Debugf("assignValue: assigned %#v to %#v", destOptionValue, src)
   784  				return
   785  			}
   786  			panic(fmt.Errorf("%s is not assignable to %s", src.Type(), destOptionValue.Type()))
   787  		}
   788  	}
   789  	// make copy so we can reliably Addr it to see if it fits the
   790  	// Option interface.
   791  	srcCopy := reflect.New(src.Type()).Elem()
   792  	srcCopy.Set(src)
   793  	if option, ok := srcCopy.Addr().Interface().(option); ok {
   794  		srcOptionValue := reflect.ValueOf(option.GetValue())
   795  		if srcOptionValue.Type().AssignableTo(dest.Type()) {
   796  			m.assignValue(dest, srcOptionValue, overwrite)
   797  			return
   798  		} else {
   799  			panic(fmt.Errorf("%s is not assinable to %s", srcOptionValue.Type(), dest.Type()))
   800  		}
   801  	}
   802  }
   803  
   804  func fromInterface(v reflect.Value) (reflect.Value, func()) {
   805  	if v.Kind() == reflect.Interface {
   806  		realV := reflect.ValueOf(v.Interface())
   807  		if !realV.IsValid() {
   808  			realV = reflect.New(v.Type()).Elem()
   809  			v.Set(realV)
   810  			return v, func() {}
   811  		}
   812  		tmp := reflect.New(realV.Type()).Elem()
   813  		tmp.Set(realV)
   814  		return tmp, func() {
   815  			v.Set(tmp)
   816  		}
   817  	}
   818  	return v, func() {}
   819  }
   820  
   821  func (m *Merger) mergeStructs(ov, nv reflect.Value) {
   822  	ov = reflect.Indirect(ov)
   823  	nv = reflect.Indirect(nv)
   824  
   825  	ov, restore := fromInterface(ov)
   826  	defer restore()
   827  
   828  	if nv.Kind() == reflect.Interface {
   829  		nv = reflect.ValueOf(nv.Interface())
   830  	}
   831  
   832  	if ov.Kind() == reflect.Map {
   833  		if nv.Kind() == reflect.Struct {
   834  			nv = structToMap(nv)
   835  		}
   836  		m.mergeMaps(ov, nv)
   837  		return
   838  	}
   839  
   840  	if ov.Kind() == reflect.Struct && nv.Kind() == reflect.Map {
   841  		nv = m.mapToStruct(nv)
   842  	}
   843  
   844  	if !ov.IsValid() || !nv.IsValid() {
   845  		Log.Debugf("Valid: ov:%v nv:%t", ov.IsValid(), nv.IsValid())
   846  		return
   847  	}
   848  
   849  	for i := 0; i < nv.NumField(); i++ {
   850  		nvField := nv.Field(i)
   851  		if nvField.Kind() == reflect.Interface {
   852  			nvField = reflect.ValueOf(nvField.Interface())
   853  		}
   854  		if !nvField.IsValid() {
   855  			continue
   856  		}
   857  
   858  		nvStructField := nv.Type().Field(i)
   859  		ovStructField, ok := ov.Type().FieldByName(nvStructField.Name)
   860  		if !ok {
   861  			if nvStructField.Anonymous {
   862  				// this is an embedded struct, and the destination does not contain
   863  				// the same embeded struct, so try to merge the embedded struct
   864  				// directly with the destination
   865  				m.mergeStructs(ov, nvField)
   866  				continue
   867  			}
   868  			// if original value does not have the same struct field
   869  			// then just skip this field.
   870  			continue
   871  		}
   872  
   873  		// PkgPath is empty for upper case (exported) field names.
   874  		if ovStructField.PkgPath != "" || nvStructField.PkgPath != "" {
   875  			// unexported field, skipping
   876  			continue
   877  		}
   878  		fieldName := yamlFieldName(ovStructField)
   879  
   880  		ovField := ov.FieldByName(nvStructField.Name)
   881  		ovField, restore := fromInterface(ovField)
   882  		defer restore()
   883  
   884  		if m.mustIgnore(fieldName) {
   885  			continue
   886  		}
   887  
   888  		if (isZero(ovField) || isDefault(ovField) || m.mustOverwrite(fieldName)) && !isSame(ovField, nvField) {
   889  			Log.Debugf("Setting %s to %#v", nv.Type().Field(i).Name, nvField.Interface())
   890  			m.assignValue(ovField, nvField, m.mustOverwrite(fieldName))
   891  		}
   892  		switch ovField.Kind() {
   893  		case reflect.Map:
   894  			Log.Debugf("Merging Map: %#v with %#v", ovField, nvField)
   895  			m.mergeStructs(ovField, nvField)
   896  		case reflect.Slice:
   897  			if nvField.Len() > 0 {
   898  				Log.Debugf("Merging Slice: %#v with %#v", ovField, nvField)
   899  				ovField.Set(m.mergeArrays(ovField, nvField))
   900  			}
   901  		case reflect.Array:
   902  			if nvField.Len() > 0 {
   903  				Log.Debugf("Merging Array: %v with %v", ovField, nvField)
   904  				ovField.Set(m.mergeArrays(ovField, nvField))
   905  			}
   906  		case reflect.Struct:
   907  			// only merge structs if they are not an Option type:
   908  			if _, ok := ovField.Addr().Interface().(option); !ok {
   909  				Log.Debugf("Merging Struct: %v with %v", ovField, nvField)
   910  				m.mergeStructs(ovField, nvField)
   911  			}
   912  		}
   913  	}
   914  }
   915  
   916  func (m *Merger) mergeMaps(ov, nv reflect.Value) {
   917  	for _, key := range nv.MapKeys() {
   918  		if !ov.MapIndex(key).IsValid() {
   919  			ovElem := reflect.New(ov.Type().Elem()).Elem()
   920  			m.assignValue(ovElem, nv.MapIndex(key), false)
   921  			if ov.IsNil() {
   922  				if !ov.CanSet() {
   923  					continue
   924  				}
   925  				ov.Set(reflect.MakeMap(ov.Type()))
   926  			}
   927  			Log.Debugf("Setting %v to %#v", key.Interface(), ovElem.Interface())
   928  			ov.SetMapIndex(key, ovElem)
   929  		} else {
   930  			ovi := reflect.ValueOf(ov.MapIndex(key).Interface())
   931  			nvi := reflect.ValueOf(nv.MapIndex(key).Interface())
   932  			if !nvi.IsValid() {
   933  				continue
   934  			}
   935  			switch ovi.Kind() {
   936  			case reflect.Map:
   937  				Log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
   938  				m.mergeStructs(ovi, nvi)
   939  			case reflect.Slice:
   940  				Log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
   941  				ov.SetMapIndex(key, m.mergeArrays(ovi, nvi))
   942  			case reflect.Array:
   943  				Log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
   944  				ov.SetMapIndex(key, m.mergeArrays(ovi, nvi))
   945  			default:
   946  				if isZero(ovi) {
   947  					if !ovi.IsValid() || nvi.Type().AssignableTo(ovi.Type()) {
   948  						ov.SetMapIndex(key, nvi)
   949  					} else {
   950  						// to check for the Option interface we need the Addr of the value, but
   951  						// we cannot take the Addr of a map value, so we have to first copy
   952  						// it, meh not optimal
   953  						newVal := reflect.New(nvi.Type())
   954  						newVal.Elem().Set(nvi)
   955  						if nOption, ok := newVal.Interface().(option); ok {
   956  							ov.SetMapIndex(key, reflect.ValueOf(nOption.GetValue()))
   957  							continue
   958  						}
   959  						panic(fmt.Errorf("map value %T is not assignable to %T", nvi.Interface(), ovi.Interface()))
   960  					}
   961  
   962  				}
   963  			}
   964  		}
   965  	}
   966  }
   967  
   968  func (m *Merger) mergeArrays(ov, nv reflect.Value) reflect.Value {
   969  	var zero interface{}
   970  Outer:
   971  	for ni := 0; ni < nv.Len(); ni++ {
   972  		niv := nv.Index(ni)
   973  
   974  		n := niv
   975  		if n.CanAddr() {
   976  			if nOption, ok := n.Addr().Interface().(option); ok {
   977  				if !nOption.IsDefined() {
   978  					continue
   979  				}
   980  				n = reflect.ValueOf(nOption.GetValue())
   981  			}
   982  		}
   983  
   984  		if reflect.DeepEqual(n.Interface(), zero) {
   985  			continue
   986  		}
   987  
   988  		for oi := 0; oi < ov.Len(); oi++ {
   989  			o := ov.Index(oi)
   990  			if o.CanAddr() {
   991  				if oOption, ok := o.Addr().Interface().(option); ok {
   992  					o = reflect.ValueOf(oOption.GetValue())
   993  				}
   994  			}
   995  			if reflect.DeepEqual(n.Interface(), o.Interface()) {
   996  				continue Outer
   997  			}
   998  		}
   999  
  1000  		nvElem := reflect.New(ov.Type().Elem()).Elem()
  1001  		m.assignValue(nvElem, niv, false)
  1002  
  1003  		Log.Debugf("Appending %v to %v", nvElem.Interface(), ov)
  1004  		ov = reflect.Append(ov, nvElem)
  1005  	}
  1006  	return ov
  1007  }
  1008  
  1009  func (f *FigTree) formatEnvName(name string) string {
  1010  	name = fmt.Sprintf("%s_%s", f.envPrefix, strings.ToUpper(name))
  1011  
  1012  	return strings.Map(func(r rune) rune {
  1013  		if unicode.IsDigit(r) || unicode.IsLetter(r) {
  1014  			return r
  1015  		}
  1016  		return '_'
  1017  	}, name)
  1018  }
  1019  
  1020  func (f *FigTree) formatEnvValue(value reflect.Value) (string, bool) {
  1021  	switch t := value.Interface().(type) {
  1022  	case string:
  1023  		return t, true
  1024  	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
  1025  		return fmt.Sprintf("%v", t), true
  1026  	default:
  1027  		switch value.Kind() {
  1028  		case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
  1029  			if value.IsNil() {
  1030  				return "", false
  1031  			}
  1032  		}
  1033  		if t == nil {
  1034  			return "", false
  1035  		}
  1036  		type definable interface {
  1037  			IsDefined() bool
  1038  		}
  1039  		if def, ok := t.(definable); ok {
  1040  			// skip fields that are not defined
  1041  			if !def.IsDefined() {
  1042  				return "", false
  1043  			}
  1044  		}
  1045  		type gettable interface {
  1046  			GetValue() interface{}
  1047  		}
  1048  		if get, ok := t.(gettable); ok {
  1049  			return fmt.Sprintf("%v", get.GetValue()), true
  1050  		} else {
  1051  			if b, err := json.Marshal(t); err == nil {
  1052  				val := strings.TrimSpace(string(b))
  1053  				if val == "null" {
  1054  					return "", true
  1055  				}
  1056  				return val, true
  1057  			}
  1058  		}
  1059  	}
  1060  	return "", false
  1061  }
  1062  
  1063  func (f *FigTree) PopulateEnv(data interface{}) (changeSet map[string]*string) {
  1064  	changeSet = make(map[string]*string)
  1065  
  1066  	options := reflect.ValueOf(data)
  1067  	if options.Kind() == reflect.Ptr {
  1068  		options = reflect.ValueOf(options.Elem().Interface())
  1069  	}
  1070  	if options.Kind() == reflect.Map {
  1071  		for _, key := range options.MapKeys() {
  1072  			if strKey, ok := key.Interface().(string); ok {
  1073  				// first chunk up string so that `foo-bar` becomes ["foo", "bar"]
  1074  				parts := strings.FieldsFunc(strKey, func(r rune) bool {
  1075  					return !unicode.IsLetter(r) && !unicode.IsNumber(r)
  1076  				})
  1077  				// now for each chunk split again on camelcase so ["fooBar", "baz"]
  1078  				// becomes ["foo", "Bar", "baz"]
  1079  				allParts := []string{}
  1080  				for _, part := range parts {
  1081  					allParts = append(allParts, camelcase.Split(part)...)
  1082  				}
  1083  
  1084  				name := strings.Join(allParts, "_")
  1085  				envName := f.formatEnvName(name)
  1086  				val, ok := f.formatEnvValue(options.MapIndex(key))
  1087  				if ok {
  1088  					changeSet[envName] = &val
  1089  				} else {
  1090  					changeSet[envName] = nil
  1091  				}
  1092  			}
  1093  		}
  1094  	} else if options.Kind() == reflect.Struct {
  1095  		for i := 0; i < options.NumField(); i++ {
  1096  			structField := options.Type().Field(i)
  1097  			// PkgPath is empty for upper case (exported) field names.
  1098  			if structField.PkgPath != "" {
  1099  				// unexported field, skipping
  1100  				continue
  1101  			}
  1102  
  1103  			envNames := []string{strings.Join(camelcase.Split(structField.Name), "_")}
  1104  			formatName := true
  1105  			if tag := structField.Tag.Get("figtree"); tag != "" {
  1106  				if strings.Contains(tag, ",inline") {
  1107  					// if we have a tag like: `figtree:",inline"` then we
  1108  					// want to the field as a top level member and not serialize
  1109  					// the raw struct to json, so just recurse here
  1110  					nestedEnvSet := f.PopulateEnv(options.Field(i).Interface())
  1111  					for k, v := range nestedEnvSet {
  1112  						changeSet[k] = v
  1113  					}
  1114  					continue
  1115  				}
  1116  				if strings.Contains(tag, ",raw") {
  1117  					formatName = false
  1118  				}
  1119  				// next look for `figtree:"env,..."` to set the env name to that
  1120  				parts := strings.Split(tag, ",")
  1121  				if len(parts) > 0 {
  1122  					// if the env name is "-" then we should not populate this data into the env
  1123  					if parts[0] == "-" {
  1124  						continue
  1125  					}
  1126  					envNames = strings.Split(parts[0], ";")
  1127  				}
  1128  			}
  1129  			for _, name := range envNames {
  1130  				envName := name
  1131  				if formatName {
  1132  					envName = f.formatEnvName(name)
  1133  				}
  1134  				val, ok := f.formatEnvValue(options.Field(i))
  1135  				if ok {
  1136  					changeSet[envName] = &val
  1137  				} else {
  1138  					changeSet[envName] = nil
  1139  				}
  1140  			}
  1141  		}
  1142  	}
  1143  
  1144  	return changeSet
  1145  }