mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 16:57:14 -06:00
Alerting: Receiver API Get+List+Delete (#90384)
This commit is contained in:
parent
efdb08ed8c
commit
b7f422b68d
@ -1,5 +1,7 @@
|
||||
package v0alpha1
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Integration defines model for Integration.
|
||||
// +k8s:openapi-gen=true
|
||||
type Integration struct {
|
||||
@ -7,9 +9,9 @@ type Integration struct {
|
||||
// +mapType=atomic
|
||||
SecureFields map[string]bool `json:"SecureFields,omitempty"`
|
||||
// +listType=atomic
|
||||
Settings []byte `json:"settings"`
|
||||
Type string `json:"type"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
Settings json.RawMessage `json:"settings"`
|
||||
Type string `json:"type"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
// ReceiverSpec defines model for Spec.
|
||||
|
@ -8,6 +8,8 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@ -28,7 +30,7 @@ func (in *Integration) DeepCopyInto(out *Integration) {
|
||||
}
|
||||
if in.Settings != nil {
|
||||
in, out := &in.Settings, &out.Settings
|
||||
*out = make([]byte, len(*in))
|
||||
*out = make(json.RawMessage, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Uid != nil {
|
||||
|
@ -4,14 +4,18 @@
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
)
|
||||
|
||||
// IntegrationApplyConfiguration represents an declarative configuration of the Integration type for use
|
||||
// with apply.
|
||||
type IntegrationApplyConfiguration struct {
|
||||
DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"`
|
||||
SecureFields map[string]bool `json:"SecureFields,omitempty"`
|
||||
Settings []byte `json:"settings,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"`
|
||||
SecureFields map[string]bool `json:"SecureFields,omitempty"`
|
||||
Settings *json.RawMessage `json:"settings,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
// IntegrationApplyConfiguration constructs an declarative configuration of the Integration type for use with
|
||||
@ -42,13 +46,11 @@ func (b *IntegrationApplyConfiguration) WithSecureFields(entries map[string]bool
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSettings adds the given value to the Settings field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Settings field.
|
||||
func (b *IntegrationApplyConfiguration) WithSettings(values ...byte) *IntegrationApplyConfiguration {
|
||||
for i := range values {
|
||||
b.Settings = append(b.Settings, values[i])
|
||||
}
|
||||
// WithSettings sets the Settings field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Settings field is set to the value of the last call.
|
||||
func (b *IntegrationApplyConfiguration) WithSettings(value json.RawMessage) *IntegrationApplyConfiguration {
|
||||
b.Settings = &value
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package receiver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
|
||||
@ -48,7 +49,7 @@ func convertToK8sResource(orgID int64, receiver definitions.GettableApiReceiver,
|
||||
Uid: &integration.UID,
|
||||
Type: integration.Type,
|
||||
DisableResolveMessage: &integration.DisableResolveMessage,
|
||||
Settings: integration.Settings,
|
||||
Settings: json.RawMessage(integration.Settings),
|
||||
SecureFields: integration.SecureFields,
|
||||
})
|
||||
}
|
||||
@ -83,7 +84,7 @@ func convertToDomainModel(receiver *model.Receiver) (definitions.GettableApiRece
|
||||
grafanaIntegration := definitions.GettableGrafanaReceiver{
|
||||
Name: receiver.Spec.Title,
|
||||
Type: integration.Type,
|
||||
Settings: integration.Settings,
|
||||
Settings: definitions.RawMessage(integration.Settings),
|
||||
SecureFields: integration.SecureFields,
|
||||
//Provenance: "", //TODO: Convert provenance?
|
||||
}
|
||||
|
@ -93,9 +93,8 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := models.GetReceiverQuery{
|
||||
q := models.GetReceiversQuery{
|
||||
OrgID: info.OrgID,
|
||||
Name: uid, // TODO: Name/UID mapping or change signature of service.
|
||||
//Decrypt: ctx.QueryBool("decrypt"), // TODO: Query params.
|
||||
}
|
||||
|
||||
@ -104,12 +103,18 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := s.service.GetReceiver(ctx, q, user)
|
||||
res, err := s.service.GetReceivers(ctx, q, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertToK8sResource(info.OrgID, res, s.namespacer)
|
||||
for _, r := range res {
|
||||
if getUID(r) == uid {
|
||||
return convertToK8sResource(info.OrgID, r, s.namespacer)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFound(resourceInfo.GroupResource(), uid)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Create(ctx context.Context,
|
||||
@ -211,13 +216,9 @@ func (s *legacyStorage) Delete(ctx context.Context, uid string, deleteValidation
|
||||
if options.Preconditions != nil && options.Preconditions.ResourceVersion != nil {
|
||||
version = *options.Preconditions.ResourceVersion
|
||||
}
|
||||
p, ok := old.(*notifications.Receiver)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected receiver but got %s", old.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
|
||||
err = s.service.DeleteReceiver(ctx, p.Spec.Title, info.OrgID, definitions.Provenance(models.ProvenanceNone), version) // TODO add support for dry-run option
|
||||
return old, false, err // false - will be deleted async
|
||||
err = s.service.DeleteReceiver(ctx, uid, info.OrgID, definitions.Provenance(models.ProvenanceNone), version) // TODO add support for dry-run option
|
||||
return old, false, err // false - will be deleted async
|
||||
}
|
||||
|
||||
func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
||||
|
@ -80,7 +80,7 @@ func (t NotificationsAPIBuilder) GetAPIGroupInfo(
|
||||
return nil, fmt.Errorf("failed to initialize time-interval storage: %w", err)
|
||||
}
|
||||
|
||||
recvStorage, err := receiver.NewStorage(nil, t.namespacer, scheme, optsGetter, dualWriteBuilder) // TODO: add receiver service
|
||||
recvStorage, err := receiver.NewStorage(t.ng.Api.ReceiverService, t.namespacer, scheme, optsGetter, dualWriteBuilder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize receiver storage: %w", err)
|
||||
}
|
||||
|
@ -5,13 +5,17 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
)
|
||||
|
||||
@ -22,6 +26,11 @@ var (
|
||||
ErrNotFound = errors.New("not found") // TODO: convert to errutil
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReceiverInUse = errutil.Conflict("alerting.notifications.receiver.used", errutil.WithPublicMessage("Receiver is used by one or many notification policies"))
|
||||
ErrVersionConflict = errutil.Conflict("alerting.notifications.receiver.conflict")
|
||||
)
|
||||
|
||||
// ReceiverService is the service for managing alertmanager receivers.
|
||||
type ReceiverService struct {
|
||||
ac accesscontrol.AccessControl
|
||||
@ -30,6 +39,7 @@ type ReceiverService struct {
|
||||
encryptionService secrets.Service
|
||||
xact transactionManager
|
||||
log log.Logger
|
||||
validator validation.ProvenanceStatusTransitionValidator
|
||||
}
|
||||
|
||||
type configStore interface {
|
||||
@ -39,6 +49,7 @@ type configStore interface {
|
||||
|
||||
type provisoningStore interface {
|
||||
GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]models.Provenance, error)
|
||||
DeleteProvenance(ctx context.Context, o models.Provisionable, org int64) error
|
||||
}
|
||||
|
||||
type transactionManager interface {
|
||||
@ -60,6 +71,7 @@ func NewReceiverService(
|
||||
encryptionService: encryptionService,
|
||||
xact: xact,
|
||||
log: log,
|
||||
validator: validation.ValidateProvenanceRelaxed,
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +131,7 @@ func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiver
|
||||
return definitions.GettableApiReceiver{}, err
|
||||
}
|
||||
|
||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, "contactPoint")
|
||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
||||
if err != nil {
|
||||
return definitions.GettableApiReceiver{}, err
|
||||
}
|
||||
@ -158,7 +170,7 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, "contactPoint")
|
||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -213,6 +225,83 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// DeleteReceiver deletes a receiver by uid.
|
||||
// UID field currently does not exist, we assume the uid is a particular hashed value of the receiver name.
|
||||
func (rs *ReceiverService) DeleteReceiver(ctx context.Context, uid string, orgID int64, callerProvenance definitions.Provenance, version string) error {
|
||||
//TODO: Check delete permissions.
|
||||
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := definitions.PostableUserConfig{}
|
||||
err = json.Unmarshal([]byte(baseCfg.AlertmanagerConfiguration), &cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idx, recv := getReceiverByUID(cfg, uid)
|
||||
if recv == nil {
|
||||
return ErrNotFound // TODO: nil?
|
||||
}
|
||||
|
||||
// TODO: Implement + check optimistic concurrency.
|
||||
|
||||
storedProvenance, err := rs.getContactPointProvenance(ctx, recv, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rs.validator(storedProvenance, models.Provenance(callerProvenance)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isReceiverInUse(recv.Name, []*definitions.Route{cfg.AlertmanagerConfig.Route}) {
|
||||
return ErrReceiverInUse.Errorf("")
|
||||
}
|
||||
|
||||
// Remove the receiver from the configuration.
|
||||
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers[:idx], cfg.AlertmanagerConfig.Receivers[idx+1:]...)
|
||||
|
||||
return rs.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
serialized, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := models.SaveAlertmanagerConfigurationCmd{
|
||||
AlertmanagerConfiguration: string(serialized),
|
||||
ConfigurationVersion: baseCfg.ConfigurationVersion,
|
||||
FetchedConfigurationHash: baseCfg.ConfigurationHash,
|
||||
Default: false,
|
||||
OrgID: orgID,
|
||||
}
|
||||
|
||||
err = rs.cfgStore.UpdateAlertmanagerConfiguration(ctx, &cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove provenance for all integrations in the receiver.
|
||||
for _, integration := range recv.GrafanaManagedReceivers {
|
||||
target := definitions.EmbeddedContactPoint{UID: integration.UID}
|
||||
if err := rs.provisioningStore.DeleteProvenance(ctx, &target, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (rs *ReceiverService) CreateReceiver(ctx context.Context, r definitions.GettableApiReceiver, orgID int64) (definitions.GettableApiReceiver, error) {
|
||||
// TODO: Stub
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r definitions.GettableApiReceiver, orgID int64) (definitions.GettableApiReceiver, error) {
|
||||
// TODO: Stub
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (rs *ReceiverService) decryptOrRedact(ctx context.Context, decrypt bool, name, fallback string) func(value string) string {
|
||||
return func(value string) string {
|
||||
if !decrypt {
|
||||
@ -232,3 +321,61 @@ func (rs *ReceiverService) decryptOrRedact(ctx context.Context, decrypt bool, na
|
||||
return string(decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
// getContactPointProvenance determines the provenance of a definitions.PostableApiReceiver based on the provenance of its integrations.
|
||||
func (rs *ReceiverService) getContactPointProvenance(ctx context.Context, r *definitions.PostableApiReceiver, orgID int64) (models.Provenance, error) {
|
||||
if len(r.GrafanaManagedReceivers) == 0 {
|
||||
return models.ProvenanceNone, nil
|
||||
}
|
||||
|
||||
storedProvenances, err := rs.provisioningStore.GetProvenances(ctx, orgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Current provisioning works on the integration level, so we need some way to determine the provenance of the
|
||||
// entire receiver. All integrations in a receiver should have the same provenance, but we don't want to rely on
|
||||
// this assumption in case the first provenance is None and a later one is not. To this end, we return the first
|
||||
// non-zero provenance we find.
|
||||
for _, contactPoint := range r.GrafanaManagedReceivers {
|
||||
if p, exists := storedProvenances[contactPoint.UID]; exists && p != models.ProvenanceNone {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return models.ProvenanceNone, nil
|
||||
}
|
||||
|
||||
// getReceiverByUID returns the index and receiver with the given UID.
|
||||
func getReceiverByUID(cfg definitions.PostableUserConfig, uid string) (int, *definitions.PostableApiReceiver) {
|
||||
for i, r := range cfg.AlertmanagerConfig.Receivers {
|
||||
if getUID(r) == uid {
|
||||
return i, r
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// getUID returns the UID of a PostableApiReceiver.
|
||||
// Currently, the UID is a hash of the receiver name.
|
||||
func getUID(t *definitions.PostableApiReceiver) string { // TODO replace to stable UID when we switch to normal storage
|
||||
sum := fnv.New64()
|
||||
_, _ = sum.Write([]byte(t.Name))
|
||||
return fmt.Sprintf("%016x", sum.Sum64())
|
||||
}
|
||||
|
||||
// TODO: Check if the contact point is used directly in an alert rule.
|
||||
// isReceiverInUse checks if a receiver is used in a route or any of its sub-routes.
|
||||
func isReceiverInUse(name string, routes []*definitions.Route) bool {
|
||||
if len(routes) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, route := range routes {
|
||||
if route.Receiver == name {
|
||||
return true
|
||||
}
|
||||
if isReceiverInUse(name, route.Routes) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
@ -183,12 +184,13 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *Receive
|
||||
provisioningStore := fakes.NewFakeProvisioningStore()
|
||||
|
||||
return &ReceiverService{
|
||||
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||
provisioningStore,
|
||||
store,
|
||||
encryptSvc,
|
||||
xact,
|
||||
log.NewNopLogger(),
|
||||
ac: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||
provisioningStore: provisioningStore,
|
||||
cfgStore: store,
|
||||
encryptionService: encryptSvc,
|
||||
xact: xact,
|
||||
log: log.NewNopLogger(),
|
||||
validator: validation.ValidateProvenanceRelaxed,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -485,7 +486,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if canUpdate := canUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
if canUpdate := validation.CanUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
}
|
||||
}
|
||||
@ -502,7 +503,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if canUpdate := canUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
if canUpdate := validation.CanUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
return fmt.Errorf("cannot update with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
}
|
||||
updates = append(updates, models.UpdateRule{
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var ErrValidation = fmt.Errorf("invalid object specification")
|
||||
@ -16,11 +15,6 @@ var (
|
||||
ErrNoAlertmanagerConfiguration = errutil.Internal("alerting.notification.configMissing", errutil.WithPublicMessage("No alertmanager configuration present in this organization"))
|
||||
ErrBadAlertmanagerConfiguration = errutil.Internal("alerting.notification.configCorrupted").MustTemplate("Failed to unmarshal the Alertmanager configuration", errutil.WithPublic("Current Alertmanager configuration in the storage is corrupted. Reset the configuration or rollback to a recent valid one."))
|
||||
|
||||
ErrProvenanceChangeNotAllowed = errutil.Forbidden("alerting.notifications.invalidProvenance").MustTemplate(
|
||||
"Resource with provenance status '{{ .Public.SourceProvenance }}' cannot be managed via API that handles resources with provenance status '{{ .Public.TargetProvenance }}'",
|
||||
errutil.WithPublic("Resource with provenance status '{{ .Public.SourceProvenance }}' cannot be managed via API that handles resources with provenance status '{{ .Public.TargetProvenance }}'. You must use appropriate API to manage this resource"),
|
||||
)
|
||||
|
||||
ErrVersionConflict = errutil.Conflict("alerting.notifications.conflict")
|
||||
|
||||
ErrTimeIntervalNotFound = errutil.NotFound("alerting.notifications.time-intervals.notFound")
|
||||
@ -53,19 +47,3 @@ func MakeErrTimeIntervalInvalid(err error) error {
|
||||
|
||||
return ErrTimeIntervalInvalid.Build(data)
|
||||
}
|
||||
|
||||
func MakeErrProvenanceChangeNotAllowed(from, to models.Provenance) error {
|
||||
if to == "" {
|
||||
to = "none"
|
||||
}
|
||||
if from == "" {
|
||||
from = "none"
|
||||
}
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"TargetProvenance": to,
|
||||
"SourceProvenance": from,
|
||||
},
|
||||
}
|
||||
return ErrProvenanceChangeNotAllowed.Build(data)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||
)
|
||||
|
||||
type MuteTimingService struct {
|
||||
@ -20,7 +21,7 @@ type MuteTimingService struct {
|
||||
provenanceStore ProvisioningStore
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
validator ProvenanceStatusTransitionValidator
|
||||
validator validation.ProvenanceStatusTransitionValidator
|
||||
}
|
||||
|
||||
func NewMuteTimingService(config AMConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger) *MuteTimingService {
|
||||
@ -29,7 +30,7 @@ func NewMuteTimingService(config AMConfigStore, prov ProvisioningStore, xact Tra
|
||||
provenanceStore: prov,
|
||||
xact: xact,
|
||||
log: log,
|
||||
validator: ValidateProvenanceRelaxed,
|
||||
validator: validation.ValidateProvenanceRelaxed,
|
||||
}
|
||||
}
|
||||
|
||||
|
29
pkg/services/ngalert/provisioning/validation/errors.go
Normal file
29
pkg/services/ngalert/provisioning/validation/errors.go
Normal file
@ -0,0 +1,29 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProvenanceChangeNotAllowed = errutil.Forbidden("alerting.notifications.invalidProvenance").MustTemplate(
|
||||
"Resource with provenance status '{{ .Public.SourceProvenance }}' cannot be managed via API that handles resources with provenance status '{{ .Public.TargetProvenance }}'",
|
||||
errutil.WithPublic("Resource with provenance status '{{ .Public.SourceProvenance }}' cannot be managed via API that handles resources with provenance status '{{ .Public.TargetProvenance }}'. You must use appropriate API to manage this resource"),
|
||||
)
|
||||
)
|
||||
|
||||
func MakeErrProvenanceChangeNotAllowed(from, to models.Provenance) error {
|
||||
if to == "" {
|
||||
to = "none"
|
||||
}
|
||||
if from == "" {
|
||||
from = "none"
|
||||
}
|
||||
data := errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"TargetProvenance": to,
|
||||
"SourceProvenance": from,
|
||||
},
|
||||
}
|
||||
return ErrProvenanceChangeNotAllowed.Build(data)
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package provisioning
|
||||
package validation
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
// canUpdateProvenanceInRuleGroup checks if a provenance can be updated for a rule group and its alerts.
|
||||
// CanUpdateProvenanceInRuleGroup checks if a provenance can be updated for a rule group and its alerts.
|
||||
// ReplaceRuleGroup function intends to replace an entire rule group: inserting, updating, and removing rules.
|
||||
func canUpdateProvenanceInRuleGroup(storedProvenance, provenance models.Provenance) bool {
|
||||
func CanUpdateProvenanceInRuleGroup(storedProvenance, provenance models.Provenance) bool {
|
||||
return storedProvenance == provenance ||
|
||||
storedProvenance == models.ProvenanceNone ||
|
||||
(storedProvenance == models.ProvenanceAPI && provenance == models.ProvenanceNone)
|
@ -1,4 +1,4 @@
|
||||
package provisioning
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
Loading…
Reference in New Issue
Block a user