diff --git a/pkg/sqlcache/informer/listoption_indexer.go b/pkg/sqlcache/informer/listoption_indexer.go index 6aec0112..9ca403fc 100644 --- a/pkg/sqlcache/informer/listoption_indexer.go +++ b/pkg/sqlcache/informer/listoption_indexer.go @@ -57,7 +57,7 @@ const ( )` createFieldsIndexFmt = `CREATE INDEX "%s_%s_index" ON "%s_fields"("%s")` - failedToGetFromSliceFmt = "[listoption indexer] failed to get subfield [%s] from slice items: %w" + failedToGetFromSliceFmt = "[listoption indexer] failed to get subfield [%s] from slice items" createLabelsTableFmt = `CREATE TABLE IF NOT EXISTS "%s_labels" ( key TEXT NOT NULL REFERENCES "%s"(key) ON DELETE CASCADE, @@ -903,13 +903,19 @@ func getField(a any, field string) (any, error) { for index, v := range t { itemVal, ok := v.(map[string]interface{}) if !ok { - return nil, fmt.Errorf(failedToGetFromSliceFmt, subField, err) + return nil, fmt.Errorf(failedToGetFromSliceFmt, subField) } - itemStr, ok := itemVal[subField].(string) - if !ok { - return nil, fmt.Errorf(failedToGetFromSliceFmt, subField, err) + + _, found := itemVal[subField] + if found { + itemStr, ok := itemVal[subField].(string) + if !ok { + return nil, fmt.Errorf(failedToGetFromSliceFmt, subField) + } + result[index] = itemStr + } else { + result[index] = "" } - result[index] = itemStr } return result, nil } diff --git a/pkg/sqlcache/informer/listoption_indexer_test.go b/pkg/sqlcache/informer/listoption_indexer_test.go index 4248edf1..13db685b 100644 --- a/pkg/sqlcache/informer/listoption_indexer_test.go +++ b/pkg/sqlcache/informer/listoption_indexer_test.go @@ -11,14 +11,15 @@ import ( "database/sql" "errors" "fmt" - "github.com/rancher/steve/pkg/sqlcache/sqltypes" "reflect" "strings" "testing" "github.com/rancher/steve/pkg/sqlcache/db" "github.com/rancher/steve/pkg/sqlcache/partition" + "github.com/rancher/steve/pkg/sqlcache/sqltypes" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -1790,3 +1791,163 @@ func TestBuildSortLabelsClause(t *testing.T) { }) } } + +func TestGetField(t *testing.T) { + tests := []struct { + name string + obj any + field string + expectedResult any + expectedErr bool + }{ + { + name: "simple", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "foo": "bar", + }, + }, + field: "foo", + expectedResult: "bar", + }, + { + name: "nested", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "foo": map[string]any{ + "bar": "baz", + }, + }, + }, + field: "foo.bar", + expectedResult: "baz", + }, + { + name: "array", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "theList": []any{ + "foo", "bar", "baz", + }, + }, + }, + field: "theList[1]", + expectedResult: "bar", + }, + { + name: "array of object", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "theList": []any{ + map[string]any{ + "name": "foo", + }, + map[string]any{ + "name": "bar", + }, + map[string]any{ + "name": "baz", + }, + }, + }, + }, + field: "theList.name", + expectedResult: []string{"foo", "bar", "baz"}, + }, + { + name: "annotation", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "annotations": map[string]any{ + "with.dot.in.it/and-slash": "foo", + }, + }, + }, + field: "annotations[with.dot.in.it/and-slash]", + expectedResult: "foo", + }, + { + name: "field not found", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "spec": map[string]any{ + "rules": []any{ + map[string]any{}, + map[string]any{ + "host": "example.com", + }, + }, + }, + }, + }, + field: "spec.rules.host", + expectedResult: []string{"", "example.com"}, + }, + { + name: "array index invalid", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "theList": []any{ + "foo", "bar", "baz", + }, + }, + }, + field: "theList[a]", + expectedErr: true, + }, + { + name: "array index out of bound", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "theList": []any{ + "foo", "bar", "baz", + }, + }, + }, + field: "theList[3]", + expectedErr: true, + }, + { + name: "invalid array", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "spec": map[string]any{ + "rules": []any{ + 1, + }, + }, + }, + }, + field: "spec.rules.host", + expectedErr: true, + }, + { + name: "invalid array nested", + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "spec": map[string]any{ + "rules": []any{ + map[string]any{ + "host": 1, + }, + }, + }, + }, + }, + field: "spec.rules.host", + expectedErr: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := getField(test.obj, test.field) + if test.expectedErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, test.expectedResult, result) + }) + } +}