mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 03:54:29 +08:00
Compare commits
3 Commits
docs/add-t
...
zserge/ann
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3205914a05 | ||
|
|
3db55d5135 | ||
|
|
268622abd4 |
246
pkg/registry/apps/annotation/kv.go
Normal file
246
pkg/registry/apps/annotation/kv.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package annotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
annotationV0 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kvStore struct {
|
||||||
|
db *badger.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKVStore(dbdir string) (Store, error) {
|
||||||
|
opts := badger.DefaultOptions(dbdir)
|
||||||
|
db, err := badger.Open(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &kvStore{db: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) Close() error { return kv.db.Close() }
|
||||||
|
|
||||||
|
// TODO: namespace!!!!
|
||||||
|
func keyUUID(id string) []byte { return []byte("a:uuid:" + id) }
|
||||||
|
func keyTime(t int64, id string) []byte { return []byte(fmt.Sprintf("a:time:%10d:%s", t/1000, id)) }
|
||||||
|
func keyDash(d string, id string) []byte { return []byte("a:dash:" + d + ":" + id) }
|
||||||
|
func keyTag(tag, id string) []byte { return []byte("a:tag:" + tag + ":" + id) }
|
||||||
|
|
||||||
|
func (kv *kvStore) Get(ctx context.Context, namespace, name string) (*annotationV0.Annotation, error) {
|
||||||
|
var result *annotationV0.Annotation
|
||||||
|
err := kv.db.View(func(txn *badger.Txn) error {
|
||||||
|
// TODO: namespace
|
||||||
|
a, err := kv.load(txn, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result = a
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) List(ctx context.Context, namespace string, opts ListOptions) (*AnnotationList, error) {
|
||||||
|
result := []annotationV0.Annotation{}
|
||||||
|
|
||||||
|
// if *tag != "" {
|
||||||
|
// prefix := []byte("a:tag:" + *tag + ":")
|
||||||
|
// db.View(func(txn *badger.Txn) error {
|
||||||
|
// it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||||
|
// for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||||
|
// parts := bytes.Split(it.Item().Key(), []byte(":"))
|
||||||
|
// id := string(parts[len(parts)-1])
|
||||||
|
// a, err := loadUUID(txn, id)
|
||||||
|
// if err == nil && a.TimeEnd >= from && a.Time <= to {
|
||||||
|
// if *dash == "" || a.DashboardUID == *dash {
|
||||||
|
// result = append(result, a)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// it.Close()
|
||||||
|
// return nil
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
prefix := []byte("a:time:")
|
||||||
|
fromKey := []byte(fmt.Sprintf("a:time:%020d:", opts.From))
|
||||||
|
kv.db.View(func(txn *badger.Txn) error {
|
||||||
|
// TODO: limit
|
||||||
|
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||||
|
for it.Seek(fromKey); it.ValidForPrefix(prefix); it.Next() {
|
||||||
|
k := it.Item().Key()
|
||||||
|
fmt.Println("key", string(k))
|
||||||
|
parts := bytes.Split(k, []byte(":"))
|
||||||
|
t, _ := strconv.ParseInt(string(parts[2]), 10, 64)
|
||||||
|
if t > opts.To {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id := string(parts[3])
|
||||||
|
a, err := kv.load(txn, id)
|
||||||
|
if err == nil {
|
||||||
|
result = append(result, *a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.Close()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return &AnnotationList{Items: result}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) load(txn *badger.Txn, id string) (*annotationV0.Annotation, error) {
|
||||||
|
var a annotationV0.Annotation
|
||||||
|
item, e := txn.Get(keyUUID(id))
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
err := item.Value(func(v []byte) error {
|
||||||
|
return json.Unmarshal(v, &a.Spec)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.Name = id
|
||||||
|
return &a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) put(k, v []byte) error {
|
||||||
|
return kv.db.Update(func(txn *badger.Txn) error { return txn.Set(k, v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) Create(ctx context.Context, a *annotationV0.Annotation) (*annotationV0.Annotation, error) {
|
||||||
|
b, err := json.Marshal(a.Spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO: name, namespace
|
||||||
|
if a.Name == "" {
|
||||||
|
a.Name = uuid.New().String()
|
||||||
|
}
|
||||||
|
fmt.Println("name", a.Name)
|
||||||
|
if err := kv.put(keyUUID(a.Name), b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := kv.put(keyTime(a.Spec.Time, a.Name), []byte{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if a.Spec.DashboardUID != nil {
|
||||||
|
if err := kv.put(keyDash(*a.Spec.DashboardUID, a.Name), []byte{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, t := range a.Spec.Tags {
|
||||||
|
if err := kv.put(keyTag(t, a.Name), []byte{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) Update(ctx context.Context, a *annotationV0.Annotation) error {
|
||||||
|
b, err := json.Marshal(a.Spec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kv.db.Update(func(txn *badger.Txn) error {
|
||||||
|
old, err := kv.load(txn, a.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(a.Spec.Tags)
|
||||||
|
slices.Sort(old.Spec.Tags)
|
||||||
|
|
||||||
|
tagsChanged := false
|
||||||
|
if len(a.Spec.Tags) != len(old.Spec.Tags) {
|
||||||
|
tagsChanged = true
|
||||||
|
} else {
|
||||||
|
for i := range a.Spec.Tags {
|
||||||
|
if a.Spec.Tags[i] != old.Spec.Tags[i] {
|
||||||
|
tagsChanged = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagsChanged {
|
||||||
|
for _, t := range old.Spec.Tags {
|
||||||
|
txn.Delete(keyTag(t, a.Name))
|
||||||
|
}
|
||||||
|
for _, t := range a.Spec.Tags {
|
||||||
|
txn.Set(keyTag(t, a.Name), []byte{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return txn.Set(keyUUID(a.Name), b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) Delete(ctx context.Context, namespace, name string) error {
|
||||||
|
a, err := kv.Get(ctx, namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kv.db.Update(func(txn *badger.Txn) error {
|
||||||
|
if err := txn.Delete(keyUUID(a.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := txn.Delete(keyTime(a.Spec.Time, a.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if a.Spec.DashboardUID != nil {
|
||||||
|
if err := txn.Delete(keyDash(*a.Spec.DashboardUID, a.Name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, t := range a.Spec.Tags {
|
||||||
|
txn.Delete(keyTag(t, a.Name))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *kvStore) Tags(ctx context.Context, namespace string, opts TagListOptions) ([]Tag, error) {
|
||||||
|
tagCounts := make(map[string]int64)
|
||||||
|
|
||||||
|
prefix := []byte("a:tag:")
|
||||||
|
err := kv.db.View(func(txn *badger.Txn) error {
|
||||||
|
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||||
|
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||||
|
k := it.Item().Key()
|
||||||
|
fmt.Println("tag key", string(k))
|
||||||
|
parts := bytes.Split(k, []byte(":"))
|
||||||
|
if len(parts) < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tag := string(parts[2])
|
||||||
|
if opts.Prefix == "" || strings.HasPrefix(tag, opts.Prefix) {
|
||||||
|
tagCounts[tag]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.Close()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := make([]Tag, 0, len(tagCounts))
|
||||||
|
for name, count := range tagCounts {
|
||||||
|
tags = append(tags, Tag{Name: name, Count: count})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: sort tags by count or name?
|
||||||
|
|
||||||
|
if opts.Limit > 0 && len(tags) > opts.Limit {
|
||||||
|
tags = tags[:opts.Limit]
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
@@ -98,20 +98,20 @@ func (m *memoryStore) Create(ctx context.Context, anno *annotationV0.Annotation)
|
|||||||
return created, nil
|
return created, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) Update(ctx context.Context, anno *annotationV0.Annotation) (*annotationV0.Annotation, error) {
|
func (m *memoryStore) Update(ctx context.Context, anno *annotationV0.Annotation) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
key := anno.Namespace + "/" + anno.Name
|
key := anno.Namespace + "/" + anno.Name
|
||||||
|
|
||||||
if _, exists := m.data[key]; !exists {
|
if _, exists := m.data[key]; !exists {
|
||||||
return nil, fmt.Errorf("annotation not found")
|
return fmt.Errorf("annotation not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
updated := anno.DeepCopy()
|
updated := anno.DeepCopy()
|
||||||
m.data[key] = updated
|
m.data[key] = updated
|
||||||
|
|
||||||
return updated, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) Delete(ctx context.Context, namespace, name string) error {
|
func (m *memoryStore) Delete(ctx context.Context, namespace, name string) error {
|
||||||
@@ -156,3 +156,7 @@ func (m *memoryStore) ListTags(ctx context.Context, namespace string, opts TagLi
|
|||||||
|
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *memoryStore) Tags(ctx context.Context, namespace string, opts TagListOptions) ([]Tag, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package annotation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -39,7 +40,7 @@ var (
|
|||||||
type AnnotationAppInstaller struct {
|
type AnnotationAppInstaller struct {
|
||||||
appsdkapiserver.AppInstaller
|
appsdkapiserver.AppInstaller
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
legacy *legacyStorage
|
legacy *restStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAppInstaller(
|
func RegisterAppInstaller(
|
||||||
@@ -55,13 +56,18 @@ func RegisterAppInstaller(
|
|||||||
var tagHandler func(context.Context, app.CustomRouteResponseWriter, *app.CustomRouteRequest) error
|
var tagHandler func(context.Context, app.CustomRouteResponseWriter, *app.CustomRouteRequest) error
|
||||||
if service != nil {
|
if service != nil {
|
||||||
mapper := grafrequest.GetNamespaceMapper(cfg)
|
mapper := grafrequest.GetNamespaceMapper(cfg)
|
||||||
sqlAdapter := NewSQLAdapter(service, cleaner, mapper, cfg)
|
kvAdapter, err := NewKVStore("kv")
|
||||||
installer.legacy = &legacyStorage{
|
if err != nil {
|
||||||
store: sqlAdapter,
|
return nil, err
|
||||||
|
}
|
||||||
|
// sqlAdapter := NewSQLAdapter(service, cleaner, mapper, cfg)
|
||||||
|
installer.legacy = &restStorage{
|
||||||
|
// store: sqlAdapter,
|
||||||
|
store: kvAdapter,
|
||||||
mapper: mapper,
|
mapper: mapper,
|
||||||
}
|
}
|
||||||
// Create the tags handler using the sqlAdapter as TagProvider
|
// Create the tags handler using the sqlAdapter as TagProvider
|
||||||
tagHandler = newTagsHandler(sqlAdapter)
|
tagHandler = newTagsHandler(installer.legacy.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := simple.NewAppProvider(apis.LocalManifest(), nil, annotationapp.New)
|
provider := simple.NewAppProvider(apis.LocalManifest(), nil, annotationapp.New)
|
||||||
@@ -116,47 +122,52 @@ func (a *AnnotationAppInstaller) GetLegacyStorage(requested schema.GroupVersionR
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ rest.Scoper = (*legacyStorage)(nil)
|
_ rest.Scoper = (*restStorage)(nil)
|
||||||
_ rest.SingularNameProvider = (*legacyStorage)(nil)
|
_ rest.SingularNameProvider = (*restStorage)(nil)
|
||||||
_ rest.Getter = (*legacyStorage)(nil)
|
_ rest.Getter = (*restStorage)(nil)
|
||||||
_ rest.Storage = (*legacyStorage)(nil)
|
_ rest.Storage = (*restStorage)(nil)
|
||||||
_ rest.Creater = (*legacyStorage)(nil)
|
_ rest.Creater = (*restStorage)(nil)
|
||||||
_ rest.Updater = (*legacyStorage)(nil)
|
_ rest.Updater = (*restStorage)(nil)
|
||||||
_ rest.GracefulDeleter = (*legacyStorage)(nil)
|
_ rest.GracefulDeleter = (*restStorage)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type legacyStorage struct {
|
type restStorage struct {
|
||||||
store Store
|
store Store
|
||||||
mapper grafrequest.NamespaceMapper
|
mapper grafrequest.NamespaceMapper
|
||||||
tableConverter rest.TableConvertor
|
tableConverter rest.TableConvertor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) New() runtime.Object {
|
func (s *restStorage) New() runtime.Object {
|
||||||
return annotationV0.AnnotationKind().ZeroValue()
|
return annotationV0.AnnotationKind().ZeroValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) Destroy() {}
|
func (s *restStorage) Destroy() {}
|
||||||
|
|
||||||
func (s *legacyStorage) NamespaceScoped() bool {
|
func (s *restStorage) NamespaceScoped() bool {
|
||||||
return true // namespace == org
|
return true // namespace == org
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) GetSingularName() string {
|
func (s *restStorage) GetSingularName() string {
|
||||||
return strings.ToLower(annotationV0.AnnotationKind().Kind())
|
return strings.ToLower(annotationV0.AnnotationKind().Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) NewList() runtime.Object {
|
func (s *restStorage) NewList() runtime.Object {
|
||||||
return annotationV0.AnnotationKind().ZeroListValue()
|
return annotationV0.AnnotationKind().ZeroListValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
func (s *restStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
// TODO: hierarchy of list options (where do tags fit in - seems to be mutually exclusive with dashboard query?)
|
||||||
|
// 1. No options: list all for namespace
|
||||||
|
// 2. By dashboard: list all annotations for namespace+dashboard (i.e. remove by dashboard)
|
||||||
|
// 3. By panel: list all annotations for namespace+dashboard+panel (i.e. remove by dashboard+panel)
|
||||||
|
// 4. By time range: list all annotations for namespace in time range
|
||||||
|
func (s *restStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
namespace := request.NamespaceValue(ctx)
|
namespace := request.NamespaceValue(ctx)
|
||||||
|
|
||||||
opts := ListOptions{}
|
opts := ListOptions{To: time.Now().UnixMilli()}
|
||||||
if options.FieldSelector != nil {
|
if options.FieldSelector != nil {
|
||||||
for _, r := range options.FieldSelector.Requirements() {
|
for _, r := range options.FieldSelector.Requirements() {
|
||||||
switch r.Field {
|
switch r.Field {
|
||||||
@@ -232,12 +243,12 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
|
|||||||
return &annotationV0.AnnotationList{Items: result.Items}, nil
|
return &annotationV0.AnnotationList{Items: result.Items}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
func (s *restStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||||
namespace := request.NamespaceValue(ctx)
|
namespace := request.NamespaceValue(ctx)
|
||||||
return s.store.Get(ctx, namespace, name)
|
return s.store.Get(ctx, namespace, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) Create(ctx context.Context,
|
func (s *restStorage) Create(ctx context.Context,
|
||||||
obj runtime.Object,
|
obj runtime.Object,
|
||||||
createValidation rest.ValidateObjectFunc,
|
createValidation rest.ValidateObjectFunc,
|
||||||
options *metav1.CreateOptions,
|
options *metav1.CreateOptions,
|
||||||
@@ -249,7 +260,7 @@ func (s *legacyStorage) Create(ctx context.Context,
|
|||||||
return s.store.Create(ctx, resource)
|
return s.store.Create(ctx, resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) Update(ctx context.Context,
|
func (s *restStorage) Update(ctx context.Context,
|
||||||
name string,
|
name string,
|
||||||
objInfo rest.UpdatedObjectInfo,
|
objInfo rest.UpdatedObjectInfo,
|
||||||
createValidation rest.ValidateObjectFunc,
|
createValidation rest.ValidateObjectFunc,
|
||||||
@@ -257,15 +268,34 @@ func (s *legacyStorage) Update(ctx context.Context,
|
|||||||
forceAllowCreate bool,
|
forceAllowCreate bool,
|
||||||
options *metav1.UpdateOptions,
|
options *metav1.UpdateOptions,
|
||||||
) (runtime.Object, bool, error) {
|
) (runtime.Object, bool, error) {
|
||||||
return nil, false, errors.New("not implemented")
|
old, err := s.Get(ctx, name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
obj, err := objInfo.UpdatedObject(ctx, old)
|
||||||
|
if err != nil {
|
||||||
|
return old, false, err
|
||||||
|
}
|
||||||
|
newObj, ok := obj.(*annotationV0.Annotation)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, k8serrors.NewBadRequest("expected valid annotation")
|
||||||
|
}
|
||||||
|
if updateValidation != nil {
|
||||||
|
if err := updateValidation(ctx, obj, old); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: validate that only name/tags are modified
|
||||||
|
err = s.store.Update(ctx, newObj)
|
||||||
|
return obj, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
func (s *restStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||||
namespace := request.NamespaceValue(ctx)
|
namespace := request.NamespaceValue(ctx)
|
||||||
err := s.store.Delete(ctx, namespace, name)
|
err := s.store.Delete(ctx, namespace, name)
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
func (s *restStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
return nil, fmt.Errorf("DeleteCollection for annotation is not available")
|
return nil, fmt.Errorf("DeleteCollection for annotation is not available")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,20 +125,20 @@ func (a *sqlAdapter) Create(ctx context.Context, anno *annotationV0.Annotation)
|
|||||||
return created, nil
|
return created, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *sqlAdapter) Update(ctx context.Context, anno *annotationV0.Annotation) (*annotationV0.Annotation, error) {
|
func (a *sqlAdapter) Update(ctx context.Context, anno *annotationV0.Annotation) error {
|
||||||
orgID, err := namespaceToOrgID(ctx, anno.Namespace)
|
orgID, err := namespaceToOrgID(ctx, anno.Namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
item := a.fromK8sResource(anno)
|
item := a.fromK8sResource(anno)
|
||||||
item.OrgID = orgID
|
item.OrgID = orgID
|
||||||
|
|
||||||
if err := a.repo.Update(ctx, item); err != nil {
|
if err := a.repo.Update(ctx, item); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return anno, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *sqlAdapter) Delete(ctx context.Context, namespace, name string) error {
|
func (a *sqlAdapter) Delete(ctx context.Context, namespace, name string) error {
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import (
|
|||||||
type Store interface {
|
type Store interface {
|
||||||
Get(ctx context.Context, namespace, name string) (*annotationV0.Annotation, error)
|
Get(ctx context.Context, namespace, name string) (*annotationV0.Annotation, error)
|
||||||
List(ctx context.Context, namespace string, opts ListOptions) (*AnnotationList, error)
|
List(ctx context.Context, namespace string, opts ListOptions) (*AnnotationList, error)
|
||||||
|
// TODO: return id only?
|
||||||
Create(ctx context.Context, annotation *annotationV0.Annotation) (*annotationV0.Annotation, error)
|
Create(ctx context.Context, annotation *annotationV0.Annotation) (*annotationV0.Annotation, error)
|
||||||
Update(ctx context.Context, annotation *annotationV0.Annotation) (*annotationV0.Annotation, error)
|
Update(ctx context.Context, annotation *annotationV0.Annotation) error
|
||||||
Delete(ctx context.Context, namespace, name string) error
|
Delete(ctx context.Context, namespace, name string) error
|
||||||
|
|
||||||
|
Tags(ctx context.Context, namespace string, opts TagListOptions) ([]Tag, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
@@ -22,26 +25,20 @@ type ListOptions struct {
|
|||||||
Limit int64
|
Limit int64
|
||||||
Continue string
|
Continue string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnnotationList struct {
|
type AnnotationList struct {
|
||||||
Items []annotationV0.Annotation
|
Items []annotationV0.Annotation
|
||||||
Continue string
|
Continue string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LifecycleManager interface {
|
|
||||||
Cleanup(ctx context.Context) (int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TagProvider interface {
|
|
||||||
ListTags(ctx context.Context, namespace string, opts TagListOptions) ([]Tag, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TagListOptions struct {
|
type TagListOptions struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
Limit int
|
Limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string
|
Name string
|
||||||
Count int64
|
Count int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LifecycleManager interface {
|
||||||
|
Cleanup(ctx context.Context) (int64, error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ type tagItem struct {
|
|||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTagsHandler(tagProvider TagProvider) func(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
func newTagsHandler(tagProvider Store) func(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
||||||
return func(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
return func(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
||||||
fmt.Println("Handling /tags request")
|
fmt.Println("Handling /tags request")
|
||||||
namespace := request.ResourceIdentifier.Namespace
|
namespace := request.ResourceIdentifier.Namespace
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
namespace = "default"
|
namespace = "default"
|
||||||
}
|
}
|
||||||
tags, err := tagProvider.ListTags(ctx, namespace, TagListOptions{})
|
tags, err := tagProvider.Tags(ctx, namespace, TagListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user