2023-10-12 07:43:10 -05:00
package migration
2021-05-31 07:00:58 -05:00
import (
"bytes"
"fmt"
"io"
"math/rand"
"os"
"path/filepath"
2021-08-12 08:04:09 -05:00
"strconv"
2021-05-31 07:00:58 -05:00
"time"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
pb "github.com/prometheus/alertmanager/silence/silencepb"
2021-11-04 15:42:34 -05:00
"github.com/prometheus/common/model"
2021-05-31 07:00:58 -05:00
2024-01-24 14:56:19 -06:00
"github.com/grafana/grafana/pkg/infra/log"
2023-10-12 07:43:10 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
2024-01-24 14:56:19 -06:00
ngstate "github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/util"
2021-05-31 07:00:58 -05:00
)
2024-01-24 14:56:19 -06:00
// TimeNow makes it possible to test usage of time
var TimeNow = time . Now
2021-11-25 04:46:47 -06:00
2024-01-24 14:56:19 -06:00
// silenceHandler is a helper for managing and writing migration silences.
type silenceHandler struct {
rulesWithErrorSilenceLabels int
rulesWithNoDataSilenceLabels int
createSilenceFile func ( filename string ) ( io . WriteCloser , error )
2021-11-04 15:42:34 -05:00
2024-01-24 14:56:19 -06:00
dataPath string
}
// handleSilenceLabels adds labels to the alert rule if the rule requires silence labels for error/nodata keep_state.
func ( sh * silenceHandler ) handleSilenceLabels ( ar * models . AlertRule , parsedSettings dashAlertSettings ) {
if parsedSettings . ExecutionErrorState == "keep_state" {
sh . rulesWithErrorSilenceLabels ++
ar . Labels [ models . MigratedSilenceLabelErrorKeepState ] = "true"
}
if parsedSettings . NoDataState == "keep_state" {
sh . rulesWithNoDataSilenceLabels ++
ar . Labels [ models . MigratedSilenceLabelNodataKeepState ] = "true"
}
}
// createSilences creates silences and writes them to a file.
func ( sh * silenceHandler ) createSilences ( orgID int64 , log log . Logger ) error {
var silences [ ] * pb . MeshSilence
if sh . rulesWithErrorSilenceLabels > 0 {
log . Info ( "Creating silence for rules with ExecutionErrorState = keep_state" , "rules" , sh . rulesWithErrorSilenceLabels )
silences = append ( silences , errorSilence ( ) )
}
if sh . rulesWithNoDataSilenceLabels > 0 {
log . Info ( "Creating silence for rules with NoDataState = keep_state" , "rules" , sh . rulesWithNoDataSilenceLabels )
silences = append ( silences , noDataSilence ( ) )
}
if len ( silences ) > 0 {
log . Debug ( "Writing silences file" , "silences" , len ( silences ) )
if err := sh . writeSilencesFile ( orgID , silences ) ; err != nil {
return fmt . Errorf ( "write silence file: %w" , err )
}
2021-11-25 04:46:47 -06:00
}
2024-01-24 14:56:19 -06:00
return nil
}
2021-11-25 04:46:47 -06:00
2024-01-24 14:56:19 -06:00
// errorSilence creates a silence that matches DatasourceError alerts for rules which have a label attached when ExecutionErrorState was set to keep_state.
func errorSilence ( ) * pb . MeshSilence {
return & pb . MeshSilence {
2021-11-25 04:46:47 -06:00
Silence : & pb . Silence {
2024-01-24 14:56:19 -06:00
Id : util . GenerateShortUID ( ) ,
2021-11-25 04:46:47 -06:00
Matchers : [ ] * pb . Matcher {
{
Type : pb . Matcher_EQUAL ,
Name : model . AlertNameLabel ,
2024-01-24 14:56:19 -06:00
Pattern : ngstate . ErrorAlertName ,
2021-11-25 04:46:47 -06:00
} ,
{
Type : pb . Matcher_EQUAL ,
2024-01-24 14:56:19 -06:00
Name : models . MigratedSilenceLabelErrorKeepState ,
Pattern : "true" ,
2021-11-25 04:46:47 -06:00
} ,
} ,
2024-01-24 14:56:19 -06:00
StartsAt : TimeNow ( ) ,
EndsAt : TimeNow ( ) . AddDate ( 1 , 0 , 0 ) , // 1 year
2021-11-25 04:46:47 -06:00
CreatedBy : "Grafana Migration" ,
2024-01-24 14:56:19 -06:00
Comment : "Created during migration to unified alerting to silence Error state when the option 'Keep Last State' was selected for Error state" ,
2021-11-25 04:46:47 -06:00
} ,
2024-01-24 14:56:19 -06:00
ExpiresAt : TimeNow ( ) . AddDate ( 1 , 0 , 0 ) , // 1 year
2021-11-25 04:46:47 -06:00
}
}
2024-01-24 14:56:19 -06:00
// noDataSilence creates a silence that matches DatasourceNoData alerts for rules which have a label attached when NoDataState was set to keep_state.
func noDataSilence ( ) * pb . MeshSilence {
return & pb . MeshSilence {
2021-11-04 15:42:34 -05:00
Silence : & pb . Silence {
2024-01-24 14:56:19 -06:00
Id : util . GenerateShortUID ( ) ,
2021-11-04 15:42:34 -05:00
Matchers : [ ] * pb . Matcher {
{
Type : pb . Matcher_EQUAL ,
Name : model . AlertNameLabel ,
2024-01-24 14:56:19 -06:00
Pattern : ngstate . NoDataAlertName ,
2021-11-04 15:42:34 -05:00
} ,
{
Type : pb . Matcher_EQUAL ,
2024-01-24 14:56:19 -06:00
Name : models . MigratedSilenceLabelNodataKeepState ,
Pattern : "true" ,
2021-11-04 15:42:34 -05:00
} ,
} ,
2024-01-24 14:56:19 -06:00
StartsAt : TimeNow ( ) ,
EndsAt : TimeNow ( ) . AddDate ( 1 , 0 , 0 ) , // 1 year.
2021-11-04 15:42:34 -05:00
CreatedBy : "Grafana Migration" ,
2024-01-24 14:56:19 -06:00
Comment : "Created during migration to unified alerting to silence NoData state when the option 'Keep Last State' was selected for NoData state" ,
2021-11-04 15:42:34 -05:00
} ,
2024-01-24 14:56:19 -06:00
ExpiresAt : TimeNow ( ) . AddDate ( 1 , 0 , 0 ) , // 1 year.
2021-11-04 15:42:34 -05:00
}
}
2024-01-24 14:56:19 -06:00
func ( sh * silenceHandler ) writeSilencesFile ( orgId int64 , silences [ ] * pb . MeshSilence ) error {
2021-05-31 07:00:58 -05:00
var buf bytes . Buffer
2024-01-05 04:37:13 -06:00
for _ , e := range silences {
2021-05-31 07:00:58 -05:00
if _ , err := pbutil . WriteDelimited ( & buf , e ) ; err != nil {
return err
}
}
2024-01-24 14:56:19 -06:00
f , err := sh . createSilenceFile ( silencesFileNameForOrg ( sh . dataPath , orgId ) )
2021-05-31 07:00:58 -05:00
if err != nil {
return err
}
if _ , err := io . Copy ( f , bytes . NewReader ( buf . Bytes ( ) ) ) ; err != nil {
return err
}
return f . Close ( )
}
2023-10-12 07:43:10 -05:00
func silencesFileNameForOrg ( dataPath string , orgID int64 ) string {
return filepath . Join ( dataPath , "alerting" , strconv . Itoa ( int ( orgID ) ) , "silences" )
2021-05-31 07:00:58 -05:00
}
// replaceFile wraps a file that is moved to another filename on closing.
type replaceFile struct {
* os . File
filename string
}
func ( f * replaceFile ) Close ( ) error {
if err := f . File . Sync ( ) ; err != nil {
return err
}
if err := f . File . Close ( ) ; err != nil {
return err
}
return os . Rename ( f . File . Name ( ) , f . filename )
}
// openReplace opens a new temporary file that is moved to filename on closing.
2024-01-24 14:56:19 -06:00
func openReplace ( filename string ) ( io . WriteCloser , error ) {
2021-05-31 07:00:58 -05:00
tmpFilename := fmt . Sprintf ( "%s.%x" , filename , uint64 ( rand . Int63 ( ) ) )
2021-08-12 08:04:09 -05:00
if err := os . MkdirAll ( filepath . Dir ( tmpFilename ) , os . ModePerm ) ; err != nil {
return nil , err
}
2022-09-12 05:03:49 -05:00
//nolint:gosec
2021-05-31 07:00:58 -05:00
f , err := os . Create ( tmpFilename )
if err != nil {
return nil , err
}
rf := & replaceFile {
File : f ,
filename : filename ,
}
return rf , nil
}