Files
grafana/pkg/services/store/entity/sqlstash/delete.go
2024-07-18 08:03:18 -07:00

119 lines
3.7 KiB
Go

package sqlstash
import (
"context"
"errors"
"fmt"
"time"
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/storage/unified/sql/sqltemplate"
)
func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequest) (*entity.DeleteEntityResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.Delete")
defer span.End()
if err := s.Init(); err != nil {
return nil, err
}
key, err := grafanaregistry.ParseKey(r.Key)
if err != nil {
return nil, fmt.Errorf("delete entity: parse entity key: %w", err)
}
updatedBy, err := getCurrentUser(ctx)
if err != nil {
return nil, fmt.Errorf("delete entity: %w", err)
}
ret := new(entity.DeleteEntityResponse)
err = s.sqlDB.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
// Pre-locking: get the latest version of the entity
previous, err := readEntity(ctx, tx, s.sqlDialect, key, r.PreviousVersion, true, false)
if errors.Is(err, ErrNotFound) {
ret.Status = entity.DeleteEntityResponse_NOTFOUND
return nil
}
if err != nil {
return err
}
// Pre-locking: remove this entity's labels
delLabelsReq := sqlEntityLabelsDeleteRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
GUID: previous.Guid,
}
if _, err = exec(ctx, tx, sqlEntityLabelsDelete, delLabelsReq); err != nil {
return fmt.Errorf("delete all labels of entity with guid %q: %w",
previous.Guid, err)
}
// TODO: Pre-locking: remove this entity's refs from `entity_ref`
// Pre-locking: delete from "entity"
delEntityReq := sqlEntityDeleteRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
Key: key,
}
if _, err = exec(ctx, tx, sqlEntityDelete, delEntityReq); err != nil {
return fmt.Errorf("delete entity with key %#v: %w", key, err)
}
// Pre-locking: rebuild the whole folder tree structure if we're
// deleting a folder
if previous.Group == folder.GROUP && previous.Resource == folder.RESOURCE {
if err = s.updateFolderTree(ctx, tx, key.Namespace); err != nil {
return fmt.Errorf("rebuild folder tree structure: %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
}
// k8s expects us to return the entity as it was before the deletion,
// but with the updated RV
previous.ResourceVersion = newVersion
// build the new row to be inserted
deletedVersion := *previous // copy marshaled data since it won't change
deletedVersion.Entity = cloneEntity(previous.Entity) // clone entity
deletedVersion.Action = entity.Entity_DELETED
deletedVersion.UpdatedAt = time.Now().UnixMilli()
deletedVersion.UpdatedBy = updatedBy
// 2. Insert into entity history
insEntity := sqlEntityInsertRequest{
SQLTemplate: sqltemplate.New(s.sqlDialect),
Entity: &deletedVersion,
}
if _, err = exec(ctx, tx, sqlEntityInsert, insEntity); err != nil {
return fmt.Errorf("insert into entity_history: %w", err)
}
// success
ret.Status = entity.DeleteEntityResponse_DELETED
ret.Entity = previous.Entity
return nil
})
if err != nil {
// TODO: should we populate the Error field and how? (i.e. how to
// determine what information can be disclosed to the user?)
return nil, fmt.Errorf("delete entity: %w", err)
}
return ret, nil
}