missing files

This commit is contained in:
Serge Zaitsev 2025-09-29 23:51:46 +02:00
parent 0819b35d12
commit f7c0dd775c
2 changed files with 324 additions and 203 deletions

View File

@ -1,203 +0,0 @@
package app
import (
"context"
"strconv"
"strings"
v0alpha1 "github.com/grafana/grafana/apps/annotations/pkg/apis/annotation/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/annotations"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
)
var (
_ rest.Storage = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.Scoper = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.SingularNameProvider = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.Getter = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.Lister = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.TableConvertor = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.Creater = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.Updater = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.GracefulDeleter = (*SimpleLegacyAnnotationStorage)(nil)
_ rest.CollectionDeleter = (*SimpleLegacyAnnotationStorage)(nil)
)
type SimpleLegacyAnnotationStorage struct {
legacyService annotations.Repository
tableConverter rest.TableConvertor
}
func NewSimpleLegacyAnnotationStorage(legacyService annotations.Repository) *SimpleLegacyAnnotationStorage {
return &SimpleLegacyAnnotationStorage{
legacyService: legacyService,
}
}
func (s *SimpleLegacyAnnotationStorage) SetTableConverter(converter rest.TableConvertor) {
s.tableConverter = converter
}
func (s *SimpleLegacyAnnotationStorage) New() runtime.Object {
return &v0alpha1.Annotation{}
}
func (s *SimpleLegacyAnnotationStorage) NewList() runtime.Object {
return &v0alpha1.AnnotationList{}
}
func (s *SimpleLegacyAnnotationStorage) Destroy() {}
func (s *SimpleLegacyAnnotationStorage) NamespaceScoped() bool {
return true // annotations are namespaced by org
}
func (s *SimpleLegacyAnnotationStorage) GetSingularName() string {
return strings.ToLower(v0alpha1.AnnotationKind().Kind())
}
func (s *SimpleLegacyAnnotationStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
if s.tableConverter != nil {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
return rest.NewDefaultTableConvertor(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}).ConvertToTable(ctx, object, tableOptions)
}
func (s *SimpleLegacyAnnotationStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
annotationID, err := s.parseAnnotationID(name)
if err != nil {
return nil, apierrors.NewBadRequest("invalid annotation name: " + err.Error())
}
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("namespace not found in context")
}
orgID, err := ExtractOrgIDFromNamespace(namespace)
if err != nil {
return nil, apierrors.NewBadRequest("invalid namespace: " + err.Error())
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, apierrors.NewUnauthorized("could not get user from context")
}
query := &annotations.ItemQuery{
OrgID: orgID,
AnnotationID: annotationID,
SignedInUser: user,
Limit: 1,
}
items, err := s.legacyService.Find(ctx, query)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
if len(items) == 0 {
return nil, apierrors.NewNotFound(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, name)
}
annotation, err := ConvertLegacyAnnotationToK8s(items[0], orgID)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
return annotation, nil
}
func (s *SimpleLegacyAnnotationStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("namespace not found in context")
}
orgID, err := ExtractOrgIDFromNamespace(namespace)
if err != nil {
return nil, apierrors.NewBadRequest("invalid namespace: " + err.Error())
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, apierrors.NewUnauthorized("could not get user from context")
}
query := &annotations.ItemQuery{
OrgID: orgID,
SignedInUser: user,
Limit: 100,
}
if options.Limit > 0 {
query.Limit = options.Limit
}
items, err := s.legacyService.Find(ctx, query)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
annotations := make([]v0alpha1.Annotation, 0, len(items))
for _, item := range items {
annotation, err := ConvertLegacyAnnotationToK8s(item, orgID)
if err != nil {
continue
}
annotations = append(annotations, *annotation)
}
return &v0alpha1.AnnotationList{
TypeMeta: metav1.TypeMeta{
APIVersion: v0alpha1.GroupVersion.String(),
Kind: "AnnotationList",
},
Items: annotations,
}, nil
}
func (s *SimpleLegacyAnnotationStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return nil, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "create")
}
func (s *SimpleLegacyAnnotationStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
return nil, false, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "update")
}
func (s *SimpleLegacyAnnotationStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
return nil, false, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "delete")
}
func (s *SimpleLegacyAnnotationStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
return nil, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "deletecollection")
}
func (s *SimpleLegacyAnnotationStorage) parseAnnotationID(name string) (int64, error) {
return strconv.ParseInt(name, 10, 64)
}

View File

@ -0,0 +1,324 @@
package app
import (
"context"
"fmt"
"strconv"
"strings"
v0alpha1 "github.com/grafana/grafana/apps/annotations/pkg/apis/annotation/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/annotations"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
)
var (
_ rest.Storage = (*LegacyAnnotationStorage)(nil)
_ rest.Scoper = (*LegacyAnnotationStorage)(nil)
_ rest.SingularNameProvider = (*LegacyAnnotationStorage)(nil)
_ rest.Getter = (*LegacyAnnotationStorage)(nil)
_ rest.Lister = (*LegacyAnnotationStorage)(nil)
_ rest.TableConvertor = (*LegacyAnnotationStorage)(nil)
_ rest.Creater = (*LegacyAnnotationStorage)(nil)
_ rest.Updater = (*LegacyAnnotationStorage)(nil)
_ rest.GracefulDeleter = (*LegacyAnnotationStorage)(nil)
_ rest.CollectionDeleter = (*LegacyAnnotationStorage)(nil)
)
type LegacyAnnotationStorage struct {
legacyService annotations.Repository
tableConverter rest.TableConvertor
}
func NewLegacyAnnotationStorage(legacyService annotations.Repository) *LegacyAnnotationStorage {
return &LegacyAnnotationStorage{
legacyService: legacyService,
}
}
func (s *LegacyAnnotationStorage) SetTableConverter(converter rest.TableConvertor) {
s.tableConverter = converter
}
func (s *LegacyAnnotationStorage) New() runtime.Object {
return &v0alpha1.Annotation{}
}
func (s *LegacyAnnotationStorage) NewList() runtime.Object {
return &v0alpha1.AnnotationList{}
}
func (s *LegacyAnnotationStorage) Destroy() {
}
func (s *LegacyAnnotationStorage) NamespaceScoped() bool {
return true
}
func (s *LegacyAnnotationStorage) GetSingularName() string {
return strings.ToLower(v0alpha1.AnnotationKind().Kind())
}
func (s *LegacyAnnotationStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
if s.tableConverter != nil {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
return rest.NewDefaultTableConvertor(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}).ConvertToTable(ctx, object, tableOptions)
}
func (s *LegacyAnnotationStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
annotationID, err := s.parseAnnotationID(name)
if err != nil {
return nil, apierrors.NewBadRequest("invalid annotation name: " + err.Error())
}
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("namespace not found in context")
}
orgID, err := ExtractOrgIDFromNamespace(namespace)
if err != nil {
return nil, apierrors.NewBadRequest("invalid namespace: " + err.Error())
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, apierrors.NewUnauthorized("could not get user from context")
}
query := &annotations.ItemQuery{
OrgID: orgID,
AnnotationID: annotationID,
SignedInUser: user,
Limit: 1,
}
items, err := s.legacyService.Find(ctx, query)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
if len(items) == 0 {
return nil, apierrors.NewNotFound(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, name)
}
annotation, err := ConvertLegacyAnnotationToK8s(items[0], orgID)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
return annotation, nil
}
func (s *LegacyAnnotationStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("namespace not found in context")
}
orgID, err := ExtractOrgIDFromNamespace(namespace)
if err != nil {
return nil, apierrors.NewBadRequest("invalid namespace: " + err.Error())
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, apierrors.NewUnauthorized("could not get user from context")
}
query := &annotations.ItemQuery{
OrgID: orgID,
SignedInUser: user,
Limit: 100,
}
if options.Limit > 0 {
query.Limit = options.Limit
}
items, err := s.legacyService.Find(ctx, query)
if err != nil {
return nil, apierrors.NewInternalError(err)
}
annotations := make([]v0alpha1.Annotation, 0, len(items))
for _, item := range items {
annotation, err := ConvertLegacyAnnotationToK8s(item, orgID)
if err != nil {
continue
}
annotations = append(annotations, *annotation)
}
return &v0alpha1.AnnotationList{
TypeMeta: metav1.TypeMeta{
APIVersion: v0alpha1.GroupVersion.String(),
Kind: "AnnotationList",
},
Items: annotations,
}, nil
}
func (s *LegacyAnnotationStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
annotation, ok := obj.(*v0alpha1.Annotation)
if !ok {
return nil, apierrors.NewBadRequest("expected annotation object")
}
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("namespace not found in context")
}
orgID, err := ExtractOrgIDFromNamespace(namespace)
if err != nil {
return nil, apierrors.NewBadRequest("invalid namespace: " + err.Error())
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, apierrors.NewUnauthorized("could not get user from context")
}
item, err := ConvertK8sAnnotationToLegacy(annotation, orgID)
if err != nil {
return nil, apierrors.NewBadRequest("invalid annotation: " + err.Error())
}
if userID, err := strconv.ParseInt(user.GetID(), 10, 64); err == nil {
item.UserID = userID
}
legacyItem := ConvertItemDTOToItem(item, orgID)
if err := s.legacyService.Save(ctx, legacyItem); err != nil {
return nil, apierrors.NewInternalError(err)
}
item.ID = legacyItem.ID
annotation.Name = strconv.FormatInt(item.ID, 10)
annotation.Namespace = GenerateNamespaceFromOrgID(orgID)
return annotation, nil
}
func (s *LegacyAnnotationStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
return nil, false, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "update")
}
func (s *LegacyAnnotationStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
return nil, false, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "delete")
}
func (s *LegacyAnnotationStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
return nil, apierrors.NewMethodNotSupported(schema.GroupResource{
Group: "annotation.grafana.app",
Resource: "annotations",
}, "deletecollection")
}
func (s *LegacyAnnotationStorage) parseAnnotationID(name string) (int64, error) {
return strconv.ParseInt(name, 10, 64)
}
// Helper functions
func ExtractOrgIDFromNamespace(namespace string) (int64, error) {
if strings.HasPrefix(namespace, "org-") {
orgIDStr := strings.TrimPrefix(namespace, "org-")
return strconv.ParseInt(orgIDStr, 10, 64)
}
return strconv.ParseInt(namespace, 10, 64)
}
func GenerateNamespaceFromOrgID(orgID int64) string {
return fmt.Sprintf("org-%d", orgID)
}
func ConvertLegacyAnnotationToK8s(item *annotations.ItemDTO, orgID int64) (*v0alpha1.Annotation, error) {
annotation := &v0alpha1.Annotation{
ObjectMeta: metav1.ObjectMeta{
Name: strconv.FormatInt(item.ID, 10),
Namespace: GenerateNamespaceFromOrgID(orgID),
},
Spec: v0alpha1.AnnotationSpec{
Text: item.Text,
},
}
if item.DashboardUID != nil && *item.DashboardUID != "" {
annotation.Spec.DashboardUID = item.DashboardUID
}
if item.PanelID != 0 {
annotation.Spec.PanelId = &item.PanelID
}
if item.Created > 0 {
annotation.ObjectMeta.CreationTimestamp = metav1.Unix(item.Created/1000, (item.Created%1000)*1000000)
}
return annotation, nil
}
func ConvertK8sAnnotationToLegacy(annotation *v0alpha1.Annotation, orgID int64) (*annotations.ItemDTO, error) {
id, err := strconv.ParseInt(annotation.Name, 10, 64)
if err != nil {
id = 0
}
item := &annotations.ItemDTO{
ID: id,
Text: annotation.Spec.Text,
DashboardUID: annotation.Spec.DashboardUID,
}
if annotation.Spec.PanelId != nil {
item.PanelID = *annotation.Spec.PanelId
}
if !annotation.ObjectMeta.CreationTimestamp.IsZero() {
item.Created = annotation.ObjectMeta.CreationTimestamp.Unix() * 1000
}
return item, nil
}
func ConvertItemDTOToItem(dto *annotations.ItemDTO, orgID int64) *annotations.Item {
item := &annotations.Item{
ID: dto.ID,
OrgID: orgID,
UserID: dto.UserID,
PanelID: dto.PanelID,
Text: dto.Text,
AlertID: dto.AlertID,
Created: dto.Created,
Updated: dto.Updated,
Tags: dto.Tags,
}
if dto.DashboardUID != nil {
item.DashboardUID = *dto.DashboardUID
}
return item
}