Skip to content

Commit

Permalink
added dcomprasion pkg which allows to get deep difference of maps
Browse files Browse the repository at this point in the history
  • Loading branch information
Bohdan Siryk authored and ribaraka committed Jan 3, 2024
1 parent f40daf0 commit 7dc4d3d
Show file tree
Hide file tree
Showing 10 changed files with 476 additions and 8 deletions.
2 changes: 1 addition & 1 deletion controllers/clusterresources/cassandrauser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

"github.com/instaclustr/operator/apis/clusterresources/v1beta1"
"github.com/instaclustr/operator/pkg/helpers/utils"
"github.com/instaclustr/operator/pkg/instaclustr"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/ratelimiter"
"github.com/instaclustr/operator/pkg/utils"
)

// CassandraUserReconciler reconciles a CassandraUser object
Expand Down
2 changes: 1 addition & 1 deletion controllers/clusterresources/opensearchuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

clusterresourcesv1beta1 "github.com/instaclustr/operator/apis/clusterresources/v1beta1"
"github.com/instaclustr/operator/pkg/helpers/utils"
"github.com/instaclustr/operator/pkg/instaclustr"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/ratelimiter"
"github.com/instaclustr/operator/pkg/utils"
)

// OpenSearchUserReconciler reconciles a OpenSearchUser object
Expand Down
2 changes: 1 addition & 1 deletion controllers/clusterresources/postgresqluser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ import (

clusterresourcesv1beta1 "github.com/instaclustr/operator/apis/clusterresources/v1beta1"
"github.com/instaclustr/operator/pkg/exposeservice"
"github.com/instaclustr/operator/pkg/helpers/utils"
"github.com/instaclustr/operator/pkg/instaclustr"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/ratelimiter"
"github.com/instaclustr/operator/pkg/utils"
)

// PostgreSQLUserReconciler reconciles a PostgreSQLUser object
Expand Down
2 changes: 1 addition & 1 deletion controllers/clusterresources/redisuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

"github.com/instaclustr/operator/apis/clusterresources/v1beta1"
"github.com/instaclustr/operator/pkg/helpers/utils"
"github.com/instaclustr/operator/pkg/instaclustr"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/ratelimiter"
"github.com/instaclustr/operator/pkg/utils"
)

// RedisUserReconciler reconciles a RedisUser object
Expand Down
33 changes: 30 additions & 3 deletions controllers/clusters/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
"sort"
"strings"

"github.com/hashicorp/go-version"
v1 "k8s.io/api/core/v1"
Expand All @@ -32,6 +33,7 @@ import (

"github.com/instaclustr/operator/apis/clusters/v1beta1"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/utils/dcomparison"
)

// confirmDeletion confirms if resource is deleting and set appropriate annotation.
Expand Down Expand Up @@ -202,10 +204,35 @@ func createSpecDifferenceMessage(k8sSpec, iSpec any) (string, error) {
return "", err
}

msg := "There are external changes on the Instaclustr console. Please reconcile the specification manually. "
specDifference := fmt.Sprintf("k8s spec: %s; data from instaclustr: %s", k8sData, iData)
var k8sSpecMap map[string]any
err = json.Unmarshal(k8sData, &k8sSpecMap)
if err != nil {
return "", err
}

var iSpecMap map[string]any
err = json.Unmarshal(iData, &iSpecMap)
if err != nil {
return "", err
}

diffs := dcomparison.MapsDiff(models.SpecPath, k8sSpecMap, iSpecMap)

return fmt.Sprintf("%s Diffs: %s", models.ExternalChangesBaseMessage, prepareDiffMessage(diffs)), nil
}

func prepareDiffMessage(diffs dcomparison.ObjectDiffs) string {
var diffMessages []string
for _, diff := range diffs {
diffMessages = append(diffMessages, fmt.Sprintf(
"{field: %s, k8sValue: %v, instaclustrValue: %v}",
diff.Field,
diff.Value1,
diff.Value2,
))
}

return msg + specDifference, nil
return strings.Join(diffMessages, ", ")
}

var msgDeleteClusterWithTwoFactorDelete = "Please confirm cluster deletion via email or phone. " +
Expand Down
2 changes: 1 addition & 1 deletion controllers/kafkamanagement/usercertificate_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ import (
clustersv1beta1 "github.com/instaclustr/operator/apis/clusters/v1beta1"
kafkamanagementv1beta1 "github.com/instaclustr/operator/apis/kafkamanagement/v1beta1"
"github.com/instaclustr/operator/pkg/apiextensions"
"github.com/instaclustr/operator/pkg/helpers/utils"
"github.com/instaclustr/operator/pkg/instaclustr"
"github.com/instaclustr/operator/pkg/models"
"github.com/instaclustr/operator/pkg/ratelimiter"
"github.com/instaclustr/operator/pkg/utils"
)

// UserCertificateReconciler reconciles a CertificateSigningRequest object
Expand Down
5 changes: 5 additions & 0 deletions pkg/models/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,8 @@ const (
CSRSecretKey = "csr"
PrivateKeySecretKey = "privateKey"
)

const (
ExternalChangesBaseMessage = "There are external changes on the Instaclustr console. Please reconcile the specification manually."
SpecPath = "path"
)
130 changes: 130 additions & 0 deletions pkg/utils/dcomparison/map_diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
Copyright 2023.
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 dcomparison provides a solution for deeply comparing two maps,
// including their nested maps and slices. It is designed to identify differences
// between two maps that can contain a variety of data types, such as strings,
// integers, other maps, and slices.
package dcomparison

import (
"fmt"
"reflect"
)

// ObjectDiff stores the path to a value and the differing values from two maps.
type ObjectDiff struct {
Field string
Value1 any
Value2 any
}

// ObjectDiffs is a slice of ObjectDiff
type ObjectDiffs []ObjectDiff

func (o *ObjectDiffs) Append(diff ObjectDiff) {
*o = append(*o, diff)
}

// compare recursively compares values and populates diffs.
func compare(path string, value1, value2 any, diffs *ObjectDiffs) {
if reflect.DeepEqual(value1, value2) {
return
}

v1Type := reflect.TypeOf(value1)
v2Type := reflect.TypeOf(value2)

if v1Type == v2Type && v1Type != nil {
switch v1Type.Kind() {
case reflect.Map:
map1 := reflect.ValueOf(value1)
map2 := reflect.ValueOf(value2)

for _, key := range map1.MapKeys() {
currentField := fmt.Sprintf("%s.%v", path, key)

map1Value := map1.MapIndex(key)
map2Value := map2.MapIndex(key)
if map2Value.IsValid() {
compare(currentField, map1Value.Interface(), map2Value.Interface(), diffs)
} else {
diffs.Append(ObjectDiff{
Field: currentField,
Value1: map1Value.Interface(),
Value2: nil,
})
}
}

for _, key := range map2.MapKeys() {
if !map1.MapIndex(key).IsValid() {
subPath := fmt.Sprintf("%s.%v", path, key)
diffs.Append(ObjectDiff{
Field: subPath,
Value1: nil,
Value2: map2.MapIndex(key).Interface(),
})
}
}
case reflect.Slice:
slice1 := reflect.ValueOf(value1)
slice2 := reflect.ValueOf(value2)
maxLength := max(slice1.Len(), slice2.Len())

for i := 0; i < maxLength; i++ {
elem1, elem2 := getSliceElement(slice1, i), getSliceElement(slice2, i)
subPath := fmt.Sprintf("%s[%d]", path, i)
compare(subPath, elem1, elem2, diffs)
}
default:
diffs.Append(ObjectDiff{
Field: path,
Value1: value1,
Value2: value2,
})
}
} else {
diffs.Append(ObjectDiff{
Field: path,
Value1: value1,
Value2: value2,
})
}
}

// getSliceElement safely gets the element at index i from a reflect.Value of a slice.
func getSliceElement(slice reflect.Value, i int) any {
if i < slice.Len() {
return slice.Index(i).Interface()
}
return nil
}

// max returns the maximum of two integers.
func max(a, b int) int {
if a > b {
return a
}
return b
}

// MapsDiff compares two maps and returns a slice of ObjectDiff with the differences.
func MapsDiff(path string, map1, map2 map[string]any) ObjectDiffs {
var diffs ObjectDiffs
compare(path, map1, map2, &diffs)
return diffs
}
Loading

0 comments on commit 7dc4d3d

Please sign in to comment.