mirror of https://github.com/grafana/grafana
K8s: Move GrafanaMetaAccessor into grafana-apiserver and remove usage of kinds metadata (#79602)
* move GrafanaMetaAccessor into pkg/apis, add support for Spec.Title & Spec.Name * K8s: Move GrafanaMetaAccessor (PR into another) (#79728) * access titles * remove title * remove title * remove kinds metadata accessor * remove kinds metadata accessor * fixes * error handling * fix tests --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/80495/head
parent
da894994d4
commit
d76defe517
@ -1,35 +0,0 @@ |
|||||||
package kinds |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/stretchr/testify/require" |
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
|
||||||
) |
|
||||||
|
|
||||||
func TestMetaAccessor(t *testing.T) { |
|
||||||
originInfo := &ResourceOriginInfo{ |
|
||||||
Name: "test", |
|
||||||
Path: "a/b/c", |
|
||||||
Key: "kkk", |
|
||||||
} |
|
||||||
|
|
||||||
// Verify that you can set annotations when they do not exist
|
|
||||||
dummy := &GrafanaResourceMetadata{} |
|
||||||
dummy.SetOriginInfo(originInfo) |
|
||||||
dummy.SetFolder("folderUID") |
|
||||||
|
|
||||||
// with any k8s object
|
|
||||||
obj := &unstructured.Unstructured{} |
|
||||||
meta := MetaAccessor(obj) |
|
||||||
meta.SetOriginInfo(originInfo) |
|
||||||
meta.SetFolder("folderUID") |
|
||||||
|
|
||||||
require.Equal(t, map[string]string{ |
|
||||||
"grafana.app/originName": "test", |
|
||||||
"grafana.app/originPath": "a/b/c", |
|
||||||
"grafana.app/originKey": "kkk", |
|
||||||
"grafana.app/folder": "folderUID", |
|
||||||
}, dummy.Annotations) |
|
||||||
require.Equal(t, dummy.Annotations, obj.GetAnnotations()) |
|
||||||
} |
|
@ -0,0 +1,266 @@ |
|||||||
|
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()) |
||||||
|
} |
||||||
|
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.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 |
||||||
|
} |
@ -0,0 +1,208 @@ |
|||||||
|
package utils_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"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/services/grafana-apiserver/utils" |
||||||
|
) |
||||||
|
|
||||||
|
type TestResource struct { |
||||||
|
metav1.TypeMeta `json:",inline"` |
||||||
|
// Standard object's metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"` |
||||||
|
|
||||||
|
Spec Spec `json:"spec,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TestResource) DeepCopyInto(out *TestResource) { |
||||||
|
*out = *in |
||||||
|
out.TypeMeta = in.TypeMeta |
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) |
||||||
|
in.Spec.DeepCopyInto(&out.Spec) |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Playlist.
|
||||||
|
func (in *TestResource) DeepCopy() *TestResource { |
||||||
|
if in == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
out := new(TestResource) |
||||||
|
in.DeepCopyInto(out) |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TestResource) DeepCopyObject() runtime.Object { |
||||||
|
if c := in.DeepCopy(); c != nil { |
||||||
|
return c |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Spec defines model for Spec.
|
||||||
|
type Spec struct { |
||||||
|
// Name of the object.
|
||||||
|
Title string `json:"title"` |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Spec) DeepCopyInto(out *Spec) { |
||||||
|
*out = *in |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec.
|
||||||
|
func (in *Spec) DeepCopy() *Spec { |
||||||
|
if in == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
out := new(Spec) |
||||||
|
in.DeepCopyInto(out) |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
type TestResource2 struct { |
||||||
|
metav1.TypeMeta `json:",inline"` |
||||||
|
// Standard object's metadata
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"` |
||||||
|
|
||||||
|
Spec Spec2 `json:"spec,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TestResource2) DeepCopyInto(out *TestResource2) { |
||||||
|
*out = *in |
||||||
|
out.TypeMeta = in.TypeMeta |
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) |
||||||
|
in.Spec.DeepCopyInto(&out.Spec) |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Playlist.
|
||||||
|
func (in *TestResource2) DeepCopy() *TestResource2 { |
||||||
|
if in == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
out := new(TestResource2) |
||||||
|
in.DeepCopyInto(out) |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TestResource2) DeepCopyObject() runtime.Object { |
||||||
|
if c := in.DeepCopy(); c != nil { |
||||||
|
return c |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Spec defines model for Spec.
|
||||||
|
type Spec2 struct{} |
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Spec2) DeepCopyInto(out *Spec2) { |
||||||
|
*out = *in |
||||||
|
} |
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec.
|
||||||
|
func (in *Spec2) DeepCopy() *Spec2 { |
||||||
|
if in == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
out := new(Spec2) |
||||||
|
in.DeepCopyInto(out) |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
func TestMetaAccessor(t *testing.T) { |
||||||
|
originInfo := &utils.ResourceOriginInfo{ |
||||||
|
Name: "test", |
||||||
|
Path: "a/b/c", |
||||||
|
Key: "kkk", |
||||||
|
} |
||||||
|
|
||||||
|
t.Run("fails for non resource objects", func(t *testing.T) { |
||||||
|
_, err := utils.MetaAccessor("hello") |
||||||
|
require.Error(t, err) |
||||||
|
|
||||||
|
_, err = utils.MetaAccessor(unstructured.Unstructured{}) |
||||||
|
require.Error(t, err) // Not a pointer!
|
||||||
|
|
||||||
|
_, err = utils.MetaAccessor(&unstructured.Unstructured{}) |
||||||
|
require.NoError(t, err) // Must be a pointer
|
||||||
|
|
||||||
|
_, err = utils.MetaAccessor(&TestResource{ |
||||||
|
Spec: Spec{ |
||||||
|
Title: "HELLO", |
||||||
|
}, |
||||||
|
}) |
||||||
|
require.NoError(t, err) // Must be a pointer
|
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("get and set grafana metadata", func(t *testing.T) { |
||||||
|
res := &unstructured.Unstructured{} |
||||||
|
meta, err := utils.MetaAccessor(res) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
meta.SetOriginInfo(originInfo) |
||||||
|
meta.SetFolder("folderUID") |
||||||
|
|
||||||
|
require.Equal(t, map[string]string{ |
||||||
|
"grafana.app/originName": "test", |
||||||
|
"grafana.app/originPath": "a/b/c", |
||||||
|
"grafana.app/originKey": "kkk", |
||||||
|
"grafana.app/folder": "folderUID", |
||||||
|
}, res.GetAnnotations()) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("find titles", func(t *testing.T) { |
||||||
|
// with a k8s object that has Spec.Title
|
||||||
|
obj := &TestResource{ |
||||||
|
Spec: Spec{ |
||||||
|
Title: "HELLO", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
meta, err := utils.MetaAccessor(obj) |
||||||
|
require.NoError(t, err) |
||||||
|
meta.SetOriginInfo(originInfo) |
||||||
|
meta.SetFolder("folderUID") |
||||||
|
|
||||||
|
require.Equal(t, map[string]string{ |
||||||
|
"grafana.app/originName": "test", |
||||||
|
"grafana.app/originPath": "a/b/c", |
||||||
|
"grafana.app/originKey": "kkk", |
||||||
|
"grafana.app/folder": "folderUID", |
||||||
|
}, obj.GetAnnotations()) |
||||||
|
|
||||||
|
require.Equal(t, "HELLO", obj.Spec.Title) |
||||||
|
require.Equal(t, "HELLO", meta.FindTitle("")) |
||||||
|
obj.Spec.Title = "" |
||||||
|
require.Equal(t, "", meta.FindTitle("xxx")) |
||||||
|
|
||||||
|
// with a k8s object without Spec.Title
|
||||||
|
obj2 := &TestResource2{} |
||||||
|
|
||||||
|
meta, err = utils.MetaAccessor(obj2) |
||||||
|
require.NoError(t, err) |
||||||
|
meta.SetOriginInfo(originInfo) |
||||||
|
meta.SetFolder("folderUID") |
||||||
|
|
||||||
|
require.Equal(t, map[string]string{ |
||||||
|
"grafana.app/originName": "test", |
||||||
|
"grafana.app/originPath": "a/b/c", |
||||||
|
"grafana.app/originKey": "kkk", |
||||||
|
"grafana.app/folder": "folderUID", |
||||||
|
}, obj2.GetAnnotations()) |
||||||
|
|
||||||
|
require.Equal(t, "xxx", meta.FindTitle("xxx")) |
||||||
|
}) |
||||||
|
} |
@ -1,57 +0,0 @@ |
|||||||
package model |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/kinds/librarypanel" |
|
||||||
"github.com/stretchr/testify/require" |
|
||||||
) |
|
||||||
|
|
||||||
func TestLibaryPanelConversion(t *testing.T) { |
|
||||||
body := `{}` |
|
||||||
|
|
||||||
src := LibraryElementDTO{ |
|
||||||
Kind: 0, // always library panel
|
|
||||||
FolderUID: "TheFolderUID", |
|
||||||
UID: "TheUID", |
|
||||||
Version: 10, |
|
||||||
Model: json.RawMessage(body), |
|
||||||
Meta: LibraryElementDTOMeta{ |
|
||||||
Created: time.UnixMilli(946713600000).UTC(), // 2000-01-01
|
|
||||||
Updated: time.UnixMilli(1262332800000).UTC(), // 2010-01-01,
|
|
||||||
CreatedBy: librarypanel.LibraryElementDTOMetaUser{ |
|
||||||
Id: 11, |
|
||||||
}, |
|
||||||
UpdatedBy: librarypanel.LibraryElementDTOMetaUser{ |
|
||||||
Id: 12, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
dst := src.ToResource() |
|
||||||
|
|
||||||
require.Equal(t, src.UID, dst.Metadata.Name) |
|
||||||
|
|
||||||
out, err := json.MarshalIndent(dst, "", " ") |
|
||||||
require.NoError(t, err) |
|
||||||
fmt.Printf("%s", string(out)) |
|
||||||
require.JSONEq(t, `{ |
|
||||||
"apiVersion": "v0-0-alpha", |
|
||||||
"kind": "LibraryPanel", |
|
||||||
"metadata": { |
|
||||||
"name": "TheUID", |
|
||||||
"resourceVersion": "10", |
|
||||||
"creationTimestamp": "2000-01-01T08:00:00Z", |
|
||||||
"annotations": { |
|
||||||
"grafana.app/createdBy": "user:11", |
|
||||||
"grafana.app/folder": "TheFolderUID", |
|
||||||
"grafana.app/updatedBy": "user:12", |
|
||||||
"grafana.app/updatedTimestamp": "2010-01-01T08:00:00Z" |
|
||||||
} |
|
||||||
}, |
|
||||||
"spec": {} |
|
||||||
}`, string(out)) |
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
package team |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/stretchr/testify/require" |
|
||||||
) |
|
||||||
|
|
||||||
func TestTeamConversion(t *testing.T) { |
|
||||||
src := Team{ |
|
||||||
ID: 123, |
|
||||||
UID: "abc", |
|
||||||
Name: "TeamA", |
|
||||||
Email: "team@a.org", |
|
||||||
OrgID: 11, |
|
||||||
Created: time.UnixMilli(946713600000).UTC(), // 2000-01-01
|
|
||||||
Updated: time.UnixMilli(1262332800000).UTC(), // 2010-01-01
|
|
||||||
} |
|
||||||
|
|
||||||
dst := src.ToResource() |
|
||||||
|
|
||||||
require.Equal(t, src.Name, dst.Spec.Name) |
|
||||||
|
|
||||||
out, err := json.MarshalIndent(dst, "", " ") |
|
||||||
require.NoError(t, err) |
|
||||||
fmt.Printf("%s", string(out)) |
|
||||||
require.JSONEq(t, `{ |
|
||||||
"apiVersion": "v0-0-alpha", |
|
||||||
"kind": "Team", |
|
||||||
"metadata": { |
|
||||||
"name": "abc", |
|
||||||
"creationTimestamp": "2000-01-01T08:00:00Z", |
|
||||||
"annotations": { |
|
||||||
"grafana.app/updatedTimestamp": "2010-01-01T08:00:00Z" |
|
||||||
} |
|
||||||
}, |
|
||||||
"spec": { |
|
||||||
"email": "team@a.org", |
|
||||||
"name": "TeamA" |
|
||||||
} |
|
||||||
}`, string(out)) |
|
||||||
} |
|
Loading…
Reference in new issue