2018-02-26 02:36:19 +00:00
|
|
|
package diff
|
|
|
|
|
|
|
|
|
|
import (
|
2018-03-01 07:36:00 +00:00
|
|
|
"errors"
|
2018-02-26 02:36:19 +00:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/yudai/gojsondiff"
|
|
|
|
|
"github.com/yudai/gojsondiff/formatter"
|
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type DiffResult struct {
|
2018-03-01 07:36:00 +00:00
|
|
|
Diff gojsondiff.Diff
|
|
|
|
|
Modified bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DiffResultList struct {
|
|
|
|
|
Diffs []DiffResult
|
|
|
|
|
Modified bool
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Diff performs a diff on two unstructured objects
|
|
|
|
|
func Diff(left, right *unstructured.Unstructured) *DiffResult {
|
2018-02-27 11:16:18 +00:00
|
|
|
var leftObj, rightObj map[string]interface{}
|
|
|
|
|
if left != nil {
|
|
|
|
|
leftObj = left.Object
|
|
|
|
|
}
|
|
|
|
|
if right != nil {
|
2018-04-30 20:29:35 +00:00
|
|
|
rightObj = RemoveMapFields(leftObj, right.Object)
|
2018-02-27 11:16:18 +00:00
|
|
|
}
|
|
|
|
|
gjDiff := gojsondiff.New().CompareObjects(leftObj, rightObj)
|
2018-02-28 00:58:25 +00:00
|
|
|
dr := DiffResult{
|
|
|
|
|
Diff: gjDiff,
|
|
|
|
|
Modified: gjDiff.Modified(),
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
2018-02-28 00:58:25 +00:00
|
|
|
return &dr
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DiffArray performs a diff on a list of unstructured objects. Objects are expected to match
|
|
|
|
|
// environments
|
|
|
|
|
func DiffArray(leftArray, rightArray []*unstructured.Unstructured) (*DiffResultList, error) {
|
|
|
|
|
numItems := len(leftArray)
|
|
|
|
|
if len(rightArray) != numItems {
|
|
|
|
|
return nil, fmt.Errorf("left and right arrays have mismatched lengths")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diffResultList := DiffResultList{
|
|
|
|
|
Diffs: make([]DiffResult, numItems),
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < numItems; i++ {
|
|
|
|
|
left := leftArray[i]
|
|
|
|
|
right := rightArray[i]
|
|
|
|
|
diffRes := Diff(left, right)
|
|
|
|
|
diffResultList.Diffs[i] = *diffRes
|
|
|
|
|
if diffRes.Modified {
|
|
|
|
|
diffResultList.Modified = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &diffResultList, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-01 07:36:00 +00:00
|
|
|
// ASCIIFormat returns the ASCII format of the diff
|
|
|
|
|
func (d *DiffResult) ASCIIFormat(left *unstructured.Unstructured, formatOpts formatter.AsciiFormatterConfig) (string, error) {
|
2018-02-28 00:58:25 +00:00
|
|
|
if !d.Diff.Modified() {
|
2018-03-01 07:36:00 +00:00
|
|
|
return "", nil
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
2018-03-01 07:36:00 +00:00
|
|
|
if left == nil {
|
|
|
|
|
return "", errors.New("Supplied nil left object")
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
2018-03-01 07:36:00 +00:00
|
|
|
asciiFmt := formatter.NewAsciiFormatter(left.Object, formatOpts)
|
|
|
|
|
return asciiFmt.Format(d.Diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://github.com/ksonnet/ksonnet/blob/master/pkg/kubecfg/diff.go
|
|
|
|
|
func removeFields(config, live interface{}) interface{} {
|
|
|
|
|
switch c := config.(type) {
|
|
|
|
|
case map[string]interface{}:
|
2018-04-30 20:29:35 +00:00
|
|
|
return RemoveMapFields(c, live.(map[string]interface{}))
|
2018-03-01 07:36:00 +00:00
|
|
|
case []interface{}:
|
|
|
|
|
return removeListFields(c, live.([]interface{}))
|
|
|
|
|
default:
|
|
|
|
|
return live
|
2018-02-28 00:58:25 +00:00
|
|
|
}
|
2018-03-01 07:36:00 +00:00
|
|
|
}
|
2018-02-28 00:58:25 +00:00
|
|
|
|
2018-04-30 20:29:35 +00:00
|
|
|
func RemoveMapFields(config, live map[string]interface{}) map[string]interface{} {
|
2018-03-01 07:36:00 +00:00
|
|
|
result := map[string]interface{}{}
|
|
|
|
|
for k, v1 := range config {
|
|
|
|
|
v2, ok := live[k]
|
|
|
|
|
if !ok {
|
2018-02-26 02:36:19 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2018-03-01 07:36:00 +00:00
|
|
|
result[k] = removeFields(v1, v2)
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removeListFields(config, live []interface{}) []interface{} {
|
|
|
|
|
// If live is longer than config, then the extra elements at the end of the
|
|
|
|
|
// list will be returned as is so they appear in the diff.
|
|
|
|
|
result := make([]interface{}, 0, len(live))
|
|
|
|
|
for i, v2 := range live {
|
|
|
|
|
if len(config) > i {
|
|
|
|
|
result = append(result, removeFields(config[i], v2))
|
|
|
|
|
} else {
|
|
|
|
|
result = append(result, v2)
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-03-01 07:36:00 +00:00
|
|
|
return result
|
2018-02-26 02:36:19 +00:00
|
|
|
}
|