grafana/pkg/services/ngalert/migration/models/models.go
Matthew Jacobson 3537c5440f
Alerting: Refactor migration to return pairs of legacy and upgraded structs (#79719)
Some refactoring that will simplify next changes for dry-run PRs. This should be no-op as far as the created ngalert resources and database state, though it does change some logs.

The key change here is to modify migrateOrg to return pairs of legacy struct + ngalert struct instead of actually persisting the alerts and alertmanager config. This will allow us to capture error information during dry-run migration.

It also moves most persistence-related operations such as title deduplication and folder creation to the right before we persist. This will simplify eventual partial migrations (individual alerts, dashboards, channels, ...).

Additionally it changes channel code to deal with PostableGrafanaReceiver instead of PostableApiReceiver (integration instead of contact point).
2024-01-05 05:37:13 -05:00

111 lines
3.6 KiB
Go

package models
import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/util"
)
// MaxDeduplicationAttempts is the maximum number of attempts to try to deduplicate a string using any
// individual method, such as sequential index suffixes or uids.
const MaxDeduplicationAttempts = 10
// Deduplicator is a utility for deduplicating strings. It keeps track of the strings it has seen and appends a unique
// suffix to strings that have already been seen. It can optionally truncate strings before appending the suffix to
// ensure that the resulting string is not longer than maxLen.
// This implementation will first try to deduplicate via a sequential index suffix of the form " #2", " #3", etc.
// If after MaxIncrementDeduplicationAttempts attempts it still cannot find a unique string, it will generate a new
// unique uid and append that to the string.
type Deduplicator struct {
set map[string]int
caseInsensitive bool
maxLen int
uidGenerator func() string
}
// NewDeduplicator creates a new deduplicator.
// caseInsensitive determines whether the string comparison should be case-insensitive.
// maxLen determines the maximum length of deduplicated strings. If the deduplicated string would be longer than
// maxLen, it will be truncated.
func NewDeduplicator(caseInsensitive bool, maxLen int, initial ...string) *Deduplicator {
d := &Deduplicator{
set: make(map[string]int, len(initial)),
caseInsensitive: caseInsensitive,
maxLen: maxLen,
uidGenerator: util.GenerateShortUID,
}
if len(initial) > 0 {
for _, u := range initial {
d.add(u, 0)
}
}
return d
}
// Deduplicate returns a unique string based on the given base string. If the base string has not already been seen by
// this deduplicator, it will be returned as-is. If the base string has already been seen, a unique suffix will be
// appended to the base string to make it unique.
func (s *Deduplicator) Deduplicate(base string) (string, error) {
if s.maxLen > 0 && len(base) > s.maxLen {
base = base[:s.maxLen]
}
cnt, ok := s.contains(base)
if !ok {
s.add(base, 0)
return base, nil
}
// Start at 2, so we get a, a_2, a_3, etc.
for i := 2 + cnt; i < 2+cnt+MaxDeduplicationAttempts; i++ {
dedup := s.appendSuffix(base, fmt.Sprintf(" #%d", i))
if _, ok := s.contains(dedup); !ok {
s.add(dedup, 0)
return dedup, nil
}
}
// None of the simple suffixes worked, so we generate a new uid. We try a few times, just in case, but this should
// almost always create a unique string on the first try.
for i := 0; i < MaxDeduplicationAttempts; i++ {
dedup := s.appendSuffix(base, fmt.Sprintf("_%s", s.uidGenerator()))
if _, ok := s.contains(dedup); !ok {
s.add(dedup, 0)
return dedup, nil
}
}
return "", fmt.Errorf("failed to deduplicate %q", base)
}
// contains checks whether the given string has already been seen by this deduplicator.
func (s *Deduplicator) contains(u string) (int, bool) {
dedup := u
if s.caseInsensitive {
dedup = strings.ToLower(dedup)
}
if s.maxLen > 0 && len(dedup) > s.maxLen {
dedup = dedup[:s.maxLen]
}
cnt, seen := s.set[dedup]
return cnt, seen
}
// appendSuffix appends the given suffix to the given base string. If the resulting string would be longer than maxLen,
// the base string will be truncated.
func (s *Deduplicator) appendSuffix(base, suffix string) string {
if s.maxLen > 0 && len(base)+len(suffix) > s.maxLen {
return base[:s.maxLen-len(suffix)] + suffix
}
return base + suffix
}
// add adds the given string to the deduplicator.
func (s *Deduplicator) add(uid string, cnt int) {
dedup := uid
if s.caseInsensitive {
dedup = strings.ToLower(dedup)
}
s.set[dedup] = cnt
}