refactor(unified-storage): move generated protos to own pkg (#105356)

pull/105497/head
Jean-Philippe Quéméner 2 months ago committed by GitHub
parent aa2cf8e398
commit 002f46736a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      Dockerfile
  2. 4
      Makefile
  3. 2
      apps/alerting/notifications/go.mod
  4. 4
      apps/alerting/notifications/go.sum
  5. 2
      apps/dashboard/go.mod
  6. 4
      apps/dashboard/go.sum
  7. 2
      apps/playlist/go.mod
  8. 4
      apps/playlist/go.sum
  9. 2
      go.mod
  10. 3
      go.sum
  11. 1
      go.work
  12. 1
      go.work.sum
  13. 2
      pkg/aggregator/go.mod
  14. 4
      pkg/aggregator/go.sum
  15. 2
      pkg/apimachinery/go.mod
  16. 4
      pkg/apimachinery/go.sum
  17. 2
      pkg/apis/secret/go.mod
  18. 4
      pkg/apis/secret/go.sum
  19. 2
      pkg/apiserver/go.mod
  20. 4
      pkg/apiserver/go.sum
  21. 2
      pkg/build/go.mod
  22. 4
      pkg/build/go.sum
  23. 6
      pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go
  24. 2
      pkg/promlib/go.mod
  25. 4
      pkg/promlib/go.sum
  26. 29
      pkg/registry/apis/dashboard/legacy/client.go
  27. 17
      pkg/registry/apis/dashboard/legacy/legacy_migrator_mock.go
  28. 64
      pkg/registry/apis/dashboard/legacy/migrate.go
  29. 32
      pkg/registry/apis/dashboard/legacy/storage.go
  30. 5
      pkg/registry/apis/dashboard/legacy/storage_test.go
  31. 5
      pkg/registry/apis/dashboard/legacy/types.go
  32. 42
      pkg/registry/apis/dashboard/legacysearcher/search_client.go
  33. 117
      pkg/registry/apis/dashboard/legacysearcher/search_client_test.go
  34. 41
      pkg/registry/apis/dashboard/search.go
  35. 113
      pkg/registry/apis/dashboard/search_test.go
  36. 4
      pkg/registry/apis/dashboard/sub_dto.go
  37. 9
      pkg/registry/apis/folders/mocks.go
  38. 8
      pkg/registry/apis/folders/register.go
  39. 14
      pkg/registry/apis/folders/register_test.go
  40. 8
      pkg/registry/apis/folders/sub_count.go
  41. 7
      pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go
  42. 50
      pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go
  43. 28
      pkg/registry/apis/provisioning/jobs/migrate/mock_bulk_process_client.go
  44. 16
      pkg/registry/apis/provisioning/jobs/migrate/mock_bulk_store_client.go
  45. 6
      pkg/registry/apis/provisioning/jobs/migrate/storage.go
  46. 6
      pkg/registry/apis/provisioning/jobs/migrate/storage_test.go
  47. 15
      pkg/registry/apis/provisioning/resources/object.go
  48. 4
      pkg/registry/apis/provisioning/usage.go
  49. 22
      pkg/registry/apis/provisioning/webhooks/pullrequest/blobstore_client_mock.go
  50. 11
      pkg/registry/apis/provisioning/webhooks/pullrequest/render.go
  51. 12
      pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go
  52. 5
      pkg/registry/apis/provisioning/webhooks/render.go
  53. 17
      pkg/services/apiserver/client/client.go
  54. 11
      pkg/services/apiserver/client/client_mock.go
  55. 6
      pkg/services/dashboards/dashboard.go
  56. 40
      pkg/services/dashboards/service/dashboard_service.go
  57. 341
      pkg/services/dashboards/service/dashboard_service_test.go
  58. 3
      pkg/services/dashboards/service/search/search.go
  59. 42
      pkg/services/dashboards/service/search/search_test.go
  60. 45
      pkg/services/folder/folderimpl/folder_unifiedstorage.go
  61. 131
      pkg/services/folder/folderimpl/folder_unifiedstorage_test.go
  62. 10
      pkg/services/folder/folderimpl/unifiedstore.go
  63. 93
      pkg/services/folder/folderimpl/unifiedstore_test.go
  64. 6
      pkg/storage/unified/apistore/fake_large.go
  65. 2
      pkg/storage/unified/apistore/go.mod
  66. 3
      pkg/storage/unified/apistore/go.sum
  67. 16
      pkg/storage/unified/apistore/large.go
  68. 11
      pkg/storage/unified/apistore/managed.go
  69. 5
      pkg/storage/unified/apistore/permissions.go
  70. 9
      pkg/storage/unified/apistore/permissions_test.go
  71. 5
      pkg/storage/unified/apistore/prepare.go
  72. 30
      pkg/storage/unified/apistore/store.go
  73. 19
      pkg/storage/unified/apistore/store_test.go
  74. 20
      pkg/storage/unified/apistore/stream.go
  75. 23
      pkg/storage/unified/apistore/util.go
  76. 62
      pkg/storage/unified/apistore/util_test.go
  77. 7
      pkg/storage/unified/apistore/watcher_test.go
  78. 3
      pkg/storage/unified/federated/client.go
  79. 6
      pkg/storage/unified/federated/federatedtests/stats_test.go
  80. 9
      pkg/storage/unified/federated/stats.go
  81. 15
      pkg/storage/unified/parquet/client.go
  82. 11
      pkg/storage/unified/parquet/reader.go
  83. 9
      pkg/storage/unified/parquet/reader_test.go
  84. 32
      pkg/storage/unified/parquet/writer.go
  85. 98
      pkg/storage/unified/proto/blob.proto
  86. 4
      pkg/storage/unified/proto/buf.gen.yaml
  87. 4
      pkg/storage/unified/proto/buf.yaml
  88. 234
      pkg/storage/unified/proto/resource.proto
  89. 140
      pkg/storage/unified/proto/search.proto
  90. 68
      pkg/storage/unified/resource/bulk.go
  91. 23
      pkg/storage/unified/resource/cdk_backend.go
  92. 16
      pkg/storage/unified/resource/cdk_blob.go
  93. 8
      pkg/storage/unified/resource/cdk_blob_test.go
  94. 73
      pkg/storage/unified/resource/client.go
  95. 57
      pkg/storage/unified/resource/document.go
  96. 4
      pkg/storage/unified/resource/document_test.go
  97. 24
      pkg/storage/unified/resource/errors.go
  98. 13
      pkg/storage/unified/resource/event.go
  99. 6
      pkg/storage/unified/resource/go.mod
  100. 4
      pkg/storage/unified/resource/go.sum
  101. Some files were not shown because too many files have changed in this diff Show More

@ -77,6 +77,7 @@ COPY pkg/build pkg/build
COPY pkg/build/wire pkg/build/wire
COPY pkg/promlib pkg/promlib
COPY pkg/storage/unified/resource pkg/storage/unified/resource
COPY pkg/storage/unified/resourcepb pkg/storage/unified/resourcepb
COPY pkg/storage/unified/apistore pkg/storage/unified/apistore
COPY pkg/semconv pkg/semconv
COPY pkg/aggregator pkg/aggregator

@ -452,11 +452,11 @@ devenv-mysql:
.PHONY: protobuf
protobuf: ## Compile protobuf definitions
bash scripts/protobuf-check.sh
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.4.0
buf generate pkg/plugins/backendplugin/pluginextensionv2 --template pkg/plugins/backendplugin/pluginextensionv2/buf.gen.yaml
buf generate pkg/apis/secret/v0alpha1/decrypt --template pkg/apis/secret/v0alpha1/decrypt/buf.gen.yaml
buf generate pkg/storage/unified/resource --template pkg/storage/unified/resource/buf.gen.yaml
buf generate pkg/storage/unified/proto --template pkg/storage/unified/proto/buf.gen.yaml
buf generate pkg/services/authz/proto/v1 --template pkg/services/authz/proto/v1/buf.gen.yaml
buf generate pkg/services/ngalert/store/proto/v1 --template pkg/services/ngalert/store/proto/v1/buf.gen.yaml

@ -98,7 +98,7 @@ require (
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

@ -335,8 +335,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -115,7 +115,7 @@ require (
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

@ -359,8 +359,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -76,7 +76,7 @@ require (
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -193,8 +193,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -187,7 +187,7 @@ require (
golang.org/x/tools v0.33.0 // indirect; @grafana/grafana-as-code
gonum.org/v1/gonum v0.15.1 // @grafana/oss-big-tent
google.golang.org/api v0.223.0 // @grafana/grafana-backend-group
google.golang.org/grpc v1.72.0 // @grafana/plugins-platform-backend
google.golang.org/grpc v1.72.1 // @grafana/plugins-platform-backend
google.golang.org/protobuf v1.36.6 // @grafana/plugins-platform-backend
gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend
gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group

@ -3401,8 +3401,7 @@ google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwS
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

@ -30,6 +30,7 @@ use (
./pkg/semconv
./pkg/storage/unified/apistore
./pkg/storage/unified/resource
./pkg/storage/unified/resourcepb
)
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975

@ -2129,6 +2129,7 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI=
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=

@ -152,7 +152,7 @@ require (
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect

@ -494,8 +494,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -43,7 +43,7 @@ require (
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -151,8 +151,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -5,7 +5,7 @@ go 1.24.3
require (
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250422074709-7c8433fbb2c2
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.72.0
google.golang.org/grpc v1.72.1
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.32.3

@ -304,8 +304,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -97,7 +97,7 @@ require (
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

@ -347,8 +347,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -33,7 +33,7 @@ require (
golang.org/x/text v0.25.0 // indirect; @grafana/grafana-backend-group
golang.org/x/time v0.11.0 // indirect; @grafana/grafana-backend-group
google.golang.org/api v0.223.0 // @grafana/grafana-backend-group
google.golang.org/grpc v1.72.0 // indirect; @grafana/plugins-platform-backend
google.golang.org/grpc v1.72.1 // indirect; @grafana/plugins-platform-backend
google.golang.org/protobuf v1.36.6 // indirect; @grafana/plugins-platform-backend
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend
)

@ -382,8 +382,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

@ -12,6 +12,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
authlib "github.com/grafana/authlib/types"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@ -26,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified"
"github.com/grafana/grafana/pkg/storage/unified/parquet"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// ToUnifiedStorage converts dashboards+folders into unified storage
@ -148,7 +150,7 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err
}
// Check the stats (eventually compare)
req := &resource.ResourceStatsRequest{
req := &resourcepb.ResourceStatsRequest{
Namespace: opts.Namespace,
}
for _, r := range opts.Resources {
@ -218,7 +220,7 @@ func newUnifiedClient(cfg *setting.Cfg, sqlStore db.DB) (resource.ResourceClient
}, nil, nil)
}
func newParquetClient(file *os.File) (resource.BulkStoreClient, error) {
func newParquetClient(file *os.File) (resourcepb.BulkStoreClient, error) {
writer, err := parquet.NewParquetWriter(file)
if err != nil {
return nil, err

@ -124,7 +124,7 @@ require (
google.golang.org/api v0.223.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/grpc v1.72.1 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -409,8 +409,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -7,6 +7,7 @@ import (
"google.golang.org/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
@ -23,69 +24,69 @@ type directResourceClient struct {
}
// Create implements ResourceClient.
func (d *directResourceClient) Create(ctx context.Context, in *resource.CreateRequest, opts ...grpc.CallOption) (*resource.CreateResponse, error) {
func (d *directResourceClient) Create(ctx context.Context, in *resourcepb.CreateRequest, opts ...grpc.CallOption) (*resourcepb.CreateResponse, error) {
return d.server.Create(ctx, in)
}
// Delete implements ResourceClient.
func (d *directResourceClient) Delete(ctx context.Context, in *resource.DeleteRequest, opts ...grpc.CallOption) (*resource.DeleteResponse, error) {
func (d *directResourceClient) Delete(ctx context.Context, in *resourcepb.DeleteRequest, opts ...grpc.CallOption) (*resourcepb.DeleteResponse, error) {
return d.server.Delete(ctx, in)
}
// GetBlob implements ResourceClient.
func (d *directResourceClient) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) {
func (d *directResourceClient) GetBlob(ctx context.Context, in *resourcepb.GetBlobRequest, opts ...grpc.CallOption) (*resourcepb.GetBlobResponse, error) {
return d.server.GetBlob(ctx, in)
}
// GetStats implements ResourceClient.
func (d *directResourceClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (d *directResourceClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
return d.server.GetStats(ctx, in)
}
// IsHealthy implements ResourceClient.
func (d *directResourceClient) IsHealthy(ctx context.Context, in *resource.HealthCheckRequest, opts ...grpc.CallOption) (*resource.HealthCheckResponse, error) {
func (d *directResourceClient) IsHealthy(ctx context.Context, in *resourcepb.HealthCheckRequest, opts ...grpc.CallOption) (*resourcepb.HealthCheckResponse, error) {
return d.server.IsHealthy(ctx, in)
}
// List implements ResourceClient.
func (d *directResourceClient) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
func (d *directResourceClient) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) {
return d.server.List(ctx, in)
}
func (d *directResourceClient) ListManagedObjects(ctx context.Context, in *resource.ListManagedObjectsRequest, opts ...grpc.CallOption) (*resource.ListManagedObjectsResponse, error) {
func (d *directResourceClient) ListManagedObjects(ctx context.Context, in *resourcepb.ListManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.ListManagedObjectsResponse, error) {
return d.server.ListManagedObjects(ctx, in)
}
func (d *directResourceClient) CountManagedObjects(ctx context.Context, in *resource.CountManagedObjectsRequest, opts ...grpc.CallOption) (*resource.CountManagedObjectsResponse, error) {
func (d *directResourceClient) CountManagedObjects(ctx context.Context, in *resourcepb.CountManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.CountManagedObjectsResponse, error) {
return d.server.CountManagedObjects(ctx, in)
}
// PutBlob implements ResourceClient.
func (d *directResourceClient) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
func (d *directResourceClient) PutBlob(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption) (*resourcepb.PutBlobResponse, error) {
return d.server.PutBlob(ctx, in)
}
// Read implements ResourceClient.
func (d *directResourceClient) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) {
func (d *directResourceClient) Read(ctx context.Context, in *resourcepb.ReadRequest, opts ...grpc.CallOption) (*resourcepb.ReadResponse, error) {
return d.server.Read(ctx, in)
}
// Search implements ResourceClient.
func (d *directResourceClient) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
func (d *directResourceClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
return d.server.Search(ctx, in)
}
// Update implements ResourceClient.
func (d *directResourceClient) Update(ctx context.Context, in *resource.UpdateRequest, opts ...grpc.CallOption) (*resource.UpdateResponse, error) {
func (d *directResourceClient) Update(ctx context.Context, in *resourcepb.UpdateRequest, opts ...grpc.CallOption) (*resourcepb.UpdateResponse, error) {
return d.server.Update(ctx, in)
}
// Watch implements ResourceClient.
func (d *directResourceClient) Watch(ctx context.Context, in *resource.WatchRequest, opts ...grpc.CallOption) (resource.ResourceStore_WatchClient, error) {
func (d *directResourceClient) Watch(ctx context.Context, in *resourcepb.WatchRequest, opts ...grpc.CallOption) (resourcepb.ResourceStore_WatchClient, error) {
return nil, fmt.Errorf("watch not supported with direct resource client")
}
// BulkProcess implements resource.ResourceClient.
func (d *directResourceClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error) {
func (d *directResourceClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) {
return nil, fmt.Errorf("BulkProcess not supported with direct resource client")
}

@ -5,7 +5,8 @@ package legacy
import (
context "context"
resource "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
mock "github.com/stretchr/testify/mock"
)
@ -23,23 +24,23 @@ func (_m *MockLegacyMigrator) EXPECT() *MockLegacyMigrator_Expecter {
}
// Migrate provides a mock function with given fields: ctx, opts
func (_m *MockLegacyMigrator) Migrate(ctx context.Context, opts MigrateOptions) (*resource.BulkResponse, error) {
func (_m *MockLegacyMigrator) Migrate(ctx context.Context, opts MigrateOptions) (*resourcepb.BulkResponse, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for Migrate")
}
var r0 *resource.BulkResponse
var r0 *resourcepb.BulkResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, MigrateOptions) (*resource.BulkResponse, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, MigrateOptions) (*resourcepb.BulkResponse, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, MigrateOptions) *resource.BulkResponse); ok {
if rf, ok := ret.Get(0).(func(context.Context, MigrateOptions) *resourcepb.BulkResponse); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resource.BulkResponse)
r0 = ret.Get(0).(*resourcepb.BulkResponse)
}
}
@ -71,12 +72,12 @@ func (_c *MockLegacyMigrator_Migrate_Call) Run(run func(ctx context.Context, opt
return _c
}
func (_c *MockLegacyMigrator_Migrate_Call) Return(_a0 *resource.BulkResponse, _a1 error) *MockLegacyMigrator_Migrate_Call {
func (_c *MockLegacyMigrator_Migrate_Call) Return(_a0 *resourcepb.BulkResponse, _a1 error) *MockLegacyMigrator_Migrate_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockLegacyMigrator_Migrate_Call) RunAndReturn(run func(context.Context, MigrateOptions) (*resource.BulkResponse, error)) *MockLegacyMigrator_Migrate_Call {
func (_c *MockLegacyMigrator_Migrate_Call) RunAndReturn(run func(context.Context, MigrateOptions) (*resourcepb.BulkResponse, error)) *MockLegacyMigrator_Migrate_Call {
_c.Call.Return(run)
return _c
}

@ -11,6 +11,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
authlib "github.com/grafana/authlib/types"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
@ -21,13 +22,14 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type MigrateOptions struct {
Namespace string
Store resource.BulkStoreClient
Store resourcepb.BulkStoreClient
LargeObjects apistore.LargeObjectSupport
BlobStore resource.BlobStoreClient
BlobStore resourcepb.BlobStoreClient
Resources []schema.GroupResource
WithHistory bool // only applies to dashboards
OnlyCount bool // just count the values
@ -38,7 +40,7 @@ type MigrateOptions struct {
//
//go:generate mockery --name LegacyMigrator --structname MockLegacyMigrator --inpackage --filename legacy_migrator_mock.go --with-expecter
type LegacyMigrator interface {
Migrate(ctx context.Context, opts MigrateOptions) (*resource.BulkResponse, error)
Migrate(ctx context.Context, opts MigrateOptions) (*resourcepb.BulkResponse, error)
}
// This can migrate Folders, Dashboards and LibraryPanels
@ -56,9 +58,9 @@ type BlobStoreInfo struct {
}
// migrate function -- works for a single kind
type migrator = func(ctx context.Context, orgId int64, opts MigrateOptions, stream resource.BulkStore_BulkProcessClient) (*BlobStoreInfo, error)
type migrator = func(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error)
func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (*resource.BulkResponse, error) {
func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (*resourcepb.BulkResponse, error) {
info, err := authlib.ParseNamespace(opts.Namespace)
if err != nil {
return nil, err
@ -82,7 +84,7 @@ func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (
switch fmt.Sprintf("%s/%s", res.Group, res.Resource) {
case "folder.grafana.app/folders":
migrators = append(migrators, a.migrateFolders)
settings.Collection = append(settings.Collection, &resource.ResourceKey{
settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: folders.GROUP,
Resource: folders.RESOURCE,
@ -90,7 +92,7 @@ func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (
case "dashboard.grafana.app/librarypanels":
migrators = append(migrators, a.migratePanels)
settings.Collection = append(settings.Collection, &resource.ResourceKey{
settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: dashboard.GROUP,
Resource: dashboard.LIBRARY_PANEL_RESOURCE,
@ -98,7 +100,7 @@ func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (
case "dashboard.grafana.app/dashboards":
migrators = append(migrators, a.migrateDashboards)
settings.Collection = append(settings.Collection, &resource.ResourceKey{
settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -134,7 +136,7 @@ func (a *dashboardSqlAccess) Migrate(ctx context.Context, opts MigrateOptions) (
return stream.CloseAndRecv()
}
func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOptions) (*resource.BulkResponse, error) {
func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOptions) (*resourcepb.BulkResponse, error) {
sql, err := a.sql(ctx)
if err != nil {
return nil, err
@ -144,12 +146,12 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption
return nil, err
}
orgId := ns.OrgID
rsp := &resource.BulkResponse{}
rsp := &resourcepb.BulkResponse{}
err = sql.DB.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
for _, res := range opts.Resources {
switch fmt.Sprintf("%s/%s", res.Group, res.Resource) {
case "folder.grafana.app/folders":
summary := &resource.BulkResponse_Summary{}
summary := &resourcepb.BulkResponse_Summary{}
summary.Group = folders.GROUP
summary.Group = folders.RESOURCE
_, err = sess.SQL("SELECT COUNT(*) FROM "+sql.Table("dashboard")+
@ -157,7 +159,7 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption
rsp.Summary = append(rsp.Summary, summary)
case "dashboard.grafana.app/librarypanels":
summary := &resource.BulkResponse_Summary{}
summary := &resourcepb.BulkResponse_Summary{}
summary.Group = dashboard.GROUP
summary.Resource = dashboard.LIBRARY_PANEL_RESOURCE
_, err = sess.SQL("SELECT COUNT(*) FROM "+sql.Table("library_element")+
@ -165,7 +167,7 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption
rsp.Summary = append(rsp.Summary, summary)
case "dashboard.grafana.app/dashboards":
summary := &resource.BulkResponse_Summary{}
summary := &resourcepb.BulkResponse_Summary{}
summary.Group = dashboard.GROUP
summary.Resource = dashboard.DASHBOARD_RESOURCE
rsp.Summary = append(rsp.Summary, summary)
@ -177,7 +179,7 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption
}
// Also count history
_, err = sess.SQL(`SELECT COUNT(*)
_, err = sess.SQL(`SELECT COUNT(*)
FROM `+sql.Table("dashboard_version")+` as dv
JOIN `+sql.Table("dashboard")+` as dd
ON dd.id = dv.dashboard_id
@ -192,7 +194,7 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption
return rsp, nil
}
func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, opts MigrateOptions, stream resource.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
query := &DashboardQuery{
OrgID: orgId,
Limit: 100000000,
@ -234,8 +236,8 @@ func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64,
return blobs, err
}
req := &resource.BulkRequest{
Key: &resource.ResourceKey{
req := &resourcepb.BulkRequest{
Key: &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -243,12 +245,12 @@ func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64,
},
Value: body,
Folder: rows.row.FolderUID,
Action: resource.BulkRequest_ADDED,
Action: resourcepb.BulkRequest_ADDED,
}
if dash.Generation > 1 {
req.Action = resource.BulkRequest_MODIFIED
req.Action = resourcepb.BulkRequest_MODIFIED
} else if dash.Generation < 0 {
req.Action = resource.BulkRequest_DELETED
req.Action = resourcepb.BulkRequest_DELETED
}
// With large object support
@ -301,7 +303,7 @@ func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64,
return blobs, err
}
func (a *dashboardSqlAccess) migrateFolders(ctx context.Context, orgId int64, opts MigrateOptions, stream resource.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
func (a *dashboardSqlAccess) migrateFolders(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
query := &DashboardQuery{
OrgID: orgId,
Limit: 100000000,
@ -347,8 +349,8 @@ func (a *dashboardSqlAccess) migrateFolders(ctx context.Context, orgId int64, op
return nil, err
}
req := &resource.BulkRequest{
Key: &resource.ResourceKey{
req := &resourcepb.BulkRequest{
Key: &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: "folder.grafana.app",
Resource: "folders",
@ -356,12 +358,12 @@ func (a *dashboardSqlAccess) migrateFolders(ctx context.Context, orgId int64, op
},
Value: body,
Folder: rows.row.FolderUID,
Action: resource.BulkRequest_ADDED,
Action: resourcepb.BulkRequest_ADDED,
}
if dash.Generation > 1 {
req.Action = resource.BulkRequest_MODIFIED
req.Action = resourcepb.BulkRequest_MODIFIED
} else if dash.Generation < 0 {
req.Action = resource.BulkRequest_DELETED
req.Action = resourcepb.BulkRequest_DELETED
}
opts.Progress(i, fmt.Sprintf("[v:%d] %s (%d)", dash.Generation, dash.Name, len(req.Value)))
@ -383,7 +385,7 @@ func (a *dashboardSqlAccess) migrateFolders(ctx context.Context, orgId int64, op
return nil, err
}
func (a *dashboardSqlAccess) migratePanels(ctx context.Context, orgId int64, opts MigrateOptions, stream resource.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
func (a *dashboardSqlAccess) migratePanels(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) {
opts.Progress(-1, "migrating library panels...")
panels, err := a.GetLibraryPanels(ctx, LibraryPanelQuery{
OrgID: orgId,
@ -402,8 +404,8 @@ func (a *dashboardSqlAccess) migratePanels(ctx context.Context, orgId int64, opt
return nil, err
}
req := &resource.BulkRequest{
Key: &resource.ResourceKey{
req := &resourcepb.BulkRequest{
Key: &resourcepb.ResourceKey{
Namespace: opts.Namespace,
Group: dashboard.GROUP,
Resource: dashboard.LIBRARY_PANEL_RESOURCE,
@ -411,10 +413,10 @@ func (a *dashboardSqlAccess) migratePanels(ctx context.Context, orgId int64, opt
},
Value: body,
Folder: meta.GetFolder(),
Action: resource.BulkRequest_ADDED,
Action: resourcepb.BulkRequest_ADDED,
}
if panel.Generation > 1 {
req.Action = resource.BulkRequest_MODIFIED
req.Action = resourcepb.BulkRequest_MODIFIED
}
opts.Progress(i, fmt.Sprintf("[v:%d] %s (%d)", i, meta.GetName(), len(req.Value)))

@ -13,6 +13,8 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
@ -59,7 +61,7 @@ func getProvisioningDataFromEvent(event resource.WriteEvent) (*dashboards.Dashbo
return provisioning, nil
}
func isDashboardKey(key *resource.ResourceKey, requireName bool) error {
func isDashboardKey(key *resourcepb.ResourceKey, requireName bool) error {
gr := dashboard.DashboardResourceInfo.GroupResource()
if key.Group != gr.Group {
return fmt.Errorf("expecting dashboard group (%s != %s)", key.Group, gr.Group)
@ -83,13 +85,13 @@ func (a *dashboardSqlAccess) WriteEvent(ctx context.Context, event resource.Writ
}
switch event.Type {
case resource.WatchEvent_DELETED:
case resourcepb.WatchEvent_DELETED:
{
_, _, err = a.DeleteDashboard(ctx, info.OrgID, event.Key.Name)
//rv = ???
}
// The difference depends on embedded internal ID
case resource.WatchEvent_ADDED, resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_ADDED, resourcepb.WatchEvent_MODIFIED:
{
dash, err := getDashboardFromEvent(event)
if err != nil {
@ -119,7 +121,7 @@ func (a *dashboardSqlAccess) WriteEvent(ctx context.Context, event resource.Writ
rv = int64(after.Version)
}
} else {
failOnExisting := event.Type == resource.WatchEvent_ADDED
failOnExisting := event.Type == resourcepb.WatchEvent_ADDED
after, _, err := a.SaveDashboard(ctx, info.OrgID, dash, failOnExisting)
if err != nil {
return 0, err
@ -187,7 +189,7 @@ func (a *dashboardSqlAccess) GetDashboard(ctx context.Context, orgId int64, uid
}
// Read implements ResourceStoreServer.
func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.BackendReadResponse {
func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse {
rsp := &resource.BackendReadResponse{}
info, err := claims.ParseNamespace(req.Key.Namespace)
if err == nil {
@ -208,7 +210,7 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea
return rsp
}
if dash == nil {
rsp.Error = &resource.ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusNotFound,
}
} else {
@ -228,7 +230,7 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea
}
// List implements AppendingStore.
func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
if req.ResourceVersion != 0 {
return 0, apierrors.NewBadRequest("List with explicit resourceVersion is not supported with this storage backend")
}
@ -262,12 +264,12 @@ func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.Lis
}
switch req.Source {
case resource.ListRequest_HISTORY:
case resourcepb.ListRequest_HISTORY:
query.GetHistory = true
query.UID = req.Options.Key.Name
case resource.ListRequest_TRASH:
case resourcepb.ListRequest_TRASH:
query.GetTrash = true
case resource.ListRequest_STORE:
case resourcepb.ListRequest_STORE:
// normal
}
@ -321,23 +323,23 @@ func (a *dashboardSqlAccess) WatchWriteEvents(ctx context.Context) (<-chan *reso
}
// Simple wrapper for index implementation
func (a *dashboardSqlAccess) Read(ctx context.Context, req *resource.ReadRequest) (*resource.BackendReadResponse, error) {
func (a *dashboardSqlAccess) Read(ctx context.Context, req *resourcepb.ReadRequest) (*resource.BackendReadResponse, error) {
return a.ReadResource(ctx, req), nil
}
func (a *dashboardSqlAccess) Search(ctx context.Context, req *resource.ResourceSearchRequest) (*resource.ResourceSearchResponse, error) {
func (a *dashboardSqlAccess) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
return a.dashboardSearchClient.Search(ctx, req)
}
func (a *dashboardSqlAccess) ListManagedObjects(ctx context.Context, req *resource.ListManagedObjectsRequest) (*resource.ListManagedObjectsResponse, error) {
func (a *dashboardSqlAccess) ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
return nil, fmt.Errorf("not implemented")
}
func (a *dashboardSqlAccess) CountManagedObjects(context.Context, *resource.CountManagedObjectsRequest) (*resource.CountManagedObjectsResponse, error) {
func (a *dashboardSqlAccess) CountManagedObjects(context.Context, *resourcepb.CountManagedObjectsRequest) (*resourcepb.CountManagedObjectsResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// GetStats implements ResourceServer.
func (a *dashboardSqlAccess) GetStats(ctx context.Context, req *resource.ResourceStatsRequest) (*resource.ResourceStatsResponse, error) {
func (a *dashboardSqlAccess) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
return a.dashboardSearchClient.GetStats(ctx, req)
}

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestGetProvisioningDataFromEvent(t *testing.T) {
@ -86,7 +87,7 @@ func TestWriteProvisioningEvent(t *testing.T) {
dashBytes, err := json.Marshal(dashData)
require.NoError(t, err)
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: dashboard.DashboardResourceInfo.GroupResource().Group,
Resource: dashboard.DashboardResourceInfo.GroupResource().Resource,
Name: "test-dashboard",
@ -109,7 +110,7 @@ func TestWriteProvisioningEvent(t *testing.T) {
})
event := resource.WriteEvent{
Type: resource.WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Key: key,
Object: meta,
Value: dashBytes,

@ -6,6 +6,7 @@ import (
dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// This does not check if you have permissions!
@ -30,7 +31,7 @@ type DashboardQuery struct {
GetFolders bool
// The label requirements
Labels []*resource.Requirement
Labels []*resourcepb.Requirement
// DESC|ASC, how to order the IDs
Order string // asc required to use lastID, desc required for export with history
@ -52,7 +53,7 @@ type LibraryPanelQuery struct {
type DashboardAccess interface {
resource.StorageBackend
resource.ResourceIndexServer
resourcepb.ResourceIndexServer
LegacyMigrator
GetDashboard(ctx context.Context, orgId int64, uid string, version int64) (*dashboardV1.Dashboard, int64, error)

@ -11,6 +11,7 @@ import (
"k8s.io/apimachinery/pkg/selection"
claims "github.com/grafana/authlib/types"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@ -20,11 +21,12 @@ import (
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
)
type DashboardSearchClient struct {
resource.ResourceIndexClient
resourcepb.ResourceIndexClient
dashboardStore dashboards.Store
sorter sort.Service
}
@ -64,7 +66,7 @@ func ParseSortName(sortName string) (string, bool, error) {
}
// nolint:gocyclo
func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest, _ ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@ -147,17 +149,17 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
if err != nil {
return nil, err
}
list := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
Facet: map[string]*resource.ResourceSearchResponse_Facet{
list := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
Facet: map[string]*resourcepb.ResourceSearchResponse_Facet{
"tags": {
Terms: []*resource.ResourceSearchResponse_TermFacet{},
Terms: []*resourcepb.ResourceSearchResponse_TermFacet{},
},
},
}
for _, tag := range tags {
list.Facet["tags"].Terms = append(list.Facet["tags"].Terms, &resource.ResourceSearchResponse_TermFacet{
list.Facet["tags"].Terms = append(list.Facet["tags"].Terms, &resourcepb.ResourceSearchResponse_TermFacet{
Term: tag.Term,
Count: int64(tag.Count),
})
@ -230,7 +232,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
}
}
searchFields := resource.StandardSearchFields()
columns := []*resource.ResourceTableColumnDefinition{
columns := []*resourcepb.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
@ -238,14 +240,14 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
}
if sortByField != "" {
columns = append(columns, &resource.ResourceTableColumnDefinition{
columns = append(columns, &resourcepb.ResourceTableColumnDefinition{
Name: sortByField,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
})
}
list := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
list := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: columns,
},
}
@ -284,7 +286,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
cells = append(cells, []byte("0"))
}
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{
Key: getResourceKey(&dashboards.DashboardSearchProjection{
UID: dashboard.UID,
}, req.Options.Key.Namespace),
@ -321,7 +323,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
cells = append(cells, []byte(strconv.FormatInt(dashboard.SortMeta, 10)))
}
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{
Key: getResourceKey(dashboard, req.Options.Key.Namespace),
Cells: cells,
})
@ -332,9 +334,9 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
return list, nil
}
func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string) *resource.ResourceKey {
func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string) *resourcepb.ResourceKey {
if item.IsFolder {
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: namespace,
Group: folders.GROUP,
Resource: folders.RESOURCE,
@ -342,7 +344,7 @@ func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string
}
}
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: namespace,
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -379,7 +381,7 @@ func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards
return hitList
}
func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest, _ ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
info, err := claims.ParseNamespace(req.Namespace)
if err != nil {
return nil, fmt.Errorf("unable to read namespace")
@ -410,8 +412,8 @@ func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resource.Reso
return nil, err
}
return &resource.ResourceStatsResponse{
Stats: []*resource.ResourceStatsResponse_Stats{
return &resourcepb.ResourceStatsResponse{
Stats: []*resourcepb.ResourceStatsResponse_Stats{
{
Group: parts[0],
Resource: parts[1],

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
)
@ -31,7 +32,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
emptyTags, err := json.Marshal([]string{})
require.NoError(t, err)
dashboardKey := &resource.ResourceKey{
dashboardKey := &resourcepb.ResourceKey{
Name: "uid",
Resource: dashboard.DASHBOARD_RESOURCE,
}
@ -47,11 +48,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_TITLE,
},
@ -65,18 +66,18 @@ func TestDashboardSearchClient_Search(t *testing.T) {
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
require.Equal(t, &resourcepb.ResourceSearchResponse{
TotalHits: 2,
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
searchFields.Field(resource.SEARCH_FIELD_LEGACY_ID),
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -89,7 +90,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -123,11 +124,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed
Desc: false,
@ -138,22 +139,22 @@ func TestDashboardSearchClient_Search(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
require.Equal(t, &resourcepb.ResourceSearchResponse{
TotalHits: 1,
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
searchFields.Field(resource.SEARCH_FIELD_LEGACY_ID),
{
Name: "views_total",
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -188,11 +189,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
Desc: true,
@ -203,22 +204,22 @@ func TestDashboardSearchClient_Search(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
require.Equal(t, &resourcepb.ResourceSearchResponse{
TotalHits: 1,
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
searchFields.Field(resource.SEARCH_FIELD_LEGACY_ID),
{
Name: "errors_last_30_days",
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
@ -246,13 +247,13 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{Term: "tag2", Count: 5},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Facet: map[string]*resource.ResourceSearchRequest_Facet{
req := &resourcepb.ResourceSearchRequest{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"tags": {
Field: "tags",
},
},
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
}
@ -260,11 +261,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
Facet: map[string]*resource.ResourceSearchResponse_Facet{
require.Equal(t, &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
Facet: map[string]*resourcepb.ResourceSearchResponse_Facet{
"tags": {
Terms: []*resource.ResourceSearchResponse_TermFacet{
Terms: []*resourcepb.ResourceSearchResponse_TermFacet{
{
Term: "tag1",
Count: 1,
@ -292,8 +293,8 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
Query: "*test*",
@ -317,10 +318,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
Labels: []*resource.Requirement{
Labels: []*resourcepb.Requirement{
{
Key: utils.LabelKeyDeprecatedInternalID,
Operator: "in",
@ -350,10 +351,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
Fields: []*resource.Requirement{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_TAGS,
Operator: "in",
@ -390,10 +391,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
Fields: []*resource.Requirement{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_MANAGER_ID,
Operator: "in",
@ -423,10 +424,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
Fields: []*resource.Requirement{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_MANAGER_KIND,
Operator: "=",
@ -456,10 +457,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
Fields: []*resource.Requirement{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_MANAGER_KIND,
Operator: "=",
@ -493,11 +494,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_TITLE,
},
@ -515,11 +516,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", SortMeta: 100},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL,
},

@ -29,6 +29,7 @@ import (
foldermodel "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/util/errhttp"
)
@ -36,12 +37,12 @@ import (
// The DTO returns everything the UI needs in a single request
type SearchHandler struct {
log log.Logger
client resource.ResourceIndexClient
client resourcepb.ResourceIndexClient
tracer trace.Tracer
features featuremgmt.FeatureToggles
}
func NewSearchHandler(tracer trace.Tracer, dual dualwrite.Service, legacyDashboardSearcher resource.ResourceIndexClient, resourceClient resource.ResourceClient, features featuremgmt.FeatureToggles) *SearchHandler {
func NewSearchHandler(tracer trace.Tracer, dual dualwrite.Service, legacyDashboardSearcher resourcepb.ResourceIndexClient, resourceClient resource.ResourceClient, features featuremgmt.FeatureToggles) *SearchHandler {
searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), dashboardv0alpha1.DashboardResourceInfo.GroupResource(), resourceClient, legacyDashboardSearcher)
return &SearchHandler{
client: searchClient,
@ -237,8 +238,8 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
page, _ = strconv.Atoi(queryParams.Get("page"))
}
searchRequest := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{},
searchRequest := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{},
Query: queryParams.Get("query"),
Limit: int64(limit),
Offset: int64(offset),
@ -257,7 +258,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
searchRequest.Fields = fields
types := queryParams["type"]
var federate *resource.ResourceKey
var federate *resourcepb.ResourceKey
switch len(types) {
case 0:
// When no type specified, search for dashboards
@ -281,7 +282,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
return
}
if federate != nil {
searchRequest.Federated = []*resource.ResourceKey{federate}
searchRequest.Federated = []*resourcepb.ResourceKey{federate}
}
// Add sorting
@ -290,7 +291,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
if slices.Contains(search.DashboardFields(), sort) {
sort = resource.SEARCH_FIELD_PREFIX + sort
}
s := &resource.ResourceSearchRequest_Sort{Field: sort}
s := &resourcepb.ResourceSearchRequest_Sort{Field: sort}
if strings.HasPrefix(sort, "-") {
s.Desc = true
s.Field = s.Field[1:]
@ -301,9 +302,9 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
// The facet term fields
if facets, ok := queryParams["facet"]; ok {
searchRequest.Facet = make(map[string]*resource.ResourceSearchRequest_Facet)
searchRequest.Facet = make(map[string]*resourcepb.ResourceSearchRequest_Facet)
for _, v := range facets {
searchRequest.Facet[v] = &resource.ResourceSearchRequest_Facet{
searchRequest.Facet[v] = &resourcepb.ResourceSearchRequest_Facet{
Field: v,
Limit: 50,
}
@ -312,7 +313,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
// The tags filter
if tags, ok := queryParams["tag"]; ok {
searchRequest.Options.Fields = []*resource.Requirement{{
searchRequest.Options.Fields = []*resourcepb.Requirement{{
Key: "tags",
Operator: "=",
Values: tags,
@ -343,7 +344,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
if folder == rootFolder {
folder = "" // root folder is empty in the search index
}
searchRequest.Options.Fields = []*resource.Requirement{{
searchRequest.Options.Fields = []*resourcepb.Requirement{{
Key: "folder",
Operator: "=",
Values: []string{folder},
@ -352,9 +353,9 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
if len(names) > 0 {
if searchRequest.Options.Fields == nil {
searchRequest.Options.Fields = []*resource.Requirement{}
searchRequest.Options.Fields = []*resourcepb.Requirement{}
}
namesFilter := []*resource.Requirement{{
namesFilter := []*resourcepb.Requirement{{
Key: "name",
Operator: "in",
Values: names,
@ -395,7 +396,7 @@ func (s *SearchHandler) write(w http.ResponseWriter, obj any) {
}
// Given a namespace and type convert it to a search key
func asResourceKey(ns string, k string) (*resource.ResourceKey, error) {
func asResourceKey(ns string, k string) (*resourcepb.ResourceKey, error) {
key, err := resource.AsResourceKey(ns, k)
if err != nil {
return nil, apierrors.NewBadRequest(err.Error())
@ -433,12 +434,12 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
return sharedDashboards, err
}
dashboardSearchRequest := &resource.ResourceSearchRequest{
dashboardSearchRequest := &resourcepb.ResourceSearchRequest{
Fields: []string{"folder"},
Limit: int64(len(dashboardUids)),
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Key: key,
Fields: []*resource.Requirement{{
Fields: []*resourcepb.Requirement{{
Key: "name",
Operator: "in",
Values: dashboardUids,
@ -477,12 +478,12 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
return sharedDashboards, err
}
folderSearchRequest := &resource.ResourceSearchRequest{
folderSearchRequest := &resourcepb.ResourceSearchRequest{
Fields: []string{"folder"},
Limit: int64(len(allFolders)),
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Key: folderKey,
Fields: []*resource.Requirement{{
Fields: []*resourcepb.Requirement{{
Key: "name",
Operator: "in",
Values: allFolders,

@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestSearchFallback(t *testing.T) {
@ -277,10 +278,10 @@ func TestSearchHandler(t *testing.T) {
})
t.Run("Sort - default sort by resource", func(t *testing.T) {
rows := make([]*resource.ResourceTableRow, len(mockResults))
rows := make([]*resourcepb.ResourceTableRow, len(mockResults))
for i, r := range mockResults {
rows[i] = &resource.ResourceTableRow{
Key: &resource.ResourceKey{
rows[i] = &resourcepb.ResourceTableRow{
Key: &resourcepb.ResourceKey{
Name: r.Name,
Resource: r.Resource,
},
@ -290,9 +291,9 @@ func TestSearchHandler(t *testing.T) {
}
}
mockResponse := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: resource.SEARCH_FIELD_TITLE},
},
Rows: rows,
@ -300,7 +301,7 @@ func TestSearchHandler(t *testing.T) {
}
// Create a mock client
mockClient := &MockClient{
MockResponses: []*resource.ResourceSearchResponse{mockResponse},
MockResponses: []*resourcepb.ResourceSearchResponse{mockResponse},
}
features := featuremgmt.WithFeatures()
@ -396,23 +397,23 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
t.Run("should return empty result if user has access to folder of all shared dashboards", func(t *testing.T) {
// dashboardSearchRequest
mockResponse1 := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse1 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinroot",
Resource: "dashboard",
},
Cells: [][]byte{[]byte("")}, // root folder doesn't have uid
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinpublicfolder",
Resource: "dashboard",
},
@ -425,16 +426,16 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
}
// folderSearchRequest
mockResponse2 := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse2 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "publicfolder",
Resource: "folder",
},
@ -447,7 +448,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
}
mockClient := &MockClient{
MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2},
MockResponses: []*resourcepb.ResourceSearchResponse{mockResponse1, mockResponse2},
}
features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering)
@ -486,23 +487,23 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
t.Run("should return the dashboards shared with the user", func(t *testing.T) {
// dashboardSearchRequest
mockResponse1 := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse1 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinroot",
Resource: "dashboard",
},
Cells: [][]byte{[]byte("")}, // root folder doesn't have uid
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinprivatefolder",
Resource: "dashboard",
},
@ -511,7 +512,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinpublicfolder",
Resource: "dashboard",
},
@ -524,16 +525,16 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
}
// folderSearchRequest
mockResponse2 := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse2 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "publicfolder",
Resource: "folder",
},
@ -545,16 +546,16 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
},
}
mockResponse3 := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
mockResponse3 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "dashboardinprivatefolder",
Resource: "dashboard",
},
@ -567,7 +568,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
}
mockClient := &MockClient{
MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2, mockResponse3},
MockResponses: []*resourcepb.ResourceSearchResponse{mockResponse1, mockResponse2, mockResponse3},
}
features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering)
@ -618,14 +619,14 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
// MockClient implements the ResourceIndexClient interface for testing
type MockClient struct {
resource.ResourceIndexClient
resourcepb.ResourceIndexClient
resource.ResourceIndex
// Capture the last SearchRequest for assertions
LastSearchRequest *resource.ResourceSearchRequest
LastSearchRequest *resourcepb.ResourceSearchRequest
MockResponses []*resource.ResourceSearchResponse
MockCalls []*resource.ResourceSearchRequest
MockResponses []*resourcepb.ResourceSearchResponse
MockCalls []*resourcepb.ResourceSearchRequest
CallCount int
}
@ -658,11 +659,11 @@ var mockResults = []MockResult{
},
}
func (m *MockClient) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
func (m *MockClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
m.LastSearchRequest = in
m.MockCalls = append(m.MockCalls, in)
var response *resource.ResourceSearchResponse
var response *resourcepb.ResourceSearchResponse
if m.CallCount < len(m.MockResponses) {
response = m.MockResponses[m.CallCount]
}
@ -671,42 +672,42 @@ func (m *MockClient) Search(ctx context.Context, in *resource.ResourceSearchRequ
return response, nil
}
func (m *MockClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (m *MockClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
return nil, nil
}
func (m *MockClient) CountManagedObjects(ctx context.Context, in *resource.CountManagedObjectsRequest, opts ...grpc.CallOption) (*resource.CountManagedObjectsResponse, error) {
func (m *MockClient) CountManagedObjects(ctx context.Context, in *resourcepb.CountManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.CountManagedObjectsResponse, error) {
return nil, nil
}
func (m *MockClient) Watch(ctx context.Context, in *resource.WatchRequest, opts ...grpc.CallOption) (resource.ResourceStore_WatchClient, error) {
func (m *MockClient) Watch(ctx context.Context, in *resourcepb.WatchRequest, opts ...grpc.CallOption) (resourcepb.ResourceStore_WatchClient, error) {
return nil, nil
}
func (m *MockClient) Delete(ctx context.Context, in *resource.DeleteRequest, opts ...grpc.CallOption) (*resource.DeleteResponse, error) {
func (m *MockClient) Delete(ctx context.Context, in *resourcepb.DeleteRequest, opts ...grpc.CallOption) (*resourcepb.DeleteResponse, error) {
return nil, nil
}
func (m *MockClient) Create(ctx context.Context, in *resource.CreateRequest, opts ...grpc.CallOption) (*resource.CreateResponse, error) {
func (m *MockClient) Create(ctx context.Context, in *resourcepb.CreateRequest, opts ...grpc.CallOption) (*resourcepb.CreateResponse, error) {
return nil, nil
}
func (m *MockClient) Update(ctx context.Context, in *resource.UpdateRequest, opts ...grpc.CallOption) (*resource.UpdateResponse, error) {
func (m *MockClient) Update(ctx context.Context, in *resourcepb.UpdateRequest, opts ...grpc.CallOption) (*resourcepb.UpdateResponse, error) {
return nil, nil
}
func (m *MockClient) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) {
func (m *MockClient) Read(ctx context.Context, in *resourcepb.ReadRequest, opts ...grpc.CallOption) (*resourcepb.ReadResponse, error) {
return nil, nil
}
func (m *MockClient) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) {
func (m *MockClient) GetBlob(ctx context.Context, in *resourcepb.GetBlobRequest, opts ...grpc.CallOption) (*resourcepb.GetBlobResponse, error) {
return nil, nil
}
func (m *MockClient) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
func (m *MockClient) PutBlob(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption) (*resourcepb.PutBlobResponse, error) {
return nil, nil
}
func (m *MockClient) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
func (m *MockClient) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) {
return nil, nil
}
func (m *MockClient) ListManagedObjects(ctx context.Context, in *resource.ListManagedObjectsRequest, opts ...grpc.CallOption) (*resource.ListManagedObjectsResponse, error) {
func (m *MockClient) ListManagedObjects(ctx context.Context, in *resourcepb.ListManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.ListManagedObjectsResponse, error) {
return nil, nil
}
func (m *MockClient) IsHealthy(ctx context.Context, in *resource.HealthCheckRequest, opts ...grpc.CallOption) (*resource.HealthCheckResponse, error) {
func (m *MockClient) IsHealthy(ctx context.Context, in *resourcepb.HealthCheckRequest, opts ...grpc.CallOption) (*resourcepb.HealthCheckResponse, error) {
return nil, nil
}
func (m *MockClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error) {
func (m *MockClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) {
return nil, nil
}

@ -11,6 +11,7 @@ import (
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
@ -21,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type dtoBuilder = func(dashboard runtime.Object, access *dashboard.DashboardAccess) (runtime.Object, error)
@ -109,7 +111,7 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob
blobInfo := obj.GetBlob()
if blobInfo != nil && r.largeObjects != nil {
gr := r.largeObjects.GroupResource()
err = r.largeObjects.Reconstruct(ctx, &resource.ResourceKey{
err = r.largeObjects.Reconstruct(ctx, &resourcepb.ResourceKey{
Group: gr.Group,
Resource: gr.Resource,
Namespace: obj.GetNamespace(),

@ -3,7 +3,8 @@ package folders
import (
"context"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
@ -19,7 +20,7 @@ type storageMock struct {
type searcherMock struct {
*mock.Mock
resource.ResourceIndexClient
resourcepb.ResourceIndexClient
}
func (m storageMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
@ -75,7 +76,7 @@ func (m storageMock) Update(ctx context.Context, name string, objInfo rest.Updat
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
}
func (s searcherMock) GetStats(ctx context.Context, req *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (s searcherMock) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest, _ ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
args := s.Called(ctx, req)
return args.Get(0).(*resource.ResourceStatsResponse), args.Error(1)
return args.Get(0).(*resourcepb.ResourceStatsResponse), args.Error(1)
}

@ -14,10 +14,11 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
authtypes "github.com/grafana/authlib/types"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
@ -32,6 +33,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var _ builder.APIGroupBuilder = (*FolderAPIBuilder)(nil)
@ -54,7 +56,7 @@ type FolderAPIBuilder struct {
authorizer authorizer.Authorizer
searcher resource.ResourceIndexClient
searcher resourcepb.ResourceIndexClient
cfg *setting.Cfg
ignoreLegacy bool // skip legacy storage and only use unified storage
}
@ -260,7 +262,7 @@ func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes,
}
func (b *FolderAPIBuilder) validateOnDelete(ctx context.Context, f *folders.Folder) error {
resp, err := b.searcher.GetStats(ctx, &resource.ResourceStatsRequest{Namespace: f.Namespace, Folder: f.Name})
resp, err := b.searcher.GetStats(ctx, &resourcepb.ResourceStatsRequest{Namespace: f.Namespace, Folder: f.Name})
if err != nil {
return err
}

@ -17,7 +17,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestFolderAPIBuilder_Validate_Create(t *testing.T) {
@ -169,16 +169,16 @@ func TestFolderAPIBuilder_Validate_Create(t *testing.T) {
func TestFolderAPIBuilder_Validate_Delete(t *testing.T) {
tests := []struct {
name string
statsResponse *resource.ResourceStatsResponse_Stats
statsResponse *resourcepb.ResourceStatsResponse_Stats
wantErr bool
}{
{
name: "should allow deletion when folder is empty",
statsResponse: &resource.ResourceStatsResponse_Stats{Count: 0},
statsResponse: &resourcepb.ResourceStatsResponse_Stats{Count: 0},
},
{
name: "should return folder not empty when the folder is not empty",
statsResponse: &resource.ResourceStatsResponse_Stats{Count: 2},
statsResponse: &resourcepb.ResourceStatsResponse_Stats{Count: 2},
wantErr: true,
},
}
@ -200,9 +200,9 @@ func TestFolderAPIBuilder_Validate_Delete(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var setupFn = func(m *mock.Mock, stats *resource.ResourceStatsResponse_Stats) {
m.On("GetStats", mock.Anything, &resource.ResourceStatsRequest{Namespace: obj.Namespace, Folder: obj.Name}).Return(
&resource.ResourceStatsResponse{Stats: []*resource.ResourceStatsResponse_Stats{stats}},
var setupFn = func(m *mock.Mock, stats *resourcepb.ResourceStatsResponse_Stats) {
m.On("GetStats", mock.Anything, &resourcepb.ResourceStatsRequest{Namespace: obj.Namespace, Folder: obj.Name}).Return(
&resourcepb.ResourceStatsResponse{Stats: []*resourcepb.ResourceStatsResponse_Stats{stats}},
nil,
).Once()
}

@ -9,11 +9,11 @@ import (
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type subCountREST struct {
searcher resource.ResourceIndexClient
searcher resourcepb.ResourceIndexClient
}
var (
@ -44,7 +44,7 @@ func (r *subCountREST) NewConnectOptions() (runtime.Object, bool, string) {
return nil, false, "" // true means you can use the trailing path as a variable
}
func (r *subCountREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
func (r *subCountREST) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
@ -52,7 +52,7 @@ func (r *subCountREST) Connect(ctx context.Context, name string, opts runtime.Ob
return
}
stats, err := r.searcher.GetStats(ctx, &resource.ResourceStatsRequest{
stats, err := r.searcher.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: ns.Value,
Folder: name,
})

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature"
"github.com/grafana/grafana/pkg/storage/unified/parquet"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var _ resource.BulkResourceWriter = (*legacyResourceResourceMigrator)(nil)
@ -159,12 +160,12 @@ func (r *legacyResourceResourceMigrator) Close() error {
}
// CloseWithResults implements resource.BulkResourceWriter.
func (r *legacyResourceResourceMigrator) CloseWithResults() (*resource.BulkResponse, error) {
return &resource.BulkResponse{}, nil
func (r *legacyResourceResourceMigrator) CloseWithResults() (*resourcepb.BulkResponse, error) {
return &resourcepb.BulkResponse{}, nil
}
// Write implements resource.BulkResourceWriter.
func (r *legacyResourceResourceMigrator) Write(ctx context.Context, key *resource.ResourceKey, value []byte) error {
func (r *legacyResourceResourceMigrator) Write(ctx context.Context, key *resourcepb.ResourceKey, value []byte) error {
// Reuse the same parse+cleanup logic
parsed, err := r.parser.Parse(ctx, &repository.FileInfo{
Path: "", // empty path to ignore file system

@ -21,7 +21,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestLegacyResourcesMigrator_Migrate(t *testing.T) {
@ -96,7 +96,7 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, errors.New("legacy migrator error"))
})).Return(&resourcepb.BulkResponse{}, errors.New("legacy migrator error"))
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -311,11 +311,11 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Count phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Count phase
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return !opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{
Summary: []*resource.BulkResponse_Summary{
})).Return(&resourcepb.BulkResponse{
Summary: []*resourcepb.BulkResponse_Summary{
{
Group: "test.grafana.app",
Resource: "tests",
@ -390,7 +390,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
signature.NewGrafanaSigner(),
)
err := migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err := migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.Error(t, err)
require.Contains(t, err.Error(), "unmarshal unstructured")
@ -440,7 +440,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
signature.NewGrafanaSigner(),
)
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.NoError(t, err) // Error is recorded but not returned
mockParser.AssertExpectations(t)
@ -483,7 +483,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
mockSigner,
)
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.Error(t, err)
require.EqualError(t, err, "add author signature: signing error")
@ -543,7 +543,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
mockSigner,
)
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.NoError(t, err)
mockParser.AssertExpectations(t)
@ -592,7 +592,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
)
writeResourceFileFromObject.Return("aaaa.json", nil)
err := migrator.Write(context.Background(), &resource.ResourceKey{}, []byte(""))
err := migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte(""))
require.NoError(t, err)
require.Equal(t, "aaaa.json", migrator.history["test"], "kept track of the old files")
@ -601,7 +601,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
mockRepo.On("Delete", mock.Anything, "aaaa.json", "", "moved to: bbbb.json").
Return(nil).Once()
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte(""))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte(""))
require.NoError(t, err)
require.Equal(t, "bbbb.json", migrator.history["test"], "kept track of the old files")
@ -685,7 +685,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
signature.NewGrafanaSigner(),
)
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.NoError(t, err)
mockParser.AssertExpectations(t)
@ -731,7 +731,7 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) {
signature.NewGrafanaSigner(),
)
err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("test"))
err = migrator.Write(context.Background(), &resourcepb.ResourceKey{}, []byte("test"))
require.EqualError(t, err, "too many errors")
mockParser.AssertExpectations(t)
@ -745,7 +745,7 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, errors.New("count error"))
})).Return(&resourcepb.BulkResponse{}, errors.New("count error"))
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -774,10 +774,10 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Count phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Count phase
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return !opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, errors.New("write error")).Once() // Write phase
})).Return(&resourcepb.BulkResponse{}, errors.New("write error")).Once() // Write phase
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -806,10 +806,10 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Count phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Count phase
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return !opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Write phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Write phase
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -836,8 +836,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{
Summary: []*resource.BulkResponse_Summary{
})).Return(&resourcepb.BulkResponse{
Summary: []*resourcepb.BulkResponse_Summary{
{
Group: "test.grafana.app",
Resource: "tests",
@ -848,7 +848,7 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
}, nil).Once() // Count phase
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return !opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Write phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Write phase
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -876,8 +876,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
mockLegacyMigrator := legacy.NewMockLegacyMigrator(t)
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{
Summary: []*resource.BulkResponse_Summary{
})).Return(&resourcepb.BulkResponse{
Summary: []*resourcepb.BulkResponse_Summary{
{
Group: "test.grafana.app",
Resource: "tests",
@ -888,7 +888,7 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) {
}, nil).Once() // Count phase
mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool {
return !opts.OnlyCount && opts.Namespace == "test-namespace"
})).Return(&resource.BulkResponse{}, nil).Once() // Write phase
})).Return(&resourcepb.BulkResponse{}, nil).Once() // Write phase
progress := jobs.NewMockJobProgressRecorder(t)
progress.On("SetMessage", mock.Anything, mock.Anything).Return()
@ -930,7 +930,7 @@ func TestLegacyResourceResourceMigrator_CloseWithResults(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, response)
require.IsType(t, &resource.BulkResponse{}, response)
require.IsType(t, &resourcepb.BulkResponse{}, response)
require.Empty(t, response.Summary)
})
}

@ -7,8 +7,8 @@ import (
mock "github.com/stretchr/testify/mock"
metadata "google.golang.org/grpc/metadata"
resource "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// BulkStore_BulkProcessClient is an autogenerated mock type for the BulkStore_BulkProcessClient type
@ -25,23 +25,23 @@ func (_m *BulkStore_BulkProcessClient) EXPECT() *BulkStore_BulkProcessClient_Exp
}
// CloseAndRecv provides a mock function with no fields
func (_m *BulkStore_BulkProcessClient) CloseAndRecv() (*resource.BulkResponse, error) {
func (_m *BulkStore_BulkProcessClient) CloseAndRecv() (*resourcepb.BulkResponse, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for CloseAndRecv")
}
var r0 *resource.BulkResponse
var r0 *resourcepb.BulkResponse
var r1 error
if rf, ok := ret.Get(0).(func() (*resource.BulkResponse, error)); ok {
if rf, ok := ret.Get(0).(func() (*resourcepb.BulkResponse, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *resource.BulkResponse); ok {
if rf, ok := ret.Get(0).(func() *resourcepb.BulkResponse); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resource.BulkResponse)
r0 = ret.Get(0).(*resourcepb.BulkResponse)
}
}
@ -71,12 +71,12 @@ func (_c *BulkStore_BulkProcessClient_CloseAndRecv_Call) Run(run func()) *BulkSt
return _c
}
func (_c *BulkStore_BulkProcessClient_CloseAndRecv_Call) Return(_a0 *resource.BulkResponse, _a1 error) *BulkStore_BulkProcessClient_CloseAndRecv_Call {
func (_c *BulkStore_BulkProcessClient_CloseAndRecv_Call) Return(_a0 *resourcepb.BulkResponse, _a1 error) *BulkStore_BulkProcessClient_CloseAndRecv_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *BulkStore_BulkProcessClient_CloseAndRecv_Call) RunAndReturn(run func() (*resource.BulkResponse, error)) *BulkStore_BulkProcessClient_CloseAndRecv_Call {
func (_c *BulkStore_BulkProcessClient_CloseAndRecv_Call) RunAndReturn(run func() (*resourcepb.BulkResponse, error)) *BulkStore_BulkProcessClient_CloseAndRecv_Call {
_c.Call.Return(run)
return _c
}
@ -277,7 +277,7 @@ func (_c *BulkStore_BulkProcessClient_RecvMsg_Call) RunAndReturn(run func(interf
}
// Send provides a mock function with given fields: _a0
func (_m *BulkStore_BulkProcessClient) Send(_a0 *resource.BulkRequest) error {
func (_m *BulkStore_BulkProcessClient) Send(_a0 *resourcepb.BulkRequest) error {
ret := _m.Called(_a0)
if len(ret) == 0 {
@ -285,7 +285,7 @@ func (_m *BulkStore_BulkProcessClient) Send(_a0 *resource.BulkRequest) error {
}
var r0 error
if rf, ok := ret.Get(0).(func(*resource.BulkRequest) error); ok {
if rf, ok := ret.Get(0).(func(*resourcepb.BulkRequest) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
@ -305,9 +305,9 @@ func (_e *BulkStore_BulkProcessClient_Expecter) Send(_a0 interface{}) *BulkStore
return &BulkStore_BulkProcessClient_Send_Call{Call: _e.mock.On("Send", _a0)}
}
func (_c *BulkStore_BulkProcessClient_Send_Call) Run(run func(_a0 *resource.BulkRequest)) *BulkStore_BulkProcessClient_Send_Call {
func (_c *BulkStore_BulkProcessClient_Send_Call) Run(run func(_a0 *resourcepb.BulkRequest)) *BulkStore_BulkProcessClient_Send_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*resource.BulkRequest))
run(args[0].(*resourcepb.BulkRequest))
})
return _c
}
@ -317,7 +317,7 @@ func (_c *BulkStore_BulkProcessClient_Send_Call) Return(_a0 error) *BulkStore_Bu
return _c
}
func (_c *BulkStore_BulkProcessClient_Send_Call) RunAndReturn(run func(*resource.BulkRequest) error) *BulkStore_BulkProcessClient_Send_Call {
func (_c *BulkStore_BulkProcessClient_Send_Call) RunAndReturn(run func(*resourcepb.BulkRequest) error) *BulkStore_BulkProcessClient_Send_Call {
_c.Call.Return(run)
return _c
}

@ -9,7 +9,7 @@ import (
mock "github.com/stretchr/testify/mock"
resource "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// MockBulkStoreClient is an autogenerated mock type for the BulkStoreClient type
@ -26,7 +26,7 @@ func (_m *MockBulkStoreClient) EXPECT() *MockBulkStoreClient_Expecter {
}
// BulkProcess provides a mock function with given fields: ctx, opts
func (_m *MockBulkStoreClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error) {
func (_m *MockBulkStoreClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
@ -40,16 +40,16 @@ func (_m *MockBulkStoreClient) BulkProcess(ctx context.Context, opts ...grpc.Cal
panic("no return value specified for BulkProcess")
}
var r0 resource.BulkStore_BulkProcessClient
var r0 resourcepb.BulkStore_BulkProcessClient
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error)); ok {
return rf(ctx, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) resource.BulkStore_BulkProcessClient); ok {
if rf, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) resourcepb.BulkStore_BulkProcessClient); ok {
r0 = rf(ctx, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(resource.BulkStore_BulkProcessClient)
r0 = ret.Get(0).(resourcepb.BulkStore_BulkProcessClient)
}
}
@ -88,12 +88,12 @@ func (_c *MockBulkStoreClient_BulkProcess_Call) Run(run func(ctx context.Context
return _c
}
func (_c *MockBulkStoreClient_BulkProcess_Call) Return(_a0 resource.BulkStore_BulkProcessClient, _a1 error) *MockBulkStoreClient_BulkProcess_Call {
func (_c *MockBulkStoreClient_BulkProcess_Call) Return(_a0 resourcepb.BulkStore_BulkProcessClient, _a1 error) *MockBulkStoreClient_BulkProcess_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBulkStoreClient_BulkProcess_Call) RunAndReturn(run func(context.Context, ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error)) *MockBulkStoreClient_BulkProcess_Call {
func (_c *MockBulkStoreClient_BulkProcess_Call) RunAndReturn(run func(context.Context, ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error)) *MockBulkStoreClient_BulkProcess_Call {
_c.Call.Return(run)
return _c
}

@ -9,15 +9,17 @@ import (
"google.golang.org/grpc/metadata"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
//go:generate mockery --name BulkStoreClient --structname MockBulkStoreClient --inpackage --filename mock_bulk_store_client.go --with-expecter
//go:generate mockery --name=BulkStore_BulkProcessClient --srcpkg=github.com/grafana/grafana/pkg/storage/unified/resource --output=. --outpkg=migrate --filename=mock_bulk_process_client.go --with-expecter
type BulkStoreClient interface {
BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error)
BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error)
}
//go:generate mockery --name StorageSwapper --structname MockStorageSwapper --inpackage --filename mock_storage_swapper.go --with-expecter
@ -69,7 +71,7 @@ func (s *storageSwapper) WipeUnifiedAndSetMigratedFlag(ctx context.Context, name
}
settings := resource.BulkSettings{
RebuildCollection: true, // wipes everything in the collection
Collection: []*resource.ResourceKey{{
Collection: []*resourcepb.ResourceKey{{
Namespace: namespace,
Group: gr.Group,
Resource: gr.Resource,

@ -12,6 +12,8 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"google.golang.org/grpc/metadata"
)
@ -115,7 +117,7 @@ func TestStorageSwapper_WipeUnifiedAndSetMigratedFlag(t *testing.T) {
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
mockStream := NewBulkStore_BulkProcessClient(t)
mockStream.On("CloseAndRecv").Return(&resource.BulkResponse{}, nil)
mockStream.On("CloseAndRecv").Return(&resourcepb.BulkResponse{}, nil)
bulk.On("BulkProcess", mock.Anything, mock.Anything).Return(mockStream, nil)
dual.On("Update", mock.Anything, mock.MatchedBy(func(status dualwrite.StorageStatus) bool {
@ -143,7 +145,7 @@ func TestStorageSwapper_WipeUnifiedAndSetMigratedFlag(t *testing.T) {
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
mockStream := NewBulkStore_BulkProcessClient(t)
mockStream.On("CloseAndRecv").Return(&resource.BulkResponse{}, nil)
mockStream.On("CloseAndRecv").Return(&resourcepb.BulkResponse{}, nil)
bulk.On("BulkProcess", mock.MatchedBy(func(ctx context.Context) bool {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Get repository stats
@ -24,15 +25,15 @@ type ResourceLister interface {
}
type ResourceListerFromSearch struct {
managed resource.ManagedObjectIndexClient
index resource.ResourceIndexClient
managed resourcepb.ManagedObjectIndexClient
index resourcepb.ResourceIndexClient
legacyMigrator legacy.LegacyMigrator
storageStatus dualwrite.Service
}
func NewResourceLister(
managed resource.ManagedObjectIndexClient,
index resource.ResourceIndexClient,
managed resourcepb.ManagedObjectIndexClient,
index resourcepb.ResourceIndexClient,
legacyMigrator legacy.LegacyMigrator,
storageStatus dualwrite.Service,
) ResourceLister {
@ -46,7 +47,7 @@ func NewResourceLister(
// List implements ResourceLister.
func (o *ResourceListerFromSearch) List(ctx context.Context, namespace, repository string) (*provisioning.ResourceList, error) {
objects, err := o.managed.ListManagedObjects(ctx, &resource.ListManagedObjectsRequest{
objects, err := o.managed.ListManagedObjects(ctx, &resourcepb.ListManagedObjectsRequest{
Namespace: namespace,
Kind: string(utils.ManagerKindRepo),
Id: repository,
@ -76,7 +77,7 @@ func (o *ResourceListerFromSearch) List(ctx context.Context, namespace, reposito
// Stats implements ResourceLister.
func (o *ResourceListerFromSearch) Stats(ctx context.Context, namespace, repository string) (*provisioning.ResourceStats, error) {
req := &resource.CountManagedObjectsRequest{
req := &resourcepb.CountManagedObjectsRequest{
Namespace: namespace,
}
if repository != "" {
@ -150,7 +151,7 @@ func (o *ResourceListerFromSearch) Stats(ctx context.Context, namespace, reposit
}
// Get full instance stats
info, err := o.index.GetStats(ctx, &resource.ResourceStatsRequest{
info, err := o.index.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: namespace,
})
if err != nil {

@ -6,7 +6,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func (b *APIBuilder) collectProvisioningStats(ctx context.Context) (map[string]any, error) {
@ -22,7 +22,7 @@ func (b *APIBuilder) collectProvisioningStats(ctx context.Context) (map[string]a
// We could get namespaces from the list of repos below, but that could be zero
// while we still have resources managed by terraform, etc
ns := "default"
count, err := b.unified.CountManagedObjects(ctx, &resource.CountManagedObjectsRequest{
count, err := b.unified.CountManagedObjects(ctx, &resourcepb.CountManagedObjectsRequest{
Namespace: ns,
})
if err != nil {

@ -9,7 +9,7 @@ import (
mock "github.com/stretchr/testify/mock"
resource "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// MockBlobStoreClient is an autogenerated mock type for the BlobStoreClient type
@ -26,7 +26,7 @@ func (_m *MockBlobStoreClient) EXPECT() *MockBlobStoreClient_Expecter {
}
// PutBlob provides a mock function with given fields: ctx, in, opts
func (_m *MockBlobStoreClient) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
func (_m *MockBlobStoreClient) PutBlob(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption) (*resourcepb.PutBlobResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
@ -40,20 +40,20 @@ func (_m *MockBlobStoreClient) PutBlob(ctx context.Context, in *resource.PutBlob
panic("no return value specified for PutBlob")
}
var r0 *resource.PutBlobResponse
var r0 *resourcepb.PutBlobResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *resource.PutBlobRequest, ...grpc.CallOption) (*resource.PutBlobResponse, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, *resourcepb.PutBlobRequest, ...grpc.CallOption) (*resourcepb.PutBlobResponse, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *resource.PutBlobRequest, ...grpc.CallOption) *resource.PutBlobResponse); ok {
if rf, ok := ret.Get(0).(func(context.Context, *resourcepb.PutBlobRequest, ...grpc.CallOption) *resourcepb.PutBlobResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resource.PutBlobResponse)
r0 = ret.Get(0).(*resourcepb.PutBlobResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *resource.PutBlobRequest, ...grpc.CallOption) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, *resourcepb.PutBlobRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
@ -76,7 +76,7 @@ func (_e *MockBlobStoreClient_Expecter) PutBlob(ctx interface{}, in interface{},
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *MockBlobStoreClient_PutBlob_Call) Run(run func(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption)) *MockBlobStoreClient_PutBlob_Call {
func (_c *MockBlobStoreClient_PutBlob_Call) Run(run func(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption)) *MockBlobStoreClient_PutBlob_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
@ -84,17 +84,17 @@ func (_c *MockBlobStoreClient_PutBlob_Call) Run(run func(ctx context.Context, in
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*resource.PutBlobRequest), variadicArgs...)
run(args[0].(context.Context), args[1].(*resourcepb.PutBlobRequest), variadicArgs...)
})
return _c
}
func (_c *MockBlobStoreClient_PutBlob_Call) Return(_a0 *resource.PutBlobResponse, _a1 error) *MockBlobStoreClient_PutBlob_Call {
func (_c *MockBlobStoreClient_PutBlob_Call) Return(_a0 *resourcepb.PutBlobResponse, _a1 error) *MockBlobStoreClient_PutBlob_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBlobStoreClient_PutBlob_Call) RunAndReturn(run func(context.Context, *resource.PutBlobRequest, ...grpc.CallOption) (*resource.PutBlobResponse, error)) *MockBlobStoreClient_PutBlob_Call {
func (_c *MockBlobStoreClient_PutBlob_Call) RunAndReturn(run func(context.Context, *resourcepb.PutBlobRequest, ...grpc.CallOption) (*resourcepb.PutBlobResponse, error)) *MockBlobStoreClient_PutBlob_Call {
_c.Call.Return(run)
return _c
}

@ -14,13 +14,14 @@ import (
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"google.golang.org/grpc"
)
//go:generate mockery --name BlobStoreClient --structname MockBlobStoreClient --inpackage --filename blobstore_client_mock.go --with-expecter
type BlobStoreClient interface {
PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error)
PutBlob(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption) (*resourcepb.PutBlobResponse, error)
}
// ScreenshotRenderer is an interface for rendering a preview of a file
@ -85,14 +86,14 @@ func (r *screenshotRenderer) RenderScreenshot(ctx context.Context, repo provisio
return "", err
}
rsp, err := r.blobstore.PutBlob(ctx, &resource.PutBlobRequest{
Resource: &resource.ResourceKey{
rsp, err := r.blobstore.PutBlob(ctx, &resourcepb.PutBlobRequest{
Resource: &resourcepb.ResourceKey{
Namespace: repo.Namespace,
Group: provisioning.GROUP,
Resource: provisioning.RepositoryResourceInfo.GroupResource().Resource,
Name: repo.Name,
},
Method: resource.PutBlobRequest_GRPC,
Method: resourcepb.PutBlobRequest_GRPC,
ContentType: mime.TypeByExtension(ext), // image/png
Value: body,
})

@ -15,7 +15,7 @@ import (
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func setupTempFile(t *testing.T) (string, func()) {
@ -170,10 +170,10 @@ func TestScreenshotRenderer_RenderScreenshot(t *testing.T) {
},
setupBlobstore: func(t *testing.T) BlobStoreClient {
blobstore := NewMockBlobStoreClient(t)
blobstore.On("PutBlob", mock.Anything, mock.MatchedBy(func(req *resource.PutBlobRequest) bool {
blobstore.On("PutBlob", mock.Anything, mock.MatchedBy(func(req *resourcepb.PutBlobRequest) bool {
return req.Resource.Group == provisioning.GROUP &&
req.Resource.Resource == provisioning.RepositoryResourceInfo.GroupResource().Resource &&
req.Method == resource.PutBlobRequest_GRPC &&
req.Method == resourcepb.PutBlobRequest_GRPC &&
req.ContentType == "image/png"
})).Return(nil, errors.New("blobstore error"))
return blobstore
@ -200,7 +200,7 @@ func TestScreenshotRenderer_RenderScreenshot(t *testing.T) {
setupBlobstore: func(t *testing.T) BlobStoreClient {
blobstore := NewMockBlobStoreClient(t)
blobstore.On("PutBlob", mock.Anything, mock.Anything).
Return(&resource.PutBlobResponse{
Return(&resourcepb.PutBlobResponse{
Url: "https://example.com/test.png",
}, nil)
return blobstore
@ -227,7 +227,7 @@ func TestScreenshotRenderer_RenderScreenshot(t *testing.T) {
setupBlobstore: func(t *testing.T) BlobStoreClient {
blobstore := NewMockBlobStoreClient(t)
blobstore.On("PutBlob", mock.Anything, mock.Anything).
Return(&resource.PutBlobResponse{
Return(&resourcepb.PutBlobResponse{
Uid: "test-uid",
}, nil)
return blobstore
@ -257,7 +257,7 @@ func TestScreenshotRenderer_RenderScreenshot(t *testing.T) {
setupBlobstore: func(t *testing.T) BlobStoreClient {
blobstore := NewMockBlobStoreClient(t)
blobstore.On("PutBlob", mock.Anything, mock.Anything).
Return(&resource.PutBlobResponse{
Return(&resourcepb.PutBlobResponse{
Uid: "test-uid",
}, nil)
return blobstore

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type renderConnector struct {
@ -132,8 +133,8 @@ func (c *renderConnector) Connect(
return
}
rsp, err := c.unified.GetBlob(ctx, &resource.GetBlobRequest{
Resource: &resource.ResourceKey{
rsp, err := c.unified.GetBlob(ctx, &resourcepb.GetBlobRequest{
Resource: &resourcepb.ResourceKey{
Namespace: namespace,
Group: provisioning.GROUP,
Resource: provisioning.RepositoryResourceInfo.GroupResource().Resource,

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type K8sHandler interface {
@ -29,8 +30,8 @@ type K8sHandler interface {
Delete(ctx context.Context, name string, orgID int64, options v1.DeleteOptions) error
DeleteCollection(ctx context.Context, orgID int64) error
List(ctx context.Context, orgID int64, options v1.ListOptions) (*unstructured.UnstructuredList, error)
Search(ctx context.Context, orgID int64, in *resource.ResourceSearchRequest) (*resource.ResourceSearchResponse, error)
GetStats(ctx context.Context, orgID int64) (*resource.ResourceStatsResponse, error)
Search(ctx context.Context, orgID int64, in *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error)
GetStats(ctx context.Context, orgID int64) (*resourcepb.ResourceStatsResponse, error)
GetUsersFromMeta(ctx context.Context, userMeta []string) (map[string]*user.User, error)
}
@ -40,7 +41,7 @@ type k8sHandler struct {
namespacer request.NamespaceMapper
gvr schema.GroupVersionResource
restConfig func(context.Context) (*rest.Config, error)
searcher resource.ResourceIndexClient
searcher resourcepb.ResourceIndexClient
userService user.Service
}
@ -116,14 +117,14 @@ func (h *k8sHandler) List(ctx context.Context, orgID int64, options v1.ListOptio
return client.List(ctx, options)
}
func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resource.ResourceSearchRequest) (*resource.ResourceSearchResponse, error) {
func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
// goes directly through grpc, so doesn't need the new context
if in.Options == nil {
in.Options = &resource.ListOptions{}
in.Options = &resourcepb.ListOptions{}
}
if in.Options.Key == nil {
in.Options.Key = &resource.ResourceKey{
in.Options.Key = &resourcepb.ResourceKey{
Namespace: h.GetNamespace(orgID),
Group: h.gvr.Group,
Resource: h.gvr.Resource,
@ -133,9 +134,9 @@ func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resource.Resou
return h.searcher.Search(ctx, in)
}
func (h *k8sHandler) GetStats(ctx context.Context, orgID int64) (*resource.ResourceStatsResponse, error) {
func (h *k8sHandler) GetStats(ctx context.Context, orgID int64) (*resourcepb.ResourceStatsResponse, error) {
// goes directly through grpc, so doesn't need the new context
return h.searcher.GetStats(ctx, &resource.ResourceStatsRequest{
return h.searcher.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: h.GetNamespace(orgID),
Kinds: []string{
h.gvr.Group + "/" + h.gvr.Resource,

@ -6,7 +6,8 @@ import (
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
@ -66,20 +67,20 @@ func (m *MockK8sHandler) List(ctx context.Context, orgID int64, options v1.ListO
return args.Get(0).(*unstructured.UnstructuredList), args.Error(1)
}
func (m *MockK8sHandler) Search(ctx context.Context, orgID int64, in *resource.ResourceSearchRequest) (*resource.ResourceSearchResponse, error) {
func (m *MockK8sHandler) Search(ctx context.Context, orgID int64, in *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
args := m.Called(ctx, orgID, in)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*resource.ResourceSearchResponse), args.Error(1)
return args.Get(0).(*resourcepb.ResourceSearchResponse), args.Error(1)
}
func (m *MockK8sHandler) GetStats(ctx context.Context, orgID int64) (*resource.ResourceStatsResponse, error) {
func (m *MockK8sHandler) GetStats(ctx context.Context, orgID int64) (*resourcepb.ResourceStatsResponse, error) {
args := m.Called(ctx, orgID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*resource.ResourceStatsResponse), args.Error(1)
return args.Get(0).(*resourcepb.ResourceStatsResponse), args.Error(1)
}
func (m *MockK8sHandler) GetUsersFromMeta(ctx context.Context, usersMeta []string) (map[string]*user.User, error) {

@ -4,13 +4,15 @@ import (
"context"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -47,7 +49,7 @@ type PermissionsRegistrationService interface {
RegisterDashboardPermissions(service accesscontrol.DashboardPermissionsService)
// Used to apply default permissions in unified storage after create
SetDefaultPermissionsAfterCreate(ctx context.Context, key *resource.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
SetDefaultPermissionsAfterCreate(ctx context.Context, key *resourcepb.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
}
// PluginService is a service for operating on plugin dashboards.

@ -25,6 +25,7 @@ import (
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
dashboardv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
@ -60,6 +61,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/retryer"
)
@ -1204,7 +1206,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
}
// (sometimes) called by the k8s storage engine after creating an object
func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Context, key *resource.ResourceKey, id claims.AuthInfo, obj utils.GrafanaMetaAccessor) error {
func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Context, key *resourcepb.ResourceKey, id claims.AuthInfo, obj utils.GrafanaMetaAccessor) error {
ctx, span := tracer.Start(ctx, "dashboards.service.SetDefaultPermissionsAfterCreate")
defer span.End()
@ -1714,8 +1716,8 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resource.ResourceSearchRequest{
Facet: map[string]*resource.ResourceSearchRequest_Facet{
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resourcepb.ResourceSearchRequest{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"tags": {
Field: "tags",
Limit: 100000,
@ -1959,15 +1961,15 @@ func (dr *DashboardServiceImpl) listDashboardsThroughK8s(ctx context.Context, or
}
func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (dashboardv0.SearchResults, error) {
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
request := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Limit: 100000}
if len(query.DashboardUIDs) > 0 {
request.Options.Fields = []*resource.Requirement{{
request.Options.Fields = []*resourcepb.Requirement{{
Key: resource.SEARCH_FIELD_NAME,
Operator: string(selection.In),
Values: query.DashboardUIDs,
@ -1978,7 +1980,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
values[i] = strconv.FormatInt(id, 10)
}
request.Options.Labels = append(request.Options.Labels, &resource.Requirement{
request.Options.Labels = append(request.Options.Labels, &resourcepb.Requirement{
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
Operator: string(selection.In),
Values: values,
@ -1996,7 +1998,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
}
req := []*resource.Requirement{{
req := []*resourcepb.Requirement{{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: query.FolderUIDs,
@ -2008,7 +2010,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
values[i] = strconv.FormatInt(id, 10)
}
request.Options.Labels = append(request.Options.Labels, &resource.Requirement{
request.Options.Labels = append(request.Options.Labels, &resourcepb.Requirement{
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
Operator: string(selection.In),
Values: values,
@ -2016,7 +2018,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
if query.ManagedBy != "" {
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
request.Options.Fields = append(request.Options.Fields, &resourcepb.Requirement{
Key: resource.SEARCH_FIELD_MANAGER_KIND,
Operator: string(selection.Equals),
Values: []string{string(query.ManagedBy)},
@ -2024,7 +2026,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
if query.ManagerIdentity != "" {
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
request.Options.Fields = append(request.Options.Fields, &resourcepb.Requirement{
Key: resource.SEARCH_FIELD_MANAGER_ID,
Operator: string(selection.In),
Values: []string{query.ManagerIdentity},
@ -2032,14 +2034,14 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
if len(query.ManagerIdentityNotIn) > 0 {
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
request.Options.Fields = append(request.Options.Fields, &resourcepb.Requirement{
Key: resource.SEARCH_FIELD_MANAGER_ID,
Operator: string(selection.NotIn),
Values: query.ManagerIdentityNotIn,
})
}
if query.SourcePath != "" {
request.Options.Fields = append(request.Options.Fields, &resource.Requirement{
request.Options.Fields = append(request.Options.Fields, &resourcepb.Requirement{
Key: resource.SEARCH_FIELD_SOURCE_PATH,
Operator: string(selection.In),
Values: []string{query.SourcePath},
@ -2054,7 +2056,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
if len(query.Tags) > 0 {
req := []*resource.Requirement{{
req := []*resourcepb.Requirement{{
Key: resource.SEARCH_FIELD_TAGS,
Operator: string(selection.In),
Values: query.Tags,
@ -2084,7 +2086,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
namespace := dr.k8sclient.GetNamespace(query.OrgId)
var err error
var federate *resource.ResourceKey
var federate *resourcepb.ResourceKey
switch query.Type {
case "":
// When no type specified, search for dashboards
@ -2106,7 +2108,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
}
if federate != nil {
request.Federated = []*resource.ResourceKey{federate}
request.Federated = []*resourcepb.ResourceKey{federate}
}
if query.Sort.Name != "" {
@ -2114,7 +2116,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
if err != nil {
return dashboardv0.SearchResults{}, err
}
request.SortBy = append(request.SortBy, &resource.ResourceSearchRequest_Sort{Field: sortName, Desc: isDesc})
request.SortBy = append(request.SortBy, &resourcepb.ResourceSearchRequest_Sort{Field: sortName, Desc: isDesc})
}
res, err := dr.k8sclient.Search(ctx, query.OrgId, request)

@ -45,6 +45,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestDashboardService(t *testing.T) {
@ -355,21 +356,21 @@ func TestGetDashboard(t *testing.T) {
}
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil).Once()
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -567,36 +568,36 @@ func TestGetProvisionedDashboardData(t *testing.T) {
}, nil).Once()
repo := "test"
k8sCliMock.On("Search", mock.Anything, int64(1),
mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// make sure the kind is added to the query
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
req.Options.Fields[1].Values[0] == repo
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{},
Rows: []*resource.ResourceTableRow{},
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{},
Rows: []*resourcepb.ResourceTableRow{},
},
TotalHits: 0,
}, nil).Once()
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// make sure the kind is added to the query
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
req.Options.Fields[1].Values[0] == repo
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -670,28 +671,28 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
"title": "testing slugify",
},
}}, nil)
k8sCliMock.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{},
Rows: []*resource.ResourceTableRow{},
k8sCliMock.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{},
Rows: []*resourcepb.ResourceTableRow{},
},
TotalHits: 0,
}, nil)
k8sCliMock.On("Search", mock.Anything, int64(2), mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, int64(2), mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -764,21 +765,21 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
"title": "testing slugify",
},
}}, nil).Once()
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -873,24 +874,24 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
},
"spec": map[string]any{},
}}, nil).Once()
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// nolint:staticcheck
return req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "test" && req.Options.Fields[1].Operator == "notin"
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -904,24 +905,24 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
TotalHits: 1,
}, nil).Once()
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// nolint:staticcheck
return req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "test" && req.Options.Fields[1].Operator == "notin"
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "dashboard",
},
@ -931,7 +932,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid3",
Resource: "dashboard",
},
@ -946,8 +947,8 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
}, nil).Once()
// mock call to waitForSearchQuery()
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
TotalHits: 0,
}, nil).Twice()
@ -998,25 +999,25 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
},
},
}, nil).Once()
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// make sure the kind is added to the query
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
req.Options.Fields[1].Values[0] == repo
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -1035,14 +1036,14 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
// Mock WaitForSearchQuery()
// First call returns 1 hit
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
TotalHits: 1,
}, nil).Once()
// Second call returns 0 hits
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
TotalHits: 0,
}, nil).Once()
@ -1068,12 +1069,12 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
// Call to searchProvisionedDashboardsThroughK8s()
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
// make sure the kind is added to the query
return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck
req.Options.Fields[1].Values[0] == repo
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{},
TotalHits: 0,
}, nil)
@ -1140,21 +1141,21 @@ func TestUnprovisionDashboard(t *testing.T) {
}).Return(dashWithoutAnnotations, nil)
k8sCliMock.On("GetNamespace", mock.Anything).Return("default")
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -1208,25 +1209,25 @@ func TestGetDashboardsByPluginID(t *testing.T) {
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil)
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
return ( // gofmt comment helper
req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindPlugin) &&
req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "testing")
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -1496,21 +1497,21 @@ func TestDeleteDashboard(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -1648,25 +1649,25 @@ func TestSearchDashboards(t *testing.T) {
}
fakeFolders.ExpectedHitList = expectedFolders
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "tags",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid1",
Resource: "dashboard",
},
@ -1677,7 +1678,7 @@ func TestSearchDashboards(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "dashboard",
},
@ -1702,7 +1703,7 @@ func TestSearchDashboards(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
service.features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagKubernetesClientDashboardsFolders)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
if len(req.Options.Fields) == 0 {
return false
}
@ -1713,25 +1714,25 @@ func TestSearchDashboards(t *testing.T) {
}
}
return false
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "tags",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "shared-uid1",
Resource: "dashboard",
},
@ -1745,7 +1746,7 @@ func TestSearchDashboards(t *testing.T) {
},
TotalHits: 1,
}, nil).Once()
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
if len(req.Options.Fields) == 0 {
return false
}
@ -1755,25 +1756,25 @@ func TestSearchDashboards(t *testing.T) {
}
}
return false
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "tags",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "shared-uid1",
Resource: "dashboard",
},
@ -1878,21 +1879,21 @@ func TestGetDashboards(t *testing.T) {
k8sCliMock.On("Get", mock.Anything, "uid1", mock.Anything, mock.Anything, mock.Anything).Return(uid1Unstructured, nil)
k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(uid2Unstructured, nil)
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid1",
Resource: "dashboard",
},
@ -1902,7 +1903,7 @@ func TestGetDashboards(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "dashboard",
},
@ -1959,21 +1960,21 @@ func TestGetDashboardUIDByID(t *testing.T) {
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid1",
Resource: "dashboard",
},
@ -2078,10 +2079,10 @@ func TestGetDashboardTags(t *testing.T) {
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Facet: map[string]*resource.ResourceSearchResponse_Facet{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Facet: map[string]*resourcepb.ResourceSearchResponse_Facet{
"tags": {
Terms: []*resource.ResourceSearchResponse_TermFacet{
Terms: []*resourcepb.ResourceSearchResponse_TermFacet{
{
Term: "tag1",
Count: 1,
@ -2119,15 +2120,15 @@ func TestQuotaCount(t *testing.T) {
},
}
countOrg1 := resource.ResourceStatsResponse{
Stats: []*resource.ResourceStatsResponse_Stats{
countOrg1 := resourcepb.ResourceStatsResponse{
Stats: []*resourcepb.ResourceStatsResponse_Stats{
{
Count: 1,
},
},
}
countOrg2 := resource.ResourceStatsResponse{
Stats: []*resource.ResourceStatsResponse_Stats{
countOrg2 := resourcepb.ResourceStatsResponse{
Stats: []*resourcepb.ResourceStatsResponse_Stats{
{
Count: 2,
},
@ -2176,8 +2177,8 @@ func TestCountDashboardsInOrg(t *testing.T) {
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
count := resource.ResourceStatsResponse{
Stats: []*resource.ResourceStatsResponse_Stats{
count := resourcepb.ResourceStatsResponse{
Stats: []*resourcepb.ResourceStatsResponse_Stats{
{
Count: 3,
},
@ -2208,21 +2209,21 @@ func TestCountInFolders(t *testing.T) {
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
dashs := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
dashs := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -2232,7 +2233,7 @@ func TestCountInFolders(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "dashboard",
},
@ -2273,26 +2274,26 @@ func TestSearchDashboardsThroughK8sRaw(t *testing.T) {
Sort: sort.SortAlphaAsc,
}
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
return len(req.SortBy) == 1 &&
// should be converted to "title" due to ParseSortName
req.SortBy[0].Field == "title" &&
!req.SortBy[0].Desc
})).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
})).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -2347,21 +2348,21 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
"spec": map[string]any{},
}}
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -2371,7 +2372,7 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "dashboard",
},
@ -2514,7 +2515,7 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) {
service.RegisterDashboardPermissions(permService)
// Create test object
key := &resource.ResourceKey{Group: "dashboard.grafana.app", Resource: "dashboards", Name: "test", Namespace: "default"}
key := &resourcepb.ResourceKey{Group: "dashboard.grafana.app", Resource: "dashboards", Name: "test", Namespace: "default"}
obj := &dashboardv0.Dashboard{
TypeMeta: metav1.TypeMeta{
APIVersion: "dashboard.grafana.app/v0alpha1",

@ -6,6 +6,7 @@ import (
"fmt"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@ -37,7 +38,7 @@ var (
}
)
func ParseResults(result *resource.ResourceSearchResponse, offset int64) (v0alpha1.SearchResults, error) {
func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0alpha1.SearchResults, error) {
if result == nil {
return v0alpha1.SearchResults{}, nil
} else if result.Error != nil {

@ -3,37 +3,39 @@ package dashboardsearch
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/stretchr/testify/require"
)
// regression test - parsing int32 values from search results was causing a panic
func TestParseResults(t *testing.T) {
t.Run("should parse results", func(t *testing.T) {
resSearchResp := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
resSearchResp := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
{
Name: search.DASHBOARD_LINK_COUNT,
Type: resource.ResourceTableColumnDefinition_INT32,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},
@ -54,33 +56,33 @@ func TestParseResults(t *testing.T) {
})
t.Run("should return error when trying to parse results with mismatch length between Columns and row Cells", func(t *testing.T) {
resSearchResp := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
resSearchResp := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
{
Name: search.DASHBOARD_LINK_COUNT,
Type: resource.ResourceTableColumnDefinition_INT32,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
},
{
Name: resource.SEARCH_FIELD_LEGACY_ID,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "dashboard",
},

@ -30,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util"
)
@ -175,20 +176,20 @@ func (s *Service) searchFoldersFromApiServer(ctx context.Context, query folder.S
query.OrgID = requester.GetOrgID()
}
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
request := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: s.k8sclient.GetNamespace(query.OrgID),
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Limit: folderSearchLimit}
if len(query.UIDs) > 0 {
request.Options.Fields = []*resource.Requirement{{
request.Options.Fields = []*resourcepb.Requirement{{
Key: resource.SEARCH_FIELD_NAME,
Operator: string(selection.In),
Values: query.UIDs,
@ -199,7 +200,7 @@ func (s *Service) searchFoldersFromApiServer(ctx context.Context, query folder.S
values[i] = strconv.FormatInt(id, 10)
}
request.Options.Labels = append(request.Options.Labels, &resource.Requirement{
request.Options.Labels = append(request.Options.Labels, &resourcepb.Requirement{
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
Operator: string(selection.In),
Values: values,
@ -253,17 +254,17 @@ func (s *Service) getFolderByIDFromApiServer(ctx context.Context, id int64, orgI
return &folder.GeneralFolder, nil
}
folderkey := &resource.ResourceKey{
folderkey := &resourcepb.ResourceKey{
Namespace: s.k8sclient.GetNamespace(orgID),
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
}
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
request := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: folderkey,
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{
{
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
Operator: string(selection.In),
@ -309,23 +310,23 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64
return nil, dashboards.ErrFolderTitleEmpty
}
folderkey := &resource.ResourceKey{
folderkey := &resourcepb.ResourceKey{
Namespace: s.k8sclient.GetNamespace(orgID),
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
}
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
request := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: folderkey,
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Query: title,
Limit: folderSearchLimit}
if parentUID != nil {
req := []*resource.Requirement{{
req := []*resourcepb.Requirement{{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: []string{*parentUID},
@ -670,10 +671,10 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards -
// we cannot use the dashboard service directly due to circular dependencies, so use the search client to get the dashboards
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
request := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Labels: []*resourcepb.Requirement{},
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),

@ -42,6 +42,7 @@ import (
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type rcp struct {
@ -368,7 +369,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error - ForceDeleteRules false", func(t *testing.T) {
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{Results: &resourcepb.ResourceTable{}}, nil).Once()
publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@ -381,7 +382,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid, expectedForceDeleteRules as false,should not return access denied error", func(t *testing.T) {
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{Results: &resourcepb.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := false
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@ -394,7 +395,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid, expectedForceDeleteRules as true, should not return access denied error", func(t *testing.T) {
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{Results: &resourcepb.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := true
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@ -555,37 +556,37 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
fakeK8sClient.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
t.Run("Should call search with uids, if provided", func(t *testing.T) {
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
fakeK8sClient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_NAME,
Operator: string(selection.In),
Values: []string{"uid1", "uid2"}, // should only search by uid since it is provided
},
},
Labels: []*resource.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Limit: folderSearchLimit}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Limit: folderSearchLimit}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid1",
Resource: "folder",
},
@ -595,7 +596,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid2",
Resource: "folder",
},
@ -645,15 +646,15 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
IDs: []int64{123},
SignedInUser: user,
}
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
fakeK8sClient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{
{
Key: utils.LabelKeyDeprecatedInternalID,
Operator: string(selection.In),
@ -661,21 +662,21 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
},
},
},
Limit: folderSearchLimit}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Limit: folderSearchLimit}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "foo",
Resource: "folder",
},
@ -714,33 +715,33 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
Title: "parent title",
}
service.unifiedStore = fakeFolderStore
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
fakeK8sClient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Query: "*test*",
Fields: dashboardsearch.IncludeFields,
Limit: folderSearchLimit}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Limit: folderSearchLimit}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "folder",
},
@ -798,7 +799,7 @@ func TestGetFoldersFromApiServer(t *testing.T) {
user := &user.SignedInUser{OrgID: 1}
ctx := identity.WithRequester(context.Background(), user)
fakeK8sClient.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
folderkey := &resource.ResourceKey{
folderkey := &resourcepb.ResourceKey{
Namespace: "default",
Group: folderv1.FolderResourceInfo.GroupVersionResource().Group,
Resource: folderv1.FolderResourceInfo.GroupVersionResource().Resource,
@ -816,29 +817,29 @@ func TestGetFoldersFromApiServer(t *testing.T) {
URL: "/dashboards/f/foouid/foo-title",
}
service.unifiedStore = fakeFolderStore
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
fakeK8sClient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: folderkey,
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
Fields: []*resourcepb.Requirement{},
Labels: []*resourcepb.Requirement{},
},
Query: "foo title",
Limit: folderSearchLimit}).
Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "uid",
Resource: "folder",
},
@ -903,7 +904,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
t.Run("Should delete folder", func(t *testing.T) {
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{}).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resourcepb.ResourceSearchResponse{Results: &resourcepb.ResourceTable{}}, nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid1",
OrgID: 1,
@ -918,10 +919,10 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
fakeFolderStore.ExpectedFolders = []*folder.Folder{{UID: "uid2", ID: 2}}
dashboardK8sclient.On("Delete", mock.Anything, "test", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "test2", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Labels: []*resourcepb.Requirement{},
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -929,21 +930,21 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
},
},
},
Limit: folderSearchLimit}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
Limit: folderSearchLimit}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "test",
Resource: "dashboard",
},
@ -953,7 +954,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
},
},
{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "test2",
Resource: "dashboard",
},

@ -11,8 +11,10 @@ import (
"k8s.io/apimachinery/pkg/selection"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/infra/log"
@ -195,9 +197,9 @@ func (ss *FolderUnifiedStoreImpl) GetChildren(ctx context.Context, q folder.GetC
}
}
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -214,7 +216,7 @@ func (ss *FolderUnifiedStoreImpl) GetChildren(ctx context.Context, q folder.GetC
// only filter the folder UIDs if they are provided in the query
if len(q.FolderUIDs) > 0 {
req.Options.Fields = append(req.Options.Fields, &resource.Requirement{
req.Options.Fields = append(req.Options.Fields, &resourcepb.Requirement{
Key: resource.SEARCH_FIELD_NAME,
Operator: string(selection.In),
Values: q.FolderUIDs,

@ -6,6 +6,7 @@ import (
"testing"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/client"
@ -13,6 +14,8 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -198,9 +201,9 @@ func TestGetChildren(t *testing.T) {
orgID := int64(2)
t.Run("should be able to find children folders, and set defaults for pages", func(t *testing.T) {
mockCli.On("Search", mock.Anything, orgID, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{
mockCli.On("Search", mock.Anything, orgID, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -211,18 +214,18 @@ func TestGetChildren(t *testing.T) {
Limit: folderSearchLimit, // should default to folderSearchLimit
Offset: 0, // should be set as limit * (page - 1)
Page: 1, // should be set to 1 by default
}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING},
}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{Name: "folder2", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder2", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
{
Key: &resource.ResourceKey{Name: "folder3", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder3", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
},
@ -257,9 +260,9 @@ func TestGetChildren(t *testing.T) {
})
t.Run("should return an error if the folder is not found", func(t *testing.T) {
mockCli.On("Search", mock.Anything, orgID, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{
mockCli.On("Search", mock.Anything, orgID, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -270,18 +273,18 @@ func TestGetChildren(t *testing.T) {
Limit: folderSearchLimit, // should default to folderSearchLimit
Offset: 0, // should be set as limit * (page - 1)
Page: 1, // should be set to 1 by default
}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING},
}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{Name: "folder2", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder2", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
{
Key: &resource.ResourceKey{Name: "folder3", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder3", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
},
@ -298,9 +301,9 @@ func TestGetChildren(t *testing.T) {
})
t.Run("pages should be able to be set, general folder should be turned to empty string, and folder uids should be passed in", func(t *testing.T) {
mockCli.On("Search", mock.Anything, orgID, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{
mockCli.On("Search", mock.Anything, orgID, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -316,14 +319,14 @@ func TestGetChildren(t *testing.T) {
Limit: 10,
Offset: 20, // should be set as limit * (page - 1)
Page: 3,
}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING},
}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{Name: "folder2", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder2", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
},
@ -349,14 +352,14 @@ func TestGetChildren(t *testing.T) {
})
t.Run("k6 folder should only be returned to service accounts", func(t *testing.T) {
mockCli.On("Search", mock.Anything, orgID, mock.Anything).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING},
mockCli.On("Search", mock.Anything, orgID, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{Name: accesscontrol.K6FolderUID, Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: accesscontrol.K6FolderUID, Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
},
@ -391,9 +394,9 @@ func TestGetChildren(t *testing.T) {
})
t.Run("should not do get requests for the children if RefOnly is true", func(t *testing.T) {
mockCli.On("Search", mock.Anything, orgID, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Fields: []*resource.Requirement{
mockCli.On("Search", mock.Anything, orgID, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
@ -404,18 +407,18 @@ func TestGetChildren(t *testing.T) {
Limit: folderSearchLimit, // should default to folderSearchLimit
Offset: 0, // should be set as limit * (page - 1)
Page: 1, // should be set to 1 by default
}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING},
}).Return(&resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING},
},
Rows: []*resource.ResourceTableRow{
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resource.ResourceKey{Name: "folder2", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder2", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
{
Key: &resource.ResourceKey{Name: "folder3", Resource: "folder"},
Key: &resourcepb.ResourceKey{Name: "folder3", Resource: "folder"},
Cells: [][]byte{[]byte("folder1")},
},
},

@ -7,7 +7,7 @@ import (
dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type LargeObjectSupportFake struct {
@ -28,12 +28,12 @@ func (s *LargeObjectSupportFake) MaxSize() int {
return 10 * 1024 * 1024
}
func (s *LargeObjectSupportFake) Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
func (s *LargeObjectSupportFake) Deconstruct(_ context.Context, _ *resourcepb.ResourceKey, _ resourcepb.BlobStoreClient, _ utils.GrafanaMetaAccessor, _ []byte) error {
s.deconstructed = true
return nil
}
func (s *LargeObjectSupportFake) Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
func (s *LargeObjectSupportFake) Reconstruct(_ context.Context, _ *resourcepb.ResourceKey, _ resourcepb.BlobStoreClient, _ utils.GrafanaMetaAccessor) error {
s.reconstructed = true
return nil
}

@ -20,7 +20,7 @@ require (
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20250422074709-7c8433fbb2c2
github.com/stretchr/testify v1.10.0
gocloud.dev v0.40.0
google.golang.org/grpc v1.72.0
google.golang.org/grpc v1.72.1
k8s.io/apimachinery v0.32.3
k8s.io/apiserver v0.32.3
k8s.io/client-go v0.32.3

@ -869,8 +869,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

@ -10,7 +10,7 @@ import (
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type LargeObjectSupport interface {
@ -27,11 +27,11 @@ type LargeObjectSupport interface {
// Deconstruct takes a large object, write most of it to blob storage and leave a few metadata bits around to help with list
// NOTE: changes to the object must be handled by mutating the input obj
Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error
Deconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error
// Reconstruct will join the resource+blob back into a complete resource
// NOTE: changes to the object must be handled by mutating the input obj
Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error
Reconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor) error
}
var _ LargeObjectSupport = (*BasicLargeObjectSupport)(nil)
@ -64,7 +64,7 @@ func (s *BasicLargeObjectSupport) MaxSize() int {
}
// Deconstruct implements LargeObjectSupport.
func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
if key.Group != s.TheGroupResource.Group {
return fmt.Errorf("requested group mismatch")
}
@ -104,7 +104,7 @@ func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource
}
// Save the blob
info, err := client.PutBlob(ctx, &resource.PutBlobRequest{
info, err := client.PutBlob(ctx, &resourcepb.PutBlobRequest{
ContentType: "application/json",
Value: val,
Resource: key,
@ -125,7 +125,7 @@ func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource
}
// Reconstruct implements LargeObjectSupport.
func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
blobInfo := obj.GetBlob()
if blobInfo == nil {
return fmt.Errorf("the object does not have a blob")
@ -135,8 +135,8 @@ func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resource
if err != nil {
return err
}
rsp, err := client.GetBlob(ctx, &resource.GetBlobRequest{
Resource: &resource.ResourceKey{
rsp, err := client.GetBlob(ctx, &resourcepb.GetBlobRequest{
Resource: &resourcepb.ResourceKey{
Group: s.TheGroupResource.Group,
Resource: s.TheGroupResource.Resource,
Namespace: obj.GetNamespace(),

@ -16,8 +16,9 @@ import (
"k8s.io/client-go/rest"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var errResourceIsManagedInRepository = fmt.Errorf("this resource is managed by a repository")
@ -97,7 +98,7 @@ func enforceManagerProperties(auth authtypes.AuthInfo, obj utils.GrafanaMetaAcce
func (s *Storage) handleManagedResourceRouting(ctx context.Context,
err error,
action resource.WatchEvent_Type,
action resourcepb.WatchEvent_Type,
key string,
orig runtime.Object,
rsp runtime.Object,
@ -137,7 +138,7 @@ func (s *Storage) handleManagedResourceRouting(ctx context.Context,
return err
}
if action == resource.WatchEvent_DELETED {
if action == resourcepb.WatchEvent_DELETED {
// TODO? can we copy orig into rsp without a full get?
if err = s.Get(ctx, key, storage.GetOptions{}, rsp); err != nil { // COPY?
return err
@ -153,9 +154,9 @@ func (s *Storage) handleManagedResourceRouting(ctx context.Context,
var req *rest.Request
switch action {
case resource.WatchEvent_ADDED:
case resourcepb.WatchEvent_ADDED:
req = client.Post()
case resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_MODIFIED:
req = client.Put()
default:
return fmt.Errorf("unsupported provisioning action: %v, %w", action, err)

@ -8,14 +8,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type permissionCreatorFunc = func(ctx context.Context) error
func afterCreatePermissionCreator(ctx context.Context,
key *resource.ResourceKey,
key *resourcepb.ResourceKey,
grantPermisions string,
obj runtime.Object,
setter DefaultPermissionSetter,

@ -8,14 +8,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestAfterCreatePermissionCreator(t *testing.T) {
mockSetter := func(ctx context.Context, key *resource.ResourceKey, auth authtypes.AuthInfo, val utils.GrafanaMetaAccessor) error {
mockSetter := func(ctx context.Context, key *resourcepb.ResourceKey, auth authtypes.AuthInfo, val utils.GrafanaMetaAccessor) error {
return nil
}
@ -69,7 +70,7 @@ func TestAfterCreatePermissionCreator(t *testing.T) {
UserID: 1,
})
obj := &v0alpha1.Dashboard{}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "test",
@ -92,7 +93,7 @@ func TestAfterCreatePermissionCreator(t *testing.T) {
UserID: 1,
})
obj := &v0alpha1.Dashboard{}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "test",

@ -17,8 +17,9 @@ import (
"k8s.io/klog/v2"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func logN(n, b float64) float64 {
@ -197,7 +198,7 @@ func (s *Storage) handleLargeResources(ctx context.Context, obj utils.GrafanaMet
return nil, fmt.Errorf("request object is too big (%s > %s)", formatBytes(size), formatBytes(support.MaxSize()))
}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: s.gr.Group,
Resource: s.gr.Resource,
Namespace: obj.GetNamespace(),

@ -32,10 +32,12 @@ import (
"k8s.io/client-go/tools/cache"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
const (
@ -46,7 +48,7 @@ const (
var _ storage.Interface = (*Storage)(nil)
type DefaultPermissionSetter = func(ctx context.Context, key *resource.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
type DefaultPermissionSetter = func(ctx context.Context, key *resourcepb.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
// Optional settings that apply to a single resource
type StorageOptions struct {
@ -76,7 +78,7 @@ type Storage struct {
indexers *cache.Indexers
store resource.ResourceClient
getKey func(string) (*resource.ResourceKey, error)
getKey func(string) (*resourcepb.ResourceKey, error)
snowflake *snowflake.Node // used to enforce internal ids
configProvider RestConfigProvider // used for provisioning
@ -101,7 +103,7 @@ func NewStorage(
config *storagebackend.ConfigForResource,
store resource.ResourceClient,
keyFunc func(obj runtime.Object) (string, error),
keyParser func(key string) (*resource.ResourceKey, error),
keyParser func(key string) (*resourcepb.ResourceKey, error),
newFunc func() runtime.Object,
newListFunc func() runtime.Object,
getAttrsFunc storage.AttrFunc,
@ -139,7 +141,7 @@ func NewStorage(
// The key parsing callback allows us to support the hardcoded paths from upstream tests
if s.getKey == nil {
s.getKey = func(key string) (*resource.ResourceKey, error) {
s.getKey = func(key string) (*resourcepb.ResourceKey, error) {
k, err := grafanaregistry.ParseKey(key)
if err != nil {
return nil, err
@ -150,7 +152,7 @@ func NewStorage(
if k.Resource == "" {
return nil, apierrors.NewInternalError(fmt.Errorf("missing resource in request"))
}
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,
@ -177,10 +179,10 @@ func (s *Storage) convertToObject(data []byte, obj runtime.Object) (runtime.Obje
func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error {
var err error
var permissions string
req := &resource.CreateRequest{}
req := &resourcepb.CreateRequest{}
req.Value, permissions, err = s.prepareObjectForStorage(ctx, obj)
if err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_ADDED, key, obj, out)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_ADDED, key, obj, out)
}
req.Key, err = s.getKey(key)
@ -258,7 +260,7 @@ func (s *Storage) Delete(
if err != nil {
return err
}
cmd := &resource.DeleteRequest{Key: k}
cmd := &resourcepb.DeleteRequest{Key: k}
if preconditions != nil {
if err := preconditions.Check(key, out); err != nil {
@ -287,7 +289,7 @@ func (s *Storage) Delete(
return fmt.Errorf("unable to read object %w", err)
}
if err = checkManagerPropertiesOnDelete(info, meta); err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_DELETED, key, out, out)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_DELETED, key, out, out)
}
rsp, err := s.store.Delete(ctx, cmd)
@ -315,7 +317,7 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption
return watch.NewEmptyWatch(), nil
}
cmd := &resource.WatchRequest{
cmd := &resourcepb.WatchRequest{
Since: req.ResourceVersion,
Options: req.Options,
SendInitialEvents: false,
@ -349,7 +351,7 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error {
var err error
req := &resource.ReadRequest{}
req := &resourcepb.ReadRequest{}
req.Key, err = s.getKey(key)
if err != nil {
if opts.IgnoreNotFound {
@ -497,7 +499,7 @@ func (s *Storage) GuaranteedUpdate(
existingBytes []byte
err error
)
req := &resource.UpdateRequest{}
req := &resourcepb.UpdateRequest{}
req.Key, err = s.getKey(key)
if err != nil {
return err
@ -511,7 +513,7 @@ func (s *Storage) GuaranteedUpdate(
for attempt := 1; attempt <= MaxUpdateAttempts; attempt = attempt + 1 {
// Read the latest value
readResponse, err := s.store.Read(ctx, &resource.ReadRequest{Key: req.Key})
readResponse, err := s.store.Read(ctx, &resourcepb.ReadRequest{Key: req.Key})
if err != nil {
return resource.GetError(resource.AsErrorResult(err))
}
@ -587,7 +589,7 @@ func (s *Storage) GuaranteedUpdate(
req.Value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj)
if err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_MODIFIED, key, updatedObj, destination)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_MODIFIED, key, updatedObj, destination)
}
var rv uint64

@ -29,10 +29,11 @@ import (
"k8s.io/apiserver/pkg/storage/storagebackend"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
storagetesting "github.com/grafana/grafana/pkg/apiserver/storage/testing"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func init() {
@ -160,17 +161,17 @@ func TestDeleteWithSuggestionAndConflict(t *testing.T) {
}
type resourceClientMock struct {
resource.ResourceStoreClient
resource.ResourceIndexClient
resource.ManagedObjectIndexClient
resource.BulkStoreClient
resource.BlobStoreClient
resource.DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
// always return GRPC Unauthenticated code
func (r resourceClientMock) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
return &resource.ListResponse{}, status.Error(codes.Unauthenticated, "missing token")
func (r resourceClientMock) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) {
return &resourcepb.ListResponse{}, status.Error(codes.Unauthenticated, "missing token")
}
func TestGRPCtoHTTPStatusMapping(t *testing.T) {

@ -15,11 +15,11 @@ import (
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type streamDecoder struct {
client resource.ResourceStore_WatchClient
client resourcepb.ResourceStore_WatchClient
newFunc func() runtime.Object
predicate storage.SelectionPredicate
codec runtime.Codec
@ -27,7 +27,7 @@ type streamDecoder struct {
done sync.WaitGroup
}
func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc) *streamDecoder {
func newStreamDecoder(client resourcepb.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc) *streamDecoder {
return &streamDecoder{
client: client,
newFunc: newFunc,
@ -36,7 +36,7 @@ func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func()
cancelWatch: cancelWatch,
}
}
func (d *streamDecoder) toObject(w *resource.WatchEvent_Resource) (runtime.Object, error) {
func (d *streamDecoder) toObject(w *resourcepb.WatchEvent_Resource) (runtime.Object, error) {
var obj runtime.Object
var err error
obj, _, err = d.codec.Decode(w.Value, nil, d.newFunc())
@ -56,7 +56,7 @@ func (d *streamDecoder) Decode() (action watch.EventType, object runtime.Object,
defer d.done.Done()
decode:
for {
var evt *resource.WatchEvent
var evt *resourcepb.WatchEvent
var err error
select {
case <-d.client.Context().Done():
@ -79,7 +79,7 @@ decode:
}
// Error event
if evt.Type == resource.WatchEvent_ERROR {
if evt.Type == resourcepb.WatchEvent_ERROR {
err = fmt.Errorf("stream error")
klog.Errorf("client: error receiving result: %s", err)
return watch.Error, nil, err
@ -90,7 +90,7 @@ decode:
continue decode
}
if evt.Type == resource.WatchEvent_BOOKMARK {
if evt.Type == resourcepb.WatchEvent_BOOKMARK {
obj := d.newFunc()
// here k8s expects an empty object with just resource version and k8s.io/initial-events-end annotation
@ -113,7 +113,7 @@ decode:
var watchAction watch.EventType
switch evt.Type {
case resource.WatchEvent_ADDED:
case resourcepb.WatchEvent_ADDED:
// apply any predicates not handled in storage
matches, err := d.predicate.Matches(obj)
if err != nil {
@ -125,7 +125,7 @@ decode:
}
watchAction = watch.Added
case resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_MODIFIED:
watchAction = watch.Modified
// apply any predicates not handled in storage
@ -175,7 +175,7 @@ decode:
// if the object didn't previously match, send an Added event
watchAction = watch.Added
}
case resource.WatchEvent_DELETED:
case resourcepb.WatchEvent_DELETED:
watchAction = watch.Deleted
// if we have a previous object, return that in the deleted event

@ -15,15 +15,14 @@ import (
"k8s.io/apiserver/pkg/storage"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource.ListRequest, storage.SelectionPredicate, error) {
func toListRequest(k *resourcepb.ResourceKey, opts storage.ListOptions) (*resourcepb.ListRequest, storage.SelectionPredicate, error) {
predicate := opts.Predicate
req := &resource.ListRequest{
req := &resourcepb.ListRequest{
Limit: opts.Predicate.Limit,
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Key: k,
},
NextPageToken: predicate.Continue,
@ -39,11 +38,11 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
switch opts.ResourceVersionMatch {
case "":
req.VersionMatchV2 = resource.ResourceVersionMatchV2_Unset
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Unset
case metav1.ResourceVersionMatchNotOlderThan:
req.VersionMatchV2 = resource.ResourceVersionMatchV2_NotOlderThan
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_NotOlderThan
case metav1.ResourceVersionMatchExact:
req.VersionMatchV2 = resource.ResourceVersionMatchV2_Exact
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Exact
default:
return nil, predicate, apierrors.NewBadRequest(
fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch),
@ -85,12 +84,12 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
switch v {
case utils.LabelKeyGetTrash:
req.Source = resource.ListRequest_TRASH
req.Source = resourcepb.ListRequest_TRASH
if vals[0] != "true" {
return nil, predicate, apierrors.NewBadRequest("expecting true for: " + v)
}
case utils.LabelKeyGetHistory:
req.Source = resource.ListRequest_HISTORY
req.Source = resourcepb.ListRequest_HISTORY
req.Options.Key.Name = vals[0]
}
@ -99,7 +98,7 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
return req, storage.Everything, nil
}
req.Options.Labels = append(req.Options.Labels, &resource.Requirement{
req.Options.Labels = append(req.Options.Labels, &resourcepb.Requirement{
Key: v,
Operator: string(r.Operator()),
Values: r.Values().List(),
@ -110,7 +109,7 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
if opts.Predicate.Field != nil && !opts.Predicate.Field.Empty() {
requirements := opts.Predicate.Field.Requirements()
for _, r := range requirements {
requirement := &resource.Requirement{Key: r.Field, Operator: string(r.Operator)}
requirement := &resourcepb.Requirement{Key: r.Field, Operator: string(r.Operator)}
if r.Value != "" {
requirement.Values = append(requirement.Values, r.Value)
}

@ -11,30 +11,30 @@ import (
"k8s.io/apiserver/pkg/storage"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestToListRequest(t *testing.T) {
tests := []struct {
name string
key *resource.ResourceKey
key *resourcepb.ResourceKey
opts storage.ListOptions
want *resource.ListRequest
want *resourcepb.ListRequest
wantPredicate storage.SelectionPredicate
wantErr error
}{
{
name: "basic list request",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
},
opts: storage.ListOptions{},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -46,7 +46,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with resource version",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -54,11 +54,11 @@ func TestToListRequest(t *testing.T) {
opts: storage.ListOptions{
ResourceVersion: "123",
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
ResourceVersion: 123,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -70,7 +70,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "invalid resource version",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -84,7 +84,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with label selector",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -94,15 +94,15 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{"key": "value"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
},
Labels: []*resource.Requirement{
Labels: []*resourcepb.Requirement{
{
Key: "key",
Operator: string(selection.Equals),
@ -118,7 +118,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with trash label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -128,13 +128,13 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelKeyGetTrash: "true"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Source: resource.ListRequest_TRASH,
Options: &resource.ListOptions{
Source: resourcepb.ListRequest_TRASH,
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -146,7 +146,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with history label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -156,13 +156,13 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelKeyGetHistory: "test-name"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -175,7 +175,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with fullpath label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@ -185,12 +185,12 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelGetFullpath: "true"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",

@ -37,6 +37,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
"github.com/grafana/grafana/pkg/tests/testsuite"
@ -130,7 +131,7 @@ func testSetup(t testing.TB, opts ...setupOption) (context.Context, storage.Inte
require.NoError(t, err)
// Issue a health check to ensure the server is initialized
_, err = server.IsHealthy(ctx, &resource.HealthCheckRequest{})
_, err = server.IsHealthy(ctx, &resourcepb.HealthCheckRequest{})
require.NoError(t, err)
case StorageTypeUnified:
if testing.Short() {
@ -377,7 +378,7 @@ func newPodList() runtime.Object {
return &example.PodList{}
}
func testKeyParser(val string) (*resource.ResourceKey, error) {
func testKeyParser(val string) (*resourcepb.ResourceKey, error) {
k, err := grafanaregistry.ParseKey(val)
if err != nil {
if strings.HasPrefix(val, "pods/") {
@ -407,7 +408,7 @@ func testKeyParser(val string) (*resource.ResourceKey, error) {
if k.Resource == "" {
return nil, apierrors.NewInternalError(fmt.Errorf("missing resource in request"))
}
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func NewFederatedClient(base resource.ResourceClient, sql legacysql.LegacyDatabaseProvider) resource.ResourceClient {
@ -26,7 +27,7 @@ type federatedClient struct {
}
// Get the resource stats
func (s *federatedClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (s *federatedClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
rsp, err := s.ResourceClient.GetStats(ctx, in, opts...)
if err != nil {
return nil, err

@ -31,7 +31,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/federated"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
@ -114,7 +114,7 @@ func TestDirectSQLStats(t *testing.T) {
ctx := context.Background()
ctx = request.WithNamespace(ctx, "default")
stats, err := store.GetStats(ctx, &resource.ResourceStatsRequest{
stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: "default",
Folder: folder1UID,
})
@ -147,7 +147,7 @@ func TestDirectSQLStats(t *testing.T) {
ctx := context.Background()
ctx = request.WithNamespace(ctx, "default")
stats, err := store.GetStats(ctx, &resource.ResourceStatsRequest{
stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: "default",
Folder: folder2UID,
})

@ -5,9 +5,10 @@ import (
"fmt"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Read stats from legacy SQL
@ -15,7 +16,7 @@ type LegacyStatsGetter struct {
SQL legacysql.LegacyDatabaseProvider
}
func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceStatsRequest) (*resource.ResourceStatsResponse, error) {
func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
info, err := claims.ParseNamespace(in.Namespace)
if err != nil {
return nil, fmt.Errorf("unable to read namespace")
@ -29,7 +30,7 @@ func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceS
return nil, err
}
rsp := &resource.ResourceStatsResponse{}
rsp := &resourcepb.ResourceStatsResponse{}
err = helper.DB.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
fn := func(table, where, g, r string, existCheck bool) error {
// if existCheck is true, do not error out if the table does not exist
@ -46,7 +47,7 @@ func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceS
if err != nil {
return err
}
rsp.Stats = append(rsp.Stats, &resource.ResourceStatsResponse_Stats{
rsp.Stats = append(rsp.Stats, &resourcepb.ResourceStatsResponse_Stats{
Group: g, // all legacy for now
Resource: r,
Count: count,

@ -9,11 +9,12 @@ import (
"google.golang.org/grpc/metadata"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
_ resource.BulkStoreClient = (*writerClient)(nil)
_ resource.BulkStore_BulkProcessClient = (*writerClient)(nil)
_ resourcepb.BulkStoreClient = (*writerClient)(nil)
_ resourcepb.BulkStore_BulkProcessClient = (*writerClient)(nil)
errUnimplemented = errors.New("not implemented (BulkResourceWriter as BulkStoreClient shim)")
)
@ -29,12 +30,12 @@ func NewBulkResourceWriterClient(writer resource.BulkResourceWriter) *writerClie
}
// Send implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) Send(req *resource.BulkRequest) error {
func (w *writerClient) Send(req *resourcepb.BulkRequest) error {
return w.writer.Write(w.ctx, req.Key, req.Value)
}
// BulkProcess implements resource.ResourceStoreClient.
func (w *writerClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error) {
func (w *writerClient) BulkProcess(ctx context.Context, _ ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) {
if w.ctx != nil {
return nil, fmt.Errorf("only one batch request supported")
}
@ -43,7 +44,7 @@ func (w *writerClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption)
}
// CloseAndRecv implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) CloseAndRecv() (*resource.BulkResponse, error) {
func (w *writerClient) CloseAndRecv() (*resourcepb.BulkResponse, error) {
return w.writer.CloseWithResults()
}
@ -63,12 +64,12 @@ func (w *writerClient) Header() (metadata.MD, error) {
}
// RecvMsg implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) RecvMsg(m any) error {
func (w *writerClient) RecvMsg(_ any) error {
return errUnimplemented
}
// SendMsg implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) SendMsg(m any) error {
func (w *writerClient) SendMsg(_ any) error {
return errUnimplemented
}

@ -7,6 +7,7 @@ import (
"github.com/apache/arrow-go/v18/parquet/file"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
@ -39,7 +40,7 @@ type parquetReader struct {
bufferIndex int
rowGroupIDX int
req *resource.BulkRequest
req *resourcepb.BulkRequest
err error
}
@ -60,14 +61,14 @@ func (r *parquetReader) Next() bool {
i := r.bufferIndex
r.bufferIndex++
r.req = &resource.BulkRequest{
Key: &resource.ResourceKey{
r.req = &resourcepb.BulkRequest{
Key: &resourcepb.ResourceKey{
Group: r.group.buffer[i].String(),
Resource: r.resource.buffer[i].String(),
Namespace: r.namespace.buffer[i].String(),
Name: r.name.buffer[i].String(),
},
Action: resource.BulkRequest_Action(r.action.buffer[i]),
Action: resourcepb.BulkRequest_Action(r.action.buffer[i]),
Value: r.value.buffer[i].Bytes(),
Folder: r.folder.buffer[i].String(),
}
@ -88,7 +89,7 @@ func (r *parquetReader) Next() bool {
}
// Request implements resource.BulkRequestIterator.
func (r *parquetReader) Request() *resource.BulkRequest {
func (r *parquetReader) Request() *resourcepb.BulkRequest {
return r.req
}

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestParquetWriteThenRead(t *testing.T) {
@ -75,7 +76,7 @@ func TestParquetWriteThenRead(t *testing.T) {
require.NoError(t, err)
for reader.Next() {
req := reader.Request()
keys = append(keys, req.Key.SearchID())
keys = append(keys, resource.SearchID(req.Key))
}
// Verify that we read all values
@ -101,14 +102,14 @@ func TestParquetWriteThenRead(t *testing.T) {
require.NoError(t, err)
for reader.Next() {
req := reader.Request()
keys = append(keys, req.Key.SearchID())
keys = append(keys, resource.SearchID(req.Key))
}
require.NoError(t, reader.err)
require.Empty(t, keys)
})
}
func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructured.Unstructured) (context.Context, *resource.ResourceKey, []byte) {
func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructured.Unstructured) (context.Context, *resourcepb.ResourceKey, []byte) {
if obj.GetKind() == "" {
obj.SetKind(res)
}
@ -116,7 +117,7 @@ func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructu
obj.SetAPIVersion(group + "/vXyz")
}
data, _ := obj.MarshalJSON()
return ctx, &resource.ResourceKey{
return ctx, &resourcepb.ResourceKey{
Namespace: obj.GetNamespace(),
Resource: res,
Group: group,

@ -13,8 +13,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
@ -28,8 +30,8 @@ func NewParquetWriter(f io.Writer) (*parquetWriter, error) {
schema: newSchema(nil),
buffer: 1024 * 10 * 100 * 10, // 10MB
logger: logging.DefaultLogger.With("logger", "parquet.writer"),
rsp: &resource.BulkResponse{},
summary: make(map[string]*resource.BulkResponse_Summary),
rsp: &resourcepb.BulkResponse{},
summary: make(map[string]*resourcepb.BulkResponse_Summary),
}
props := parquet.NewWriterProperties(
@ -44,7 +46,7 @@ func NewParquetWriter(f io.Writer) (*parquetWriter, error) {
}
// ProcessBulk implements resource.BulkProcessingBackend.
func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resource.BulkResponse {
func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resourcepb.BulkResponse {
defer func() { _ = w.Close() }()
var err error
@ -66,7 +68,7 @@ func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSe
w.logger.Warn("error closing parquet file", "err", err)
}
if rsp == nil {
rsp = &resource.BulkResponse{}
rsp = &resourcepb.BulkResponse{}
}
if err != nil {
rsp.Error = resource.AsErrorResult(err)
@ -92,11 +94,11 @@ type parquetWriter struct {
action *array.Int8Builder
value *array.StringBuilder
rsp *resource.BulkResponse
summary map[string]*resource.BulkResponse_Summary
rsp *resourcepb.BulkResponse
summary map[string]*resourcepb.BulkResponse_Summary
}
func (w *parquetWriter) CloseWithResults() (*resource.BulkResponse, error) {
func (w *parquetWriter) CloseWithResults() (*resourcepb.BulkResponse, error) {
err := w.Close()
return w.rsp, err
}
@ -143,7 +145,7 @@ func (w *parquetWriter) init() error {
return nil
}
func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, value []byte) error {
func (w *parquetWriter) Write(ctx context.Context, key *resourcepb.ResourceKey, value []byte) error {
w.rsp.Processed++
obj := &unstructured.Unstructured{}
err := obj.UnmarshalJSON(value)
@ -164,14 +166,14 @@ func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, va
w.folder.Append(meta.GetFolder())
w.value.Append(string(value))
var action resource.WatchEvent_Type
var action resourcepb.WatchEvent_Type
switch meta.GetGeneration() {
case 0, 1:
action = resource.WatchEvent_ADDED
action = resourcepb.WatchEvent_ADDED
case utils.DeletedGeneration:
action = resource.WatchEvent_DELETED
action = resourcepb.WatchEvent_DELETED
default:
action = resource.WatchEvent_MODIFIED
action = resourcepb.WatchEvent_MODIFIED
}
w.action.Append(int8(action))
@ -181,14 +183,14 @@ func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, va
return w.flush()
}
summary := w.summary[key.NSGR()]
summary := w.summary[resource.NSGR(key)]
if summary == nil {
summary = &resource.BulkResponse_Summary{
summary = &resourcepb.BulkResponse_Summary{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
}
w.summary[key.NSGR()] = summary
w.summary[resource.NSGR(key)] = summary
w.rsp.Summary = append(w.rsp.Summary, summary)
}
summary.Count++

@ -0,0 +1,98 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
import "resource.proto";
//----------------------------
// Blob Support
//----------------------------
message PutBlobRequest {
enum Method {
// Use the inline raw []byte
GRPC = 0;
// Get a signed URL and PUT the value
HTTP = 1;
}
// The resource that will use this blob
// NOTE: the name may not yet exist, but group+resource are required
ResourceKey resource = 1;
// How to upload
Method method = 2;
// Content type header
string content_type = 3;
// Raw value to write
// Not valid when method == HTTP
bytes value = 4;
}
message PutBlobResponse {
// Error details
ErrorResult error = 1;
// The blob uid. This must be saved into the resource to support access
string uid = 2;
// The URL where this value can be PUT
string url = 3;
// Size of the uploaded blob
int64 size = 4;
// Content hash used for an etag
string hash = 5;
// Validated mimetype (from content_type)
string mime_type = 6;
// Validated charset (from content_type)
string charset = 7;
}
message GetBlobRequest {
ResourceKey resource = 1;
// The new resource version
int64 resource_version = 2;
// Do not return a pre-signed URL (when possible)
bool must_proxy_bytes = 3;
// The blob UID -- when empty, the value is loaded from annotations in the matching resource
string uid = 4;
}
message GetBlobResponse {
// Error details
ErrorResult error = 1;
// (optional) When possible, the system will return a presigned URL
// that can be used to actually read the full blob+metadata
// When this is set, neither info nor value will be set
string url = 2;
// Content type
string content_type = 3;
// The raw object value
bytes value = 4;
}
service BlobStore {
// Upload a blob that will be saved in a resource
rpc PutBlob(PutBlobRequest) returns (PutBlobResponse);
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
rpc GetBlob(GetBlobRequest) returns (GetBlobResponse);
// NOTE: there is no direct access to delete blobs
// >> cleanup will be managed via garbage collection or direct access to the underlying storage
}

@ -1,10 +1,10 @@
version: v1
plugins:
- plugin: go
out: pkg/storage/unified/resource
out: pkg/storage/unified/resourcepb
opt: paths=source_relative
- plugin: go-grpc
out: pkg/storage/unified/resource
out: pkg/storage/unified/resourcepb
opt:
- paths=source_relative
- require_unimplemented_servers=false

@ -1,4 +1,6 @@
version: v1
version: v2
modules:
- path: .
breaking:
use:
- FILE

@ -1,7 +1,7 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resource";
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
message ResourceKey {
// Namespace (tenant)
@ -331,7 +331,7 @@ message WatchEvent {
message BulkRequest {
enum Action {
// will be an error
UNKNOWN = 0;
UNKNOWN = 0;
// Matches Watch event enum
ADDED = 1;
@ -345,7 +345,7 @@ message BulkRequest {
// Requested action
Action action = 2;
// The resource value
// The resource value
bytes value = 3;
// Hint that a new version will be written on-top of this
@ -382,135 +382,10 @@ message BulkResponse {
// Summary status for the processed values
repeated Summary summary = 3;
// Rejected
// Rejected
repeated Rejected rejected = 4;
}
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
message ResourceStatsRequest {
// Namespace (tenant)
string namespace = 1;
// An optional list of group/resource identifiers
// when empty, we assume searching across everything
// NOTE, this query may need to federate across a few storage instances
repeated string kinds = 2;
// Limit the stats within a folder (not recursive!)
string folder = 3;
}
message ResourceStatsResponse {
message Stats {
// Resource group
string group = 1;
// Resource name
string resource = 2;
// Number of items
int64 count = 3;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
repeated Stats stats = 2;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
string field = 1;
bool desc = 2; // defaults to ascending
}
message Facet {
string field = 1;
int64 limit = 2;
// For now, only term queries, eventually?
// numeric queries
// date queries
}
// The key must include namespace + group + resource
ListOptions options = 1;
// To search additional resource types, add additional keys to this list
// NOTE: queries will only support federation across kinds with common fields
repeated ResourceKey federated = 2;
// When a query exists, it is parsed and used to influence
// query string for chosen implementation (currently just bleve)
// The score is only relevant when a query exists
string query = 3;
// max results
int64 limit = 4;
// where to start the query (eg, From)
int64 offset = 5;
// sorting
repeated Sort sortBy = 6;
// calculate field statistics
map<string,Facet> facet = 7;
// the return fields (empty will return everything)
repeated string fields = 8;
// explain each result (added to the each row)
bool explain = 9;
bool is_deleted = 10;
int64 page = 11;
int64 permission = 12;
}
message ResourceSearchResponse {
message Facet {
string field = 1;
// The distinct terms
int64 total = 2;
// The number of documents that do *not* have this field
int64 missing = 3;
// Top term stats
repeated TermFacet terms = 4;
// numeric range
// date range facets
}
message TermFacet {
string term = 1;
int64 count = 2;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
ResourceKey key = 2;
// Query results
ResourceTable results = 3;
// The total hit count
int64 total_hits = 4;
// indicates how expensive was the query with respect to bytes read
double query_cost = 5;
// maximum score across all fields
double max_score = 6;
// Facet results
map<string,Facet> facet = 7;
}
// List items within a resource type & repository name
// Access control is managed above this request
message ListManagedObjectsRequest {
@ -712,86 +587,6 @@ message ResourceTableRow {
bytes object = 4;
}
//----------------------------
// Blob Support
//----------------------------
message PutBlobRequest {
enum Method {
// Use the inline raw []byte
GRPC = 0;
// Get a signed URL and PUT the value
HTTP = 1;
}
// The resource that will use this blob
// NOTE: the name may not yet exist, but group+resource are required
ResourceKey resource = 1;
// How to upload
Method method = 2;
// Content type header
string content_type = 3;
// Raw value to write
// Not valid when method == HTTP
bytes value = 4;
}
message PutBlobResponse {
// Error details
ErrorResult error = 1;
// The blob uid. This must be saved into the resource to support access
string uid = 2;
// The URL where this value can be PUT
string url = 3;
// Size of the uploaded blob
int64 size = 4;
// Content hash used for an etag
string hash = 5;
// Validated mimetype (from content_type)
string mime_type = 6;
// Validated charset (from content_type)
string charset = 7;
}
message GetBlobRequest {
ResourceKey resource = 1;
// The new resource version
int64 resource_version = 2;
// Do not return a pre-signed URL (when possible)
bool must_proxy_bytes = 3;
// The blob UID -- when empty, the value is loaded from annotations in the matching resource
string uid = 4;
}
message GetBlobResponse {
// Error details
ErrorResult error = 1;
// (optional) When possible, the system will return a presigned URL
// that can be used to actually read the full blob+metadata
// When this is set, neither info nor value will be set
string url = 2;
// Content type
string content_type = 3;
// The raw object value
bytes value = 4;
}
// This provides the CRUD+List+Watch support needed for a k8s apiserver
// The semantics and behaviors of this service are constrained by kubernetes
// This does not understand the resource schemas, only deals with json bytes
@ -820,15 +615,6 @@ service BulkStore {
rpc BulkProcess(stream BulkRequest) returns (BulkResponse);
}
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
}
// Query managed objects
// Results access control is based on access to the repository *not* the items
service ManagedObjectIndex {
@ -839,18 +625,6 @@ service ManagedObjectIndex {
rpc ListManagedObjects(ListManagedObjectsRequest) returns (ListManagedObjectsResponse);
}
service BlobStore {
// Upload a blob that will be saved in a resource
rpc PutBlob(PutBlobRequest) returns (PutBlobResponse);
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
rpc GetBlob(GetBlobRequest) returns (GetBlobResponse);
// NOTE: there is no direct access to delete blobs
// >> cleanup will be managed via garbage collection or direct access to the underlying storage
}
// Clients can use this service directly
// NOTE: This is read only, and no read afer write guarantees
service Diagnostics {

@ -0,0 +1,140 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
import "resource.proto";
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
}
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
message ResourceStatsRequest {
// Namespace (tenant)
string namespace = 1;
// An optional list of group/resource identifiers
// when empty, we assume searching across everything
// NOTE, this query may need to federate across a few storage instances
repeated string kinds = 2;
// Limit the stats within a folder (not recursive!)
string folder = 3;
}
message ResourceStatsResponse {
message Stats {
// Resource group
string group = 1;
// Resource name
string resource = 2;
// Number of items
int64 count = 3;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
repeated Stats stats = 2;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
string field = 1;
bool desc = 2; // defaults to ascending
}
message Facet {
string field = 1;
int64 limit = 2;
// For now, only term queries, eventually?
// numeric queries
// date queries
}
// The key must include namespace + group + resource
ListOptions options = 1;
// To search additional resource types, add additional keys to this list
// NOTE: queries will only support federation across kinds with common fields
repeated ResourceKey federated = 2;
// When a query exists, it is parsed and used to influence
// query string for chosen implementation (currently just bleve)
// The score is only relevant when a query exists
string query = 3;
// max results
int64 limit = 4;
// where to start the query (eg, From)
int64 offset = 5;
// sorting
repeated Sort sortBy = 6;
// calculate field statistics
map<string,Facet> facet = 7;
// the return fields (empty will return everything)
repeated string fields = 8;
// explain each result (added to the each row)
bool explain = 9;
bool is_deleted = 10;
int64 page = 11;
int64 permission = 12;
}
message ResourceSearchResponse {
message Facet {
string field = 1;
// The distinct terms
int64 total = 2;
// The number of documents that do *not* have this field
int64 missing = 3;
// Top term stats
repeated TermFacet terms = 4;
// numeric range
// date range facets
}
message TermFacet {
string term = 1;
int64 count = 2;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
ResourceKey key = 2;
// Query results
ResourceTable results = 3;
// The total hit count
int64 total_hits = 4;
// indicates how expensive was the query with respect to bytes read
double query_cost = 5;
// maximum score across all fields
double max_score = 6;
// Facet results
map<string,Facet> facet = 7;
}

@ -10,7 +10,9 @@ import (
"google.golang.org/grpc/metadata"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
const grpcMetaKeyCollection = "x-gf-batch-collection"
@ -25,28 +27,28 @@ type BulkRequestIterator interface {
Next() bool
// The next event we should process
Request() *BulkRequest
Request() *resourcepb.BulkRequest
// Rollback requested
RollbackRequested() bool
}
type BulkProcessingBackend interface {
ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *BulkResponse
ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *resourcepb.BulkResponse
}
type BulkResourceWriter interface {
io.Closer
Write(ctx context.Context, key *ResourceKey, value []byte) error
Write(ctx context.Context, key *resourcepb.ResourceKey, value []byte) error
// Called when finished writing
CloseWithResults() (*BulkResponse, error)
CloseWithResults() (*resourcepb.BulkResponse, error)
}
type BulkSettings struct {
// All requests will be within this namespace/group/resource
Collection []*ResourceKey
Collection []*resourcepb.ResourceKey
// The batch will include everything from the collection
// - all existing values will be removed/replaced if the batch completes successfully
@ -60,7 +62,7 @@ func (x *BulkSettings) ToMD() metadata.MD {
md := make(metadata.MD)
if len(x.Collection) > 0 {
for _, v := range x.Collection {
md[grpcMetaKeyCollection] = append(md[grpcMetaKeyCollection], v.SearchID())
md[grpcMetaKeyCollection] = append(md[grpcMetaKeyCollection], SearchID(v))
}
}
if x.RebuildCollection {
@ -78,8 +80,8 @@ func NewBulkSettings(md metadata.MD) (BulkSettings, error) {
switch k {
case grpcMetaKeyCollection:
for _, c := range v {
key := &ResourceKey{}
err := key.ReadSearchID(c)
key := &resourcepb.ResourceKey{}
err := ReadSearchID(key, c)
if err != nil {
return settings, fmt.Errorf("error reading collection metadata: %s / %w", c, err)
}
@ -96,12 +98,12 @@ func NewBulkSettings(md metadata.MD) (BulkSettings, error) {
// BulkWrite implements ResourceServer.
// All requests must be to the same NAMESPACE/GROUP/RESOURCE
func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
func (s *server) BulkProcess(stream resourcepb.BulkStore_BulkProcessServer) error {
ctx := stream.Context()
user, ok := authlib.AuthInfoFrom(ctx)
if !ok || user == nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
},
@ -110,8 +112,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "unable to read metadata gRPC request",
Code: http.StatusPreconditionFailed,
},
@ -123,8 +125,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
settings, err := NewBulkSettings(md)
if err != nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "error reading settings",
Reason: err.Error(),
Code: http.StatusPreconditionFailed,
@ -133,8 +135,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
if len(settings.Collection) < 1 {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Missing target collection(s) in request header",
Code: http.StatusBadRequest,
},
@ -151,8 +153,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
Verb: utils.VerbDeleteCollection,
})
if err != nil || !rsp.Allowed {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: fmt.Sprintf("Requester must be able to: %s", utils.VerbDeleteCollection),
Code: http.StatusForbidden,
},
@ -160,15 +162,15 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
// This will be called for each request -- with the folder ID
runner.checker[k.NSGR()], err = s.access.Compile(ctx, user, authlib.ListRequest{
runner.checker[NSGR(k)], err = s.access.Compile(ctx, user, authlib.ListRequest{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,
Verb: utils.VerbCreate,
})
if err != nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Unable to check `create` permission",
Code: http.StatusForbidden,
},
@ -176,8 +178,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
}
} else {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Bulk currently only supports RebuildCollection",
Code: http.StatusBadRequest,
},
@ -186,8 +188,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
backend, ok := s.backend.(BulkProcessingBackend)
if !ok {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "The server backend does not support batch processing",
Code: http.StatusNotImplemented,
},
@ -197,8 +199,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
// BulkProcess requests
rsp := backend.ProcessBulk(ctx, settings, runner)
if rsp == nil {
rsp = &BulkResponse{
Error: &ErrorResult{
rsp = &resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Code: http.StatusInternalServerError,
Message: "Nothing returned from process batch",
},
@ -218,7 +220,7 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}, summary.Count, summary.ResourceVersion)
if err != nil {
s.log.Warn("error building search index after batch load", "err", err)
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusInternalServerError,
Message: "err building search index: " + summary.Resource,
Reason: err.Error(),
@ -234,9 +236,9 @@ var (
)
type batchRunner struct {
stream BulkStore_BulkProcessServer
stream resourcepb.BulkStore_BulkProcessServer
rollback bool
request *BulkRequest
request *resourcepb.BulkRequest
err error
checker map[string]authlib.ItemChecker
}
@ -262,7 +264,7 @@ func (b *batchRunner) Next() bool {
if b.request != nil {
key := b.request.Key
k := key.NSGR()
k := NSGR(key)
checker, ok := b.checker[k]
if !ok {
b.err = fmt.Errorf("missing access control for: %s", k)
@ -277,7 +279,7 @@ func (b *batchRunner) Next() bool {
}
// Request implements BulkRequestIterator.
func (b *batchRunner) Request() *BulkRequest {
func (b *batchRunner) Request() *resourcepb.BulkRequest {
if b.rollback {
return nil
}

@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type CDKBackendOptions struct {
@ -73,7 +74,7 @@ type cdkBackend struct {
stream chan<- *WrittenEvent
}
func (s *cdkBackend) getPath(key *ResourceKey, rv int64) string {
func (s *cdkBackend) getPath(key *resourcepb.ResourceKey, rv int64) string {
var buffer bytes.Buffer
buffer.WriteString(s.root)
@ -116,9 +117,9 @@ func (s *cdkBackend) GetResourceStats(ctx context.Context, namespace string, min
}
func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64, err error) {
if event.Type == WatchEvent_ADDED {
if event.Type == resourcepb.WatchEvent_ADDED {
// ReadResource deals with deleted values (i.e. a file exists but has generation -999).
resp := s.ReadResource(ctx, &ReadRequest{Key: event.Key})
resp := s.ReadResource(ctx, &resourcepb.ReadRequest{Key: event.Key})
if resp.Error != nil && resp.Error.Code != http.StatusNotFound {
return 0, GetError(resp.Error)
}
@ -155,7 +156,7 @@ func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64
return rv, err
}
func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *BackendReadResponse {
func (s *cdkBackend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *BackendReadResponse {
rv := req.ResourceVersion
path := s.getPath(req.Key, rv)
@ -184,12 +185,12 @@ func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *Backen
if raw == nil && req.ResourceVersion > 0 {
if req.ResourceVersion > s.rv.Load() {
return &BackendReadResponse{
Error: &ErrorResult{
Error: &resourcepb.ErrorResult{
Code: http.StatusGatewayTimeout,
Reason: string(metav1.StatusReasonTimeout), // match etcd behavior
Message: "ResourceVersion is larger than max",
Details: &ErrorDetails{
Causes: []*ErrorCause{
Details: &resourcepb.ErrorDetails{
Causes: []*resourcepb.ErrorCause{
{
Reason: string(metav1.CauseTypeResourceVersionTooLarge),
Message: fmt.Sprintf("requested: %d, current %d", req.ResourceVersion, s.rv.Load()),
@ -201,7 +202,7 @@ func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *Backen
}
// If the there was an explicit request, get the latest
rsp := s.ReadResource(ctx, &ReadRequest{Key: req.Key})
rsp := s.ReadResource(ctx, &resourcepb.ReadRequest{Key: req.Key})
if rsp != nil && len(rsp.Value) > 0 {
raw = rsp.Value
rv = rsp.ResourceVersion
@ -233,8 +234,8 @@ func isDeletedValue(raw []byte) bool {
return false
}
func (s *cdkBackend) ListIterator(ctx context.Context, req *ListRequest, cb func(ListIterator) error) (int64, error) {
if req.Source != ListRequest_STORE {
func (s *cdkBackend) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(ListIterator) error) (int64, error) {
if req.Source != resourcepb.ListRequest_STORE {
return 0, fmt.Errorf("listing from history not supported in CDK backend")
}
@ -356,7 +357,7 @@ func (c *cdkListIterator) Folder() string {
var _ ListIterator = (*cdkListIterator)(nil)
func buildTree(ctx context.Context, s *cdkBackend, key *ResourceKey) (*cdkListIterator, error) {
func buildTree(ctx context.Context, s *cdkBackend, key *resourcepb.ResourceKey) (*cdkListIterator, error) {
byPrefix := make(map[string]*cdkResource)
path := s.getPath(key, 0)
iter := s.bucket.List(&blob.ListOptions{Prefix: path, Delimiter: ""}) // "" is recursive

@ -2,7 +2,7 @@ package resource
import (
"bytes"
context "context"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
@ -16,6 +16,7 @@ import (
"gocloud.dev/blob"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
// Supported drivers
_ "gocloud.dev/blob/azureblob"
@ -85,7 +86,7 @@ type cdkBlobSupport struct {
expiration time.Duration
}
func (s *cdkBlobSupport) getBlobPath(key *ResourceKey, info *utils.BlobInfo) (string, error) {
func (s *cdkBlobSupport) getBlobPath(key *resourcepb.ResourceKey, info *utils.BlobInfo) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(s.root)
@ -129,7 +130,7 @@ func (s *cdkBlobSupport) SupportsSignedURLs() bool {
return s.cansignurls
}
func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobRequest) (*PutBlobResponse, error) {
func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
info := &utils.BlobInfo{
UID: uuid.New().String(),
}
@ -139,8 +140,8 @@ func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobReques
return nil, err
}
rsp := &PutBlobResponse{Uid: info.UID, MimeType: info.MimeType, Charset: info.Charset}
if req.Method == PutBlobRequest_HTTP {
rsp := &resourcepb.PutBlobResponse{Uid: info.UID, MimeType: info.MimeType, Charset: info.Charset}
if req.Method == resourcepb.PutBlobRequest_HTTP {
rsp.Url, err = s.bucket.SignedURL(ctx, path, &blob.SignedURLOptions{
Method: "PUT",
Expiry: s.expiration,
@ -176,12 +177,13 @@ func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobReques
return rsp, err
}
func (s *cdkBlobSupport) GetResourceBlob(ctx context.Context, resource *ResourceKey, info *utils.BlobInfo, mustProxy bool) (*GetBlobResponse, error) {
func (s *cdkBlobSupport) GetResourceBlob(ctx context.Context, resource *resourcepb.ResourceKey, info *utils.BlobInfo,
mustProxy bool) (*resourcepb.GetBlobResponse, error) {
path, err := s.getBlobPath(resource, info)
if err != nil {
return nil, err
}
rsp := &GetBlobResponse{ContentType: info.ContentType()}
rsp := &resourcepb.GetBlobResponse{ContentType: info.ContentType()}
if mustProxy || !s.cansignurls {
rsp.Value, err = s.bucket.ReadAll(ctx, path)
return rsp, err

@ -7,6 +7,8 @@ import (
"testing"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
"gocloud.dev/blob/fileblob"
"gocloud.dev/blob/memblob"
@ -36,16 +38,16 @@ func TestCDKBlobStore(t *testing.T) {
t.Run("can write then read a blob", func(t *testing.T) {
raw := []byte(`{"hello": "world"}`)
key := &ResourceKey{
key := &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "rrrr", // can be anything
Namespace: "default",
Name: "fdgsv37qslr0ga",
}
rsp, err := store.PutResourceBlob(ctx, &PutBlobRequest{
rsp, err := store.PutResourceBlob(ctx, &resourcepb.PutBlobRequest{
Resource: key,
Method: PutBlobRequest_GRPC,
Method: resourcepb.PutBlobRequest_GRPC,
ContentType: "application/json",
Value: raw,
})

@ -23,36 +23,37 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type ResourceClient interface {
ResourceStoreClient
ResourceIndexClient
ManagedObjectIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
// Internal implementation
type resourceClient struct {
ResourceStoreClient
ResourceIndexClient
ManagedObjectIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
func NewLegacyResourceClient(channel grpc.ClientConnInterface) ResourceClient {
cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
@ -63,12 +64,12 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
grpcAuthInt := grpcutils.NewUnsafeAuthenticator(tracer)
for _, desc := range []*grpc.ServiceDesc{
&ResourceStore_ServiceDesc,
&ResourceIndex_ServiceDesc,
&ManagedObjectIndex_ServiceDesc,
&BlobStore_ServiceDesc,
&BulkStore_ServiceDesc,
&Diagnostics_ServiceDesc,
&resourcepb.ResourceStore_ServiceDesc,
&resourcepb.ResourceIndex_ServiceDesc,
&resourcepb.ManagedObjectIndex_ServiceDesc,
&resourcepb.BlobStore_ServiceDesc,
&resourcepb.BulkStore_ServiceDesc,
&resourcepb.Diagnostics_ServiceDesc,
} {
channel.RegisterService(
grpchan.InterceptServer(
@ -87,12 +88,12 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
@ -129,12 +130,12 @@ func NewRemoteResourceClient(tracer trace.Tracer, conn grpc.ClientConnInterface,
cc := grpchan.InterceptClientConn(conn, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}, nil
}

@ -10,18 +10,19 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Convert raw resource bytes into an IndexableDocument
type DocumentBuilder interface {
// Convert raw bytes into an document that can be written
BuildDocument(ctx context.Context, key *ResourceKey, rv int64, value []byte) (*IndexableDocument, error)
BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*IndexableDocument, error)
}
// Registry of the searchable document fields
type SearchableDocumentFields interface {
Fields() []string
Field(name string) *ResourceTableColumnDefinition
Field(name string) *resourcepb.ResourceTableColumnDefinition
}
// Some kinds will require special processing for their namespace
@ -51,7 +52,7 @@ type DocumentBuilderSupplier interface {
// Although public, this is *NOT* an end user interface
type IndexableDocument struct {
// The resource key
Key *ResourceKey `json:"key"`
Key *resourcepb.ResourceKey `json:"key"`
// The k8s name
Name string `json:"name,omitempty"`
@ -165,7 +166,7 @@ func (m ResourceReferences) Less(i, j int) bool {
}
// Create a new indexable document based on a generic k8s resource
func NewIndexableDocument(key *ResourceKey, rv int64, obj utils.GrafanaMetaAccessor) *IndexableDocument {
func NewIndexableDocument(key *resourcepb.ResourceKey, rv int64, obj utils.GrafanaMetaAccessor) *IndexableDocument {
title := obj.FindTitle(key.Name)
if title == key.Name {
// TODO: something wrong with FindTitle
@ -216,7 +217,7 @@ func StandardDocumentBuilder() DocumentBuilder {
type standardDocumentBuilder struct{}
func (s *standardDocumentBuilder) BuildDocument(ctx context.Context, key *ResourceKey, rv int64, value []byte) (*IndexableDocument, error) {
func (s *standardDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*IndexableDocument, error) {
tmp := &unstructured.Unstructured{}
err := tmp.UnmarshalJSON(value)
if err != nil {
@ -238,7 +239,7 @@ type searchableDocumentFields struct {
}
// This requires unique names
func NewSearchableDocumentFields(columns []*ResourceTableColumnDefinition) (SearchableDocumentFields, error) {
func NewSearchableDocumentFields(columns []*resourcepb.ResourceTableColumnDefinition) (SearchableDocumentFields, error) {
f := &searchableDocumentFields{
names: make([]string, len(columns)),
fields: make(map[string]*resourceTableColumn),
@ -261,7 +262,7 @@ func (x *searchableDocumentFields) Fields() []string {
return x.names
}
func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefinition {
func (x *searchableDocumentFields) Field(name string) *resourcepb.ResourceTableColumnDefinition {
name = strings.TrimPrefix(name, SEARCH_FIELD_PREFIX)
f, ok := x.fields[name]
@ -308,94 +309,94 @@ var standardSearchFields SearchableDocumentFields
func StandardSearchFields() SearchableDocumentFields {
standardSearchFieldsInit.Do(func() {
var err error
standardSearchFields, err = NewSearchableDocumentFields([]*ResourceTableColumnDefinition{
standardSearchFields, err = NewSearchableDocumentFields([]*resourcepb.ResourceTableColumnDefinition{
{
Name: SEARCH_FIELD_ID,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Unique Identifier. {namespace}/{group}/{resource}/{name}",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_GROUP_RESOURCE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "The resource kind: {group}/{resource}",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_NAMESPACE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Tenant isolation",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_NAME,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Kubernetes name. Unique identifier within a namespace+group+resource",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_TITLE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Display name for the resource",
},
{
Name: SEARCH_FIELD_DESCRIPTION,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "An account of the resource.",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
FreeText: true,
},
},
{
Name: SEARCH_FIELD_TAGS,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
IsArray: true,
Description: "Unique tags",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: SEARCH_FIELD_FOLDER,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Kubernetes name for the folder",
},
{
Name: SEARCH_FIELD_RV,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "resource version",
},
{
Name: SEARCH_FIELD_CREATED,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "created timestamp", // date?
},
{
Name: SEARCH_FIELD_EXPLAIN,
Type: ResourceTableColumnDefinition_OBJECT,
Type: resourcepb.ResourceTableColumnDefinition_OBJECT,
Description: "Explain why this result matches (depends on the engine)",
},
{
Name: SEARCH_FIELD_SCORE,
Type: ResourceTableColumnDefinition_DOUBLE,
Type: resourcepb.ResourceTableColumnDefinition_DOUBLE,
Description: "The search score",
},
{
Name: SEARCH_FIELD_LEGACY_ID,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Deprecated legacy id of the resource",
},
{
Name: SEARCH_FIELD_MANAGER_KIND,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Type of manager, which is responsible for managing the resource",
},
})

@ -8,6 +8,8 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestStandardDocumentBuilder(t *testing.T) {
@ -16,7 +18,7 @@ func TestStandardDocumentBuilder(t *testing.T) {
body, err := os.ReadFile("testdata/playlist-resource.json")
require.NoError(t, err)
doc, err := builder.BuildDocument(ctx, &ResourceKey{
doc, err := builder.BuildDocument(ctx, &resourcepb.ResourceKey{
Namespace: "default",
Group: "playlists.grafana.app",
Resource: "playlists",

@ -10,6 +10,8 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
grpcstatus "google.golang.org/grpc/status"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Package-level errors.
@ -29,18 +31,18 @@ var (
}
)
func NewBadRequestError(msg string) *ErrorResult {
return &ErrorResult{
func NewBadRequestError(msg string) *resourcepb.ErrorResult {
return &resourcepb.ErrorResult{
Message: msg,
Code: http.StatusBadRequest,
Reason: string(metav1.StatusReasonBadRequest),
}
}
func NewNotFoundError(key *ResourceKey) *ErrorResult {
return &ErrorResult{
func NewNotFoundError(key *resourcepb.ResourceKey) *resourcepb.ErrorResult {
return &resourcepb.ErrorResult{
Code: http.StatusNotFound,
Details: &ErrorDetails{
Details: &resourcepb.ErrorDetails{
Group: key.Group,
Kind: key.Resource, // yup, resource as kind same is true in apierrors.NewNotFound()
Name: key.Name,
@ -49,7 +51,7 @@ func NewNotFoundError(key *ResourceKey) *ErrorResult {
}
// Convert golang errors to status result errors that can be returned to a client
func AsErrorResult(err error) *ErrorResult {
func AsErrorResult(err error) *resourcepb.ErrorResult {
if err == nil {
return nil
}
@ -57,13 +59,13 @@ func AsErrorResult(err error) *ErrorResult {
var apistatus apierrors.APIStatus
if errors.As(err, &apistatus) {
s := apistatus.Status()
res := &ErrorResult{
res := &resourcepb.ErrorResult{
Message: s.Message,
Reason: string(s.Reason),
Code: s.Code,
}
if s.Details != nil {
res.Details = &ErrorDetails{
res.Details = &resourcepb.ErrorDetails{
Group: s.Details.Group,
Kind: s.Details.Kind,
Name: s.Details.Name,
@ -71,7 +73,7 @@ func AsErrorResult(err error) *ErrorResult {
RetryAfterSeconds: s.Details.RetryAfterSeconds,
}
for _, c := range s.Details.Causes {
res.Details.Causes = append(res.Details.Causes, &ErrorCause{
res.Details.Causes = append(res.Details.Causes, &resourcepb.ErrorCause{
Reason: string(c.Type),
Message: c.Message,
Field: c.Field,
@ -88,13 +90,13 @@ func AsErrorResult(err error) *ErrorResult {
code = runtime.HTTPStatusFromCode(st.Code())
}
return &ErrorResult{
return &resourcepb.ErrorResult{
Message: err.Error(),
Code: int32(code),
}
}
func GetError(res *ErrorResult) error {
func GetError(res *resourcepb.ErrorResult) error {
if res == nil {
return nil
}

@ -1,15 +1,16 @@
package resource
import (
context "context"
"context"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type WriteEvent struct {
Type WatchEvent_Type // ADDED, MODIFIED, DELETED
Key *ResourceKey // the request key
PreviousRV int64 // only for Update+Delete
Type resourcepb.WatchEvent_Type // ADDED, MODIFIED, DELETED
Key *resourcepb.ResourceKey // the request key
PreviousRV int64 // only for Update+Delete
// The json payload (without resourceVersion)
Value []byte
@ -23,8 +24,8 @@ type WriteEvent struct {
// WrittenEvent is a WriteEvent reported with a resource version.
type WrittenEvent struct {
Type WatchEvent_Type
Key *ResourceKey
Type resourcepb.WatchEvent_Type
Key *resourcepb.ResourceKey
PreviousRV int64
// The json payload (without resourceVersion)

@ -7,6 +7,7 @@ replace (
github.com/grafana/grafana/apps/folder => ../../../../apps/folder
github.com/grafana/grafana/pkg/apimachinery => ../../../apimachinery
github.com/grafana/grafana/pkg/apiserver => ../../../apiserver
github.com/grafana/grafana/pkg/storage/unified/resourcepb => ../resourcepb
)
require (
@ -21,6 +22,7 @@ require (
github.com/grafana/grafana/apps/dashboard v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/apps/folder v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/pkg/storage/unified/resourcepb v0.0.0-00010101000000-000000000000
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
github.com/hashicorp/golang-lru/v2 v2.0.7
@ -30,8 +32,7 @@ require (
go.opentelemetry.io/otel/trace v1.35.0
gocloud.dev v0.40.0
golang.org/x/sync v0.14.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
google.golang.org/grpc v1.72.1
k8s.io/apimachinery v0.32.3
)
@ -232,6 +233,7 @@ require (
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -850,8 +850,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save