...

Source file src/github.com/prometheus/alertmanager/notify/opsgenie/opsgenie_test.go

Documentation: github.com/prometheus/alertmanager/notify/opsgenie

     1  // Copyright 2019 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package opsgenie
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"net/url"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/go-kit/log"
    27  	commoncfg "github.com/prometheus/common/config"
    28  	"github.com/prometheus/common/model"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/prometheus/alertmanager/config"
    32  	"github.com/prometheus/alertmanager/notify"
    33  	"github.com/prometheus/alertmanager/notify/test"
    34  	"github.com/prometheus/alertmanager/types"
    35  )
    36  
    37  func TestOpsGenieRetry(t *testing.T) {
    38  	notifier, err := New(
    39  		&config.OpsGenieConfig{
    40  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    41  		},
    42  		test.CreateTmpl(t),
    43  		log.NewNopLogger(),
    44  	)
    45  	require.NoError(t, err)
    46  
    47  	retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
    48  	for statusCode, expected := range test.RetryTests(retryCodes) {
    49  		actual, _ := notifier.retrier.Check(statusCode, nil)
    50  		require.Equal(t, expected, actual, fmt.Sprintf("error on status %d", statusCode))
    51  	}
    52  }
    53  
    54  func TestOpsGenieRedactedURL(t *testing.T) {
    55  	ctx, u, fn := test.GetContextWithCancelingURL()
    56  	defer fn()
    57  
    58  	key := "key"
    59  	notifier, err := New(
    60  		&config.OpsGenieConfig{
    61  			APIURL:     &config.URL{URL: u},
    62  			APIKey:     config.Secret(key),
    63  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    64  		},
    65  		test.CreateTmpl(t),
    66  		log.NewNopLogger(),
    67  	)
    68  	require.NoError(t, err)
    69  
    70  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
    71  }
    72  
    73  func TestGettingOpsGegineApikeyFromFile(t *testing.T) {
    74  	ctx, u, fn := test.GetContextWithCancelingURL()
    75  	defer fn()
    76  
    77  	key := "key"
    78  
    79  	f, err := os.CreateTemp("", "opsgenie_test")
    80  	require.NoError(t, err, "creating temp file failed")
    81  	_, err = f.WriteString(key)
    82  	require.NoError(t, err, "writing to temp file failed")
    83  
    84  	notifier, err := New(
    85  		&config.OpsGenieConfig{
    86  			APIURL:     &config.URL{URL: u},
    87  			APIKeyFile: f.Name(),
    88  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    89  		},
    90  		test.CreateTmpl(t),
    91  		log.NewNopLogger(),
    92  	)
    93  	require.NoError(t, err)
    94  
    95  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
    96  }
    97  
    98  func TestOpsGenie(t *testing.T) {
    99  	u, err := url.Parse("https://opsgenie/api")
   100  	if err != nil {
   101  		t.Fatalf("failed to parse URL: %v", err)
   102  	}
   103  	logger := log.NewNopLogger()
   104  	tmpl := test.CreateTmpl(t)
   105  
   106  	for _, tc := range []struct {
   107  		title string
   108  		cfg   *config.OpsGenieConfig
   109  
   110  		expectedEmptyAlertBody string
   111  		expectedBody           string
   112  	}{
   113  		{
   114  			title: "config without details",
   115  			cfg: &config.OpsGenieConfig{
   116  				NotifierConfig: config.NotifierConfig{
   117  					VSendResolved: true,
   118  				},
   119  				Message:     `{{ .CommonLabels.Message }}`,
   120  				Description: `{{ .CommonLabels.Description }}`,
   121  				Source:      `{{ .CommonLabels.Source }}`,
   122  				Responders: []config.OpsGenieConfigResponder{
   123  					{
   124  						Name: `{{ .CommonLabels.ResponderName1 }}`,
   125  						Type: `{{ .CommonLabels.ResponderType1 }}`,
   126  					},
   127  					{
   128  						Name: `{{ .CommonLabels.ResponderName2 }}`,
   129  						Type: `{{ .CommonLabels.ResponderType2 }}`,
   130  					},
   131  				},
   132  				Tags:       `{{ .CommonLabels.Tags }}`,
   133  				Note:       `{{ .CommonLabels.Note }}`,
   134  				Priority:   `{{ .CommonLabels.Priority }}`,
   135  				Entity:     `{{ .CommonLabels.Entity }}`,
   136  				Actions:    `{{ .CommonLabels.Actions }}`,
   137  				APIKey:     `{{ .ExternalURL }}`,
   138  				APIURL:     &config.URL{URL: u},
   139  				HTTPConfig: &commoncfg.HTTPClientConfig{},
   140  			},
   141  			expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""}
   142  `,
   143  			expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
   144  `,
   145  		},
   146  		{
   147  			title: "config with details",
   148  			cfg: &config.OpsGenieConfig{
   149  				NotifierConfig: config.NotifierConfig{
   150  					VSendResolved: true,
   151  				},
   152  				Message:     `{{ .CommonLabels.Message }}`,
   153  				Description: `{{ .CommonLabels.Description }}`,
   154  				Source:      `{{ .CommonLabels.Source }}`,
   155  				Details: map[string]string{
   156  					"Description": `adjusted {{ .CommonLabels.Description }}`,
   157  				},
   158  				Responders: []config.OpsGenieConfigResponder{
   159  					{
   160  						Name: `{{ .CommonLabels.ResponderName1 }}`,
   161  						Type: `{{ .CommonLabels.ResponderType1 }}`,
   162  					},
   163  					{
   164  						Name: `{{ .CommonLabels.ResponderName2 }}`,
   165  						Type: `{{ .CommonLabels.ResponderType2 }}`,
   166  					},
   167  				},
   168  				Tags:       `{{ .CommonLabels.Tags }}`,
   169  				Note:       `{{ .CommonLabels.Note }}`,
   170  				Priority:   `{{ .CommonLabels.Priority }}`,
   171  				Entity:     `{{ .CommonLabels.Entity }}`,
   172  				Actions:    `{{ .CommonLabels.Actions }}`,
   173  				APIKey:     `{{ .ExternalURL }}`,
   174  				APIURL:     &config.URL{URL: u},
   175  				HTTPConfig: &commoncfg.HTTPClientConfig{},
   176  			},
   177  			expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
   178  `,
   179  			expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
   180  `,
   181  		},
   182  		{
   183  			title: "config with multiple teams",
   184  			cfg: &config.OpsGenieConfig{
   185  				NotifierConfig: config.NotifierConfig{
   186  					VSendResolved: true,
   187  				},
   188  				Message:     `{{ .CommonLabels.Message }}`,
   189  				Description: `{{ .CommonLabels.Description }}`,
   190  				Source:      `{{ .CommonLabels.Source }}`,
   191  				Details: map[string]string{
   192  					"Description": `adjusted {{ .CommonLabels.Description }}`,
   193  				},
   194  				Responders: []config.OpsGenieConfigResponder{
   195  					{
   196  						Name: `{{ .CommonLabels.ResponderName3 }}`,
   197  						Type: `{{ .CommonLabels.ResponderType3 }}`,
   198  					},
   199  				},
   200  				Tags:       `{{ .CommonLabels.Tags }}`,
   201  				Note:       `{{ .CommonLabels.Note }}`,
   202  				Priority:   `{{ .CommonLabels.Priority }}`,
   203  				APIKey:     `{{ .ExternalURL }}`,
   204  				APIURL:     &config.URL{URL: u},
   205  				HTTPConfig: &commoncfg.HTTPClientConfig{},
   206  			},
   207  			expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
   208  `,
   209  			expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"TeamB","type":"team"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1"}
   210  `,
   211  		},
   212  	} {
   213  		t.Run(tc.title, func(t *testing.T) {
   214  			notifier, err := New(tc.cfg, tmpl, logger)
   215  			require.NoError(t, err)
   216  
   217  			ctx := context.Background()
   218  			ctx = notify.WithGroupKey(ctx, "1")
   219  
   220  			expectedURL, _ := url.Parse("https://opsgenie/apiv2/alerts")
   221  
   222  			// Empty alert.
   223  			alert1 := &types.Alert{
   224  				Alert: model.Alert{
   225  					StartsAt: time.Now(),
   226  					EndsAt:   time.Now().Add(time.Hour),
   227  				},
   228  			}
   229  
   230  			req, retry, err := notifier.createRequests(ctx, alert1)
   231  			require.NoError(t, err)
   232  			require.Len(t, req, 1)
   233  			require.Equal(t, true, retry)
   234  			require.Equal(t, expectedURL, req[0].URL)
   235  			require.Equal(t, "GenieKey http://am", req[0].Header.Get("Authorization"))
   236  			require.Equal(t, tc.expectedEmptyAlertBody, readBody(t, req[0]))
   237  
   238  			// Fully defined alert.
   239  			alert2 := &types.Alert{
   240  				Alert: model.Alert{
   241  					Labels: model.LabelSet{
   242  						"Message":        "message",
   243  						"Description":    "description",
   244  						"Source":         "http://prometheus",
   245  						"ResponderName1": "TeamA",
   246  						"ResponderType1": "team",
   247  						"ResponderName2": "EscalationA",
   248  						"ResponderType2": "escalation",
   249  						"ResponderName3": "TeamA,TeamB",
   250  						"ResponderType3": "teams",
   251  						"Tags":           "tag1,tag2",
   252  						"Note":           "this is a note",
   253  						"Priority":       "P1",
   254  						"Entity":         "test-domain",
   255  						"Actions":        "doThis,doThat",
   256  					},
   257  					StartsAt: time.Now(),
   258  					EndsAt:   time.Now().Add(time.Hour),
   259  				},
   260  			}
   261  			req, retry, err = notifier.createRequests(ctx, alert2)
   262  			require.NoError(t, err)
   263  			require.Equal(t, true, retry)
   264  			require.Len(t, req, 1)
   265  			require.Equal(t, tc.expectedBody, readBody(t, req[0]))
   266  
   267  			// Broken API Key Template.
   268  			tc.cfg.APIKey = "{{ kaput "
   269  			_, _, err = notifier.createRequests(ctx, alert2)
   270  			require.Error(t, err)
   271  			require.Equal(t, err.Error(), "templating error: template: :1: function \"kaput\" not defined")
   272  		})
   273  	}
   274  }
   275  
   276  func TestOpsGenieWithUpdate(t *testing.T) {
   277  	u, err := url.Parse("https://test-opsgenie-url")
   278  	require.NoError(t, err)
   279  	tmpl := test.CreateTmpl(t)
   280  	ctx := context.Background()
   281  	ctx = notify.WithGroupKey(ctx, "1")
   282  	opsGenieConfigWithUpdate := config.OpsGenieConfig{
   283  		Message:      `{{ .CommonLabels.Message }}`,
   284  		Description:  `{{ .CommonLabels.Description }}`,
   285  		UpdateAlerts: true,
   286  		APIKey:       "test-api-key",
   287  		APIURL:       &config.URL{URL: u},
   288  		HTTPConfig:   &commoncfg.HTTPClientConfig{},
   289  	}
   290  	notifierWithUpdate, err := New(&opsGenieConfigWithUpdate, tmpl, log.NewNopLogger())
   291  	alert := &types.Alert{
   292  		Alert: model.Alert{
   293  			StartsAt: time.Now(),
   294  			EndsAt:   time.Now().Add(time.Hour),
   295  			Labels: model.LabelSet{
   296  				"Message":     "new message",
   297  				"Description": "new description",
   298  			},
   299  		},
   300  	}
   301  	require.NoError(t, err)
   302  	requests, retry, err := notifierWithUpdate.createRequests(ctx, alert)
   303  	require.NoError(t, err)
   304  	require.True(t, retry)
   305  	require.Len(t, requests, 3)
   306  
   307  	body0 := readBody(t, requests[0])
   308  	body1 := readBody(t, requests[1])
   309  	body2 := readBody(t, requests[2])
   310  	key, _ := notify.ExtractGroupKey(ctx)
   311  	alias := key.Hash()
   312  
   313  	require.Equal(t, requests[0].URL.String(), "https://test-opsgenie-url/v2/alerts")
   314  	require.NotEmpty(t, body0)
   315  
   316  	require.Equal(t, requests[1].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/message?identifierType=alias", alias))
   317  	require.Equal(t, body1, `{"message":"new message"}
   318  `)
   319  	require.Equal(t, requests[2].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/description?identifierType=alias", alias))
   320  	require.Equal(t, body2, `{"description":"new description"}
   321  `)
   322  }
   323  
   324  func readBody(t *testing.T, r *http.Request) string {
   325  	t.Helper()
   326  	body, err := io.ReadAll(r.Body)
   327  	require.NoError(t, err)
   328  	return string(body)
   329  }
   330  

View as plain text