Files
kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/dag.go
Shiwen Cheng 4b86689d63 Update copyright year (#1464)
* Update copyright year to 2021 for The KubeVela Authors

* Update copyright year to 2021 for other project authors
2021-04-11 21:00:14 +08:00

145 lines
3.6 KiB
Go

/*
Copyright 2021 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package applicationconfiguration
import (
"reflect"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
)
// dag is the dependency graph for an AppConfig.
type dag struct {
Sources map[string]*dagSource
}
// dagSource represents the object information with DataOutput
type dagSource struct {
// ObjectRef refers to the object this source come from.
ObjectRef *corev1.ObjectReference
Conditions []v1alpha2.ConditionRequirement
}
// newDAG creates a fresh new DAG.
func newDAG() *dag {
return &dag{
Sources: make(map[string]*dagSource),
}
}
// AddSource adds a data output source into the DAG.
func (d *dag) AddSource(sourceName string, ref *corev1.ObjectReference, m []v1alpha2.ConditionRequirement) {
d.Sources[sourceName] = &dagSource{
ObjectRef: ref,
Conditions: m,
}
}
func fillDataInputValue(obj *unstructured.Unstructured, fs []string, val interface{}, strategyMergeKeys []string) error {
paved := fieldpath.Pave(obj.Object)
for _, fp := range fs {
toSet := val
// Special case for slice because we will append or strategyMerge instead of rewriting.
if reflect.TypeOf(val).Kind() == reflect.Slice {
raw, err := paved.GetValue(fp)
if err != nil {
if fieldpath.IsNotFound(err) {
raw = make([]interface{}, 0)
} else {
return err
}
}
l := raw.([]interface{})
toSet = strategyMergeSlice(l, val.([]interface{}), strategyMergeKeys)
}
err := paved.SetValue(fp, toSet)
if err != nil {
return errors.Wrap(err, "paved.SetValue() failed")
}
}
return nil
}
func getElementValueByKeys(ele interface{}, keys []string) map[string]string {
mappedEle, ok := ele.(map[string]interface{})
if !ok {
return nil
}
pavedEle := fieldpath.Pave(mappedEle)
var result = make(map[string]string)
for _, key := range keys {
keyValuePatch, err := pavedEle.GetString(key)
if err != nil {
continue
}
result[key] = keyValuePatch
}
return result
}
func compareResults(base, patch map[string]string) bool {
for k, v := range patch {
vv, ok := base[k]
if ok && vv == v {
return true
}
}
return false
}
func strategyMergeSlice(base, patch []interface{}, keys []string) []interface{} {
if len(keys) == 0 {
// By default, no merge keys, append only.
base = append(base, patch...)
return base
}
for _, patchElement := range patch {
// get all values by mergeKeys from patch element
patchKeyResults := getElementValueByKeys(patchElement, keys)
if len(patchKeyResults) == 0 {
base = append(base, patchElement)
continue
}
var match = false
for idx, v := range base {
// get all values by mergeKeys from base element
baseKeyResults := getElementValueByKeys(v, keys)
// compare the key value pairs
match = compareResults(baseKeyResults, patchKeyResults)
if !match {
continue
}
base[idx] = patchElement
break
}
if match {
continue
}
// if no matches, append at last
base = append(base, patchElement)
}
return base
}