1 package cmd
2
3 import (
4 "fmt"
5 "reflect"
6 "strings"
7
8 "sigs.k8s.io/yaml"
9 )
10
11 type (
12 manifest = map[string]interface{}
13
14 diff struct {
15 path []string
16 a interface{}
17 b interface{}
18 }
19 )
20
21 func splitManifests(manifest string) []string {
22 manifests := strings.Split(manifest, "\n---\n")
23 filtered := []string{}
24 for _, m := range manifests {
25 if !isManifestEmpty(m) {
26 filtered = append(filtered, m)
27 }
28 }
29 return filtered
30 }
31
32 func isManifestEmpty(manifest string) bool {
33 lines := strings.Split(manifest, "\n")
34 for _, line := range lines {
35 if line == "" || strings.HasPrefix(line, "#") || line == "---" {
36 continue
37 }
38 return false
39 }
40 return true
41 }
42
43 func manifestKey(m manifest) string {
44 kind := m["kind"].(string)
45 meta := m["metadata"].(map[string]interface{})
46 name := meta["name"].(string)
47 return fmt.Sprintf("%s/%s", kind, name)
48 }
49
50 func (d diff) String() string {
51 expected, _ := yaml.Marshal(d.a)
52 actual, _ := yaml.Marshal(d.b)
53 return fmt.Sprintf("Diff at [%s]:\nExpected:\n%s\nActual:\n%s", d.path, string(expected), string(actual))
54 }
55
56 func parseManifestList(in string) map[string]manifest {
57 manifestList := splitManifests(in)
58 manifestMap := map[string]manifest{}
59 for _, m := range manifestList {
60 manifest := manifest{}
61 yaml.Unmarshal([]byte(m), &manifest)
62 manifestMap[manifestKey(manifest)] = manifest
63 }
64 return manifestMap
65 }
66
67 func diffManifest(a manifest, b manifest, path []string) []diff {
68 diffs := []diff{}
69 for k, v := range a {
70 bv, bvExists := b[k]
71 switch val := v.(type) {
72 case manifest:
73 if !bvExists {
74 diffs = append(diffs, diff{
75 path: extend(path, k),
76 a: val,
77 b: nil,
78 })
79 } else {
80 bvm, ok := bv.(manifest)
81 if !ok {
82 diffs = append(diffs, diff{
83 path: extend(path, k),
84 a: val,
85 b: bv,
86 })
87 } else {
88 diffs = append(diffs, diffManifest(val, bvm, extend(path, k))...)
89 }
90 }
91 case []interface{}:
92 bva, ok := bv.([]interface{})
93 if !ok {
94 diffs = append(diffs, diff{
95 path: extend(path, k),
96 a: val,
97 b: bv,
98 })
99 } else if len(val) != len(bva) {
100 diffs = append(diffs, diff{
101 path: extend(path, k),
102 a: val,
103 b: bva,
104 })
105 } else {
106 diffs = append(diffs, diffArray(val, bva, extend(path, k))...)
107 }
108 default:
109 if !bvExists {
110 diffs = append(diffs, diff{
111 path: extend(path, k),
112 a: val,
113 b: nil,
114 })
115 } else if !reflect.DeepEqual(val, bv) {
116 diffs = append(diffs, diff{
117 path: extend(path, k),
118 a: val,
119 b: bv,
120 })
121 }
122 }
123 }
124 for k, v := range b {
125 _, avExists := a[k]
126 if !avExists {
127 diffs = append(diffs, diff{
128 path: extend(path, k),
129 a: nil,
130 b: v,
131 })
132 }
133 }
134 return diffs
135 }
136
137 func diffArray(a, b []interface{}, path []string) []diff {
138 diffs := []diff{}
139 for i, v := range a {
140 switch aVal := v.(type) {
141 case manifest:
142 bm, ok := b[i].(manifest)
143 if !ok {
144 diffs = append(diffs, diff{
145 path: extend(path, fmt.Sprintf("%d", i)),
146 a: aVal,
147 b: b[i],
148 })
149 } else {
150 diffs = append(diffs, diffManifest(aVal, bm, extend(path, fmt.Sprintf("%d", i)))...)
151 }
152 case []interface{}:
153 ba, ok := b[i].([]interface{})
154 if !ok {
155 diffs = append(diffs, diff{
156 path: extend(path, fmt.Sprintf("%d", i)),
157 a: aVal,
158 b: b[i],
159 })
160 } else if len(aVal) != len(ba) {
161 diffs = append(diffs, diff{
162 path: extend(path, fmt.Sprintf("%d", i)),
163 a: aVal,
164 b: b[i],
165 })
166 } else {
167 diffs = append(diffs, diffArray(aVal, ba, extend(path, fmt.Sprintf("%d", i)))...)
168 }
169 default:
170 if !reflect.DeepEqual(v, b[i]) {
171 diffs = append(diffs, diff{
172 path: extend(path, fmt.Sprintf("%d", i)),
173 a: v,
174 b: b[i],
175 })
176 }
177 }
178 }
179 return diffs
180 }
181
182 func diffManifestLists(a map[string]manifest, b map[string]manifest) map[string][]diff {
183 diffs := map[string][]diff{}
184 for k, am := range a {
185 bm, ok := b[k]
186 if !ok {
187 diffs[k] = []diff{{
188 a: am,
189 b: nil,
190 path: []string{},
191 }}
192 } else {
193 diffs[k] = diffManifest(am, bm, []string{})
194 }
195 }
196 for k, bm := range b {
197 _, ok := a[k]
198 if !ok {
199 diffs[k] = []diff{{
200 a: nil,
201 b: bm,
202 path: []string{},
203 }}
204 }
205 }
206 return diffs
207 }
208
209
210
211
212 func extend(slice []string, next string) []string {
213 new := make([]string, len(slice)+1)
214 copy(new, slice)
215 new[len(slice)] = next
216 return new
217 }
218
View as plain text