diff --git a/go.mod b/go.mod index 340bace6b..0375ab661 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/fergusstrange/embedded-postgres v1.25.0 github.com/flanksource/artifacts v1.0.4 github.com/flanksource/commons v1.20.0 - github.com/flanksource/duty v1.0.280 + github.com/flanksource/duty v1.0.287 github.com/flanksource/gomplate/v3 v3.21.0 github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7 github.com/flanksource/kommons v0.31.4 diff --git a/go.sum b/go.sum index dc591b050..c12254450 100644 --- a/go.sum +++ b/go.sum @@ -827,8 +827,8 @@ github.com/flanksource/artifacts v1.0.4 h1:KjQTwsvQ73uHqTK7o4Jwt/RW8fyxJOTJ6JLgz github.com/flanksource/artifacts v1.0.4/go.mod h1:wkbdseaTkDo4Q6k6T86vXd4Uy47M6NPCmexgHvCTDl0= github.com/flanksource/commons v1.20.0 h1:1z7FHGsCY7F7zMx4m0OlzYCUvCc1caREPp7vf1xyeCQ= github.com/flanksource/commons v1.20.0/go.mod h1:bs2nMwaTCpTQGZNtJv8KNqEVyfGjFZTGUDaAbPHB1cw= -github.com/flanksource/duty v1.0.280 h1:O02u0a09FwR3pxxR3cbRQYogypac92bL9m8iZtg86EA= -github.com/flanksource/duty v1.0.280/go.mod h1:zDVxlWxpSZjbpuBv0i9ke4vlG2elgyKjYY+656XhfPs= +github.com/flanksource/duty v1.0.287 h1:vuXyoxoTkEWh2m08d3JcaB/ZkFuTAEmLwBHQuW5e2AE= +github.com/flanksource/duty v1.0.287/go.mod h1:zDVxlWxpSZjbpuBv0i9ke4vlG2elgyKjYY+656XhfPs= github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc= github.com/flanksource/gomplate/v3 v3.21.0 h1:HbgJ7GCv2fGjjKhGBBtMmo+uA9T6pI9FVm+M/eEurOA= github.com/flanksource/gomplate/v3 v3.21.0/go.mod h1:m2WVc04GMVBOcZhtDaz/LTtrVWKejeJhFM1Jy/h9VZQ= diff --git a/hack/generate-schemas/go.mod b/hack/generate-schemas/go.mod index bd597942c..eea762bce 100644 --- a/hack/generate-schemas/go.mod +++ b/hack/generate-schemas/go.mod @@ -44,7 +44,7 @@ require ( github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/exaring/otelpgx v0.5.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/flanksource/duty v1.0.280 // indirect + github.com/flanksource/duty v1.0.287 // indirect github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7 // indirect github.com/flanksource/kommons v0.31.4 // indirect github.com/flanksource/postq v1.0.0 // indirect diff --git a/hack/generate-schemas/go.sum b/hack/generate-schemas/go.sum index 5a2463538..0bcb49952 100644 --- a/hack/generate-schemas/go.sum +++ b/hack/generate-schemas/go.sum @@ -719,8 +719,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flanksource/commons v1.20.0 h1:1z7FHGsCY7F7zMx4m0OlzYCUvCc1caREPp7vf1xyeCQ= github.com/flanksource/commons v1.20.0/go.mod h1:bs2nMwaTCpTQGZNtJv8KNqEVyfGjFZTGUDaAbPHB1cw= -github.com/flanksource/duty v1.0.280 h1:O02u0a09FwR3pxxR3cbRQYogypac92bL9m8iZtg86EA= -github.com/flanksource/duty v1.0.280/go.mod h1:zDVxlWxpSZjbpuBv0i9ke4vlG2elgyKjYY+656XhfPs= +github.com/flanksource/duty v1.0.287 h1:vuXyoxoTkEWh2m08d3JcaB/ZkFuTAEmLwBHQuW5e2AE= +github.com/flanksource/duty v1.0.287/go.mod h1:zDVxlWxpSZjbpuBv0i9ke4vlG2elgyKjYY+656XhfPs= github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc= github.com/flanksource/gomplate/v3 v3.21.0 h1:HbgJ7GCv2fGjjKhGBBtMmo+uA9T6pI9FVm+M/eEurOA= github.com/flanksource/gomplate/v3 v3.21.0/go.mod h1:m2WVc04GMVBOcZhtDaz/LTtrVWKejeJhFM1Jy/h9VZQ= diff --git a/pkg/system_api.go b/pkg/system_api.go index a0f330472..3f4887d07 100644 --- a/pkg/system_api.go +++ b/pkg/system_api.go @@ -110,6 +110,7 @@ func (components Components) Walk() Components { type Component struct { Name string `json:"name,omitempty"` ID uuid.UUID `json:"id,omitempty" gorm:"default:generate_ulid()"` //nolint + AgentID uuid.UUID `json:"agent_id,omitempty"` //nolint Text string `json:"text,omitempty"` Schedule string `json:"schedule,omitempty"` TopologyType string `json:"topology_type,omitempty"` diff --git a/pkg/topology/component_relationship_test.go b/pkg/topology/component_relationship_test.go index b381517e2..63b6a7371 100644 --- a/pkg/topology/component_relationship_test.go +++ b/pkg/topology/component_relationship_test.go @@ -1,49 +1,144 @@ package topology import ( + "fmt" + "github.com/flanksource/canary-checker/pkg" + "github.com/flanksource/commons/collections/set" + "github.com/flanksource/duty/models" "github.com/flanksource/duty/types" + "github.com/google/uuid" ginkgo "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func matchComponentsInRelationships(components pkg.Components, relationships []models.ComponentRelationship) error { + if len(components) != len(relationships) { + return fmt.Errorf("length of components & relationships should be equal") + } + + cset := set.New[string]() + for _, c := range components { + cset.Add(c.ID.String()) + } + for _, r := range relationships { + cset.Remove(r.ComponentID.String()) + } + if len(cset) != 0 { + return fmt.Errorf("mismatch in ids: %s", cset) + } + return nil +} + var _ = ginkgo.Describe("Topology relationships", ginkgo.Ordered, func() { + agent := models.Agent{ID: uuid.New(), Name: "agent"} topology := pkg.Topology{Name: "Topology ComponentRelationship"} - parentComponent := pkg.Component{ - Name: "Component", - Selectors: []types.ResourceSelector{ - { - Name: "ComponentSelector", - LabelSelector: "service=payments", + + parentComponents := pkg.Components{ + { + Name: "Component", + Selectors: []types.ResourceSelector{ + { + LabelSelector: "service=payments", + FieldSelector: "type=api", + }, + }, + }, + { + Name: "Component2", + Selectors: []types.ResourceSelector{ + { + FieldSelector: "type=api,agent_id=all", + }, + }, + }, + { + Name: "Component3", + Selectors: []types.ResourceSelector{ + { + FieldSelector: "type=api", + }, + }, + }, + { + Name: "Component4", + Selectors: []types.ResourceSelector{ + { + LabelSelector: "service=logistics", + }, + }, + }, + { + Name: "Component5", + Selectors: []types.ResourceSelector{ + { + FieldSelector: "agent_id=" + agent.ID.String(), + }, + }, + }, + { + Name: "Component6", + Selectors: []types.ResourceSelector{ + { + LabelSelector: "service=payments", + FieldSelector: "type=api", + }, + { + LabelSelector: "service=logistics", + }, }, }, } - childComponent1 := pkg.Component{ - Name: "Child-1", - Labels: map[string]string{"service": "payments"}, - } - childComponent2 := pkg.Component{ - Name: "Child-2", - Labels: map[string]string{"service": "logistics"}, - } - childComponent3 := pkg.Component{ - Name: "Child-3", - Labels: map[string]string{"service": "payments"}, + childrenComponents := pkg.Components{ + { + Name: "Child-1", + Labels: map[string]string{"service": "payments"}, + Type: "api", + }, + { + Name: "Child-2", + Labels: map[string]string{"service": "logistics"}, + }, + { + Name: "Child-3", + Labels: map[string]string{"service": "payments"}, + Type: "api", + }, + { + Name: "Child-4", + Labels: map[string]string{"service": "payments"}, + Type: "ui", + }, + { + Name: "Child-5", + Labels: map[string]string{"service": "logistics"}, + Type: "api", + }, + { + Name: "Child-6", + AgentID: agent.ID, + Labels: map[string]string{"service": "logistics"}, + Type: "api", + }, } ginkgo.BeforeAll(func() { err := DefaultContext.DB().Create(&topology).Error Expect(err).To(BeNil()) + err = DefaultContext.DB().Create(&agent).Error + Expect(err).To(BeNil()) - parentComponent.TopologyID = topology.ID + for _, c := range parentComponents { + c.TopologyID = topology.ID + } ComponentRelationshipSync.Context = DefaultContext - err = DefaultContext.DB().Create(&parentComponent).Error + err = DefaultContext.DB().Create(parentComponents).Error Expect(err).To(BeNil()) - childComponent1.TopologyID = topology.ID - childComponent2.TopologyID = topology.ID - childComponent3.TopologyID = topology.ID - err = DefaultContext.DB().Create(pkg.Components{&childComponent1, &childComponent2, &childComponent3}).Error + for _, c := range childrenComponents { + c.TopologyID = topology.ID + } + err = DefaultContext.DB().Create(childrenComponents).Error Expect(err).To(BeNil()) }) @@ -51,47 +146,91 @@ var _ = ginkgo.Describe("Topology relationships", ginkgo.Ordered, func() { ComponentRelationshipSync.Run() expectJobToPass(ComponentRelationshipSync) - relationships, err := parentComponent.GetChildren(DefaultContext.DB()) + relationships, err := parentComponents.Find("Component").GetChildren(DefaultContext.DB()) Expect(err).To(BeNil()) - // Child-1 and Child-3 should be present but not Child-2 + // Child-1 and Child-3 + Expect(len(relationships)).To(Equal(2)) + Expect(matchComponentsInRelationships(pkg.Components{childrenComponents.Find("Child-1"), childrenComponents.Find("Child-3")}, relationships)).To(BeNil()) + + relationships, err = parentComponents.Find("Component2").GetChildren(DefaultContext.DB()) + Expect(err).To(BeNil()) + + // Child-1 Child-3 Child-5 Child-6 + Expect(matchComponentsInRelationships(pkg.Components{ + childrenComponents.Find("Child-1"), childrenComponents.Find("Child-3"), + childrenComponents.Find("Child-5"), childrenComponents.Find("Child-6")}, relationships)). + To(BeNil()) + Expect(len(relationships)).To(Equal(4)) + + relationships, err = parentComponents.Find("Component3").GetChildren(DefaultContext.DB()) + Expect(err).To(BeNil()) + + Expect(matchComponentsInRelationships(pkg.Components{ + childrenComponents.Find("Child-1"), childrenComponents.Find("Child-3"), + childrenComponents.Find("Child-5")}, relationships)). + To(BeNil()) + Expect(len(relationships)).To(Equal(3)) + + relationships, err = parentComponents.Find("Component4").GetChildren(DefaultContext.DB()) + Expect(err).To(BeNil()) + + Expect(matchComponentsInRelationships(pkg.Components{childrenComponents.Find("Child-2"), childrenComponents.Find("Child-5")}, relationships)).To(BeNil()) Expect(len(relationships)).To(Equal(2)) + + relationships, err = parentComponents.Find("Component5").GetChildren(DefaultContext.DB()) + Expect(err).To(BeNil()) + + Expect(matchComponentsInRelationships(pkg.Components{childrenComponents.Find("Child-6")}, relationships)).To(BeNil()) + Expect(len(relationships)).To(Equal(1)) + + relationships, err = parentComponents.Find("Component6").GetChildren(DefaultContext.DB()) + Expect(err).To(BeNil()) + + Expect(matchComponentsInRelationships(pkg.Components{ + childrenComponents.Find("Child-1"), childrenComponents.Find("Child-2"), + childrenComponents.Find("Child-3"), childrenComponents.Find("Child-5")}, relationships)). + To(BeNil()) + + Expect(len(relationships)).To(Equal(4)) + }) ginkgo.It("should handle component relationship deletions", func() { - err := DefaultContext.DB().Delete(&childComponent3).Error + err := DefaultContext.DB().Delete(&childrenComponents[2]).Error Expect(err).To(BeNil()) ComponentRelationshipSync.Run() expectJobToPass(ComponentRelationshipSync) - relationships, err := parentComponent.GetChildren(DefaultContext.DB()) + relationships, err := parentComponents.Find("Component").GetChildren(DefaultContext.DB()) Expect(err).To(BeNil()) // Only child 1 should be present Expect(len(relationships)).To(Equal(1)) - err = DefaultContext.DB().Create(&childComponent3).Error + err = DefaultContext.DB().Create(&childrenComponents[2]).Error Expect(err).To(BeNil()) ComponentRelationshipSync.Run() expectJobToPass(ComponentRelationshipSync) - relationships, err = parentComponent.GetChildren(DefaultContext.DB()) + relationships, err = parentComponents.Find("Component").GetChildren(DefaultContext.DB()) Expect(err).To(BeNil()) // Child-1 and Child-3 should be present but not Child-2 Expect(len(relationships)).To(Equal(2)) + }) ginkgo.It("should handle component label updates", func() { - childComponent3.Labels = map[string]string{"service": "logistics"} - err := DefaultContext.DB().Save(&childComponent3).Error + childrenComponents[2].Labels = map[string]string{"service": "logistics"} + err := DefaultContext.DB().Save(&childrenComponents[2]).Error Expect(err).To(BeNil()) ComponentRelationshipSync.Run() expectJobToPass(ComponentRelationshipSync) - relationships, err := parentComponent.GetChildren(DefaultContext.DB()) + relationships, err := parentComponents.Find("Component").GetChildren(DefaultContext.DB()) Expect(err).To(BeNil()) // Only child 1 should be present as we updated the labels