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 }