mirror of https://github.com/grafana/grafana
Kinds: Use apimachinery ObjectMeta for metadata (#68668)
parent
f91c1b9897
commit
c66d5721f7
@ -0,0 +1,150 @@ |
||||
package kinds |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
) |
||||
|
||||
// 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
|
||||
_ interface{} |
||||
} |
||||
|
||||
// GrafanaResourceMetadata is standard k8s object metadata with helper functions
|
||||
type GrafanaResourceMetadata v1.ObjectMeta |
||||
|
||||
// GrafanaResource is a generic kubernetes resource with a helper for the common grafana metadata
|
||||
// This is a temporary solution until this object (or similar) can be moved to the app-sdk or kindsys
|
||||
type GrafanaResource[Spec interface{}, Status interface{}] struct { |
||||
APIVersion string `json:"apiVersion"` |
||||
Kind string `json:"kind"` |
||||
|
||||
Metadata GrafanaResourceMetadata `json:"metadata"` |
||||
Spec *Spec `json:"spec,omitempty"` |
||||
Status *Status `json:"status,omitempty"` |
||||
|
||||
// Avoid extending
|
||||
_ interface{} |
||||
} |
||||
|
||||
// Annotation keys
|
||||
const annoKeyCreatedBy = "grafana.com/createdBy" |
||||
const annoKeyUpdatedTimestamp = "grafana.com/updatedTimestamp" |
||||
const annoKeyUpdatedBy = "grafana.com/updatedBy" |
||||
|
||||
// The folder identifier
|
||||
const annoKeyFolder = "grafana.com/folder" |
||||
const annoKeySlug = "grafana.com/slug" |
||||
|
||||
// Identify where values came from
|
||||
const annoKeyOriginName = "grafana.com/origin/name" |
||||
const annoKeyOriginPath = "grafana.com/origin/path" |
||||
const annoKeyOriginKey = "grafana.com/origin/key" |
||||
const annoKeyOriginTime = "grafana.com/origin/time" |
||||
|
||||
func (m *GrafanaResourceMetadata) GetUpdatedTimestamp() *time.Time { |
||||
v, ok := m.Annotations[annoKeyUpdatedTimestamp] |
||||
if ok { |
||||
t, err := time.Parse(time.RFC3339, v) |
||||
if err != nil { |
||||
return &t |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetUpdatedTimestamp(v *time.Time) { |
||||
if v == nil { |
||||
delete(m.Annotations, annoKeyUpdatedTimestamp) |
||||
} else { |
||||
m.Annotations[annoKeyUpdatedTimestamp] = v.Format(time.RFC3339) |
||||
} |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) GetCreatedBy() string { |
||||
return m.Annotations[annoKeyCreatedBy] |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetCreatedBy(user string) { |
||||
m.Annotations[annoKeyCreatedBy] = user // user GRN
|
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) GetUpdatedBy() string { |
||||
return m.Annotations[annoKeyUpdatedBy] |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetUpdatedBy(user string) { |
||||
m.Annotations[annoKeyUpdatedBy] = user // user GRN
|
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) GetFolder() string { |
||||
return m.Annotations[annoKeyFolder] |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetFolder(uid string) { |
||||
m.Annotations[annoKeyFolder] = uid |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) GetSlug() string { |
||||
return m.Annotations[annoKeySlug] |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetSlug(v string) { |
||||
m.Annotations[annoKeySlug] = v |
||||
} |
||||
|
||||
func (m *GrafanaResourceMetadata) SetOriginInfo(info *ResourceOriginInfo) { |
||||
delete(m.Annotations, annoKeyOriginName) |
||||
delete(m.Annotations, annoKeyOriginPath) |
||||
delete(m.Annotations, annoKeyOriginKey) |
||||
delete(m.Annotations, annoKeyOriginTime) |
||||
if info != nil || info.Name != "" { |
||||
m.Annotations[annoKeyOriginName] = info.Name |
||||
if info.Path != "" { |
||||
m.Annotations[annoKeyOriginPath] = info.Path |
||||
} |
||||
if info.Key != "" { |
||||
m.Annotations[annoKeyOriginKey] = info.Key |
||||
} |
||||
if info.Timestamp != nil { |
||||
m.Annotations[annoKeyOriginTime] = info.Timestamp.Format(time.RFC3339) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// GetOriginInfo returns the origin info stored in k8s metadata annotations
|
||||
func (m *GrafanaResourceMetadata) GetOriginInfo() *ResourceOriginInfo { |
||||
v, ok := m.Annotations[annoKeyOriginName] |
||||
if !ok { |
||||
return nil |
||||
} |
||||
info := &ResourceOriginInfo{ |
||||
Name: v, |
||||
Path: m.Annotations[annoKeyOriginPath], |
||||
Key: m.Annotations[annoKeyOriginKey], |
||||
} |
||||
v, ok = m.Annotations[annoKeyOriginTime] |
||||
if ok { |
||||
t, err := time.Parse(time.RFC3339, v) |
||||
if err != nil { |
||||
info.Timestamp = &t |
||||
} |
||||
} |
||||
return info |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
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.com/createdBy": "user:11", |
||||
"grafana.com/folder": "TheFolderUID", |
||||
"grafana.com/updatedBy": "user:12", |
||||
"grafana.com/updatedTimestamp": "2010-01-01T08:00:00Z" |
||||
} |
||||
}, |
||||
"spec": {} |
||||
}`, string(out)) |
||||
} |
||||
@ -0,0 +1,64 @@ |
||||
package playlist |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/kinds/playlist" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestPlaylistConversion(t *testing.T) { |
||||
src := PlaylistDTO{ |
||||
Uid: "abc", |
||||
Name: "TeamA", |
||||
Interval: "10s", |
||||
Items: []playlist.Item{ |
||||
{Title: util.Pointer("First"), Type: playlist.ItemTypeDashboardByUid, Value: "UID0"}, |
||||
{Title: util.Pointer("Second"), Type: playlist.ItemTypeDashboardByTag, Value: "tagA"}, |
||||
{Title: util.Pointer("Third"), Type: playlist.ItemTypeDashboardById, Value: "123"}, |
||||
}, |
||||
} |
||||
|
||||
dst := PlaylistToResource(src) |
||||
|
||||
require.Equal(t, "abc", src.Uid) |
||||
require.Equal(t, "abc", dst.Metadata.Name) |
||||
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": "Playlist", |
||||
"metadata": { |
||||
"name": "abc", |
||||
"creationTimestamp": null |
||||
}, |
||||
"spec": { |
||||
"interval": "10s", |
||||
"items": [ |
||||
{ |
||||
"title": "First", |
||||
"type": "dashboard_by_uid", |
||||
"value": "UID0" |
||||
}, |
||||
{ |
||||
"title": "Second", |
||||
"type": "dashboard_by_tag", |
||||
"value": "tagA" |
||||
}, |
||||
{ |
||||
"title": "Third", |
||||
"type": "dashboard_by_id", |
||||
"value": "123" |
||||
} |
||||
], |
||||
"name": "TeamA", |
||||
"uid": "" |
||||
} |
||||
}`, string(out)) |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
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.com/updatedTimestamp": "2010-01-01T08:00:00Z" |
||||
} |
||||
}, |
||||
"spec": { |
||||
"email": "team@a.org", |
||||
"name": "TeamA" |
||||
} |
||||
}`, string(out)) |
||||
} |
||||
Loading…
Reference in new issue