Provisioning: Rename k8s origin metadata to repo (#96524)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user