From 3ae95a6eb1194e5b612bcfc9dbea32663ef4ad29 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 12 Jun 2024 14:39:37 +0300 Subject: [PATCH] K8s: Update grafana resource metadata accessor (#89074) --- pkg/apimachinery/go.mod | 1 + pkg/apimachinery/go.sum | 66 --- pkg/apimachinery/utils/meta.go | 463 ++++++++++++++++++ .../utils/meta_test.go | 13 +- pkg/apiserver/rest/dualwriter_mode2.go | 3 +- .../apis/dashboard/access/sql_dashboards.go | 5 +- pkg/registry/apis/dashboard/storage.go | 10 +- .../apis/dashboardsnapshot/conversions.go | 2 +- .../apis/dashboardsnapshot/register.go | 4 +- pkg/registry/apis/datasource/plugincontext.go | 5 +- pkg/registry/apis/datasource/register.go | 4 +- pkg/registry/apis/featuretoggle/features.go | 4 +- pkg/registry/apis/folders/conversions.go | 5 +- pkg/registry/apis/folders/legacy_storage.go | 2 +- pkg/registry/apis/folders/register.go | 5 +- pkg/registry/apis/peakq/storage.go | 4 +- pkg/registry/apis/playlist/conversions.go | 5 +- pkg/registry/apis/playlist/register.go | 4 +- pkg/registry/apis/scope/storage.go | 8 +- pkg/registry/apis/service/storage.go | 4 +- .../apiserver/storage/entity/selector.go | 8 +- .../apiserver/storage/entity/utils.go | 2 +- pkg/services/apiserver/utils/meta.go | 268 ---------- pkg/services/featuremgmt/toggles_gen_test.go | 2 +- 24 files changed, 522 insertions(+), 375 deletions(-) create mode 100644 pkg/apimachinery/utils/meta.go rename pkg/{services/apiserver => apimachinery}/utils/meta_test.go (93%) delete mode 100644 pkg/services/apiserver/utils/meta.go diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 538ea4a2837..5b9f923e651 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,6 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.21.10 require ( + github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.29.3 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 ) diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 0bf28eee126..522786f0c93 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -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/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/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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/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/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/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/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/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/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/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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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/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/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/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= -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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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/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/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/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/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go new file mode 100644 index 00000000000..ac5079a2a8e --- /dev/null +++ b/pkg/apimachinery/utils/meta.go @@ -0,0 +1,463 @@ +package utils + +import ( + "fmt" + "reflect" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +// Annotation keys + +const AnnoKeyCreatedBy = "grafana.app/createdBy" +const AnnoKeyUpdatedTimestamp = "grafana.app/updatedTimestamp" +const AnnoKeyUpdatedBy = "grafana.app/updatedBy" +const AnnoKeyFolder = "grafana.app/folder" +const AnnoKeySlug = "grafana.app/slug" + +// Identify where values came from + +const AnnoKeyOriginName = "grafana.app/originName" +const AnnoKeyOriginPath = "grafana.app/originPath" +const AnnoKeyOriginKey = "grafana.app/originKey" +const AnnoKeyOriginTimestamp = "grafana.app/originTimestamp" + +// ResourceOriginInfo is saved in annotations. This is used to identify where the resource came from +// This object can model the same data as our existing provisioning table or a more general git sync +type ResourceOriginInfo struct { + // Name of the origin/provisioning source + Name string `json:"name,omitempty"` + + // The path within the named origin above (external_id in the existing dashboard provisioing) + Path string `json:"path,omitempty"` + + // Verification/identification key (check_sum in existing dashboard provisioning) + Key string `json:"key,omitempty"` + + // Origin modification timestamp when the resource was saved + // This will be before the resource updated time + Timestamp *time.Time `json:"time,omitempty"` + + // Avoid extending + _ any `json:"-"` +} + +// Accessor functions for k8s objects +type GrafanaMetaAccessor interface { + metav1.Object + + GetGroupVersionKind() schema.GroupVersionKind + + GetUpdatedTimestamp() (*time.Time, error) + SetUpdatedTimestamp(v *time.Time) + SetUpdatedTimestampMillis(unix int64) + GetCreatedBy() string + SetCreatedBy(user string) + GetUpdatedBy() string + SetUpdatedBy(user string) + GetFolder() string + SetFolder(uid string) + GetSlug() string + SetSlug(v string) + + GetOriginInfo() (*ResourceOriginInfo, error) + SetOriginInfo(info *ResourceOriginInfo) + GetOriginName() string + GetOriginPath() string + GetOriginKey() string + GetOriginTimestamp() (*time.Time, error) + + // Find a title in the object + // This will reflect the object and try to get: + // * spec.title + // * spec.name + // * title + // and return an empty string if nothing was found + FindTitle(defaultTitle string) string +} + +var _ GrafanaMetaAccessor = (*grafanaMetaAccessor)(nil) + +type grafanaMetaAccessor struct { + raw interface{} // the original object (it implements metav1.Object) + obj metav1.Object + r reflect.Value +} + +// Accessor takes an arbitrary object pointer and returns meta.Interface. +// obj must be a pointer to an API type. An error is returned if the minimum +// required fields are missing. Fields that are not required return the default +// value and are a no-op if set. +func MetaAccessor(raw interface{}) (GrafanaMetaAccessor, error) { + obj, err := meta.Accessor(raw) + if err != nil { + return nil, err + } + + // look for Spec.Title or Spec.Name + r := reflect.ValueOf(raw) + if r.Kind() == reflect.Ptr || r.Kind() == reflect.Interface { + r = r.Elem() + } + return &grafanaMetaAccessor{raw, obj, r}, nil +} + +func (m *grafanaMetaAccessor) Object() metav1.Object { + return m.obj +} + +func (m *grafanaMetaAccessor) set(key string, val string) { + anno := m.obj.GetAnnotations() + if val == "" { + if anno != nil { + delete(anno, key) + } + } else { + if anno == nil { + anno = make(map[string]string) + } + anno[key] = val + } + m.obj.SetAnnotations(anno) +} + +func (m *grafanaMetaAccessor) get(key string) string { + return m.obj.GetAnnotations()[key] +} + +func (m *grafanaMetaAccessor) GetUpdatedTimestamp() (*time.Time, error) { + v, ok := m.obj.GetAnnotations()[AnnoKeyUpdatedTimestamp] + if !ok || v == "" { + return nil, nil + } + t, err := time.Parse(time.RFC3339, v) + if err != nil { + return nil, fmt.Errorf("invalid updated timestamp: %s", err.Error()) + } + t = t.UTC() + return &t, nil +} + +func (m *grafanaMetaAccessor) SetUpdatedTimestampMillis(v int64) { + if v > 0 { + t := time.UnixMilli(v) + m.SetUpdatedTimestamp(&t) + } else { + m.set(AnnoKeyUpdatedTimestamp, "") // will clear the annotation + } +} + +func (m *grafanaMetaAccessor) SetUpdatedTimestamp(v *time.Time) { + txt := "" + if v != nil && v.Unix() != 0 { + txt = v.UTC().Format(time.RFC3339) + } + m.set(AnnoKeyUpdatedTimestamp, txt) +} + +func (m *grafanaMetaAccessor) GetCreatedBy() string { + return m.get(AnnoKeyCreatedBy) +} + +func (m *grafanaMetaAccessor) SetCreatedBy(user string) { + m.set(AnnoKeyCreatedBy, user) +} + +func (m *grafanaMetaAccessor) GetUpdatedBy() string { + return m.get(AnnoKeyUpdatedBy) +} + +func (m *grafanaMetaAccessor) SetUpdatedBy(user string) { + m.set(AnnoKeyUpdatedBy, user) +} + +func (m *grafanaMetaAccessor) GetFolder() string { + return m.get(AnnoKeyFolder) +} + +func (m *grafanaMetaAccessor) SetFolder(uid string) { + m.set(AnnoKeyFolder, uid) +} + +func (m *grafanaMetaAccessor) GetSlug() string { + return m.get(AnnoKeySlug) +} + +func (m *grafanaMetaAccessor) SetSlug(v string) { + m.set(AnnoKeySlug, v) +} + +func (m *grafanaMetaAccessor) SetOriginInfo(info *ResourceOriginInfo) { + anno := m.obj.GetAnnotations() + if anno == nil { + if info == nil { + return + } + anno = make(map[string]string, 0) + } + + delete(anno, AnnoKeyOriginName) + delete(anno, AnnoKeyOriginPath) + delete(anno, AnnoKeyOriginKey) + delete(anno, AnnoKeyOriginTimestamp) + if info != nil && info.Name != "" { + anno[AnnoKeyOriginName] = info.Name + if info.Path != "" { + anno[AnnoKeyOriginPath] = info.Path + } + if info.Key != "" { + anno[AnnoKeyOriginKey] = info.Key + } + if info.Timestamp != nil { + anno[AnnoKeyOriginTimestamp] = info.Timestamp.UTC().Format(time.RFC3339) + } + } + m.obj.SetAnnotations(anno) +} + +func (m *grafanaMetaAccessor) GetOriginInfo() (*ResourceOriginInfo, error) { + v, ok := m.obj.GetAnnotations()[AnnoKeyOriginName] + if !ok { + return nil, nil + } + t, err := m.GetOriginTimestamp() + return &ResourceOriginInfo{ + Name: v, + Path: m.GetOriginPath(), + Key: m.GetOriginKey(), + Timestamp: t, + }, err +} + +func (m *grafanaMetaAccessor) GetOriginName() string { + return m.get(AnnoKeyOriginName) +} + +func (m *grafanaMetaAccessor) GetOriginPath() string { + return m.get(AnnoKeyOriginPath) +} + +func (m *grafanaMetaAccessor) GetOriginKey() string { + return m.get(AnnoKeyOriginKey) +} + +func (m *grafanaMetaAccessor) GetOriginTimestamp() (*time.Time, error) { + v, ok := m.obj.GetAnnotations()[AnnoKeyOriginTimestamp] + if !ok || v == "" { + return nil, nil + } + t, err := time.Parse(time.RFC3339, v) + if err != nil { + return nil, fmt.Errorf("invalid origin timestamp: %s", err.Error()) + } + return &t, nil +} + +// GetAnnotations implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetAnnotations() map[string]string { + return m.obj.GetAnnotations() +} + +// GetCreationTimestamp implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetCreationTimestamp() metav1.Time { + return m.obj.GetCreationTimestamp() +} + +// GetDeletionGracePeriodSeconds implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetDeletionGracePeriodSeconds() *int64 { + return m.obj.GetDeletionGracePeriodSeconds() +} + +// GetDeletionTimestamp implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetDeletionTimestamp() *metav1.Time { + return m.obj.GetDeletionTimestamp() +} + +// GetFinalizers implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetFinalizers() []string { + return m.obj.GetFinalizers() +} + +// GetGenerateName implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetGenerateName() string { + return m.obj.GetGenerateName() +} + +// GetGeneration implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetGeneration() int64 { + return m.obj.GetGeneration() +} + +// GetLabels implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetLabels() map[string]string { + return m.obj.GetLabels() +} + +// GetManagedFields implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetManagedFields() []metav1.ManagedFieldsEntry { + return m.obj.GetManagedFields() +} + +// GetName implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetName() string { + return m.obj.GetName() +} + +// GetNamespace implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetNamespace() string { + return m.obj.GetNamespace() +} + +// GetOwnerReferences implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetOwnerReferences() []metav1.OwnerReference { + return m.obj.GetOwnerReferences() +} + +// GetResourceVersion implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetResourceVersion() string { + return m.obj.GetResourceVersion() +} + +// GetSelfLink implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetSelfLink() string { + return m.obj.GetSelfLink() +} + +// GetUID implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) GetUID() types.UID { + return m.obj.GetUID() +} + +// SetAnnotations implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetAnnotations(annotations map[string]string) { + m.obj.SetAnnotations(annotations) +} + +// SetCreationTimestamp implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetCreationTimestamp(timestamp metav1.Time) { + m.obj.SetCreationTimestamp(timestamp) +} + +// SetDeletionGracePeriodSeconds implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetDeletionGracePeriodSeconds(v *int64) { + m.obj.SetDeletionGracePeriodSeconds(v) +} + +// SetDeletionTimestamp implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetDeletionTimestamp(timestamp *metav1.Time) { + m.obj.SetDeletionTimestamp(timestamp) +} + +// SetFinalizers implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetFinalizers(finalizers []string) { + m.obj.SetFinalizers(finalizers) +} + +// SetGenerateName implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetGenerateName(name string) { + m.obj.SetGenerateName(name) +} + +// SetGeneration implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetGeneration(generation int64) { + m.obj.SetGeneration(generation) +} + +// SetLabels implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetLabels(labels map[string]string) { + m.obj.SetLabels(labels) +} + +// SetManagedFields implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { + m.obj.SetManagedFields(managedFields) +} + +// SetName implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetName(name string) { + m.obj.SetName(name) +} + +// SetNamespace implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetNamespace(namespace string) { + m.obj.SetNamespace(namespace) +} + +// SetOwnerReferences implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetOwnerReferences(v []metav1.OwnerReference) { + m.obj.SetOwnerReferences(v) +} + +// SetResourceVersion implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetResourceVersion(version string) { + m.obj.SetResourceVersion(version) +} + +// SetSelfLink implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetSelfLink(selfLink string) { + m.obj.SetSelfLink(selfLink) +} + +// SetUID implements GrafanaMetaAccessor. +func (m *grafanaMetaAccessor) SetUID(uid types.UID) { + m.obj.SetUID(uid) +} + +func (m *grafanaMetaAccessor) GetGroupVersionKind() schema.GroupVersionKind { + obj, ok := m.raw.(runtime.Object) + if ok { + return obj.GetObjectKind().GroupVersionKind() + } + + gvk := schema.GroupVersionKind{} + apiVersion := "" + + typ, ok := m.raw.(metav1.Type) + if ok { + 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() + } + } + if apiVersion != "" { + gv, err := schema.ParseGroupVersion(apiVersion) + if err == nil { + gvk.Group = gv.Group + gvk.Version = gv.Version + } + } + return gvk +} + +func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { + // look for Spec.Title or Spec.Name + spec := m.r.FieldByName("Spec") + if spec.Kind() == reflect.Struct { + title := spec.FieldByName("Title") + if title.IsValid() && title.Kind() == reflect.String { + return title.String() + } + name := spec.FieldByName("Name") + if name.IsValid() && name.Kind() == reflect.String { + return name.String() + } + } + + title := m.r.FieldByName("Title") + if title.IsValid() && title.Kind() == reflect.String { + return title.String() + } + return defaultTitle +} diff --git a/pkg/services/apiserver/utils/meta_test.go b/pkg/apimachinery/utils/meta_test.go similarity index 93% rename from pkg/services/apiserver/utils/meta_test.go rename to pkg/apimachinery/utils/meta_test.go index e8555bdff6e..cee670a0870 100644 --- a/pkg/services/apiserver/utils/meta_test.go +++ b/pkg/apimachinery/utils/meta_test.go @@ -8,7 +8,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "github.com/grafana/grafana/pkg/services/apiserver/utils" + "github.com/grafana/grafana/pkg/apimachinery/utils" ) type TestResource struct { @@ -161,11 +161,19 @@ func TestMetaAccessor(t *testing.T) { "grafana.app/originKey": "kkk", "grafana.app/folder": "folderUID", }, 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) { // with a k8s object that has Spec.Title obj := &TestResource{ + TypeMeta: metav1.TypeMeta{ + Kind: "TestKIND", + APIVersion: "aaa/v1alpha2", + }, Spec: Spec{ Title: "HELLO", }, @@ -188,6 +196,9 @@ func TestMetaAccessor(t *testing.T) { obj.Spec.Title = "" 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 obj2 := &TestResource2{} diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index bde88142282..8ff4a653cc9 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/grafana/grafana/pkg/services/apiserver/utils" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -14,6 +13,8 @@ import ( "k8s.io/apimachinery/pkg/selection" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/klog/v2" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) type DualWriterMode2 struct { diff --git a/pkg/registry/apis/dashboard/access/sql_dashboards.go b/pkg/registry/apis/dashboard/access/sql_dashboards.go index ea16a6af7f4..13db6037eb5 100644 --- a/pkg/registry/apis/dashboard/access/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/access/sql_dashboards.go @@ -11,13 +11,14 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "github.com/grafana/grafana/pkg/apimachinery/utils" dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" "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/provisioning" "github.com/grafana/grafana/pkg/services/sqlstore/session" @@ -335,7 +336,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) { if err == nil { dash.ResourceVersion = fmt.Sprintf("%d", created.UnixMilli()) dash.Namespace = a.namespacer(orgId) - dash.UID = utils.CalculateClusterWideUID(dash) + dash.UID = gapiutil.CalculateClusterWideUID(dash) dash.SetCreationTimestamp(v1.NewTime(created)) meta, err := utils.MetaAccessor(dash) if err != nil { diff --git a/pkg/registry/apis/dashboard/storage.go b/pkg/registry/apis/dashboard/storage.go index 965fc0d5df3..1bc6b8800e7 100644 --- a/pkg/registry/apis/dashboard/storage.go +++ b/pkg/registry/apis/dashboard/storage.go @@ -4,14 +4,14 @@ import ( "fmt" "time" - "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/services/apiserver/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "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" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -34,7 +34,7 @@ func newStorage(scheme *runtime.Scheme) (*storage, error) { DeleteStrategy: strategy, } - store.TableConvertor = utils.NewTableConverter( + store.TableConvertor = gapiutil.NewTableConverter( store.DefaultQualifiedResource, []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/dashboardsnapshot/conversions.go b/pkg/registry/apis/dashboardsnapshot/conversions.go index 8071799de5a..09c2d283ab5 100644 --- a/pkg/registry/apis/dashboardsnapshot/conversions.go +++ b/pkg/registry/apis/dashboardsnapshot/conversions.go @@ -6,9 +6,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/grafana/grafana/pkg/apimachinery/utils" 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/utils" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" ) diff --git a/pkg/registry/apis/dashboardsnapshot/register.go b/pkg/registry/apis/dashboardsnapshot/register.go index 96f946245de..c9414687aa3 100644 --- a/pkg/registry/apis/dashboardsnapshot/register.go +++ b/pkg/registry/apis/dashboardsnapshot/register.go @@ -27,7 +27,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "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" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -138,7 +138,7 @@ func (b *SnapshotsAPIBuilder) GetAPIGroupInfo( namespacer: b.namespacer, options: b.options, } - legacyStore.tableConverter = utils.NewTableConverter( + legacyStore.tableConverter = gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/datasource/plugincontext.go b/pkg/registry/apis/datasource/plugincontext.go index 2c0a58aca12..5871b45ab4c 100644 --- a/pkg/registry/apis/datasource/plugincontext.go +++ b/pkg/registry/apis/datasource/plugincontext.go @@ -7,11 +7,12 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" 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/infra/appcontext" "github.com/grafana/grafana/pkg/plugins" "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/pluginsintegration/plugincontext" ) @@ -135,7 +136,7 @@ func asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceCo }, 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) if err != nil { meta.SetUpdatedTimestamp(&ds.Updated) diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index e6d2d838bd8..c1eab887823 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -28,7 +28,7 @@ import ( "github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/registry/apis/query/queryschema" "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/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" @@ -212,7 +212,7 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo( storage[conn.StoragePath()] = &connectionAccess{ datasources: b.datasources, resourceInfo: conn, - tableConverter: utils.NewTableConverter( + tableConverter: gapiutil.NewTableConverter( conn.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/featuretoggle/features.go b/pkg/registry/apis/featuretoggle/features.go index 6902d8bb3c8..e98f1b3569d 100644 --- a/pkg/registry/apis/featuretoggle/features.go +++ b/pkg/registry/apis/featuretoggle/features.go @@ -12,7 +12,7 @@ import ( common "github.com/grafana/grafana/pkg/apimachinery/apis/common/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" ) @@ -37,7 +37,7 @@ func NewFeaturesStorage() *featuresStorage { resourceInfo := v0alpha1.FeatureResourceInfo return &featuresStorage{ resource: &resourceInfo, - tableConverter: utils.NewTableConverter( + tableConverter: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/folders/conversions.go b/pkg/registry/apis/folders/conversions.go index 9c9678a4909..1c4b8435379 100644 --- a/pkg/registry/apis/folders/conversions.go +++ b/pkg/registry/apis/folders/conversions.go @@ -5,9 +5,10 @@ import ( 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/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" ) @@ -45,6 +46,6 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) if v.ParentUID != "" { meta.SetFolder(v.ParentUID) } - f.UID = utils.CalculateClusterWideUID(f) + f.UID = gapiutil.CalculateClusterWideUID(f) return f } diff --git a/pkg/registry/apis/folders/legacy_storage.go b/pkg/registry/apis/folders/legacy_storage.go index 781aacd7526..5baedfdcefa 100644 --- a/pkg/registry/apis/folders/legacy_storage.go +++ b/pkg/registry/apis/folders/legacy_storage.go @@ -11,11 +11,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "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/infra/appcontext" "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/utils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/util" diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 685d1ab737a..62eafeb7290 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -15,13 +15,14 @@ import ( common "k8s.io/kube-openapi/pkg/common" "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/apiserver/builder" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/services/accesscontrol" "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/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -111,7 +112,7 @@ func (b *FolderAPIBuilder) GetAPIGroupInfo( legacyStore := &legacyStorage{ service: b.folderSvc, namespacer: b.namespacer, - tableConverter: utils.NewTableConverter( + tableConverter: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/peakq/storage.go b/pkg/registry/apis/peakq/storage.go index d9403ba624f..33a0d305100 100644 --- a/pkg/registry/apis/peakq/storage.go +++ b/pkg/registry/apis/peakq/storage.go @@ -12,7 +12,7 @@ import ( peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" 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) @@ -31,7 +31,7 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (* PredicateFunc: grafanaregistry.Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: utils.NewTableConverter( + TableConvertor: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/playlist/conversions.go b/pkg/registry/apis/playlist/conversions.go index 632803970ab..a5a00313157 100644 --- a/pkg/registry/apis/playlist/conversions.go +++ b/pkg/registry/apis/playlist/conversions.go @@ -10,9 +10,10 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" 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/utils" + gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" 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 } diff --git a/pkg/registry/apis/playlist/register.go b/pkg/registry/apis/playlist/register.go index fa4ea3ab2e4..b9902946a6c 100644 --- a/pkg/registry/apis/playlist/register.go +++ b/pkg/registry/apis/playlist/register.go @@ -20,7 +20,7 @@ import ( grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/kvstore" "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" "github.com/grafana/grafana/pkg/setting" ) @@ -103,7 +103,7 @@ func (b *PlaylistAPIBuilder) GetAPIGroupInfo( service: b.service, namespacer: b.namespacer, } - legacyStore.tableConverter = utils.NewTableConverter( + legacyStore.tableConverter = gapiutil.NewTableConverter( resource.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/scope/storage.go b/pkg/registry/apis/scope/storage.go index bbda3223993..ce1fb7b4c51 100644 --- a/pkg/registry/apis/scope/storage.go +++ b/pkg/registry/apis/scope/storage.go @@ -15,7 +15,7 @@ import ( scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" 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) @@ -34,7 +34,7 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: utils.NewTableConverter( + TableConvertor: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -76,7 +76,7 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic. PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: utils.NewTableConverter( + TableConvertor: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, @@ -118,7 +118,7 @@ func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsG PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: utils.NewTableConverter( + TableConvertor: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/registry/apis/service/storage.go b/pkg/registry/apis/service/storage.go index 8c47eb5e564..9fb6510e5f3 100644 --- a/pkg/registry/apis/service/storage.go +++ b/pkg/registry/apis/service/storage.go @@ -12,7 +12,7 @@ import ( service "github.com/grafana/grafana/pkg/apis/service/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" 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) @@ -31,7 +31,7 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (* PredicateFunc: grafanaregistry.Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: utils.NewTableConverter( + TableConvertor: gapiutil.NewTableConverter( resourceInfo.GroupResource(), []metav1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name"}, diff --git a/pkg/services/apiserver/storage/entity/selector.go b/pkg/services/apiserver/storage/entity/selector.go index 9c5739386d1..7bd946a60c7 100644 --- a/pkg/services/apiserver/storage/entity/selector.go +++ b/pkg/services/apiserver/storage/entity/selector.go @@ -1,13 +1,13 @@ package entity import ( - "github.com/grafana/grafana/pkg/services/apiserver/utils" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) -const FolderAnnoKey = "grafana.app/folder" const SortByKey = "grafana.app/sortBy" const ListDeletedKey = "grafana.app/listDeleted" const ListHistoryKey = "grafana.app/listHistory" @@ -37,9 +37,9 @@ func ReadLabelSelectors(selector labels.Selector) (Requirements, labels.Selector for _, r := range labelSelectors { switch r.Key() { - case FolderAnnoKey: + case utils.AnnoKeyFolder: 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] requirements.Folder = &folder diff --git a/pkg/services/apiserver/storage/entity/utils.go b/pkg/services/apiserver/storage/entity/utils.go index 03588662c7a..c7f854297ca 100644 --- a/pkg/services/apiserver/storage/entity/utils.go +++ b/pkg/services/apiserver/storage/entity/utils.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" "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" ) diff --git a/pkg/services/apiserver/utils/meta.go b/pkg/services/apiserver/utils/meta.go deleted file mode 100644 index e62828c1eb0..00000000000 --- a/pkg/services/apiserver/utils/meta.go +++ /dev/null @@ -1,268 +0,0 @@ -package utils - -import ( - "fmt" - "reflect" - "time" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Annotation keys - -const AnnoKeyCreatedBy = "grafana.app/createdBy" -const AnnoKeyUpdatedTimestamp = "grafana.app/updatedTimestamp" -const AnnoKeyUpdatedBy = "grafana.app/updatedBy" -const AnnoKeyFolder = "grafana.app/folder" -const AnnoKeySlug = "grafana.app/slug" - -// Identify where values came from - -const AnnoKeyOriginName = "grafana.app/originName" -const AnnoKeyOriginPath = "grafana.app/originPath" -const AnnoKeyOriginKey = "grafana.app/originKey" -const AnnoKeyOriginTimestamp = "grafana.app/originTimestamp" - -// ResourceOriginInfo is saved in annotations. This is used to identify where the resource came from -// This object can model the same data as our existing provisioning table or a more general git sync -type ResourceOriginInfo struct { - // Name of the origin/provisioning source - Name string `json:"name,omitempty"` - - // The path within the named origin above (external_id in the existing dashboard provisioing) - Path string `json:"path,omitempty"` - - // Verification/identification key (check_sum in existing dashboard provisioning) - Key string `json:"key,omitempty"` - - // Origin modification timestamp when the resource was saved - // This will be before the resource updated time - Timestamp *time.Time `json:"time,omitempty"` - - // Avoid extending - _ any `json:"-"` -} - -// Accessor functions for k8s objects -type GrafanaResourceMetaAccessor interface { - GetUpdatedTimestamp() (*time.Time, error) - SetUpdatedTimestamp(v *time.Time) - SetUpdatedTimestampMillis(unix int64) - GetCreatedBy() string - SetCreatedBy(user string) - GetUpdatedBy() string - SetUpdatedBy(user string) - GetFolder() string - SetFolder(uid string) - GetSlug() string - SetSlug(v string) - - GetOriginInfo() (*ResourceOriginInfo, error) - SetOriginInfo(info *ResourceOriginInfo) - GetOriginName() string - GetOriginPath() string - GetOriginKey() string - GetOriginTimestamp() (*time.Time, error) - - // Find a title in the object - // This will reflect the object and try to get: - // * spec.title - // * spec.name - // * title - // and return an empty string if nothing was found - FindTitle(defaultTitle string) string -} - -var _ GrafanaResourceMetaAccessor = (*grafanaResourceMetaAccessor)(nil) - -type grafanaResourceMetaAccessor struct { - raw interface{} // the original object (it implements metav1.Object) - obj metav1.Object -} - -// Accessor takes an arbitrary object pointer and returns meta.Interface. -// obj must be a pointer to an API type. An error is returned if the minimum -// required fields are missing. Fields that are not required return the default -// value and are a no-op if set. -func MetaAccessor(raw interface{}) (GrafanaResourceMetaAccessor, error) { - obj, err := meta.Accessor(raw) - if err != nil { - return nil, err - } - return &grafanaResourceMetaAccessor{raw, obj}, nil -} - -func (m *grafanaResourceMetaAccessor) set(key string, val string) { - anno := m.obj.GetAnnotations() - if val == "" { - if anno != nil { - delete(anno, key) - } - } else { - if anno == nil { - anno = make(map[string]string) - } - anno[key] = val - } - m.obj.SetAnnotations(anno) -} - -func (m *grafanaResourceMetaAccessor) get(key string) string { - return m.obj.GetAnnotations()[key] -} - -func (m *grafanaResourceMetaAccessor) GetUpdatedTimestamp() (*time.Time, error) { - v, ok := m.obj.GetAnnotations()[AnnoKeyUpdatedTimestamp] - if !ok || v == "" { - return nil, nil - } - t, err := time.Parse(time.RFC3339, v) - if err != nil { - return nil, fmt.Errorf("invalid updated timestamp: %s", err.Error()) - } - t = t.UTC() - return &t, nil -} - -func (m *grafanaResourceMetaAccessor) SetUpdatedTimestampMillis(v int64) { - if v > 0 { - t := time.UnixMilli(v) - m.SetUpdatedTimestamp(&t) - } else { - m.set(AnnoKeyUpdatedTimestamp, "") // will clear the annotation - } -} - -func (m *grafanaResourceMetaAccessor) SetUpdatedTimestamp(v *time.Time) { - txt := "" - if v != nil && v.Unix() != 0 { - txt = v.UTC().Format(time.RFC3339) - } - m.set(AnnoKeyUpdatedTimestamp, txt) -} - -func (m *grafanaResourceMetaAccessor) GetCreatedBy() string { - return m.get(AnnoKeyCreatedBy) -} - -func (m *grafanaResourceMetaAccessor) SetCreatedBy(user string) { - m.set(AnnoKeyCreatedBy, user) -} - -func (m *grafanaResourceMetaAccessor) GetUpdatedBy() string { - return m.get(AnnoKeyUpdatedBy) -} - -func (m *grafanaResourceMetaAccessor) SetUpdatedBy(user string) { - m.set(AnnoKeyUpdatedBy, user) -} - -func (m *grafanaResourceMetaAccessor) GetFolder() string { - return m.get(AnnoKeyFolder) -} - -func (m *grafanaResourceMetaAccessor) SetFolder(uid string) { - m.set(AnnoKeyFolder, uid) -} - -func (m *grafanaResourceMetaAccessor) GetSlug() string { - return m.get(AnnoKeySlug) -} - -func (m *grafanaResourceMetaAccessor) SetSlug(v string) { - m.set(AnnoKeySlug, v) -} - -func (m *grafanaResourceMetaAccessor) SetOriginInfo(info *ResourceOriginInfo) { - anno := m.obj.GetAnnotations() - if anno == nil { - if info == nil { - return - } - anno = make(map[string]string, 0) - } - - delete(anno, AnnoKeyOriginName) - delete(anno, AnnoKeyOriginPath) - delete(anno, AnnoKeyOriginKey) - delete(anno, AnnoKeyOriginTimestamp) - if info != nil && info.Name != "" { - anno[AnnoKeyOriginName] = info.Name - if info.Path != "" { - anno[AnnoKeyOriginPath] = info.Path - } - if info.Key != "" { - anno[AnnoKeyOriginKey] = info.Key - } - if info.Timestamp != nil { - anno[AnnoKeyOriginTimestamp] = info.Timestamp.UTC().Format(time.RFC3339) - } - } - m.obj.SetAnnotations(anno) -} - -func (m *grafanaResourceMetaAccessor) GetOriginInfo() (*ResourceOriginInfo, error) { - v, ok := m.obj.GetAnnotations()[AnnoKeyOriginName] - if !ok { - return nil, nil - } - t, err := m.GetOriginTimestamp() - return &ResourceOriginInfo{ - Name: v, - Path: m.GetOriginPath(), - Key: m.GetOriginKey(), - Timestamp: t, - }, err -} - -func (m *grafanaResourceMetaAccessor) GetOriginName() string { - return m.get(AnnoKeyOriginName) -} - -func (m *grafanaResourceMetaAccessor) GetOriginPath() string { - return m.get(AnnoKeyOriginPath) -} - -func (m *grafanaResourceMetaAccessor) GetOriginKey() string { - return m.get(AnnoKeyOriginKey) -} - -func (m *grafanaResourceMetaAccessor) GetOriginTimestamp() (*time.Time, error) { - v, ok := m.obj.GetAnnotations()[AnnoKeyOriginTimestamp] - if !ok || v == "" { - return nil, nil - } - t, err := time.Parse(time.RFC3339, v) - if err != nil { - return nil, fmt.Errorf("invalid origin timestamp: %s", err.Error()) - } - return &t, nil -} - -func (m *grafanaResourceMetaAccessor) FindTitle(defaultTitle string) string { - // look for Spec.Title or Spec.Name - r := reflect.ValueOf(m.raw) - if r.Kind() == reflect.Ptr || r.Kind() == reflect.Interface { - r = r.Elem() - } - if r.Kind() == reflect.Struct { - spec := r.FieldByName("Spec") - if spec.Kind() == reflect.Struct { - title := spec.FieldByName("Title") - if title.IsValid() && title.Kind() == reflect.String { - return title.String() - } - name := spec.FieldByName("Name") - if name.IsValid() && name.Kind() == reflect.String { - return name.String() - } - } - - title := r.FieldByName("Title") - if title.IsValid() && title.Kind() == reflect.String { - return title.String() - } - } - return defaultTitle -} diff --git a/pkg/services/featuremgmt/toggles_gen_test.go b/pkg/services/featuremgmt/toggles_gen_test.go index b7ea5df8b7d..06143768bd4 100644 --- a/pkg/services/featuremgmt/toggles_gen_test.go +++ b/pkg/services/featuremgmt/toggles_gen_test.go @@ -20,8 +20,8 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/grafana/grafana/pkg/apimachinery/utils" 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" )