Files
grafana/pkg/services/ngalert/store/provisioning_store.go
Alexander Weaver 935059a376 Alerting: Create basic storage layer for provisioning (#44679)
* Simplistic store API for provenance lookups on arbitrary types

* Add a few notes in comments

* Improved type safety for provisioned objects

* Clean-up TODOs for future PRs

* Clean up provisioning model

* Clean up tests

* Restrict allowable types in interface

* Fix linter error

* Move AlertRule domain methods to same file as AlertRule definition

* Update pkg/services/ngalert/models/provisioning.go

Co-authored-by: George Robinson <george.robinson@grafana.com>

* Complete interface rename

* Pass context through store API

* More idiomatic method names

* Better error description

* Improve code-docs

* Use ORM language instead of raw sql

* Add support for records in different orgs

* ResourceTypeID -> ResourceType since it's not an ID

Co-authored-by: George Robinson <george.robinson@grafana.com>
2022-02-04 13:23:19 -06:00

86 lines
2.9 KiB
Go

package store
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
type provenanceRecord struct {
Id int `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"'org_id'"`
RecordKey string
RecordType string
Provenance models.Provenance
}
func (pr provenanceRecord) TableName() string {
return "provenance_type"
}
// ProvisioningStore is a store of provisioning data for arbitrary objects.
type ProvisioningStore interface {
GetProvenance(ctx context.Context, o models.Provisionable) (models.Provenance, error)
// TODO: API to query all provenances for a specific type?
SetProvenance(ctx context.Context, o models.Provisionable, p models.Provenance) error
}
// GetProvenance gets the provenance status for a provisionable object.
func (st DBstore) GetProvenance(ctx context.Context, o models.Provisionable) (models.Provenance, error) {
recordType := o.ResourceType()
recordKey := o.ResourceID()
orgID := o.ResourceOrgID()
provenance := models.ProvenanceNone
err := st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
filter := "record_key = ? AND record_type = ? AND org_id = ?"
var result models.Provenance
has, err := sess.Table(provenanceRecord{}).Where(filter, recordKey, recordType, orgID).Desc("id").Cols("provenance").Get(&result)
if err != nil {
return fmt.Errorf("failed to query for existing provenance status: %w", err)
}
if has {
provenance = result
}
return nil
})
if err != nil {
return models.ProvenanceNone, err
}
return provenance, nil
}
// SetProvenance changes the provenance status for a provisionable object.
func (st DBstore) SetProvenance(ctx context.Context, o models.Provisionable, p models.Provenance) error {
recordType := o.ResourceType()
recordKey := o.ResourceID()
orgID := o.ResourceOrgID()
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
// TODO: Add a unit-of-work pattern, so updating objects + provenance will happen consistently with rollbacks across stores.
// TODO: Need to make sure that writing a record where our concurrency key fails will also fail the whole transaction. That way, this gets rolled back too. can't just check that 0 updates happened inmemory. Check with jp. If not possible, we need our own concurrency key.
// TODO: Clean up stale provenance records periodically.
filter := "record_key = ? AND record_type = ? AND org_id = ?"
_, err := sess.Table(provenanceRecord{}).Where(filter, recordKey, recordType, orgID).Delete(provenanceRecord{})
if err != nil {
return fmt.Errorf("failed to delete pre-existing provisioning status: %w", err)
}
record := provenanceRecord{
RecordKey: recordKey,
RecordType: recordType,
Provenance: p,
OrgID: orgID,
}
if _, err := sess.Insert(record); err != nil {
return fmt.Errorf("failed to store provisioning status: %w", err)
}
return nil
})
}