Provisioning: Rename k8s origin metadata to repo (#96524)

This commit is contained in:
Ryan McKinley 2024-11-15 17:26:14 +03:00 committed by GitHub
parent 2e62f75166
commit cc6d057a18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 198 additions and 154 deletions

View File

@ -540,8 +540,8 @@ func TestHTTPServer_FolderMetadataK8s(t *testing.T) {
"creationTimestamp": "2024-09-17T04:16:35Z",
"annotations": {
"grafana.app/createdBy": "user:fdxsqt7t5ryf4a",
"grafana.app/originName": "SQL",
"grafana.app/originPath": "3"
"grafana.app/repoName": "SQL",
"grafana.app/repoPath": "3"
}
},
"spec": {

View File

@ -29,23 +29,35 @@ const AnnoKeyMessage = "grafana.app/message"
// Identify where values came from
const AnnoKeyOriginName = "grafana.app/originName"
const AnnoKeyOriginPath = "grafana.app/originPath"
const AnnoKeyOriginHash = "grafana.app/originHash"
const AnnoKeyOriginTimestamp = "grafana.app/originTimestamp"
const AnnoKeyRepoName = "grafana.app/repoName"
const AnnoKeyRepoPath = "grafana.app/repoPath"
const AnnoKeyRepoHash = "grafana.app/repoHash"
const AnnoKeyRepoTimestamp = "grafana.app/repoTimestamp"
// #TODO revisit keeping these folder-specific annotations once we have complete support for mode 1
// These can be removed once we verify that non of the dual-write sources
// (for dashboards/playlists/etc) depend on the saved internal ID in SQL
const oldAnnoKeyOriginName = "grafana.app/originName"
const oldAnnoKeyOriginPath = "grafana.app/originPath"
const oldAnnoKeyOriginHash = "grafana.app/originHash"
const oldAnnoKeyOriginTimestamp = "grafana.app/originTimestamp"
const AnnoKeyFullPath = "grafana.app/fullPath"
const AnnoKeyFullPathUIDs = "grafana.app/fullPathUIDs"
// annoKeyFullPath encodes the full path in folder resources
// revisit keeping these folder-specific annotations once we have complete support for mode 1
// Deprecated: this goes away when folders have a better solution
const annoKeyFullPath = "grafana.app/fullPath"
// 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
// annoKeyFullPathUIDs encodes the full path in folder resources
// Deprecated: this goes away when folders have a better solution
const annoKeyFullPathUIDs = "grafana.app/fullPathUIDs"
// ResourceRepositoryInfo is encoded into kubernetes metadata annotations.
// This value identifies indicates the state of the resource in its provisioning source when
// the spec was last saved. Currently this is derived from the dashboards provisioning table.
type ResourceRepositoryInfo struct {
// Name of the repository/provisioning source
Name string `json:"name,omitempty"`
// The path within the named origin above (external_id in the existing dashboard provisioing)
// The path within the named repository above (external_id in the existing dashboard provisioning)
Path string `json:"path,omitempty"`
// Verification/identification hash (check_sum in existing dashboard provisioning)
@ -90,12 +102,12 @@ type GrafanaMetaAccessor interface {
SetBlob(v *BlobInfo)
GetBlob() *BlobInfo
GetOriginInfo() (*ResourceOriginInfo, error)
SetOriginInfo(info *ResourceOriginInfo)
GetOriginName() string
GetOriginPath() string
GetOriginHash() string
GetOriginTimestamp() (*time.Time, error)
GetRepositoryInfo() (*ResourceRepositoryInfo, error)
SetRepositoryInfo(info *ResourceRepositoryInfo)
GetRepositoryName() string
GetRepositoryPath() string
GetRepositoryHash() string
GetRepositoryTimestamp() (*time.Time, error)
GetSpec() (any, error)
SetSpec(any) error
@ -271,7 +283,16 @@ func (m *grafanaMetaAccessor) SetSlug(v string) {
m.SetAnnotation(AnnoKeySlug, v)
}
func (m *grafanaMetaAccessor) SetOriginInfo(info *ResourceOriginInfo) {
// This allows looking up a primary and secondary key -- if either exist the value will be returned
func (m *grafanaMetaAccessor) getAnnoValue(primary, secondary string) (string, bool) {
v, ok := m.obj.GetAnnotations()[primary]
if !ok {
v, ok = m.obj.GetAnnotations()[secondary]
}
return v, ok
}
func (m *grafanaMetaAccessor) SetRepositoryInfo(info *ResourceRepositoryInfo) {
anno := m.obj.GetAnnotations()
if anno == nil {
if info == nil {
@ -280,53 +301,62 @@ func (m *grafanaMetaAccessor) SetOriginInfo(info *ResourceOriginInfo) {
anno = make(map[string]string, 0)
}
delete(anno, AnnoKeyOriginName)
delete(anno, AnnoKeyOriginPath)
delete(anno, AnnoKeyOriginHash)
delete(anno, AnnoKeyOriginTimestamp)
// remove legacy values
delete(anno, oldAnnoKeyOriginHash)
delete(anno, oldAnnoKeyOriginPath)
delete(anno, oldAnnoKeyOriginHash)
delete(anno, oldAnnoKeyOriginTimestamp)
delete(anno, AnnoKeyRepoName)
delete(anno, AnnoKeyRepoPath)
delete(anno, AnnoKeyRepoHash)
delete(anno, AnnoKeyRepoTimestamp)
if info != nil && info.Name != "" {
anno[AnnoKeyOriginName] = info.Name
anno[AnnoKeyRepoName] = info.Name
if info.Path != "" {
anno[AnnoKeyOriginPath] = info.Path
anno[AnnoKeyRepoPath] = info.Path
}
if info.Hash != "" {
anno[AnnoKeyOriginHash] = info.Hash
anno[AnnoKeyRepoHash] = info.Hash
}
if info.Timestamp != nil {
anno[AnnoKeyOriginTimestamp] = info.Timestamp.UTC().Format(time.RFC3339)
anno[AnnoKeyRepoTimestamp] = info.Timestamp.UTC().Format(time.RFC3339)
}
}
m.obj.SetAnnotations(anno)
}
func (m *grafanaMetaAccessor) GetOriginInfo() (*ResourceOriginInfo, error) {
v, ok := m.obj.GetAnnotations()[AnnoKeyOriginName]
func (m *grafanaMetaAccessor) GetRepositoryInfo() (*ResourceRepositoryInfo, error) {
v, ok := m.getAnnoValue(AnnoKeyRepoName, oldAnnoKeyOriginName)
if !ok {
return nil, nil
}
t, err := m.GetOriginTimestamp()
return &ResourceOriginInfo{
t, err := m.GetRepositoryTimestamp()
return &ResourceRepositoryInfo{
Name: v,
Path: m.GetOriginPath(),
Hash: m.GetOriginHash(),
Path: m.GetRepositoryPath(),
Hash: m.GetRepositoryHash(),
Timestamp: t,
}, err
}
func (m *grafanaMetaAccessor) GetOriginName() string {
return m.get(AnnoKeyOriginName)
func (m *grafanaMetaAccessor) GetRepositoryName() string {
v, _ := m.getAnnoValue(AnnoKeyRepoName, oldAnnoKeyOriginName)
return v // will be empty string
}
func (m *grafanaMetaAccessor) GetOriginPath() string {
return m.get(AnnoKeyOriginPath)
func (m *grafanaMetaAccessor) GetRepositoryPath() string {
v, _ := m.getAnnoValue(AnnoKeyRepoPath, oldAnnoKeyOriginPath)
return v // will be empty string
}
func (m *grafanaMetaAccessor) GetOriginHash() string {
return m.get(AnnoKeyOriginHash)
func (m *grafanaMetaAccessor) GetRepositoryHash() string {
v, _ := m.getAnnoValue(AnnoKeyRepoHash, oldAnnoKeyOriginHash)
return v // will be empty string
}
func (m *grafanaMetaAccessor) GetOriginTimestamp() (*time.Time, error) {
v, ok := m.obj.GetAnnotations()[AnnoKeyOriginTimestamp]
func (m *grafanaMetaAccessor) GetRepositoryTimestamp() (*time.Time, error) {
v, ok := m.getAnnoValue(AnnoKeyRepoTimestamp, oldAnnoKeyOriginTimestamp)
if !ok || v == "" {
return nil, nil
}
@ -617,19 +647,23 @@ func (m *grafanaMetaAccessor) SetStatus(s any) (err error) {
}
func (m *grafanaMetaAccessor) GetFullPath() string {
return m.get(AnnoKeyFullPath)
// nolint:staticcheck
return m.get(annoKeyFullPath)
}
func (m *grafanaMetaAccessor) SetFullPath(path string) {
m.SetAnnotation(AnnoKeyFullPath, path)
// nolint:staticcheck
m.SetAnnotation(annoKeyFullPath, path)
}
func (m *grafanaMetaAccessor) GetFullPathUIDs() string {
return m.get(AnnoKeyFullPathUIDs)
// nolint:staticcheck
return m.get(annoKeyFullPathUIDs)
}
func (m *grafanaMetaAccessor) SetFullPathUIDs(path string) {
m.SetAnnotation(AnnoKeyFullPathUIDs, path)
// nolint:staticcheck
m.SetAnnotation(annoKeyFullPathUIDs, path)
}
func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string {

View File

@ -4,11 +4,12 @@ import (
"encoding/json"
"testing"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
type TestResource struct {
@ -129,7 +130,7 @@ func (in *Spec2) DeepCopy() *Spec2 {
}
func TestMetaAccessor(t *testing.T) {
originInfo := &utils.ResourceOriginInfo{
repoInfo := &utils.ResourceRepositoryInfo{
Name: "test",
Path: "a/b/c",
Hash: "kkk",
@ -177,14 +178,14 @@ func TestMetaAccessor(t *testing.T) {
},
}
meta.SetOriginInfo(originInfo)
meta.SetRepositoryInfo(repoInfo)
meta.SetFolder("folderUID")
require.Equal(t, map[string]string{
"grafana.app/originName": "test",
"grafana.app/originPath": "a/b/c",
"grafana.app/originHash": "kkk",
"grafana.app/folder": "folderUID",
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "kkk",
"grafana.app/folder": "folderUID",
}, res.GetAnnotations())
meta.SetNamespace("aaa")
@ -229,14 +230,14 @@ func TestMetaAccessor(t *testing.T) {
meta, err := utils.MetaAccessor(res)
require.NoError(t, err)
meta.SetOriginInfo(originInfo)
meta.SetRepositoryInfo(repoInfo)
meta.SetFolder("folderUID")
require.Equal(t, map[string]string{
"grafana.app/originName": "test",
"grafana.app/originPath": "a/b/c",
"grafana.app/originHash": "kkk",
"grafana.app/folder": "folderUID",
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "kkk",
"grafana.app/folder": "folderUID",
}, res.GetAnnotations())
meta.SetNamespace("aaa")
@ -280,14 +281,14 @@ func TestMetaAccessor(t *testing.T) {
meta, err := utils.MetaAccessor(res)
require.NoError(t, err)
meta.SetOriginInfo(originInfo)
meta.SetRepositoryInfo(repoInfo)
meta.SetFolder("folderUID")
require.Equal(t, map[string]string{
"grafana.app/originName": "test",
"grafana.app/originPath": "a/b/c",
"grafana.app/originHash": "kkk",
"grafana.app/folder": "folderUID",
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "kkk",
"grafana.app/folder": "folderUID",
}, res.GetAnnotations())
meta.SetNamespace("aaa")
@ -321,6 +322,28 @@ func TestMetaAccessor(t *testing.T) {
require.Equal(t, "ZZ", res.Status.Title)
})
t.Run("test reading old originInfo (now repository)", func(t *testing.T) {
res := &TestResource2{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "zzz",
"grafana.app/folder": "folderUID",
},
},
Spec: Spec2{},
}
meta, err := utils.MetaAccessor(res)
require.NoError(t, err)
info, err := meta.GetRepositoryInfo()
require.NoError(t, err)
require.Equal(t, "test", info.Name)
require.Equal(t, "a/b/c", info.Path)
require.Equal(t, "zzz", info.Hash)
})
t.Run("blob info", func(t *testing.T) {
info := &utils.BlobInfo{UID: "AAA", Size: 123, Hash: "xyz", MimeType: "application/json", Charset: "utf-8"}
anno := info.String()
@ -343,14 +366,14 @@ func TestMetaAccessor(t *testing.T) {
meta, err := utils.MetaAccessor(obj)
require.NoError(t, err)
meta.SetOriginInfo(originInfo)
meta.SetRepositoryInfo(repoInfo)
meta.SetFolder("folderUID")
require.Equal(t, map[string]string{
"grafana.app/originName": "test",
"grafana.app/originPath": "a/b/c",
"grafana.app/originHash": "kkk",
"grafana.app/folder": "folderUID",
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "kkk",
"grafana.app/folder": "folderUID",
}, obj.GetAnnotations())
require.Equal(t, "HELLO", obj.Spec.Title)
@ -366,14 +389,14 @@ func TestMetaAccessor(t *testing.T) {
meta, err = utils.MetaAccessor(obj2)
require.NoError(t, err)
meta.SetOriginInfo(originInfo)
meta.SetRepositoryInfo(repoInfo)
meta.SetFolder("folderUID")
require.Equal(t, map[string]string{
"grafana.app/originName": "test",
"grafana.app/originPath": "a/b/c",
"grafana.app/originHash": "kkk",
"grafana.app/folder": "folderUID",
"grafana.app/repoName": "test",
"grafana.app/repoPath": "a/b/c",
"grafana.app/repoHash": "kkk",
"grafana.app/folder": "folderUID",
}, obj2.GetAnnotations())
require.Equal(t, "xxx", meta.FindTitle("xxx"))

View File

@ -10,10 +10,11 @@ import (
"sync"
"time"
"github.com/grafana/authlib/claims"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
@ -286,14 +287,14 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
return nil, err
}
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: origin_name.String,
Path: originPath,
Hash: origin_hash.String,
Timestamp: &ts,
})
} else if plugin_id != "" {
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "plugin",
Path: plugin_id,
})
@ -516,7 +517,7 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library
meta.SetCreatedBy(p.CreatedBy)
meta.SetUpdatedBy(p.UpdatedBy)
meta.SetUpdatedTimestamp(&p.Updated)
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: strconv.FormatInt(p.ID, 10),
})

View File

@ -108,12 +108,12 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob
UID: name,
OrgID: info.OrgID,
}
origin, err := obj.GetOriginInfo()
repo, err := obj.GetRepositoryInfo()
if err != nil {
return nil, err
}
if origin != nil && origin.Name == "SQL" {
dto.ID, err = strconv.ParseInt(origin.Path, 10, 64)
if repo != nil && repo.Name == "SQL" {
dto.ID, err = strconv.ParseInt(repo.Path, 10, 64)
if err == nil {
return nil, err
}

View File

@ -144,7 +144,7 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper)
meta.SetUpdatedTimestamp(&v.Updated)
if v.ID > 0 { // nolint:staticcheck
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: fmt.Sprintf("%d", v.ID), // nolint:staticcheck
Timestamp: &v.Created,
@ -187,13 +187,13 @@ func setParentUID(u *unstructured.Unstructured, parentUid string) error {
func getLegacyID(meta utils.GrafanaMetaAccessor) (int64, error) {
var i int64
info, err := meta.GetOriginInfo()
repo, err := meta.GetRepositoryInfo()
if err != nil {
return i, err
}
if info != nil && info.Name == "SQL" {
i, err = strconv.ParseInt(info.Path, 10, 64)
if repo != nil && repo.Name == "SQL" {
i, err = strconv.ParseInt(repo.Path, 10, 64)
if err != nil {
return i, err
}
@ -208,7 +208,7 @@ func getURL(meta utils.GrafanaMetaAccessor, title string) string {
}
func getCreated(meta utils.GrafanaMetaAccessor) (*time.Time, error) {
created, err := meta.GetOriginTimestamp()
created, err := meta.GetRepositoryTimestamp()
if err != nil {
return nil, err
}

View File

@ -110,7 +110,7 @@ func toSAItem(sa legacy.ServiceAccount, ns string) iamv0.ServiceAccount {
}
obj, _ := utils.MetaAccessor(&item)
obj.SetUpdatedTimestamp(&sa.Updated)
obj.SetOriginInfo(&utils.ResourceOriginInfo{
obj.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: strconv.FormatInt(sa.ID, 10),
})

View File

@ -136,7 +136,7 @@ func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0.Team {
}
meta, _ := utils.MetaAccessor(&obj)
meta.SetUpdatedTimestamp(&t.Updated)
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: strconv.FormatInt(t.ID, 10),
})

View File

@ -137,7 +137,7 @@ func toUserItem(u *user.User, ns string) iamv0.User {
}
obj, _ := utils.MetaAccessor(item)
obj.SetUpdatedTimestamp(&u.Updated)
obj.SetOriginInfo(&utils.ResourceOriginInfo{
obj.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: strconv.FormatInt(u.ID, 10),
})

View File

@ -97,7 +97,7 @@ func convertToK8sResource(v *playlistsvc.PlaylistDTO, namespacer request.Namespa
meta.SetUpdatedTimestampMillis(v.UpdatedAt)
if v.Id > 0 {
createdAt := time.UnixMilli(v.CreatedAt)
meta.SetOriginInfo(&utils.ResourceOriginInfo{
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
Name: "SQL",
Path: fmt.Sprintf("%d", v.Id),
Timestamp: &createdAt,
@ -135,7 +135,7 @@ func getLegacyID(item *unstructured.Unstructured) int64 {
if err != nil {
return 0
}
info, _ := meta.GetOriginInfo()
info, _ := meta.GetRepositoryInfo()
if info != nil && info.Name == "SQL" {
i, err := strconv.ParseInt(info.Path, 10, 64)
if err == nil {

View File

@ -42,9 +42,9 @@ func TestPlaylistConversion(t *testing.T) {
"resourceVersion": "54321",
"creationTimestamp": "1970-01-01T00:00:12Z",
"annotations": {
"grafana.app/originPath": "123",
"grafana.app/originName": "SQL",
"grafana.app/originTimestamp":"1970-01-01T00:00:12Z",
"grafana.app/repoPath": "123",
"grafana.app/repoName": "SQL",
"grafana.app/repoTimestamp":"1970-01-01T00:00:12Z",
"grafana.app/updatedTimestamp": "1970-01-01T00:00:54Z"
}
},

View File

@ -60,12 +60,12 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime
obj.SetResourceVersion("")
obj.SetSelfLink("")
// Read+write will verify that origin format is accurate
origin, err := obj.GetOriginInfo()
// Read+write will verify that repository format is accurate
repo, err := obj.GetRepositoryInfo()
if err != nil {
return nil, err
}
obj.SetOriginInfo(origin)
obj.SetRepositoryInfo(repo)
obj.SetUpdatedBy("")
obj.SetUpdatedTimestamp(nil)
obj.SetCreatedBy(user.GetUID())
@ -115,11 +115,11 @@ func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runti
obj.SetResourceVersion("") // removed from saved JSON because the RV is not yet calculated
// Read+write will verify that origin format is accurate
origin, err := obj.GetOriginInfo()
repo, err := obj.GetRepositoryInfo()
if err != nil {
return nil, err
}
obj.SetOriginInfo(origin)
obj.SetRepositoryInfo(repo)
obj.SetUpdatedBy(user.GetUID())
obj.SetUpdatedTimestampMillis(time.Now().UnixMilli())

View File

@ -9,7 +9,7 @@ import (
type WriteAccessHooks struct {
// When configured, this will make sure a user is allowed to save to a given origin
Origin func(ctx context.Context, user claims.AuthInfo, origin string) bool
CanWriteValueFromRepoCheck func(ctx context.Context, user claims.AuthInfo, origin string) bool
}
type LifecycleHooks interface {
@ -20,11 +20,11 @@ type LifecycleHooks interface {
Stop(context.Context) error
}
func (a *WriteAccessHooks) CanWriteOrigin(ctx context.Context, user claims.AuthInfo, uid string) error {
if a.Origin == nil || uid == "UI" {
func (a *WriteAccessHooks) CanWriteValueFromRepository(ctx context.Context, user claims.AuthInfo, uid string) error {
if a.CanWriteValueFromRepoCheck == nil || uid == "UI" {
return nil // default to OK
}
if !a.Origin(ctx, user, uid) {
if !a.CanWriteValueFromRepoCheck(ctx, user, uid) {
return fmt.Errorf("not allowed to write resource at origin")
}
return nil

View File

@ -394,12 +394,12 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
}
}
origin, err := obj.GetOriginInfo()
repo, err := obj.GetRepositoryInfo()
if err != nil {
return nil, NewBadRequestError("invalid origin info")
return nil, NewBadRequestError("invalid repository info")
}
if origin != nil {
err = s.writeHooks.CanWriteOrigin(ctx, user, origin.Name)
if repo != nil {
err = s.writeHooks.CanWriteValueFromRepository(ctx, user, repo.Name)
if err != nil {
return nil, AsErrorResult(err)
}

View File

@ -60,9 +60,9 @@ func TestSimpleServer(t *testing.T) {
"uid": "xyz",
"namespace": "default",
"annotations": {
"grafana.app/originName": "elsewhere",
"grafana.app/originPath": "path/to/item",
"grafana.app/originTimestamp": "2024-02-02T00:00:00Z"
"grafana.app/repoName": "elsewhere",
"grafana.app/repoPath": "path/to/item",
"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z"
}
},
"spec": {
@ -176,9 +176,9 @@ func TestSimpleServer(t *testing.T) {
"namespace": "default",
"uid": "xyz",
"annotations": {
"grafana.app/originName": "elsewhere",
"grafana.app/originPath": "path/to/item",
"grafana.app/originTimestamp": "2024-02-02T00:00:00Z"
"grafana.app/repoName": "elsewhere",
"grafana.app/repoPath": "path/to/item",
"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z"
}
},
"spec": {

View File

@ -8,9 +8,9 @@
"creationTimestamp": "2024-10-30T18:30:54Z",
"annotations": {
"grafana.app/createdBy": "user:be2g71ke8yoe8b",
"grafana.app/originHash": "Grafana v9.2.0 (NA)",
"grafana.app/originName": "UI",
"grafana.app/originPath": "/dashboard/new"
"grafana.app/repoHash": "Grafana v9.2.0 (NA)",
"grafana.app/repoName": "UI",
"grafana.app/repoPath": "/dashboard/new"
},
"managedFields": [
{
@ -23,9 +23,9 @@
"f:metadata": {
"f:annotations": {
".": {},
"f:grafana.app/originHash": {},
"f:grafana.app/originName": {},
"f:grafana.app/originPath": {}
"f:grafana.app/repoHash": {},
"f:grafana.app/repoName": {},
"f:grafana.app/repoPath": {}
},
"f:generateName": {}
},

View File

@ -7,10 +7,7 @@
"uid": "86ab200a-e8b0-47ce-bbc1-8c2e078b0956",
"creationTimestamp": "2024-10-30T20:24:07Z",
"annotations": {
"grafana.app/createdBy": "user:be2g71ke8yoe8b",
"grafana.app/originHash": "Grafana v9.2.0 (NA)",
"grafana.app/originName": "UI",
"grafana.app/originPath": "/dashboard/new"
"grafana.app/createdBy": "user:be2g71ke8yoe8b"
},
"managedFields": [
{

View File

@ -7,10 +7,7 @@
"uid": "86ab200a-e8b0-47ce-bbc1-8c2e078b0956-2",
"creationTimestamp": "2024-10-30T20:24:07Z",
"annotations": {
"grafana.app/createdBy": "user:be2g71ke8yoe8b",
"grafana.app/originHash": "Grafana v9.2.0 (NA)",
"grafana.app/originName": "UI",
"grafana.app/originPath": "/dashboard/new"
"grafana.app/createdBy": "user:be2g71ke8yoe8b"
},
"managedFields": [
{

View File

@ -7,10 +7,7 @@
"uid": "aaaa-bbbb",
"creationTimestamp": "2024-11-01T19:42:22Z",
"annotations": {
"grafana.app/createdBy": "user:1",
"grafana.app/originName": "SQL",
"grafana.app/originPath": "15",
"grafana.app/originTimestamp": "2024-11-01T19:42:22Z"
"grafana.app/createdBy": "user:1"
}
},
"spec": {

View File

@ -8,9 +8,9 @@
"creationTimestamp": "2024-11-01T19:42:22Z",
"annotations": {
"grafana.app/createdBy": "user:1",
"grafana.app/originName": "SQL",
"grafana.app/originPath": "15",
"grafana.app/originTimestamp": "2024-11-01T19:42:22Z"
"grafana.app/repoName": "SQL",
"grafana.app/repoPath": "15",
"grafana.app/repoTimestamp": "2024-11-01T19:42:22Z"
}
},
"spec": {

View File

@ -8,9 +8,9 @@
"creationTimestamp": "2024-11-01T19:42:22Z",
"annotations": {
"grafana.app/createdBy": "user:1",
"grafana.app/originName": "SQL",
"grafana.app/originPath": "15",
"grafana.app/originTimestamp": "2024-11-01T19:42:22Z"
"grafana.app/repoName": "SQL",
"grafana.app/repoPath": "15",
"grafana.app/repoTimestamp": "2024-11-01T19:42:22Z"
}
},
"spec": {

View File

@ -11,9 +11,6 @@ import {
ResourceList,
ResourceClient,
ObjectMeta,
AnnoKeyOriginPath,
AnnoKeyOriginHash,
AnnoKeyOriginName,
K8sAPIGroupList,
} from './types';
@ -55,12 +52,12 @@ export class ScopedResourceClient<T = object, K = string> implements ResourceCli
// THe passed in value is the suggested prefix
obj.metadata.generateName = login ? login.slice(0, 2) : 'g';
}
setOriginAsUI(obj.metadata);
setSavedFromUIAnnotation(obj.metadata);
return getBackendSrv().post(this.url, obj);
}
public async update(obj: Resource<T, K>): Promise<Resource<T, K>> {
setOriginAsUI(obj.metadata);
setSavedFromUIAnnotation(obj.metadata);
return getBackendSrv().put<Resource<T, K>>(`${this.url}/${obj.metadata.name}`, obj);
}
@ -103,13 +100,11 @@ export class ScopedResourceClient<T = object, K = string> implements ResourceCli
}
// add the origin annotations so we know what was set from the UI
function setOriginAsUI(meta: Partial<ObjectMeta>) {
function setSavedFromUIAnnotation(meta: Partial<ObjectMeta>) {
if (!meta.annotations) {
meta.annotations = {};
}
meta.annotations[AnnoKeyOriginName] = 'UI';
meta.annotations[AnnoKeyOriginPath] = window.location.pathname;
meta.annotations[AnnoKeyOriginHash] = config.buildInfo.versionString;
meta.annotations['grafana.app/saved-from-ui'] = config.buildInfo.versionString;
}
export class DatasourceAPIVersions {

View File

@ -37,10 +37,10 @@ export const AnnoKeyMessage = 'grafana.app/message';
export const AnnoKeySlug = 'grafana.app/slug';
// Identify where values came from
export const AnnoKeyOriginName = 'grafana.app/originName';
export const AnnoKeyOriginPath = 'grafana.app/originPath';
export const AnnoKeyOriginHash = 'grafana.app/originHash';
const AnnoKeyOriginTimestamp = 'grafana.app/originTimestamp';
export const AnnoKeyRepoName = 'grafana.app/repoName';
export const AnnoKeyRepoPath = 'grafana.app/repoPath';
export const AnnoKeyRepoHash = 'grafana.app/repoHash';
const AnnoKeyRepoTimestamp = 'grafana.app/repoTimestamp';
type GrafanaAnnotations = {
[AnnoKeyCreatedBy]?: string;
@ -49,10 +49,10 @@ type GrafanaAnnotations = {
[AnnoKeyFolder]?: string;
[AnnoKeySlug]?: string;
[AnnoKeyOriginName]?: string;
[AnnoKeyOriginPath]?: string;
[AnnoKeyOriginHash]?: string;
[AnnoKeyOriginTimestamp]?: string;
[AnnoKeyRepoName]?: string;
[AnnoKeyRepoPath]?: string;
[AnnoKeyRepoHash]?: string;
[AnnoKeyRepoTimestamp]?: string;
// Any key value
[key: string]: string | undefined;