Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 954cb2b

Browse files
elfinheistio-testing
authored andcommitted
Improve mandiff by ignoring specified paths in manifests. Add yaml compare tool to only output diff with neat paths. (#225)
1 parent 4220715 commit 954cb2b

File tree

5 files changed

+854
-30
lines changed

5 files changed

+854
-30
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ require (
5353
github.com/golang/protobuf v1.3.1
5454
github.com/golang/snappy v0.0.1 // indirect
5555
github.com/google/btree v1.0.0 // indirect
56+
github.com/google/go-cmp v0.3.0
5657
github.com/google/gofuzz v1.0.0 // indirect
5758
github.com/google/uuid v1.1.1 // indirect
5859
github.com/googleapis/gnostic v0.2.0 // indirect

pkg/compare/compare.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2019 Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package compare
16+
17+
import (
18+
"fmt"
19+
"path/filepath"
20+
"strings"
21+
22+
"github.com/google/go-cmp/cmp"
23+
"sigs.k8s.io/yaml"
24+
25+
"istio.io/operator/pkg/tpath"
26+
)
27+
28+
// YAMLCmpReporter is a custom reporter to generate tree based diff for YAMLs, used by cmp.Equal().
29+
type YAMLCmpReporter struct {
30+
path cmp.Path
31+
diffTree map[string]interface{}
32+
}
33+
34+
// PushStep implements interface to keep track of current path by pushing.
35+
// a step into YAMLCmpReporter.path
36+
func (r *YAMLCmpReporter) PushStep(ps cmp.PathStep) {
37+
r.path = append(r.path, ps)
38+
}
39+
40+
// PopStep implements interface to keep track of current path by popping a step out.
41+
// of YAMLCmpReporter.path
42+
func (r *YAMLCmpReporter) PopStep() {
43+
r.path = r.path[:len(r.path)-1]
44+
}
45+
46+
// Report implements interface to add diff path into YAMLCmpReporter.diffTree.
47+
func (r *YAMLCmpReporter) Report(rs cmp.Result) {
48+
if !rs.Equal() {
49+
vx, vy := r.path.Last().Values()
50+
var dm string
51+
if vx.IsValid() && !vy.IsValid() {
52+
dm = fmt.Sprintf("%v ->", vx)
53+
} else if !vx.IsValid() && vy.IsValid() {
54+
dm = fmt.Sprintf("-> %v", vy)
55+
} else if vx.IsValid() && vy.IsValid() {
56+
dm = fmt.Sprintf("%v -> %v", vx, vy)
57+
}
58+
if r.diffTree == nil {
59+
r.diffTree = make(map[string]interface{})
60+
}
61+
if err := tpath.WriteNode(r.diffTree, pathToStringList(r.path), dm); err != nil {
62+
panic(err)
63+
}
64+
}
65+
}
66+
67+
// String returns a text representation of diff tree.
68+
func (r *YAMLCmpReporter) String() string {
69+
if len(r.diffTree) == 0 {
70+
return ""
71+
}
72+
y, err := yaml.Marshal(r.diffTree)
73+
if err != nil {
74+
return err.Error()
75+
}
76+
return string(y)
77+
}
78+
79+
// YAMLCmp compares two yaml texts, return a tree based diff text.
80+
func YAMLCmp(a, b string) string {
81+
return YAMLCmpWithIgnore(a, b, nil)
82+
}
83+
84+
// YAMLCmpWithIgnore compares two yaml texts, and ignores paths in ignorePaths.
85+
func YAMLCmpWithIgnore(a, b string, ignorePaths []string) string {
86+
ao, bo := make(map[string]interface{}), make(map[string]interface{})
87+
if err := yaml.Unmarshal([]byte(a), &ao); err != nil {
88+
return err.Error()
89+
}
90+
if err := yaml.Unmarshal([]byte(b), &bo); err != nil {
91+
return err.Error()
92+
}
93+
var r YAMLCmpReporter
94+
cmp.Equal(ao, bo, cmp.Reporter(&r), genPathIgnoreOpt(ignorePaths))
95+
return r.String()
96+
}
97+
98+
// genPathIgnoreOpt returns a cmp.Option to ignore paths specified in parameter ignorePaths.
99+
func genPathIgnoreOpt(ignorePaths []string) cmp.Option {
100+
return cmp.FilterPath(func(curPath cmp.Path) bool {
101+
cp := strings.Join(pathToStringList(curPath), ".")
102+
for _, ip := range ignorePaths {
103+
if res, err := filepath.Match(ip, cp); err == nil && res {
104+
return true
105+
}
106+
}
107+
return false
108+
}, cmp.Ignore())
109+
}
110+
111+
func pathToStringList(path cmp.Path) (up []string) {
112+
for _, step := range path {
113+
switch t := step.(type) {
114+
case cmp.MapIndex:
115+
up = append(up, fmt.Sprintf("%v", t.Key()))
116+
case cmp.SliceIndex:
117+
up = append(up, fmt.Sprintf("%v", t.String()))
118+
}
119+
}
120+
return
121+
}

0 commit comments

Comments
 (0)