Chore: Annotation store interface (#114100)
* annotation legacy store with api server, read only * annotations are not addressable by ID for read operations * add ownership for an app * typo, of course * fix go workspace * update workspace * copy annotation app in dockerfile * experimenting with store interface * finalising interfaces * add tags as custom handler * implement tags handler * add missing config file * mute linter * update generated files * update workspace
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
package annotation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
annotationV0 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type sqlAdapter struct {
|
||||
repo annotations.Repository
|
||||
cleaner annotations.Cleaner
|
||||
nsMapper request.NamespaceMapper
|
||||
cfg *setting.Cfg
|
||||
}
|
||||
|
||||
func NewSQLAdapter(repo annotations.Repository, cleaner annotations.Cleaner, nsMapper request.NamespaceMapper, cfg *setting.Cfg) *sqlAdapter {
|
||||
return &sqlAdapter{
|
||||
repo: repo,
|
||||
cleaner: cleaner,
|
||||
nsMapper: nsMapper,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) Get(ctx context.Context, namespace, name string) (*annotationV0.Annotation, error) {
|
||||
id, err := parseAnnotationID(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgID, err := namespaceToOrgID(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := &annotations.ItemQuery{
|
||||
SignedInUser: user,
|
||||
OrgID: orgID,
|
||||
Limit: 1000,
|
||||
AlertID: -1,
|
||||
}
|
||||
|
||||
items, err := a.repo.Find(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if item.ID == id {
|
||||
return a.toK8sResource(item, namespace), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("annotation not found")
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) List(ctx context.Context, namespace string, opts ListOptions) (*AnnotationList, error) {
|
||||
orgID, err := namespaceToOrgID(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := &annotations.ItemQuery{
|
||||
SignedInUser: user,
|
||||
OrgID: orgID,
|
||||
DashboardUID: opts.DashboardUID,
|
||||
PanelID: opts.PanelID,
|
||||
From: opts.From,
|
||||
To: opts.To,
|
||||
Limit: opts.Limit,
|
||||
AlertID: -1,
|
||||
}
|
||||
|
||||
items, err := a.repo.Find(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]annotationV0.Annotation, 0, len(items))
|
||||
for _, item := range items {
|
||||
result = append(result, *a.toK8sResource(item, namespace))
|
||||
}
|
||||
|
||||
return &AnnotationList{
|
||||
Items: result,
|
||||
Continue: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) Create(ctx context.Context, anno *annotationV0.Annotation) (*annotationV0.Annotation, error) {
|
||||
orgID, err := namespaceToOrgID(ctx, anno.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item := a.fromK8sResource(anno)
|
||||
item.OrgID = orgID
|
||||
|
||||
if err := a.repo.Save(ctx, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
created := anno.DeepCopy()
|
||||
created.Name = fmt.Sprintf("a-%d", item.ID)
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) Update(ctx context.Context, anno *annotationV0.Annotation) (*annotationV0.Annotation, error) {
|
||||
orgID, err := namespaceToOrgID(ctx, anno.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item := a.fromK8sResource(anno)
|
||||
item.OrgID = orgID
|
||||
|
||||
if err := a.repo.Update(ctx, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return anno, nil
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) Delete(ctx context.Context, namespace, name string) error {
|
||||
id, err := parseAnnotationID(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orgID, err := namespaceToOrgID(ctx, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.repo.Delete(ctx, &annotations.DeleteParams{
|
||||
ID: id,
|
||||
OrgID: orgID,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) Cleanup(ctx context.Context) (int64, error) {
|
||||
if a.cleaner == nil {
|
||||
return 0, nil
|
||||
}
|
||||
deleted, _, err := a.cleaner.Run(ctx, a.cfg)
|
||||
return deleted, err
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) ListTags(ctx context.Context, namespace string, opts TagListOptions) ([]Tag, error) {
|
||||
orgID, err := namespaceToOrgID(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := &annotations.TagsQuery{
|
||||
OrgID: orgID,
|
||||
Limit: int64(opts.Limit),
|
||||
Tag: opts.Prefix,
|
||||
}
|
||||
|
||||
result, err := a.repo.FindTags(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := make([]Tag, len(result.Tags))
|
||||
for i, t := range result.Tags {
|
||||
tags[i] = Tag{Name: t.Tag, Count: t.Count}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) toK8sResource(item *annotations.ItemDTO, namespace string) *annotationV0.Annotation {
|
||||
anno := &annotationV0.Annotation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("a-%d", item.ID),
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: annotationV0.AnnotationSpec{
|
||||
Text: item.Text,
|
||||
Time: item.Time,
|
||||
Tags: item.Tags,
|
||||
},
|
||||
}
|
||||
|
||||
if item.DashboardUID != nil && *item.DashboardUID != "" {
|
||||
anno.Spec.DashboardUID = item.DashboardUID
|
||||
}
|
||||
if item.PanelID != 0 {
|
||||
anno.Spec.PanelID = &item.PanelID
|
||||
}
|
||||
if item.TimeEnd != 0 {
|
||||
anno.Spec.TimeEnd = &item.TimeEnd
|
||||
}
|
||||
|
||||
return anno
|
||||
}
|
||||
|
||||
func (a *sqlAdapter) fromK8sResource(anno *annotationV0.Annotation) *annotations.Item {
|
||||
item := &annotations.Item{
|
||||
Text: anno.Spec.Text,
|
||||
Epoch: anno.Spec.Time,
|
||||
Tags: anno.Spec.Tags,
|
||||
}
|
||||
|
||||
if anno.Name != "" {
|
||||
if id, err := parseAnnotationID(anno.Name); err == nil {
|
||||
item.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
if anno.Spec.DashboardUID != nil {
|
||||
item.DashboardUID = *anno.Spec.DashboardUID
|
||||
}
|
||||
if anno.Spec.PanelID != nil {
|
||||
item.PanelID = *anno.Spec.PanelID
|
||||
}
|
||||
if anno.Spec.TimeEnd != nil {
|
||||
item.EpochEnd = *anno.Spec.TimeEnd
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func parseAnnotationID(name string) (int64, error) {
|
||||
if len(name) < 3 || name[:2] != "a-" {
|
||||
return 0, fmt.Errorf("invalid annotation name format: %s", name)
|
||||
}
|
||||
return strconv.ParseInt(name[2:], 10, 64)
|
||||
}
|
||||
|
||||
func namespaceToOrgID(ctx context.Context, namespace string) (int64, error) {
|
||||
info, err := claims.ParseNamespace(namespace)
|
||||
return info.OrgID, err
|
||||
}
|
||||
Reference in New Issue
Block a user