/
githubmirror
/
grafana
Обзор
Документация
Войти
/
githubmirror
/
grafana
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/registry/apps/annotation/sql_adapter.go
304 строки
7 KB
Craig O'Donnell
Annotations: support substring match for new /tags endpoint (#130536)
12 авг 2026, 21:46
Не верифицирован
12 авг 2026, 21:46
7c0df5c
Код
Авторство
О чём код?
package annotation import ( "context" "fmt" "strconv" "time" claims "github.com/grafana/authlib/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" annotationV0 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/annotations" ) type sqlAdapter struct { repo annotations.Repository cleaner annotations.Cleaner cleanupSettings annotations.CleanupSettings } func NewSQLAdapter(repo annotations.Repository, cleaner annotations.Cleaner, cleanupSettings annotations.CleanupSettings) *sqlAdapter { return &sqlAdapter{ repo: repo, cleaner: cleaner, cleanupSettings: cleanupSettings, } } // Close is a no-op as sqlAdapter does not own the underlying sqlstore func (a *sqlAdapter) Close() error { return nil } 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, AnnotationID: id, Type: "annotation", } items, err := a.repo.Find(ctx, query) if err != nil { return nil, err } if len(items) == 0 { return nil, ErrNotFound } return a.toK8sResource(items[0], namespace), nil } 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 } offset := int64(0) if opts.Continue != "" { token, err := decodeContinueToken(opts.Continue) if err != nil { return nil, err } if token.Limit != opts.Limit { return nil, fmt.Errorf("continue token limit does not match the request limit") } offset = token.Offset } queryLimit := opts.Limit + 1 query := &annotations.ItemQuery{ SignedInUser: user, OrgID: orgID, DashboardUID: opts.DashboardUID, PanelID: opts.PanelID, From: opts.From, To: opts.To, Limit: queryLimit, Offset: offset, Type: "annotation", // CreatedBy holds the uid of the user to filter by. The SQL layer resolves // this to a numeric user_id via subquery. UserUID: opts.CreatedBy, Tags: opts.Tags, MatchAny: opts.TagsMatchAny, } items, err := a.repo.Find(ctx, query) if err != nil { return nil, err } // check if there's more items than the limit moreThanLimit := int64(len(items)) > opts.Limit if moreThanLimit { items = items[:opts.Limit] } result := make([]annotationV0.Annotation, 0, len(items)) for _, item := range items { result = append(result, *a.toK8sResource(item, namespace)) } list := &AnnotationList{Items: result} if moreThanLimit { list.Continue = encodeContinueToken(offset+opts.Limit, opts.Limit) } return list, 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 user, err := identity.GetRequester(ctx); err == nil { item.UserID, _ = identity.UserIdentifier(user.GetID()) } 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, }) } // Cleanup runs the legacy SQL cleaner; before is ignored because cleanupSettings carries its own cutoff. func (a *sqlAdapter) Cleanup(ctx context.Context, _ time.Time) (int64, error) { if a.cleaner == nil { return 0, nil } deleted, _, err := a.cleaner.Run(ctx, a.cleanupSettings) 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 } // The legacy repository only exposes a single substring match on Tag, so // either value is passed through as-is. tag := opts.Contains if tag == "" { tag = opts.Prefix } query := &annotations.TagsQuery{ OrgID: orgID, Limit: int64(opts.Limit), Tag: tag, } 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 { name := fmt.Sprintf("a-%d", item.ID) anno := &annotationV0.Annotation{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, UID: types.UID(name), }, 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.ID > 0 { SetLegacyID(anno, item.ID) } if m, err := utils.MetaAccessor(anno); err == nil { if item.UserUID != "" { m.SetCreatedBy(claims.NewTypeID(claims.TypeUser, item.UserUID)) } } 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 }