mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package apistore
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apiserver/pkg/storage"
|
|
"k8s.io/klog/v2"
|
|
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
|
)
|
|
|
|
func logN(n, b float64) float64 {
|
|
return math.Log(n) / math.Log(b)
|
|
}
|
|
|
|
// Slightly modified function from https://github.com/dustin/go-humanize (MIT).
|
|
func formatBytes(numBytes int) string {
|
|
base := 1024.0
|
|
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
if numBytes < 10 {
|
|
return fmt.Sprintf("%d B", numBytes)
|
|
}
|
|
e := math.Floor(logN(float64(numBytes), base))
|
|
suffix := sizes[int(e)]
|
|
val := math.Floor(float64(numBytes)/math.Pow(base, e)*10+0.5) / 10
|
|
return fmt.Sprintf("%.1f %s", val, suffix)
|
|
}
|
|
|
|
// Called on create
|
|
func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime.Object) ([]byte, error) {
|
|
user, err := identity.GetRequester(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
obj, err := utils.MetaAccessor(newObject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if obj.GetName() == "" {
|
|
return nil, storage.ErrResourceVersionSetOnCreate
|
|
}
|
|
if obj.GetResourceVersion() != "" {
|
|
return nil, storage.ErrResourceVersionSetOnCreate
|
|
}
|
|
|
|
obj.SetGenerateName("") // Clear the random name field
|
|
obj.SetResourceVersion("")
|
|
obj.SetSelfLink("")
|
|
|
|
// Read+write will verify that origin format is accurate
|
|
origin, err := obj.GetOriginInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.SetOriginInfo(origin)
|
|
obj.SetUpdatedBy("")
|
|
obj.SetUpdatedTimestamp(nil)
|
|
obj.SetCreatedBy(user.GetUID())
|
|
|
|
var buf bytes.Buffer
|
|
if err = s.codec.Encode(newObject, &buf); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.handleLargeResources(ctx, obj, buf)
|
|
}
|
|
|
|
// Called on update
|
|
func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runtime.Object, previousObject runtime.Object) ([]byte, error) {
|
|
user, err := identity.GetRequester(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
obj, err := utils.MetaAccessor(updateObject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if obj.GetName() == "" {
|
|
return nil, fmt.Errorf("updated object must have a name")
|
|
}
|
|
|
|
previous, err := utils.MetaAccessor(previousObject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if previous.GetUID() == "" {
|
|
klog.Errorf("object is missing UID: %s, %s", obj.GetGroupVersionKind().String(), obj.GetName())
|
|
} else if obj.GetUID() != previous.GetUID() {
|
|
klog.Errorf("object UID mismatch: %s, was:%s, now: %s", obj.GetGroupVersionKind().String(), previous.GetName(), obj.GetUID())
|
|
obj.SetUID(previous.GetUID())
|
|
}
|
|
|
|
if obj.GetName() != previous.GetName() {
|
|
return nil, fmt.Errorf("name mismatch between existing and updated object")
|
|
}
|
|
|
|
obj.SetCreatedBy(previous.GetCreatedBy())
|
|
obj.SetCreationTimestamp(previous.GetCreationTimestamp())
|
|
obj.SetResourceVersion("") // removed from saved JSON because the RV is not yet calculated
|
|
|
|
// Read+write will verify that origin format is accurate
|
|
origin, err := obj.GetOriginInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.SetOriginInfo(origin)
|
|
obj.SetUpdatedBy(user.GetUID())
|
|
obj.SetUpdatedTimestampMillis(time.Now().UnixMilli())
|
|
|
|
var buf bytes.Buffer
|
|
if err = s.codec.Encode(updateObject, &buf); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.handleLargeResources(ctx, obj, buf)
|
|
}
|
|
|
|
func (s *Storage) handleLargeResources(ctx context.Context, obj utils.GrafanaMetaAccessor, buf bytes.Buffer) ([]byte, error) {
|
|
support := s.opts.LargeObjectSupport
|
|
if support != nil {
|
|
size := buf.Len()
|
|
if size > support.Threshold() {
|
|
if support.MaxSize() > 0 && size > support.MaxSize() {
|
|
return nil, fmt.Errorf("request object is too big (%s > %s)", formatBytes(size), formatBytes(support.MaxSize()))
|
|
}
|
|
}
|
|
|
|
key := &resource.ResourceKey{
|
|
Group: s.gr.Group,
|
|
Resource: s.gr.Resource,
|
|
Namespace: obj.GetNamespace(),
|
|
Name: obj.GetName(),
|
|
}
|
|
|
|
err := support.Deconstruct(ctx, key, s.store, obj, buf.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf.Reset()
|
|
orig, ok := obj.GetRuntimeObject()
|
|
if !ok {
|
|
return nil, fmt.Errorf("error using object as runtime object")
|
|
}
|
|
|
|
// Now encode the smaller version
|
|
if err = s.codec.Encode(orig, &buf); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|