From 91a57a0570d8c4ba33eb4b9b2b4c9fbc893a4c0e Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Mon, 23 Dec 2024 14:39:45 +0530 Subject: [PATCH] feat: merge multiple property lookups (#2379) * feat: merge multiple property lookups * chore: fix topology property test --- .../topology/component-with-property-list.yml | 28 +++++++++++ pkg/topology/run.go | 50 ++++++++++++++++++- pkg/topology/run_test.go | 19 +++++++ pkg/topology/utils.go | 4 +- 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 fixtures/topology/component-with-property-list.yml diff --git a/fixtures/topology/component-with-property-list.yml b/fixtures/topology/component-with-property-list.yml new file mode 100644 index 000000000..e198d0a9d --- /dev/null +++ b/fixtures/topology/component-with-property-list.yml @@ -0,0 +1,28 @@ +apiVersion: canaries.flanksource.com/v1 +kind: Topology +metadata: + name: test-topology-property-list +spec: + schedule: "@every 10m" + components: + - name: RootComponent + properties: + - name: error_percentage + min: 0 + + # Test property lookup merge as components + - name: error_percentage_lookup + lookup: + http: + - url: https://httpbin.demo.aws.flanksource.com/status/200 + name: error_percentage_lookup_max + display: + expr: | + [ + {'name': 'error_percentage', 'max': 100} + ].toJSON() + - url: https://httpbin.demo.aws.flanksource.com/status/200 + name: error_percentage_lookup_value + display: + expr: | + {'name': 'error_percentage', 'value': 10}.toJSON() diff --git a/pkg/topology/run.go b/pkg/topology/run.go index e70dd6717..5fd0ed2a1 100644 --- a/pkg/topology/run.go +++ b/pkg/topology/run.go @@ -318,6 +318,46 @@ func lookupProperty(ctx *ComponentContext, property *v1.Property) ([]byte, error return nil, err } + // Multiple results signify that the lookup has returned + // more than one property and they are flattened + if len(results) > 1 { + var props types.Properties + for _, r := range results { + var dataStr string + var ok bool + if dataStr, ok = r.(string); !ok { + return nil, fmt.Errorf("unknown property type %T", results) + } + dataStr = strings.TrimSpace(dataStr) + if strings.HasPrefix(dataStr, "[") && strings.HasSuffix(dataStr, "]") { + if isPropertyList([]byte(dataStr)) { + var prop types.Properties + if err := json.Unmarshal([]byte(dataStr), &prop); err != nil { + return nil, fmt.Errorf("error marshaling property: %w", err) + } + if prop != nil { + props = append(props, prop...) + } + } + } else { + var msa map[string]any + if err := json.Unmarshal([]byte(dataStr), &msa); err != nil { + return nil, fmt.Errorf("error unmarshaling: %w", err) + } + if isProperty(msa) { + var prop *types.Property + if err := json.Unmarshal([]byte(dataStr), &prop); err != nil { + return nil, fmt.Errorf("error marshaling property: %w", err) + } + if prop != nil { + props = append(props, prop) + } + } + } + } + return json.Marshal(props) + } + var dataStr string var ok bool if dataStr, ok = results[0].(string); !ok { @@ -366,8 +406,14 @@ func mergeComponentProperties(components pkg.Components, propertiesRaw []byte) e if err := json.Unmarshal(propertiesRaw, &properties); err != nil { return err } - for _, comp := range components { - comp.Properties = append(comp.Properties, properties...) + for _, p := range properties { + for _, comp := range components { + if cProp := comp.Properties.Find(p.Name); cProp == nil { + comp.Properties = append(comp.Properties, p) + } else { + cProp.Merge(p) + } + } } } return nil diff --git a/pkg/topology/run_test.go b/pkg/topology/run_test.go index 8cff6a798..8c2de2268 100644 --- a/pkg/topology/run_test.go +++ b/pkg/topology/run_test.go @@ -174,6 +174,25 @@ var _ = ginkgo.Describe("Topology run", ginkgo.Ordered, func() { Expect(len(demoCluster.Components[0].Components)).To(Equal(1)) Expect(demoCluster.Components[0].Components[0].Name).To(Equal(lo.FromPtr(dummy.KubernetesNodeAKSPool1.Name))) }) + + ginkgo.It("should correctly merge multiple property lookup", func() { + t, err := yamlFileToTopology("../../fixtures/topology/component-with-property-list.yml") + if err != nil { + ginkgo.Fail("Error converting yaml to v1.Topology:" + err.Error()) + } + + rootComponent, history, err := Run(DefaultContext.WithTrace(), *t) + Expect(err).To(BeNil()) + + Expect(history.Errors).To(HaveLen(0)) + + Expect(len(rootComponent[0].Components)).To(Equal(1)) + + componentA := rootComponent[0].Components[0] + + Expect(string(componentA.Properties.AsJSON())).To(MatchJSON(`[{"name":"error_percentage","value":10,"min":0,"max":100}]`)) + }) + }) func yamlFileToTopology(file string) (*pkg.Topology, error) { diff --git a/pkg/topology/utils.go b/pkg/topology/utils.go index b688daea8..dab6d7c19 100644 --- a/pkg/topology/utils.go +++ b/pkg/topology/utils.go @@ -1,6 +1,8 @@ package topology -import "strings" +import ( + "strings" +) func isComponent(s map[string]interface{}) bool { _, name := s["name"]