This commit is contained in:
Mihaly Gyongyosi
2025-10-13 18:34:06 +02:00
parent 8a5eba6395
commit 8cedf25892
5 changed files with 190 additions and 0 deletions
+7
View File
@@ -65,6 +65,11 @@ func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBui
}, nil
})
users, err := GetUserBuilder()
if err != nil {
return nil, err
}
return []resource.DocumentBuilderInfo{
// The default builder
{
@@ -72,5 +77,7 @@ func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBui
},
// Dashboard builder
dashboards,
// User builder
users,
}, err
}
@@ -0,0 +1,17 @@
{
"key": {
"namespace": "default",
"group": "iam.grafana.app",
"resource": "users",
"name": "example"
},
"name": "example",
"rv": 1234,
"title": "example",
"title_ngram": "example",
"title_phrase": "example",
"fields": {
"email": "example@example.com",
"login": "example"
}
}
@@ -0,0 +1,13 @@
{
"apiVersion": "iam.grafana.app/v0alpha1",
"kind": "User",
"metadata": {
"name": "example",
"namespace": "default"
},
"spec": {
"login": "example",
"email": "example@example.com",
"role": "Viewer"
}
}
+80
View File
@@ -0,0 +1,80 @@
package search
import (
"context"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
USER_EMAIL = "email"
USER_LOGIN = "login"
)
func GetUserBuilder() (resource.DocumentBuilderInfo, error) {
fields, err := resource.NewSearchableDocumentFields([]*resourcepb.ResourceTableColumnDefinition{
{
Name: USER_EMAIL,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "The email address of the user",
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: USER_LOGIN,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "The login of the user",
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
UniqueValues: true,
Filterable: true,
},
},
})
return resource.DocumentBuilderInfo{
GroupResource: iamv0.UserResourceInfo.GroupResource(),
Fields: fields,
Builder: new(userDocumentBuilder),
}, err
}
var _ resource.DocumentBuilder = new(userDocumentBuilder)
type userDocumentBuilder struct{}
// BuildDocument implements resource.DocumentBuilder.
func (u *userDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
tmp := &unstructured.Unstructured{}
err := tmp.UnmarshalJSON(value)
if err != nil {
return nil, err
}
obj, err := utils.MetaAccessor(tmp)
if err != nil {
return nil, err
}
doc := resource.NewIndexableDocument(key, rv, obj)
if spec, err := obj.GetSpec(); err == nil {
if m, ok := spec.(map[string]any); ok {
email, _ := m[USER_EMAIL].(string)
login, _ := m[USER_LOGIN].(string)
if email != "" || login != "" {
doc.Fields = make(map[string]any)
if email != "" {
doc.Fields[USER_EMAIL] = email
}
if login != "" {
doc.Fields[USER_LOGIN] = login
}
}
}
}
return doc, nil
}
+73
View File
@@ -0,0 +1,73 @@
package search_test
import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
func TestUserDocumentBuilder(t *testing.T) {
info, err := search.GetUserBuilder()
require.NoError(t, err)
require.NotNil(t, info.Builder)
builder := info.Builder
base := filepath.Join("testdata", "doc")
inPath := filepath.Join(base, "user-example.json")
outPath := filepath.Join(base, "user-example-out.json")
// nolint:gosec
raw, err := os.ReadFile(inPath)
if err != nil {
t.Fatalf("missing input fixture %s: %v", inPath, err)
}
gr := iamv0.UserResourceInfo.GroupResource()
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: gr.Group,
Resource: gr.Resource,
Name: "example", // set by input file metadata.name
}
doc, err := builder.BuildDocument(context.Background(), key, 1234, raw)
require.NoError(t, err)
require.NotNil(t, doc)
// Core
require.Equal(t, key.Name, doc.Name)
require.Equal(t, int64(1234), doc.RV)
require.Equal(t, key, doc.Key)
require.Equal(t, key.Name, doc.Title)
// Custom field assertions (email/login added to Fields)
if assert.NotNil(t, doc.Fields) {
assert.Equal(t, "example@example.com", doc.Fields[search.USER_EMAIL])
assert.Equal(t, "example", doc.Fields[search.USER_LOGIN])
}
// Snapshot compare
out, err := json.MarshalIndent(doc, "", " ")
require.NoError(t, err)
// nolint:gosec
expected, readErr := os.ReadFile(outPath)
if readErr != nil {
err2 := os.MkdirAll(filepath.Dir(outPath), 0o755)
require.NoError(t, err2)
err2 = os.WriteFile(outPath, out, 0o600)
require.NoError(t, err2)
t.Fatalf("snapshot created at %s; re-run tests", outPath)
}
assert.JSONEq(t, string(expected), string(out))
}