Documentation ¶
Overview ¶
Package compare gives some utilities to compare Objects with little code required and to reconcile a list of desired Objects with a list of existing ones to lists of Objects to create and destroy.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidType = errors.New("invalid type given") ErrDifferentTypes = fmt.Errorf("%w: cannot compare structs of different types", ErrInvalidType) ErrKeyNotFound = fmt.Errorf("key not found in struct") )
Functions ¶
func Reconcile ¶
func Reconcile(target, existing interface{}, create, destroy *[]types.Object, compareAttributes ...string) error
Reconcile fills the given arrays of Objects to create and destroy, by adding the Objects in target but not in existing to create and adding the Objects in existing but not in target to destroy. Objects are compared with the Compare function in this package, using the provided compareAttributes.
Objects in target with a matching Object in existing are updated, filling e.g. the identifiers in target.
Example ¶
package main import ( "fmt" "go.anx.io/go-anxcloud/pkg/api/types" lbaasv1 "go.anx.io/go-anxcloud/pkg/apis/lbaas/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func main() { lb := lbaasv1.LoadBalancer{Identifier: "LoadBalancer identifier"} // array of objects that should exist, created by business logic targetObjects := []lbaasv1.Frontend{ { Name: "Frontend A", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, { Name: "Frontend B", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, } // array of objects found in the Engine as currently existing existingObjects := []lbaasv1.Frontend{ { Name: "Frontend A", Identifier: "Frontend identifier A", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, { Name: "Frontend C", Identifier: "Frontend identifier C", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, } // those will receive the objects to create and destroy var toCreate []types.Object var toDestroy []types.Object err := Reconcile( // array of objects we want in the end and we currently have targetObjects, existingObjects, // output arrays of objects to create and destroy to change reality into our desired state &toCreate, &toDestroy, // attributes to compare "Name", "Mode", "LoadBalancer.Identifier", ) if err != nil { fmt.Printf("Error reconciling objects: %v\n", err) return } fmt.Printf("Found %v Objects to create and %v Objects to destroy\n", len(toCreate), len(toDestroy)) for _, c := range toCreate { fmt.Printf("Going to create Object named %q\n", c.(*lbaasv1.Frontend).Name) } for _, c := range toDestroy { fmt.Printf("Going to destroy Object named %q\n", c.(*lbaasv1.Frontend).Name) } for _, f := range targetObjects { if f.Identifier != "" { fmt.Printf("Identifier of Object named %q was retrieved: %q\n", f.Name, f.Identifier) } } } var _ = Describe("Reconcile", func() { DescribeTable("errors out for invalid input", func(expectedError error, target, existing interface{}, compareAttributes ...string) { err := Reconcile(target, existing, nil, nil, compareAttributes...) Expect(err).To(MatchError(expectedError)) }, Entry("existing not an array", ErrInvalidType, []lbaasv1.Frontend{}, lbaasv1.Frontend{}), Entry("target not an array", ErrInvalidType, lbaasv1.Frontend{}, []lbaasv1.Frontend{}), // these errors are only checked when there are entries in the target and existing arrays, I think that's ok // -- Mara @LittleFox94 Grosch, 2022-03-28 Entry("invalid attribute", ErrKeyNotFound, []lbaasv1.Frontend{{}}, []lbaasv1.Frontend{{}}, "Test"), ) })
Output: Found 1 Objects to create and 1 Objects to destroy Going to create Object named "Frontend B" Going to destroy Object named "Frontend C" Identifier of Object named "Frontend A" was retrieved: "Frontend identifier A"
func Search ¶
Search iterates through the array haystack until it finds an Object matching needle by the given (nested) attribute names (using the Compare function in this package).
Example ¶
package main import ( "fmt" lbaasv1 "go.anx.io/go-anxcloud/pkg/apis/lbaas/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func main() { lb := lbaasv1.LoadBalancer{Identifier: "some identifier string"} existingObjects := []lbaasv1.Frontend{ { Name: "Frontend A", Identifier: "some identifier string A", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, { Name: "Frontend B", Identifier: "some identifier string B", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, } // positive example index, err := Search(lbaasv1.Frontend{ Name: "Frontend A", Mode: lbaasv1.TCP, LoadBalancer: &lb, }, existingObjects, "Name", "Mode", "LoadBalancer.Identifier") if err != nil { fmt.Printf("Error comparing the objects: %v\n", err) } else { if index == -1 { fmt.Printf("Did not find our Object\n") } else { fmt.Printf("Index of our Object is %v\n", index) } } // negative example index, err = Search(lbaasv1.Frontend{ Name: "Frontend C", Mode: lbaasv1.HTTP, }, existingObjects, "Name", "Mode", "LoadBalancer.Identifier") if err != nil { fmt.Printf("Error comparing the objects: %v\n", err) } else { if index == -1 { fmt.Printf("Did not find our Object\n") } else { fmt.Printf("Index of our Object is %v\n", index) } } } var _ = Describe("Search", func() { DescribeTable("returns the error from Compare", func(expectedError error, needle interface{}, haystack interface{}, fields ...string) { _, err := Search(needle, haystack, fields...) Expect(err).To(MatchError(expectedError)) }, Entry("Invalid type given", ErrInvalidType, "test", []string{"test", "test2"}), // attribute names are only checked when there is any entry in the haystack, I think that's ok // -- Mara @LittleFox94 Grosch, 2022-03-28 Entry("Invalid attribute given", ErrKeyNotFound, lbaasv1.Frontend{}, []lbaasv1.Frontend{{}}, "Test"), // the types being the same is only checked when there is any entry in the haystack, I think that's ok // -- Mara @LittleFox94 Grosch, 2022-03-28 Entry("Different types", ErrDifferentTypes, lbaasv1.Frontend{}, []lbaasv1.Backend{{}}, "Name"), ) })
Output: Index of our Object is 0 Did not find our Object
Types ¶
type Difference ¶
type Difference struct { Key string A interface{} B interface{} }
Difference gives the (nested) attribute name and the values of the attribute of the two objects differing.
func Compare ¶
func Compare(a, b interface{}, attributes ...string) ([]Difference, error)
Compare compares two objects by the given (dot-nested) attribute names.
Example ¶
lb := lbaasv1.LoadBalancer{Identifier: "some identifier string"} a := lbaasv1.Frontend{ Name: "Frontend A", Mode: lbaasv1.TCP, LoadBalancer: &lb, } b := lbaasv1.Frontend{ Name: "Frontend B", Mode: lbaasv1.TCP, LoadBalancer: &lb, } differences, err := Compare(a, b, "Name", "Mode", "LoadBalancer.Identifier") if err != nil { fmt.Printf("Error comparing the objects: %v\n", err) } else if len(differences) > 0 { for _, difference := range differences { fmt.Printf("Difference between objects on attribute %q: %q != %q\n", difference.Key, difference.A, difference.B) } } else { fmt.Printf("Found no difference between A and B\n") }
Output: Difference between objects on attribute "Name": "Frontend A" != "Frontend B"
Example (NestedNil) ¶
lb := lbaasv1.LoadBalancer{Identifier: "some identifier string"} a := lbaasv1.Frontend{ Name: "Frontend A", Mode: lbaasv1.TCP, } b := lbaasv1.Frontend{ Name: "Frontend B", Mode: lbaasv1.TCP, LoadBalancer: &lb, } differences, err := Compare(a, b, "Name", "Mode", "LoadBalancer.Identifier") if err != nil { fmt.Printf("Error comparing the objects: %v\n", err) } else if len(differences) > 0 { for _, difference := range differences { fmt.Printf("Difference between objects on attribute %q: '%v' != '%v'\n", difference.Key, difference.A, difference.B) } } else { fmt.Printf("Found no difference between A and B\n") }
Output: Difference between objects on attribute "Name": 'Frontend A' != 'Frontend B' Difference between objects on attribute "LoadBalancer.Identifier": '<nil>' != 'some identifier string'