mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 10:02:33 +08:00
135 lines
4.0 KiB
Go
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
|
|
}
|