The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/services/store/entity/sqlstash/create.go

134 lines
3.9 KiB

package sqlstash
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"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 := entity.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 *entity.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
}