mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Alerting: Manage remote Alertmanager silences * fix typo * check errors when encoding json in fake external AM * take path from configured URL, check for nil responses
191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
package notifier
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
httptransport "github.com/go-openapi/runtime/client"
|
|
"github.com/go-openapi/strfmt"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
amclient "github.com/prometheus/alertmanager/api/v2/client"
|
|
amsilence "github.com/prometheus/alertmanager/api/v2/client/silence"
|
|
)
|
|
|
|
type externalAlertmanager struct {
|
|
log log.Logger
|
|
url string
|
|
tenantID string
|
|
orgID int64
|
|
amClient *amclient.AlertmanagerAPI
|
|
httpClient *http.Client
|
|
defaultConfig string
|
|
}
|
|
|
|
type externalAlertmanagerConfig struct {
|
|
URL string
|
|
TenantID string
|
|
BasicAuthPassword string
|
|
DefaultConfig string
|
|
}
|
|
|
|
func newExternalAlertmanager(cfg externalAlertmanagerConfig, orgID int64) (*externalAlertmanager, error) {
|
|
client := http.Client{
|
|
Transport: &roundTripper{
|
|
tenantID: cfg.TenantID,
|
|
basicAuthPassword: cfg.BasicAuthPassword,
|
|
next: http.DefaultTransport,
|
|
},
|
|
}
|
|
|
|
if cfg.URL == "" {
|
|
return nil, fmt.Errorf("empty URL for tenant %s", cfg.TenantID)
|
|
}
|
|
|
|
u, err := url.Parse(cfg.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
u = u.JoinPath(amclient.DefaultBasePath)
|
|
|
|
transport := httptransport.NewWithClient(u.Host, u.Path, []string{u.Scheme}, &client)
|
|
|
|
_, err = Load([]byte(cfg.DefaultConfig))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &externalAlertmanager{
|
|
amClient: amclient.New(transport, nil),
|
|
httpClient: &client,
|
|
log: log.New("ngalert.notifier.external-alertmanager"),
|
|
url: cfg.URL,
|
|
tenantID: cfg.TenantID,
|
|
orgID: orgID,
|
|
defaultConfig: cfg.DefaultConfig,
|
|
}, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.PostableUserConfig) error {
|
|
return nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) CreateSilence(ctx context.Context, silence *apimodels.PostableSilence) (string, error) {
|
|
params := amsilence.NewPostSilencesParamsWithContext(ctx).WithSilence(silence)
|
|
res, err := am.amClient.Silence.PostSilences(params)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return res.Payload.SilenceID, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) DeleteSilence(ctx context.Context, silenceID string) error {
|
|
params := amsilence.NewDeleteSilenceParamsWithContext(ctx).WithSilenceID(strfmt.UUID(silenceID))
|
|
_, err := am.amClient.Silence.DeleteSilence(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) GetSilence(ctx context.Context, silenceID string) (apimodels.GettableSilence, error) {
|
|
params := amsilence.NewGetSilenceParamsWithContext(ctx).WithSilenceID(strfmt.UUID(silenceID))
|
|
res, err := am.amClient.Silence.GetSilence(params)
|
|
if err != nil {
|
|
return apimodels.GettableSilence{}, err
|
|
}
|
|
|
|
if res != nil {
|
|
return *res.Payload, nil
|
|
}
|
|
|
|
// In theory, this should never happen as is not possible for GetSilence to return an empty payload but no error.
|
|
return apimodels.GettableSilence{}, fmt.Errorf("unexpected error while trying to fetch silence: %s", silenceID)
|
|
}
|
|
|
|
func (am *externalAlertmanager) ListSilences(ctx context.Context, filter []string) (apimodels.GettableSilences, error) {
|
|
params := amsilence.NewGetSilencesParamsWithContext(ctx).WithFilter(filter)
|
|
res, err := am.amClient.Silence.GetSilences(params)
|
|
if err != nil {
|
|
return apimodels.GettableSilences{}, err
|
|
}
|
|
|
|
return res.Payload, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) GetStatus() apimodels.GettableStatus {
|
|
return apimodels.GettableStatus{}
|
|
}
|
|
|
|
func (am *externalAlertmanager) GetAlerts(active, silenced, inhibited bool, filter []string, receiver string) (apimodels.GettableAlerts, error) {
|
|
return apimodels.GettableAlerts{}, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) GetAlertGroups(active, silenced, inhibited bool, filter []string, receiver string) (apimodels.AlertGroups, error) {
|
|
return apimodels.AlertGroups{}, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) PutAlerts(postableAlerts apimodels.PostableAlerts) error {
|
|
return nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) GetReceivers(ctx context.Context) []apimodels.Receiver {
|
|
return []apimodels.Receiver{}
|
|
}
|
|
|
|
func (am *externalAlertmanager) ApplyConfig(ctx context.Context, config *models.AlertConfiguration) error {
|
|
return nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) {
|
|
return &TestReceiversResult{}, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) TestTemplate(ctx context.Context, c apimodels.TestTemplatesConfigBodyParams) (*TestTemplatesResults, error) {
|
|
return &TestTemplatesResults{}, nil
|
|
}
|
|
|
|
func (am *externalAlertmanager) StopAndWait() {
|
|
}
|
|
|
|
func (am *externalAlertmanager) Ready() bool {
|
|
return false
|
|
}
|
|
|
|
func (am *externalAlertmanager) FileStore() *FileStore {
|
|
return &FileStore{}
|
|
}
|
|
|
|
func (am *externalAlertmanager) OrgID() int64 {
|
|
return am.orgID
|
|
}
|
|
|
|
func (am *externalAlertmanager) ConfigHash() [16]byte {
|
|
return [16]byte{}
|
|
}
|
|
|
|
type roundTripper struct {
|
|
tenantID string
|
|
basicAuthPassword string
|
|
next http.RoundTripper
|
|
}
|
|
|
|
// RoundTrip implements the http.RoundTripper interface
|
|
// while adding the `X-Scope-OrgID` header and basic auth credentials.
|
|
func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
req.Header.Set("X-Scope-OrgID", r.tenantID)
|
|
if r.tenantID != "" && r.basicAuthPassword != "" {
|
|
req.SetBasicAuth(r.tenantID, r.basicAuthPassword)
|
|
}
|
|
|
|
return r.next.RoundTrip(req)
|
|
}
|