Fix the disjuction of panels (#39412)

[As code] Correct trim apply defaults when import/export dashboards
This commit is contained in:
ying-jeanne
2021-10-06 11:39:23 +02:00
committed by GitHub
parent ccd6a2eae3
commit 227c8403b5
25 changed files with 27834 additions and 131 deletions
+155 -38
View File
@@ -5,16 +5,12 @@ import (
"errors"
"fmt"
"math/bits"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
errs "cuelang.org/go/cue/errors"
cuejson "cuelang.org/go/pkg/encoding/json"
)
var ctx = cuecontext.New()
// CueError wraps Errors caused by malformed cue files.
type CueError struct {
ErrorMap map[int]string
@@ -282,11 +278,16 @@ func ApplyDefaults(r Resource, scue cue.Value) (Resource, error) {
if name == "" {
name = "resource"
}
rv := ctx.CompileString(r.Value.(string), cue.Filename(name))
rv := scue.Context().CompileString(r.Value.(string), cue.Filename(name))
if rv.Err() != nil {
return r, rv.Err()
}
rvUnified := rv.Unify(scue)
rvUnified, err := applyDefaultHelper(rv, scue)
if err != nil {
return r, err
}
re, err := convertCUEValueToString(rvUnified)
if err != nil {
return r, err
@@ -294,6 +295,68 @@ func ApplyDefaults(r Resource, scue cue.Value) (Resource, error) {
return Resource{Value: re}, nil
}
func applyDefaultHelper(input cue.Value, scue cue.Value) (cue.Value, error) {
switch scue.IncompleteKind() {
case cue.ListKind:
// if list element exist
ele := scue.LookupPath(cue.MakePath(cue.AnyIndex))
// if input is not a concrete list, we must have list elements exist to be used to trim defaults
if ele.Exists() {
if ele.IncompleteKind() == cue.BottomKind {
return input, errors.New("can't get the element of list")
}
iter, err := input.List()
if err != nil {
return input, errors.New("can't apply defaults for list")
}
var iterlist []cue.Value
for iter.Next() {
ref, err := getBranch(ele, iter.Value())
if err != nil {
return input, err
}
re, err := applyDefaultHelper(iter.Value(), ref)
if err == nil {
iterlist = append(iterlist, re)
}
}
liInstance := scue.Context().NewList(iterlist...)
if liInstance.Err() != nil {
return input, liInstance.Err()
}
return liInstance, nil
} else {
return input.Unify(scue), nil
}
case cue.StructKind:
iter, err := scue.Fields(cue.Optional(true))
if err != nil {
return input, err
}
for iter.Next() {
lable, _ := iter.Value().Label()
lv := input.LookupPath(cue.MakePath(cue.Str(lable)))
if err != nil {
continue
}
if lv.Exists() {
res, err := applyDefaultHelper(lv, iter.Value())
if err != nil {
continue
}
input = input.FillPath(cue.MakePath(cue.Str(lable)), res)
} else if !iter.IsOptional() {
input = input.FillPath(cue.MakePath(cue.Str(lable)), iter.Value().Eval())
}
}
return input, nil
default:
input = input.Unify(scue)
}
return input, nil
}
func convertCUEValueToString(inputCUE cue.Value) (string, error) {
re, err := cuejson.Marshal(inputCUE)
if err != nil {
@@ -315,34 +378,67 @@ func TrimDefaults(r Resource, scue cue.Value) (Resource, error) {
if name == "" {
name = "resource"
}
rvInstance := ctx.CompileString(r.Value.(string), cue.Filename(name))
rvInstance := scue.Context().CompileString(r.Value.(string), cue.Filename(name))
if rvInstance.Err() != nil {
return r, rvInstance.Err()
}
rv, _, err := removeDefaultHelper(scue, rvInstance)
if err != nil {
return r, err
}
re, err := convertCUEValueToString(rv)
if err != nil {
return r, err
}
return Resource{Value: re}, nil
}
func getDefault(icue cue.Value) (cue.Value, bool) {
d, exist := icue.Default()
if exist && d.Kind() == cue.ListKind {
len, err := d.Len().Int64()
if err != nil {
return d, false
}
var defaultExist bool
if len <= 0 {
op, vals := icue.Expr()
if op == cue.OrOp {
for _, val := range vals {
vallen, _ := val.Len().Int64()
if val.Kind() == cue.ListKind && vallen <= 0 {
defaultExist = true
break
}
}
if !defaultExist {
exist = false
}
} else {
exist = false
}
}
}
return d, exist
}
func isCueValueEqual(inputdef cue.Value, input cue.Value) bool {
val, _ := inputdef.Default()
return input.Subsume(val) == nil && val.Subsume(input) == nil
d, exist := getDefault(inputdef)
if exist {
return input.Subsume(d) == nil && d.Subsume(input) == nil
}
return false
}
func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, error) {
// To include all optional fields, we need to use inputdef for iteration,
// since the lookuppath with optional field doesn't work very well
rvInstance := ctx.CompileString("", cue.Filename("helper"))
if rvInstance.Err() != nil {
return input, false, rvInstance.Err()
rv := inputdef.Context().CompileString("", cue.Filename("helper"))
if rv.Err() != nil {
return input, false, rv.Err()
}
rv := rvInstance.Value()
switch inputdef.IncompleteKind() {
case cue.StructKind:
@@ -357,7 +453,7 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool,
keySet[lable] = true
lv := input.LookupPath(cue.MakePath(cue.Str(lable)))
if err != nil {
continue
return rv, false, err
}
if lv.Exists() {
re, isEqual, err := removeDefaultHelper(iter.Value(), lv)
@@ -382,34 +478,40 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool,
if isCueValueEqual(inputdef, input) {
return rv, true, nil
}
// take every element of the list
ele := inputdef.LookupPath(cue.MakePath(cue.AnyIndex))
if ele.IncompleteKind() == cue.BottomKind {
return rv, true, nil
}
iter, err := input.List()
if err != nil {
return rv, true, nil
}
// The following code is workaround since today overwrite list element doesn't work
var iterlist []string
for iter.Next() {
re, isEqual, err := removeDefaultHelper(ele, iter.Value())
if err == nil && !isEqual {
reString, err := convertCUEValueToString(re)
if err != nil {
return rv, true, nil
}
iterlist = append(iterlist, reString)
// if input is not a concrete list, we must have list elements exist to be used to trim defaults
if ele.Exists() {
if ele.IncompleteKind() == cue.BottomKind {
return rv, true, nil
}
iter, err := input.List()
if err != nil {
return rv, true, nil
}
var iterlist []cue.Value
for iter.Next() {
ref, err := getBranch(ele, iter.Value())
if err != nil {
iterlist = append(iterlist, iter.Value())
continue
}
re, isEqual, err := removeDefaultHelper(ref, iter.Value())
if err == nil && !isEqual {
iterlist = append(iterlist, re)
} else {
iterlist = append(iterlist, iter.Value())
}
}
liInstance := inputdef.Context().NewList(iterlist...)
return liInstance, false, liInstance.Err()
}
iterlistContent := fmt.Sprintf("[%s]", strings.Join(iterlist, ","))
liInstance := ctx.CompileString(iterlistContent, cue.Filename("resource"))
if liInstance.Err() != nil {
return rv, false, err
}
return liInstance, false, nil
// now when ele is empty, we don't trim anything
return input, false, nil
default:
if isCueValueEqual(inputdef, input) {
return input, true, nil
@@ -418,6 +520,21 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool,
}
}
func getBranch(schemaObj cue.Value, concretObj cue.Value) (cue.Value, error) {
op, defs := schemaObj.Expr()
if op == cue.OrOp {
for _, def := range defs {
err := def.Unify(concretObj).Validate(cue.Concrete(true))
if err == nil {
return def, nil
}
}
// no matching branches? wtf
return schemaObj, errors.New("no branch is found for list")
}
return schemaObj, nil
}
// A Resource represents a concrete data object - e.g., JSON
// representing a dashboard.
//