pull/89331/head
Ryan McKinley 1 year ago
commit c9c18717f5
  1. 2
      go.work.sum
  2. 1
      pkg/apimachinery/auth/identity.go
  3. 1
      pkg/apimachinery/go.mod
  4. 66
      pkg/apimachinery/go.sum
  5. 44
      pkg/apimachinery/utils/meta.go
  6. 13
      pkg/apimachinery/utils/meta_test.go
  7. 3
      pkg/apiserver/rest/dualwriter_mode2.go
  8. 5
      pkg/registry/apis/dashboard/access/sql_dashboards.go
  9. 10
      pkg/registry/apis/dashboard/storage.go
  10. 2
      pkg/registry/apis/dashboardsnapshot/conversions.go
  11. 4
      pkg/registry/apis/dashboardsnapshot/register.go
  12. 5
      pkg/registry/apis/datasource/plugincontext.go
  13. 4
      pkg/registry/apis/datasource/register.go
  14. 4
      pkg/registry/apis/featuretoggle/features.go
  15. 5
      pkg/registry/apis/folders/conversions.go
  16. 2
      pkg/registry/apis/folders/legacy_storage.go
  17. 5
      pkg/registry/apis/folders/register.go
  18. 4
      pkg/registry/apis/peakq/storage.go
  19. 5
      pkg/registry/apis/playlist/conversions.go
  20. 4
      pkg/registry/apis/playlist/register.go
  21. 8
      pkg/registry/apis/scope/storage.go
  22. 4
      pkg/registry/apis/service/storage.go
  23. 8
      pkg/services/apiserver/storage/entity/selector.go
  24. 2
      pkg/services/apiserver/storage/entity/utils.go
  25. 2
      pkg/services/featuremgmt/toggles_gen_test.go
  26. 183
      pkg/storage/api/event.go
  27. 20
      pkg/storage/api/go.mod
  28. 23
      pkg/storage/api/go.sum
  29. 6
      pkg/storage/api/resource_test.go
  30. 196
      pkg/storage/api/validator.go
  31. 156
      pkg/storage/server/go.mod

@ -551,6 +551,7 @@ github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/
github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU= github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU=
github.com/grafana/grafana-plugin-sdk-go v0.229.0/go.mod h1:6V6ikT4ryva8MrAp7Bdz5fTJx3/ztzKvpMJFfpzr4CI= github.com/grafana/grafana-plugin-sdk-go v0.229.0/go.mod h1:6V6ikT4ryva8MrAp7Bdz5fTJx3/ztzKvpMJFfpzr4CI=
github.com/grafana/grafana-plugin-sdk-go v0.231.1-0.20240523124942-62dae9836284/go.mod h1:bNgmNmub1I7Mc8dzIncgNqHC5jTgSZPPHlZ3aG8HKJQ= github.com/grafana/grafana-plugin-sdk-go v0.231.1-0.20240523124942-62dae9836284/go.mod h1:bNgmNmub1I7Mc8dzIncgNqHC5jTgSZPPHlZ3aG8HKJQ=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240612101930-58f7032b3983/go.mod h1:0kbxZoLg70Lj6A8qDFjzx1rxqD5WwiGt3OjQqR6Kq/0=
github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
@ -949,7 +950,6 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g=
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=

@ -3,6 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
go 1.21.10 go 1.21.10
require ( require (
github.com/stretchr/testify v1.9.0
k8s.io/apimachinery v0.29.3 k8s.io/apimachinery v0.29.3
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
) )

@ -1,102 +1,36 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

@ -7,6 +7,8 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
) )
@ -49,8 +51,7 @@ type ResourceOriginInfo struct {
type GrafanaMetaAccessor interface { type GrafanaMetaAccessor interface {
metav1.Object metav1.Object
GetAPIVersion() string GetGroupVersionKind() schema.GroupVersionKind
GetKind() string
GetUpdatedTimestamp() (*time.Time, error) GetUpdatedTimestamp() (*time.Time, error)
SetUpdatedTimestamp(v *time.Time) SetUpdatedTimestamp(v *time.Time)
@ -407,28 +408,37 @@ func (m *grafanaMetaAccessor) SetUID(uid types.UID) {
m.obj.SetUID(uid) m.obj.SetUID(uid)
} }
func (m *grafanaMetaAccessor) GetAPIVersion() string { func (m *grafanaMetaAccessor) GetGroupVersionKind() schema.GroupVersionKind {
typ, ok := m.raw.(metav1.Type) obj, ok := m.raw.(runtime.Object)
if ok { if ok {
return typ.GetAPIVersion() return obj.GetObjectKind().GroupVersionKind()
}
val := m.r.FieldByName("APIVersion")
if val.IsValid() && val.Kind() == reflect.String {
return val.String()
} }
return ""
}
func (m *grafanaMetaAccessor) GetKind() string { gvk := schema.GroupVersionKind{}
apiVersion := ""
typ, ok := m.raw.(metav1.Type) typ, ok := m.raw.(metav1.Type)
if ok { if ok {
return typ.GetKind() apiVersion = typ.GetAPIVersion()
gvk.Kind = typ.GetKind()
} else {
val := m.r.FieldByName("APIVersion")
if val.IsValid() && val.Kind() == reflect.String {
apiVersion = val.String()
}
val = m.r.FieldByName("Kind")
if val.IsValid() && val.Kind() == reflect.String {
gvk.Kind = val.String()
}
} }
val := m.r.FieldByName("Kind") if apiVersion != "" {
if val.IsValid() && val.Kind() == reflect.String { gv, err := schema.ParseGroupVersion(apiVersion)
return val.String() if err == nil {
gvk.Group = gv.Group
gvk.Version = gv.Version
}
} }
return "" return gvk
} }
func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string {

@ -8,7 +8,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/apimachinery/utils"
) )
type TestResource struct { type TestResource struct {
@ -161,11 +161,19 @@ func TestMetaAccessor(t *testing.T) {
"grafana.app/originKey": "kkk", "grafana.app/originKey": "kkk",
"grafana.app/folder": "folderUID", "grafana.app/folder": "folderUID",
}, res.GetAnnotations()) }, res.GetAnnotations())
meta.SetNamespace("aaa")
require.Equal(t, "aaa", res.GetNamespace())
require.Equal(t, "aaa", meta.GetNamespace())
}) })
t.Run("find titles", func(t *testing.T) { t.Run("find titles", func(t *testing.T) {
// with a k8s object that has Spec.Title // with a k8s object that has Spec.Title
obj := &TestResource{ obj := &TestResource{
TypeMeta: metav1.TypeMeta{
Kind: "TestKIND",
APIVersion: "aaa/v1alpha2",
},
Spec: Spec{ Spec: Spec{
Title: "HELLO", Title: "HELLO",
}, },
@ -188,6 +196,9 @@ func TestMetaAccessor(t *testing.T) {
obj.Spec.Title = "" obj.Spec.Title = ""
require.Equal(t, "", meta.FindTitle("xxx")) require.Equal(t, "", meta.FindTitle("xxx"))
gvk := meta.GetGroupVersionKind()
require.Equal(t, "aaa/v1alpha2, Kind=TestKIND", gvk.String())
// with a k8s object without Spec.Title // with a k8s object without Spec.Title
obj2 := &TestResource2{} obj2 := &TestResource2{}

@ -4,7 +4,6 @@ import (
"context" "context"
"time" "time"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
@ -14,6 +13,8 @@ import (
"k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/selection"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/apimachinery/utils"
) )
type DualWriterMode2 struct { type DualWriterMode2 struct {

@ -11,13 +11,14 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"github.com/grafana/grafana/pkg/apimachinery/utils"
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/sqlstore/session"
@ -335,7 +336,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
if err == nil { if err == nil {
dash.ResourceVersion = fmt.Sprintf("%d", created.UnixMilli()) dash.ResourceVersion = fmt.Sprintf("%d", created.UnixMilli())
dash.Namespace = a.namespacer(orgId) dash.Namespace = a.namespacer(orgId)
dash.UID = utils.CalculateClusterWideUID(dash) dash.UID = gapiutil.CalculateClusterWideUID(dash)
dash.SetCreationTimestamp(v1.NewTime(created)) dash.SetCreationTimestamp(v1.NewTime(created))
meta, err := utils.MetaAccessor(dash) meta, err := utils.MetaAccessor(dash)
if err != nil { if err != nil {

@ -4,14 +4,14 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
) )
var _ grafanarest.Storage = (*storage)(nil) var _ grafanarest.Storage = (*storage)(nil)
@ -34,7 +34,7 @@ func newStorage(scheme *runtime.Scheme) (*storage, error) {
DeleteStrategy: strategy, DeleteStrategy: strategy,
} }
store.TableConvertor = utils.NewTableConverter( store.TableConvertor = gapiutil.NewTableConverter(
store.DefaultQualifiedResource, store.DefaultQualifiedResource,
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -6,9 +6,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1" dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
) )

@ -27,7 +27,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -138,7 +138,7 @@ func (b *SnapshotsAPIBuilder) GetAPIGroupInfo(
namespacer: b.namespacer, namespacer: b.namespacer,
options: b.options, options: b.options,
} }
legacyStore.tableConverter = utils.NewTableConverter( legacyStore.tableConverter = gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -7,11 +7,12 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1" "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
) )
@ -135,7 +136,7 @@ func asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceCo
}, },
Title: ds.Name, Title: ds.Name,
} }
v.UID = utils.CalculateClusterWideUID(v) // indicates if the value changed on the server v.UID = gapiutil.CalculateClusterWideUID(v) // indicates if the value changed on the server
meta, err := utils.MetaAccessor(v) meta, err := utils.MetaAccessor(v)
if err != nil { if err != nil {
meta.SetUpdatedTimestamp(&ds.Updated) meta.SetUpdatedTimestamp(&ds.Updated)

@ -28,7 +28,7 @@ import (
"github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/models"
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema" "github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds"
@ -212,7 +212,7 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo(
storage[conn.StoragePath()] = &connectionAccess{ storage[conn.StoragePath()] = &connectionAccess{
datasources: b.datasources, datasources: b.datasources,
resourceInfo: conn, resourceInfo: conn,
tableConverter: utils.NewTableConverter( tableConverter: gapiutil.NewTableConverter(
conn.GroupResource(), conn.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -12,7 +12,7 @@ import (
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1" "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
) )
@ -37,7 +37,7 @@ func NewFeaturesStorage() *featuresStorage {
resourceInfo := v0alpha1.FeatureResourceInfo resourceInfo := v0alpha1.FeatureResourceInfo
return &featuresStorage{ return &featuresStorage{
resource: &resourceInfo, resource: &resourceInfo,
tableConverter: utils.NewTableConverter( tableConverter: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -5,9 +5,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
) )
@ -45,6 +46,6 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper)
if v.ParentUID != "" { if v.ParentUID != "" {
meta.SetFolder(v.ParentUID) meta.SetFolder(v.ParentUID)
} }
f.UID = utils.CalculateClusterWideUID(f) f.UID = gapiutil.CalculateClusterWideUID(f)
return f return f
} }

@ -11,11 +11,11 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/storage/entity" "github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"

@ -15,13 +15,14 @@ import (
common "k8s.io/kube-openapi/pkg/common" common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/spec3"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/apiserver/builder" "github.com/grafana/grafana/pkg/apiserver/builder"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
@ -111,7 +112,7 @@ func (b *FolderAPIBuilder) GetAPIGroupInfo(
legacyStore := &legacyStorage{ legacyStore := &legacyStorage{
service: b.folderSvc, service: b.folderSvc,
namespacer: b.namespacer, namespacer: b.namespacer,
tableConverter: utils.NewTableConverter( tableConverter: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -12,7 +12,7 @@ import (
peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
) )
var _ grafanarest.Storage = (*storage)(nil) var _ grafanarest.Storage = (*storage)(nil)
@ -31,7 +31,7 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*
PredicateFunc: grafanaregistry.Matcher, PredicateFunc: grafanaregistry.Matcher,
DefaultQualifiedResource: resourceInfo.GroupResource(), DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: utils.NewTableConverter( TableConvertor: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -10,9 +10,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
playlistsvc "github.com/grafana/grafana/pkg/services/playlist" playlistsvc "github.com/grafana/grafana/pkg/services/playlist"
) )
@ -100,7 +101,7 @@ func convertToK8sResource(v *playlistsvc.PlaylistDTO, namespacer request.Namespa
} }
} }
p.UID = utils.CalculateClusterWideUID(p) p.UID = gapiutil.CalculateClusterWideUID(p)
return p return p
} }

@ -20,7 +20,7 @@ import (
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
playlistsvc "github.com/grafana/grafana/pkg/services/playlist" playlistsvc "github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -103,7 +103,7 @@ func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
service: b.service, service: b.service,
namespacer: b.namespacer, namespacer: b.namespacer,
} }
legacyStore.tableConverter = utils.NewTableConverter( legacyStore.tableConverter = gapiutil.NewTableConverter(
resource.GroupResource(), resource.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -15,7 +15,7 @@ import (
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1" scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
) )
var _ grafanarest.Storage = (*storage)(nil) var _ grafanarest.Storage = (*storage)(nil)
@ -34,7 +34,7 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette
PredicateFunc: Matcher, PredicateFunc: Matcher,
DefaultQualifiedResource: resourceInfo.GroupResource(), DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: utils.NewTableConverter( TableConvertor: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},
@ -76,7 +76,7 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.
PredicateFunc: Matcher, PredicateFunc: Matcher,
DefaultQualifiedResource: resourceInfo.GroupResource(), DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: utils.NewTableConverter( TableConvertor: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},
@ -118,7 +118,7 @@ func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsG
PredicateFunc: Matcher, PredicateFunc: Matcher,
DefaultQualifiedResource: resourceInfo.GroupResource(), DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: utils.NewTableConverter( TableConvertor: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -12,7 +12,7 @@ import (
service "github.com/grafana/grafana/pkg/apis/service/v0alpha1" service "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
) )
var _ grafanarest.Storage = (*storage)(nil) var _ grafanarest.Storage = (*storage)(nil)
@ -31,7 +31,7 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*
PredicateFunc: grafanaregistry.Matcher, PredicateFunc: grafanaregistry.Matcher,
DefaultQualifiedResource: resourceInfo.GroupResource(), DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: utils.NewTableConverter( TableConvertor: gapiutil.NewTableConverter(
resourceInfo.GroupResource(), resourceInfo.GroupResource(),
[]metav1.TableColumnDefinition{ []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"}, {Name: "Name", Type: "string", Format: "name"},

@ -1,13 +1,13 @@
package entity package entity
import ( import (
"github.com/grafana/grafana/pkg/services/apiserver/utils"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/selection"
"github.com/grafana/grafana/pkg/apimachinery/utils"
) )
const FolderAnnoKey = "grafana.app/folder"
const SortByKey = "grafana.app/sortBy" const SortByKey = "grafana.app/sortBy"
const ListDeletedKey = "grafana.app/listDeleted" const ListDeletedKey = "grafana.app/listDeleted"
const ListHistoryKey = "grafana.app/listHistory" const ListHistoryKey = "grafana.app/listHistory"
@ -37,9 +37,9 @@ func ReadLabelSelectors(selector labels.Selector) (Requirements, labels.Selector
for _, r := range labelSelectors { for _, r := range labelSelectors {
switch r.Key() { switch r.Key() {
case FolderAnnoKey: case utils.AnnoKeyFolder:
if (r.Operator() != selection.Equals) && (r.Operator() != selection.DoubleEquals) { if (r.Operator() != selection.Equals) && (r.Operator() != selection.DoubleEquals) {
return requirements, newSelector, apierrors.NewBadRequest(FolderAnnoKey + " label selector only supports equality") return requirements, newSelector, apierrors.NewBadRequest(utils.AnnoKeyFolder + " label selector only supports equality")
} }
folder := r.Values().List()[0] folder := r.Values().List()[0]
requirements.Folder = &folder requirements.Folder = &folder

@ -15,7 +15,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/apimachinery/utils"
entityStore "github.com/grafana/grafana/pkg/services/store/entity" entityStore "github.com/grafana/grafana/pkg/services/store/entity"
) )

@ -20,8 +20,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
featuretoggleapi "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1" featuretoggleapi "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt/strcase" "github.com/grafana/grafana/pkg/services/featuremgmt/strcase"
) )

@ -1,16 +1,10 @@
package api package api
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"sync/atomic"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
) )
@ -42,178 +36,3 @@ func (e *WriteEvent) BadRequest(err error, message string, a ...any) *WriteEvent
} }
return e return e
} }
// Verify that all required fields are set, and the user has permission to set the common metadata fields
type EventValidator interface {
PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error)
PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error)
}
type EventValidatorOptions struct {
// Get the next EventID
NextEventID func() int64
// Check if a user has access to write folders
// When this is nil, no resources can have folders configured
FolderAccess func(ctx context.Context, user identity.Requester, uid string) bool
// When configured, this will make sure a user is allowed to save to a given origin
OriginAccess func(ctx context.Context, user identity.Requester, origin string) bool
}
type eventValidator struct {
opts EventValidatorOptions
}
func NewEventValidator(opts EventValidatorOptions) EventValidator {
if opts.NextEventID == nil {
counter := atomic.Int64{}
opts.NextEventID = func() int64 {
return counter.Add(1)
}
}
return &eventValidator{opts}
}
type dummyObject struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
}
var _ EventValidator = &eventValidator{}
func (v *eventValidator) newEvent(ctx context.Context, key *ResourceKey, value, oldValue []byte) *WriteEvent {
var err error
event := &WriteEvent{
EventID: v.opts.NextEventID(),
Key: key,
Value: value,
}
event.Requester, err = appcontext.User(ctx)
if err != nil {
return event.BadRequest(err, "unable to get user")
}
dummy := &dummyObject{}
err = json.Unmarshal(value, dummy)
if err != nil {
return event.BadRequest(err, "error reading json")
}
obj, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object in json")
}
if obj.GetUID() == "" {
return event.BadRequest(nil, "the UID must be set")
}
if obj.GetGenerateName() != "" {
return event.BadRequest(nil, "can not save value with generate name")
}
if obj.GetKind() == "" {
return event.BadRequest(nil, "expecting resources with a kind in the body")
}
if obj.GetName() != key.Name {
return event.BadRequest(nil, "key name does not match the name in the body")
}
if obj.GetNamespace() != key.Namespace {
return event.BadRequest(nil, "key namespace does not match the namespace in the body")
}
folder := obj.GetFolder()
if folder != "" {
if v.opts.FolderAccess == nil {
return event.BadRequest(err, "folders are not supported")
} else if !v.opts.FolderAccess(ctx, event.Requester, folder) {
return event.BadRequest(err, "unable to add resource to folder") // 403?
}
}
origin, err := obj.GetOriginInfo()
if err != nil {
return event.BadRequest(err, "invalid origin info")
}
if origin != nil && v.opts.OriginAccess != nil {
if !v.opts.OriginAccess(ctx, event.Requester, origin.Name) {
return event.BadRequest(err, "not allowed to write resource to origin (%s)", origin.Name)
}
}
event.Object = obj
// This is an update
if oldValue != nil {
dummy := &dummyObject{}
err = json.Unmarshal(oldValue, dummy)
if err != nil {
return event.BadRequest(err, "error reading old json value")
}
old, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object inside old json")
}
if key.Name != old.GetName() {
return event.BadRequest(err, "the old value has a different name (%s != %s)", key.Name, old.GetName())
}
// Can not change creation timestamps+user
if obj.GetCreatedBy() != old.GetCreatedBy() {
return event.BadRequest(err, "can not change the created by metadata (%s != %s)", obj.GetCreatedBy(), old.GetCreatedBy())
}
if obj.GetCreationTimestamp() != old.GetCreationTimestamp() {
return event.BadRequest(err, "can not change the CreationTimestamp metadata (%v != %v)", obj.GetCreationTimestamp(), old.GetCreationTimestamp())
}
oldFolder := obj.GetFolder()
if oldFolder != folder {
event.FolderChanged = true
}
event.OldObject = old
} else if folder != "" {
event.FolderChanged = true
}
return event
}
func (v *eventValidator) PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, nil)
event.Operation = ResourceOperation_CREATED
if event.Status != nil {
return event, nil
}
// Make sure the created by user is accurate
//----------------------------------------
val := event.Object.GetCreatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
// Create can not have updated properties
//----------------------------------------
if event.Object.GetUpdatedBy() != "" {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
ts, err := event.Object.GetUpdatedTimestamp()
if err != nil {
return event.BadRequest(nil, fmt.Sprintf("invalid timestamp: %s", err)), nil
}
if ts != nil {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyUpdatedTimestamp), nil
}
return event, nil
}
func (v *eventValidator) PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, current)
event.Operation = ResourceOperation_UPDATED
if event.Status != nil {
return event, nil
}
// Make sure the update user is accurate
//----------------------------------------
val := event.Object.GetUpdatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyUpdatedBy), nil
}
return event, nil
}

@ -5,11 +5,31 @@ go 1.21.10
require ( require (
google.golang.org/grpc v1.64.0 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1 google.golang.org/protobuf v1.34.1
k8s.io/apimachinery v0.29.3
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240612101930-58f7032b3983
) )
require ( require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/net v0.26.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
) )

@ -1,7 +1,30 @@
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=

@ -1,16 +1,16 @@
package resource_test package api_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/store/resource" "github.com/grafana/grafana/pkg/storage/api"
) )
func TestResourceModels(t *testing.T) { func TestResourceModels(t *testing.T) {
t.Run("key namespaced path", func(t *testing.T) { t.Run("key namespaced path", func(t *testing.T) {
key := &resource.Key{} key := &api.ResourceKey{}
require.Equal(t, "__cluster__", key.NamespacedPath()) require.Equal(t, "__cluster__", key.NamespacedPath())
key.Namespace = "ns" key.Namespace = "ns"

@ -0,0 +1,196 @@
package api
import (
"context"
"encoding/json"
"fmt"
"sync/atomic"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
// Verify that all required fields are set, and the user has permission to set the common metadata fields
type EventValidator interface {
PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error)
PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error)
}
type EventValidatorOptions struct {
// Get the next EventID
NextEventID func() int64
// Check if a user has access to write folders
// When this is nil, no resources can have folders configured
FolderAccess func(ctx context.Context, user identity.Requester, uid string) bool
// When configured, this will make sure a user is allowed to save to a given origin
OriginAccess func(ctx context.Context, user identity.Requester, origin string) bool
}
type eventValidator struct {
opts EventValidatorOptions
}
func NewEventValidator(opts EventValidatorOptions) EventValidator {
if opts.NextEventID == nil {
counter := atomic.Int64{}
opts.NextEventID = func() int64 {
return counter.Add(1)
}
}
return &eventValidator{opts}
}
type dummyObject struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
}
var _ EventValidator = &eventValidator{}
func (v *eventValidator) newEvent(ctx context.Context, key *ResourceKey, value, oldValue []byte) *WriteEvent {
var err error
event := &WriteEvent{
EventID: v.opts.NextEventID(),
Key: key,
Value: value,
}
event.Requester, err = appcontext.User(ctx)
if err != nil {
return event.BadRequest(err, "unable to get user")
}
dummy := &dummyObject{}
err = json.Unmarshal(value, dummy)
if err != nil {
return event.BadRequest(err, "error reading json")
}
obj, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object in json")
}
if obj.GetUID() == "" {
return event.BadRequest(nil, "the UID must be set")
}
if obj.GetGenerateName() != "" {
return event.BadRequest(nil, "can not save value with generate name")
}
gvk := obj.GetGroupVersionKind()
if gvk.Kind == "" {
return event.BadRequest(nil, "expecting resources with a kind in the body")
}
if gvk.Version == "" {
return event.BadRequest(nil, "expecting resources with an apiVersion")
}
if gvk.Group != "" && gvk.Group != key.Group {
return event.BadRequest(nil, "group in key does not match group in the body (%s != %s)", key.Group, gvk.Group)
}
if obj.GetName() != key.Name {
return event.BadRequest(nil, "key name does not match the name in the body")
}
if obj.GetNamespace() != key.Namespace {
return event.BadRequest(nil, "key namespace does not match the namespace in the body")
}
folder := obj.GetFolder()
if folder != "" {
if v.opts.FolderAccess == nil {
return event.BadRequest(err, "folders are not supported")
} else if !v.opts.FolderAccess(ctx, event.Requester, folder) {
return event.BadRequest(err, "unable to add resource to folder") // 403?
}
}
origin, err := obj.GetOriginInfo()
if err != nil {
return event.BadRequest(err, "invalid origin info")
}
if origin != nil && v.opts.OriginAccess != nil {
if !v.opts.OriginAccess(ctx, event.Requester, origin.Name) {
return event.BadRequest(err, "not allowed to write resource to origin (%s)", origin.Name)
}
}
event.Object = obj
// This is an update
if oldValue != nil {
dummy := &dummyObject{}
err = json.Unmarshal(oldValue, dummy)
if err != nil {
return event.BadRequest(err, "error reading old json value")
}
old, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object inside old json")
}
if key.Name != old.GetName() {
return event.BadRequest(err, "the old value has a different name (%s != %s)", key.Name, old.GetName())
}
// Can not change creation timestamps+user
if obj.GetCreatedBy() != old.GetCreatedBy() {
return event.BadRequest(err, "can not change the created by metadata (%s != %s)", obj.GetCreatedBy(), old.GetCreatedBy())
}
if obj.GetCreationTimestamp() != old.GetCreationTimestamp() {
return event.BadRequest(err, "can not change the CreationTimestamp metadata (%v != %v)", obj.GetCreationTimestamp(), old.GetCreationTimestamp())
}
oldFolder := obj.GetFolder()
if oldFolder != folder {
event.FolderChanged = true
}
event.OldObject = old
} else if folder != "" {
event.FolderChanged = true
}
return event
}
func (v *eventValidator) PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, nil)
event.Operation = ResourceOperation_CREATED
if event.Status != nil {
return event, nil
}
// Make sure the created by user is accurate
//----------------------------------------
val := event.Object.GetCreatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
// Create can not have updated properties
//----------------------------------------
if event.Object.GetUpdatedBy() != "" {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
ts, err := event.Object.GetUpdatedTimestamp()
if err != nil {
return event.BadRequest(nil, fmt.Sprintf("invalid timestamp: %s", err)), nil
}
if ts != nil {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyUpdatedTimestamp), nil
}
return event, nil
}
func (v *eventValidator) PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, current)
event.Operation = ResourceOperation_UPDATED
if event.Status != nil {
return event, nil
}
// Make sure the update user is accurate
//----------------------------------------
val := event.Object.GetUpdatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyUpdatedBy), nil
}
return event, nil
}

@ -1,156 +0,0 @@
module github.com/grafana/grafana/pkg/storage/server
go 1.21.10
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/gorilla/mux v1.8.1
github.com/grafana/grafana-plugin-sdk-go v0.234.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f
github.com/prometheus/client_golang v1.19.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel/trace v1.26.0
golang.org/x/mod v0.18.0
k8s.io/apimachinery v0.29.3
k8s.io/apiserver v0.29.2
k8s.io/client-go v0.29.3
k8s.io/component-base v0.29.2
k8s.io/klog/v2 v2.120.1
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getkin/kin-openapi v0.124.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.17.7 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240416155748-26353dc0451f // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.14.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
github.com/urfave/cli v1.22.15 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.3 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
Loading…
Cancel
Save