-
Notifications
You must be signed in to change notification settings - Fork 172
/
Copy pathsubsetdiff.go
166 lines (141 loc) · 3.69 KB
/
subsetdiff.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package kubernetes
import (
"strings"
"github.com/pkg/errors"
"github.com/grafana/tanka/pkg/kubernetes/client"
"github.com/grafana/tanka/pkg/kubernetes/manifest"
"github.com/grafana/tanka/pkg/kubernetes/util"
)
type difference struct {
name string
live, merged string
}
// SubsetDiffer returns a implementation of Differ that computes the diff by
// comparing only the fields present in the desired state. This algorithm might
// miss information, but is all that's possible on cluster versions lower than
// 1.13.
func SubsetDiffer(c client.Client) Differ {
return func(state manifest.List) (*string, error) {
docs := []difference{}
errCh := make(chan error)
resultCh := make(chan difference)
for _, rawShould := range state {
go parallelSubsetDiff(c, rawShould, resultCh, errCh)
}
var lastErr error
for i := 0; i < len(state); i++ {
select {
case d := <-resultCh:
docs = append(docs, d)
case err := <-errCh:
lastErr = err
}
}
close(resultCh)
close(errCh)
if lastErr != nil {
return nil, errors.Wrap(lastErr, "calculating subset")
}
var diffs string
for _, d := range docs {
diffStr, err := util.DiffStr(d.name, d.live, d.merged)
if err != nil {
return nil, errors.Wrap(err, "invoking diff")
}
if diffStr != "" {
diffStr += "\n"
}
diffs += diffStr
}
diffs = strings.TrimSuffix(diffs, "\n")
if diffs == "" {
return nil, nil
}
return &diffs, nil
}
}
func parallelSubsetDiff(c client.Client, should manifest.Manifest, r chan difference, e chan error) {
diff, err := subsetDiff(c, should)
if err != nil {
e <- err
return
}
r <- *diff
}
func subsetDiff(c client.Client, m manifest.Manifest) (*difference, error) {
name := util.DiffName(m)
// kubectl output -> current state
rawIs, err := c.Get(
m.Metadata().Namespace(),
m.Kind(),
m.Metadata().Name(),
)
if _, ok := err.(client.ErrorNotFound); ok {
rawIs = map[string]interface{}{}
} else if err != nil {
return nil, errors.Wrap(err, "getting state from cluster")
}
should := m.String(false)
sub := subset(m, rawIs)
is := manifest.Manifest(sub).String(false)
if is == "{}\n" {
is = ""
}
return &difference{
name: name,
live: is,
merged: should,
}, nil
}
// subset removes all keys from big, that are not present in small.
// It makes big a subset of small.
// Kubernetes returns more keys than we can know about.
// This means, we need to remove all keys from the kubectl output, that are not present locally.
func subset(small, big map[string]interface{}) map[string]interface{} {
if small["namespace"] != nil {
big["namespace"] = small["namespace"]
}
// just ignore the apiVersion for now, too much bloat
if small["apiVersion"] != nil && big["apiVersion"] != nil {
big["apiVersion"] = small["apiVersion"]
}
for k, v := range big {
if _, ok := small[k]; !ok {
delete(big, k)
continue
}
switch b := v.(type) {
case map[string]interface{}:
if a, ok := small[k].(map[string]interface{}); ok {
big[k] = subset(a, b)
}
case []map[string]interface{}:
for i := range b {
if a, ok := small[k].([]map[string]interface{}); ok {
b[i] = subset(a[i], b[i])
}
}
case []interface{}:
for i := range b {
if a, ok := small[k].([]interface{}); ok {
if i >= len(a) {
// slice in config shorter than in live. Abort, as there are no entries to diff anymore
break
}
// value not a dict, no recursion needed
cShould, ok := a[i].(map[string]interface{})
if !ok {
continue
}
// value not a dict, no recursion needed
cIs, ok := b[i].(map[string]interface{})
if !ok {
continue
}
b[i] = subset(cShould, cIs)
}
}
}
}
return big
}