diff --git a/internal/restResources/restResources.go b/internal/restResources/restResources.go index bc14496..e73ac27 100644 --- a/internal/restResources/restResources.go +++ b/internal/restResources/restResources.go @@ -196,12 +196,18 @@ func (h *handler) Observe(ctx context.Context, mg *unstructured.Unstructured) (c log.Debug("Updating status", "error", err) return controller.ExternalObservation{}, err } - ok, err := isCRUpdated(mg, *body) + res, err := isCRUpdated(mg, *body) if err != nil { log.Debug("Checking if CR is updated", "error", err) return controller.ExternalObservation{}, err } - if !ok { + if !res.IsEqual { + cond := condition.Unavailable() + if res.Reason != nil { + cond.Reason = fmt.Sprintf("Resource is not up-to-date due to %s - spec value: %s, remote value: %s", res.Reason.Reason, res.Reason.FirstValue, res.Reason.SecondValue) + } + + unstructuredtools.SetCondition(mg, cond) log.Debug("External resource not up-to-date", "kind", mg.GetKind()) return controller.ExternalObservation{ ResourceExists: true, diff --git a/internal/restResources/support.go b/internal/restResources/support.go index 077535a..211e626 100644 --- a/internal/restResources/support.go +++ b/internal/restResources/support.go @@ -122,23 +122,41 @@ func processFields(callInfo *CallInfo, fields map[string]interface{}, reqConfigu } reqConfiguration.Query[field] = stringVal } else if callInfo.ReqParams.Body.Contains(field) { - mapBody[field] = value + if mapBody[field] == nil { + mapBody[field] = value + } } } } // isCRUpdated checks if the CR was updated by comparing the fields in the CR with the response from the API call, if existing cr fields are different from the response, it returns false -func isCRUpdated(mg *unstructured.Unstructured, rm map[string]interface{}) (bool, error) { +func isCRUpdated(mg *unstructured.Unstructured, rm map[string]interface{}) (ComparisonResult, error) { m, err := unstructuredtools.GetFieldsFromUnstructured(mg, "spec") if err != nil { - return false, fmt.Errorf("error getting spec fields: %w", err) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "error getting spec fields", + }, + }, fmt.Errorf("error getting spec fields: %w", err) } - return compareExisting(m, rm), nil + return compareExisting(m, rm) +} + +type Reason struct { + Reason string + FirstValue any + SecondValue any +} + +type ComparisonResult struct { + IsEqual bool + Reason *Reason } // compareExisting recursively compares fields between two maps and logs differences. -func compareExisting(mg map[string]interface{}, rm map[string]interface{}, path ...string) bool { +func compareExisting(mg map[string]interface{}, rm map[string]interface{}, path ...string) (ComparisonResult, error) { for key, value := range mg { currentPath := append(path, key) pathStr := fmt.Sprintf("%v", currentPath) @@ -148,63 +166,179 @@ func compareExisting(mg map[string]interface{}, rm map[string]interface{}, path continue } + // fmt.Println("Comparing", pathStr, value, rmValue) + + if reflect.TypeOf(value).Kind() != reflect.TypeOf(rmValue).Kind() { + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "types differ", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("types differ at %s - %s is different from %s", pathStr, reflect.TypeOf(value).Kind(), reflect.TypeOf(rmValue).Kind()) + } + switch reflect.TypeOf(value).Kind() { case reflect.Map: mgMap, ok1 := value.(map[string]interface{}) if !ok1 { - fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) - continue + // fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("type assertion failed for map at %s", pathStr) } rmMap, ok2 := rmValue.(map[string]interface{}) if !ok2 { - fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) - continue + // fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("type assertion failed for map at %s", pathStr) } - if !compareExisting(mgMap, rmMap, currentPath...) { - fmt.Printf("Values differ at '%s'\n", pathStr) - return false + res, err := compareExisting(mgMap, rmMap, currentPath...) + if err != nil { + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "error comparing maps", + FirstValue: value, + SecondValue: rmValue, + }, + }, err + } + if !res.IsEqual { + // fmt.Printf("Values differ at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values differ", + FirstValue: value, + SecondValue: rmValue, + }, + }, nil } case reflect.Slice: valueSlice, ok1 := value.([]interface{}) if !ok1 || reflect.TypeOf(rmValue).Kind() != reflect.Slice { - fmt.Printf("Values are not both slices or type assertion failed at '%s'\n", pathStr) - continue + // fmt.Printf("Values are not both slices or type assertion failed at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values are not both slices or type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("values are not both slices or type assertion failed at %s", pathStr) } rmSlice, ok2 := rmValue.([]interface{}) if !ok2 { - fmt.Printf("Type assertion failed for slice at '%s'\n", pathStr) - continue + // fmt.Printf("Type assertion failed for slice at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values are not both slices or type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("type assertion failed for slice at %s", pathStr) } for i, v := range valueSlice { if reflect.TypeOf(v).Kind() == reflect.Map { mgMap, ok1 := v.(map[string]interface{}) if !ok1 { - fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) - continue + // fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("type assertion failed for map at %s", pathStr) } rmMap, ok2 := rmSlice[i].(map[string]interface{}) if !ok2 { - fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) - continue + // fmt.Printf("Type assertion failed for map at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "type assertion failed", + FirstValue: value, + SecondValue: rmValue, + }, + }, fmt.Errorf("type assertion failed for map at %s", pathStr) + } + res, err := compareExisting(mgMap, rmMap, currentPath...) + if err != nil { + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "error comparing maps", + FirstValue: value, + SecondValue: rmValue, + }, + }, err } - if !compareExisting(mgMap, rmMap, currentPath...) { - fmt.Printf("Values differ at '%s'\n", pathStr) - return false + if !res.IsEqual { + // fmt.Printf("Values differ at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values differ", + FirstValue: value, + SecondValue: rmValue, + }, + }, nil } } else if v != rmSlice[i] { - fmt.Printf("Values differ at '%s'\n", pathStr) - return false + // fmt.Printf("Values differ at '%s'\n", pathStr) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values differ", + FirstValue: value, + SecondValue: rmValue, + }, + }, nil } } default: - if !compareAny(value, rmValue) { - fmt.Printf("Values differ at '%s' %s %s\n", pathStr, value, rmValue) - return false + ok, err := compareAny(value, rmValue) + if err != nil { + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "error comparing values", + FirstValue: value, + SecondValue: rmValue, + }, + }, err + } + if !ok { + // fmt.Printf("Values differ at '%s' %s %s\n", pathStr, value, rmValue) + return ComparisonResult{ + IsEqual: false, + Reason: &Reason{ + Reason: "values differ", + FirstValue: value, + SecondValue: rmValue, + }, + }, nil } } } - return true + return ComparisonResult{IsEqual: true}, nil } func numberCaster(value interface{}) int64 { switch v := value.(type) { @@ -237,23 +371,35 @@ func numberCaster(value interface{}) int64 { } } -func compareAny(a any, b any) bool { +func compareAny(a any, b any) (bool, error) { //if is number compare as number switch a.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: ia := numberCaster(a) ib := numberCaster(b) - return ia == ib + return ia == ib, nil case string: - sa := a.(string) - sb := b.(string) - return sa == sb + sa, ok := a.(string) + if !ok { + return false, fmt.Errorf("type assertion failed - to string: %v", a) + } + sb, ok := b.(string) + if !ok { + return false, fmt.Errorf("type assertion failed - to string: %v", b) + } + return sa == sb, nil case bool: - ba := a.(bool) - bb := b.(bool) - return ba == bb + ba, ok := a.(bool) + if !ok { + return false, fmt.Errorf("type assertion failed - to bool: %v", a) + } + bb, ok := b.(bool) + if !ok { + return false, fmt.Errorf("type assertion failed - to bool: %v", b) + } + return ba == bb, nil default: - return reflect.DeepEqual(a, b) + return reflect.DeepEqual(a, b), nil } }