Files
2024-06-25 10:06:03 -04:00

135 lines
4.0 KiB
Go

package sqlstash
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/store/entity/db"
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
)
func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequest) (*entity.CreateEntityResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.Create")
defer span.End()
if err := s.Init(); err != nil {
return nil, err
}
key, err := grafanaregistry.ParseKey(r.Entity.Key)
if err != nil {
return nil, fmt.Errorf("create entity: parse entity key: %w", err)
}
// validate and process the request to get the information we need to run
// the query
newEntity, err := entityForCreate(ctx, r, key)
if err != nil {
return nil, fmt.Errorf("create entity: entity from create entity request: %w", err)
}
err = s.sqlDB.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
if len(newEntity.Entity.Labels) > 0 {
// Pre-locking: register this entity's labels
insLabels := sqlEntityLabelsInsertRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
GUID: newEntity.Guid,
Labels: newEntity.Entity.Labels,
}
if _, err = exec(ctx, tx, sqlEntityLabelsInsert, insLabels); err != nil {
return fmt.Errorf("insert into entity_labels: %w", err)
}
}
// up to this point, we have done all the work possible before having to
// lock kind_version
// 1. Atomically increpement resource version for this kind
newVersion, err := kindVersionAtomicInc(ctx, tx, s.sqlDialect, key.Group, key.Resource)
if err != nil {
return err
}
newEntity.ResourceVersion = newVersion
// 2. Insert into entity
insEntity := sqlEntityInsertRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
Entity: newEntity,
TableEntity: true,
}
if _, err = exec(ctx, tx, sqlEntityInsert, insEntity); err != nil {
return fmt.Errorf("insert into entity: %w", err)
}
// 3. Insert into entity history
insEntityHistory := sqlEntityInsertRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
Entity: newEntity,
}
if _, err = exec(ctx, tx, sqlEntityInsert, insEntityHistory); err != nil {
return fmt.Errorf("insert into entity_history: %w", err)
}
// 4. Rebuild the whole folder tree structure if we're creating a folder
if newEntity.Group == folder.GROUP && newEntity.Resource == folder.RESOURCE {
if err = s.updateFolderTree(ctx, tx, key.Namespace); err != nil {
return fmt.Errorf("rebuild folder tree structure: %w", err)
}
}
return nil
})
if err != nil {
// TODO: should we define the "Error" field here and how? (i.e. how
// to determine what information can be disclosed to the user?)
return nil, fmt.Errorf("create entity: %w", err)
}
return &entity.CreateEntityResponse{
Entity: newEntity.Entity,
Status: entity.CreateEntityResponse_CREATED,
}, nil
}
// entityForCreate validates the given request and returns a *returnsEntity
// populated accordingly.
func entityForCreate(ctx context.Context, r *entity.CreateEntityRequest, key *grafanaregistry.Key) (*returnsEntity, error) {
newEntity := &returnsEntity{
Entity: cloneEntity(r.Entity),
}
if err := newEntity.marshal(); err != nil {
return nil, fmt.Errorf("serialize entity data for db: %w", err)
}
createdAt := time.Now().UnixMilli()
createdBy, err := getCurrentUser(ctx)
if err != nil {
return nil, err
}
newEntity.Guid = uuid.New().String()
newEntity.Group = key.Group
newEntity.Resource = key.Resource
newEntity.Namespace = key.Namespace
newEntity.Name = key.Name
newEntity.Size = int64(len(r.Entity.Body))
newEntity.ETag = createETag(r.Entity.Body, r.Entity.Meta, r.Entity.Status)
newEntity.CreatedAt = createdAt
newEntity.CreatedBy = createdBy
newEntity.UpdatedAt = createdAt
newEntity.UpdatedBy = createdBy
newEntity.Action = entity.Entity_CREATED
return newEntity, nil
}