Skip to content

Instantly share code, notes, and snippets.

@anguslees
Created September 6, 2022 03:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anguslees/c58410bd6c8f2d59e5f7375f9eeafd35 to your computer and use it in GitHub Desktop.
Save anguslees/c58410bd6c8f2d59e5f7375f9eeafd35 to your computer and use it in GitHub Desktop.
Quick and dirty program to compare two directory trees of yaml (with some opinionated details)
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"gopkg.in/yaml.v3"
)
// Convert all numbers to float64, as the json gods intended.
// (go-yaml goes to some effort to represent ints as int, with no option to turn that off)
func floatify(obj interface{}) interface{} {
switch o := obj.(type) {
case map[string]interface{}:
ret := make(map[string]interface{}, len(o))
for k, v := range o {
ret[k] = floatify(v)
}
return ret
case []interface{}:
ret := make([]interface{}, len(o))
for i, v := range o {
ret[i] = floatify(v)
}
return ret
case nil:
return o
case bool:
return o
case string:
return o
case int:
return float64(o) // <- This is the bit we want
case int64:
return float64(o) // NB: Possibly loses precision
case float64:
return o
default:
panic(fmt.Sprintf("Encountered unexpected type %T (%#v)", obj, obj))
}
}
func readYamlTree(base string) ([]map[string]interface{}, error) {
var ret []map[string]interface{}
err := filepath.Walk(base,
func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
decoder := yaml.NewDecoder(f)
for {
d := make(map[string]interface{})
if err := decoder.Decode(&d); err != nil {
if err == io.EOF {
break
}
return err
}
d = floatify(d).(map[string]interface{})
ret = append(ret, d)
}
return nil
})
sort.Slice(ret, func(i, j int) bool {
return k8sLess(ret[i], ret[j])
})
return ret, err
}
func k8sLess(a, b map[string]interface{}) bool {
if ret := strings.Compare(a["apiVersion"].(string), b["apiVersion"].(string)); ret != 0 {
return ret == -1
}
if ret := strings.Compare(a["kind"].(string), b["kind"].(string)); ret != 0 {
return ret == -1
}
amd := a["metadata"].(map[string]interface{})
bmd := b["metadata"].(map[string]interface{})
if amd["namespace"] != nil || bmd["namespace"] != nil {
if ret := strings.Compare(amd["namespace"].(string), bmd["namespace"].(string)); ret != 0 {
return ret == -1
}
}
if ret := strings.Compare(amd["name"].(string), bmd["name"].(string)); ret != 0 {
return ret == -1
}
return false // equal
}
func compareDirs(pathA, pathB string) error {
fmt.Println("reading", pathA)
a, err := readYamlTree(pathA)
if err != nil {
return err
}
fmt.Printf("read %d objects\n", len(a))
fmt.Println("reading", pathB)
b, err := readYamlTree(pathB)
if err != nil {
return err
}
fmt.Printf("read %d objects\n", len(b))
fmt.Println("comparing")
if len(a) != len(b) {
return fmt.Errorf("len(a)=%d != len(b)=%d", len(a), len(b))
}
for i := range a {
ai, bi := a[i], b[i]
if !reflect.DeepEqual(ai, bi) {
fmt.Printf("a:\n%#v\n", ai)
fmt.Printf("b:\n%#v\n", bi)
return fmt.Errorf("%d: ai != bi !!", i)
}
}
return nil
}
func main() {
treeA, treeB := os.Args[1], os.Args[2]
leafDirs := make(map[string]bool)
err := filepath.Walk(treeA, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
p, err := filepath.Rel(treeA, path)
if err != nil {
panic(err)
}
leafDirs[filepath.Dir(p)] = true
}
return nil
})
if err != nil {
panic(err)
}
for d := range leafDirs {
fmt.Println(d)
if err := compareDirs(filepath.Join(treeA, d), filepath.Join(treeB, d)); err != nil {
panic(err)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment