diff --git a/pkg/customresourcestate/example_config.yaml b/pkg/customresourcestate/example_config.yaml index 39a479b2b..819e00551 100644 --- a/pkg/customresourcestate/example_config.yaml +++ b/pkg/customresourcestate/example_config.yaml @@ -47,6 +47,13 @@ spec: path: [status, other] errorLogV: 5 + - name: "other_count_array_deep" + each: + type: Gauge + gauge: + path: [ status, other, '[*]', detail ] + errorLogV: 5 + - name: "info" each: type: Info diff --git a/pkg/customresourcestate/registry_factory.go b/pkg/customresourcestate/registry_factory.go index 2a2cb067f..56f4df804 100644 --- a/pkg/customresourcestate/registry_factory.go +++ b/pkg/customresourcestate/registry_factory.go @@ -279,6 +279,25 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) } case []interface{}: for i, it := range iter { + // Check if it is a [][]interface{} if yes loop over it + // Else process normally + if nestedIter, ok := it.([]interface{}); ok { + for j, nestedIt := range nestedIter { + value, err := c.value(nestedIt) + + if err != nil { + onError(fmt.Errorf("[%d]: %w", j, err)) + continue + } + if value == nil { + continue + } + addPathLabels(nestedIt, c.LabelFromPath(), value.Labels) + result = append(result, *value) + } + continue + } + value, err := c.value(it) if err != nil { onError(fmt.Errorf("[%d]: %w", i, err)) @@ -582,7 +601,24 @@ func (p valuePath) String() string { func compilePath(path []string) (out valuePath, _ error) { for i := range path { part := path[i] + if strings.HasPrefix(part, "[") && strings.HasSuffix(part, "]") { + + // Wildcard with filter: [*] + if part == "[*]" { + // function to return all elements in a list + out = append(out, pathOp{ + part: part, + op: func(m interface{}) interface{} { + if s, ok := m.([]interface{}); ok { + return s + } + return nil + }, + }) + continue + } + // list lookup: [key=value] eq := strings.SplitN(part[1:len(part)-1], "=", 2) if len(eq) != 2 { @@ -641,21 +677,31 @@ func compilePath(path []string) (out valuePath, _ error) { } return mp[part] } else if s, ok := m.([]interface{}); ok { + // case part is an integer index i, err := strconv.Atoi(part) - if err != nil { - // This means we are here: [ , , ... ] (eg., [ "foo", "0", ... ], i.e., .foo[0]... - // ^ - // Skip over. - return nil - } - if i < 0 { - // negative index - i += len(s) + if err == nil { + if i < 0 { + // negative index + i += len(s) + } + if i < 0 || i >= len(s) { + return fmt.Errorf("list index out of range: %s", part) + } + return s[i] } - if i < 0 || i >= len(s) { - return fmt.Errorf("list index out of range: %s", part) + + // case we have a list and the part is an index + var result []interface{} + for _, el := range s { + if m, ok := el.(map[string]interface{}); ok { + if v, ok := m[part]; ok { + result = append(result, v) + } + } else { + continue + } } - return s[i] + return result } return nil }, diff --git a/pkg/customresourcestate/registry_factory_test.go b/pkg/customresourcestate/registry_factory_test.go index 7f5d1a092..d25ddcb66 100644 --- a/pkg/customresourcestate/registry_factory_test.go +++ b/pkg/customresourcestate/registry_factory_test.go @@ -88,6 +88,64 @@ func init() { "status": "False", }, }, + "parents": Array{ + Obj{ + "conditions": Array{ + Obj{ + "type": "Ready", + "status": "True", + "message": "All good", + "reason": "AsExpected", + "lastTransitionTime": "2022-06-28T00:00:00Z", + "observedGeneration": 42, + }, + Obj{ + "type": "Synced", + "status": "False", + "message": "Not synced", + "reason": "SyncError", + "lastTransitionTime": "2022-06-28T00:00:00Z", + "observedGeneration": 43, + }, + }, + "controllerName": "foo.bar/baz", + "parentRef": Obj{ + "group": "foo.bar", + "kind": "Baz", + "name": "baz-1", + "namespace": "default", + }, + }, + Obj{ + "conditions": Array{ + Obj{ + "type": "Ready", + "status": "False", + "message": "Not ready", + "reason": "NotReady", + "lastTransitionTime": "2022-06-28T00:00:00Z", + "observedGeneration": 44, + }, + }, + "controllerName": "qux.corge/grault", + "parentRef": Obj{ + "group": "qux.corge", + "kind": "Grault", + "name": "grault-1", + "namespace": "default", + }, + }, + Obj{ + "conditions": Array{}, + "controllerName": "garply.waldo/fred", + "parentRef": Obj{ + "group": "garply.waldo", + "kind": "Fred", + "name": "fred-1", + "namespace": "default", + }, + }, + }, }, "metadata": Obj{ "name": "foo", @@ -352,6 +410,19 @@ func Test_values(t *testing.T) { newEachValue(t, 0, "type", "Provisioned"), newEachValue(t, 1, "type", "Ready"), }}, + {name: "status_parents_conditions", each: &compiledGauge{ + compiledCommon: compiledCommon{ + path: mustCompilePath(t, "status", "parents", "[*]", "conditions"), + labelFromPath: map[string]valuePath{ + "reason": mustCompilePath(t, "reason"), + }, + }, + ValueFrom: mustCompilePath(t, "status"), + }, wantResult: []eachValue{ + newEachValue(t, 1, "reason", "AsExpected"), + newEachValue(t, 0, "reason", "NotReady"), + newEachValue(t, 0, "reason", "SyncError"), + }}, {name: "= expression matching", each: &compiledInfo{ compiledCommon: compiledCommon{ labelFromPath: map[string]valuePath{