...

Source file src/github.com/prometheus/alertmanager/config/config_test.go

Documentation: github.com/prometheus/alertmanager/config

     1  // Copyright 2016 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 config
    15  
    16  import (
    17  	"encoding/json"
    18  	"net/url"
    19  	"os"
    20  	"reflect"
    21  	"regexp"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	commoncfg "github.com/prometheus/common/config"
    27  	"github.com/prometheus/common/model"
    28  	"github.com/stretchr/testify/require"
    29  	"gopkg.in/yaml.v2"
    30  )
    31  
    32  func TestLoadEmptyString(t *testing.T) {
    33  	var in string
    34  	_, err := Load(in)
    35  
    36  	expected := "no route provided in config"
    37  
    38  	if err == nil {
    39  		t.Fatalf("no error returned, expected:\n%v", expected)
    40  	}
    41  	if err.Error() != expected {
    42  		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
    43  	}
    44  }
    45  
    46  func TestDefaultReceiverExists(t *testing.T) {
    47  	in := `
    48  route:
    49     group_wait: 30s
    50  `
    51  	_, err := Load(in)
    52  
    53  	expected := "root route must specify a default receiver"
    54  
    55  	if err == nil {
    56  		t.Fatalf("no error returned, expected:\n%v", expected)
    57  	}
    58  	if err.Error() != expected {
    59  		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
    60  	}
    61  }
    62  
    63  func TestReceiverNameIsUnique(t *testing.T) {
    64  	in := `
    65  route:
    66      receiver: team-X
    67  
    68  receivers:
    69  - name: 'team-X'
    70  - name: 'team-X'
    71  `
    72  	_, err := Load(in)
    73  
    74  	expected := "notification config name \"team-X\" is not unique"
    75  
    76  	if err == nil {
    77  		t.Fatalf("no error returned, expected:\n%q", expected)
    78  	}
    79  	if err.Error() != expected {
    80  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
    81  	}
    82  }
    83  
    84  func TestReceiverExists(t *testing.T) {
    85  	in := `
    86  route:
    87      receiver: team-X
    88  
    89  receivers:
    90  - name: 'team-Y'
    91  `
    92  	_, err := Load(in)
    93  
    94  	expected := "undefined receiver \"team-X\" used in route"
    95  
    96  	if err == nil {
    97  		t.Fatalf("no error returned, expected:\n%q", expected)
    98  	}
    99  	if err.Error() != expected {
   100  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   101  	}
   102  }
   103  
   104  func TestReceiverExistsForDeepSubRoute(t *testing.T) {
   105  	in := `
   106  route:
   107      receiver: team-X
   108      routes:
   109        - match:
   110            foo: bar
   111          routes:
   112          - match:
   113              foo: bar
   114            receiver: nonexistent
   115  
   116  receivers:
   117  - name: 'team-X'
   118  `
   119  	_, err := Load(in)
   120  
   121  	expected := "undefined receiver \"nonexistent\" used in route"
   122  
   123  	if err == nil {
   124  		t.Fatalf("no error returned, expected:\n%q", expected)
   125  	}
   126  	if err.Error() != expected {
   127  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   128  	}
   129  }
   130  
   131  func TestReceiverHasName(t *testing.T) {
   132  	in := `
   133  route:
   134  
   135  receivers:
   136  - name: ''
   137  `
   138  	_, err := Load(in)
   139  
   140  	expected := "missing name in receiver"
   141  
   142  	if err == nil {
   143  		t.Fatalf("no error returned, expected:\n%q", expected)
   144  	}
   145  	if err.Error() != expected {
   146  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   147  	}
   148  }
   149  
   150  func TestMuteTimeExists(t *testing.T) {
   151  	in := `
   152  route:
   153      receiver: team-Y
   154      routes:
   155      -  match:
   156          severity: critical
   157         mute_time_intervals:
   158         - business_hours
   159  
   160  receivers:
   161  - name: 'team-Y'
   162  `
   163  	_, err := Load(in)
   164  
   165  	expected := "undefined time interval \"business_hours\" used in route"
   166  
   167  	if err == nil {
   168  		t.Fatalf("no error returned, expected:\n%q", expected)
   169  	}
   170  	if err.Error() != expected {
   171  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   172  	}
   173  }
   174  
   175  func TestActiveTimeExists(t *testing.T) {
   176  	in := `
   177  route:
   178      receiver: team-Y
   179      routes:
   180      -  match:
   181          severity: critical
   182         active_time_intervals:
   183         - business_hours
   184  
   185  receivers:
   186  - name: 'team-Y'
   187  `
   188  	_, err := Load(in)
   189  
   190  	expected := "undefined time interval \"business_hours\" used in route"
   191  
   192  	if err == nil {
   193  		t.Fatalf("no error returned, expected:\n%q", expected)
   194  	}
   195  	if err.Error() != expected {
   196  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   197  	}
   198  }
   199  
   200  func TestTimeIntervalHasName(t *testing.T) {
   201  	in := `
   202  time_intervals:
   203  - name: 
   204    time_intervals:
   205    - times:
   206       - start_time: '09:00'
   207         end_time: '17:00'
   208  
   209  receivers:
   210  - name: 'team-X-mails'
   211  
   212  route:
   213    receiver: 'team-X-mails'
   214    routes:
   215    -  match:
   216        severity: critical
   217       mute_time_intervals:
   218       - business_hours
   219  `
   220  	_, err := Load(in)
   221  
   222  	expected := "missing name in time interval"
   223  
   224  	if err == nil {
   225  		t.Fatalf("no error returned, expected:\n%q", expected)
   226  	}
   227  	if err.Error() != expected {
   228  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   229  	}
   230  }
   231  
   232  func TestMuteTimeNoDuplicates(t *testing.T) {
   233  	in := `
   234  mute_time_intervals:
   235  - name: duplicate
   236    time_intervals:
   237    - times:
   238       - start_time: '09:00'
   239         end_time: '17:00'
   240  - name: duplicate
   241    time_intervals:
   242    - times:
   243       - start_time: '10:00'
   244         end_time: '14:00'
   245  
   246  receivers:
   247  - name: 'team-X-mails'
   248  
   249  route:
   250    receiver: 'team-X-mails'
   251    routes:
   252    -  match:
   253        severity: critical
   254       mute_time_intervals:
   255       - business_hours
   256  `
   257  	_, err := Load(in)
   258  
   259  	expected := "mute time interval \"duplicate\" is not unique"
   260  
   261  	if err == nil {
   262  		t.Fatalf("no error returned, expected:\n%q", expected)
   263  	}
   264  	if err.Error() != expected {
   265  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   266  	}
   267  }
   268  
   269  func TestGroupByHasNoDuplicatedLabels(t *testing.T) {
   270  	in := `
   271  route:
   272    group_by: ['alertname', 'cluster', 'service', 'cluster']
   273  
   274  receivers:
   275  - name: 'team-X-mails'
   276  `
   277  	_, err := Load(in)
   278  
   279  	expected := "duplicated label \"cluster\" in group_by"
   280  
   281  	if err == nil {
   282  		t.Fatalf("no error returned, expected:\n%q", expected)
   283  	}
   284  	if err.Error() != expected {
   285  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   286  	}
   287  }
   288  
   289  func TestWildcardGroupByWithOtherGroupByLabels(t *testing.T) {
   290  	in := `
   291  route:
   292    group_by: ['alertname', 'cluster', '...']
   293    receiver: team-X-mails
   294  receivers:
   295  - name: 'team-X-mails'
   296  `
   297  	_, err := Load(in)
   298  
   299  	expected := "cannot have wildcard group_by (`...`) and other other labels at the same time"
   300  
   301  	if err == nil {
   302  		t.Fatalf("no error returned, expected:\n%q", expected)
   303  	}
   304  	if err.Error() != expected {
   305  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   306  	}
   307  }
   308  
   309  func TestGroupByInvalidLabel(t *testing.T) {
   310  	in := `
   311  route:
   312    group_by: ['-invalid-']
   313    receiver: team-X-mails
   314  receivers:
   315  - name: 'team-X-mails'
   316  `
   317  	_, err := Load(in)
   318  
   319  	expected := "invalid label name \"-invalid-\" in group_by list"
   320  
   321  	if err == nil {
   322  		t.Fatalf("no error returned, expected:\n%q", expected)
   323  	}
   324  	if err.Error() != expected {
   325  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   326  	}
   327  }
   328  
   329  func TestRootRouteExists(t *testing.T) {
   330  	in := `
   331  receivers:
   332  - name: 'team-X-mails'
   333  `
   334  	_, err := Load(in)
   335  
   336  	expected := "no routes provided"
   337  
   338  	if err == nil {
   339  		t.Fatalf("no error returned, expected:\n%q", expected)
   340  	}
   341  	if err.Error() != expected {
   342  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   343  	}
   344  }
   345  
   346  func TestRootRouteNoMuteTimes(t *testing.T) {
   347  	in := `
   348  mute_time_intervals:
   349  - name: my_mute_time
   350    time_intervals:
   351    - times:
   352       - start_time: '09:00'
   353         end_time: '17:00'
   354  
   355  receivers:
   356  - name: 'team-X-mails'
   357  
   358  route:
   359    receiver: 'team-X-mails'
   360    mute_time_intervals:
   361    - my_mute_time
   362  `
   363  	_, err := Load(in)
   364  
   365  	expected := "root route must not have any mute time intervals"
   366  
   367  	if err == nil {
   368  		t.Fatalf("no error returned, expected:\n%q", expected)
   369  	}
   370  	if err.Error() != expected {
   371  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   372  	}
   373  }
   374  
   375  func TestRootRouteNoActiveTimes(t *testing.T) {
   376  	in := `
   377  time_intervals:
   378  - name: my_active_time
   379    time_intervals:
   380    - times:
   381       - start_time: '09:00'
   382         end_time: '17:00'
   383  
   384  receivers:
   385  - name: 'team-X-mails'
   386  
   387  route:
   388    receiver: 'team-X-mails'
   389    active_time_intervals:
   390    - my_active_time
   391  `
   392  	_, err := Load(in)
   393  
   394  	expected := "root route must not have any active time intervals"
   395  
   396  	if err == nil {
   397  		t.Fatalf("no error returned, expected:\n%q", expected)
   398  	}
   399  	if err.Error() != expected {
   400  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   401  	}
   402  }
   403  
   404  func TestRootRouteHasNoMatcher(t *testing.T) {
   405  	testCases := []struct {
   406  		name string
   407  		in   string
   408  	}{
   409  		{
   410  			name: "Test deprecated matchers on root route not allowed",
   411  			in: `
   412  route:
   413    receiver: 'team-X'
   414    match:
   415      severity: critical
   416  receivers:
   417  - name: 'team-X'
   418  `,
   419  		},
   420  		{
   421  			name: "Test matchers not allowed on root route",
   422  			in: `
   423  route:
   424    receiver: 'team-X'
   425    matchers:
   426      - severity=critical
   427  receivers:
   428  - name: 'team-X'
   429  `,
   430  		},
   431  	}
   432  	expected := "root route must not have any matchers"
   433  
   434  	for _, tc := range testCases {
   435  		t.Run(tc.name, func(t *testing.T) {
   436  			_, err := Load(tc.in)
   437  
   438  			if err == nil {
   439  				t.Fatalf("no error returned, expected:\n%q", expected)
   440  			}
   441  			if err.Error() != expected {
   442  				t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   443  			}
   444  		})
   445  	}
   446  }
   447  
   448  func TestContinueErrorInRouteRoot(t *testing.T) {
   449  	in := `
   450  route:
   451      receiver: team-X-mails
   452      continue: true
   453  
   454  receivers:
   455  - name: 'team-X-mails'
   456  `
   457  	_, err := Load(in)
   458  
   459  	expected := "cannot have continue in root route"
   460  
   461  	if err == nil {
   462  		t.Fatalf("no error returned, expected:\n%q", expected)
   463  	}
   464  	if err.Error() != expected {
   465  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   466  	}
   467  }
   468  
   469  func TestGroupIntervalIsGreaterThanZero(t *testing.T) {
   470  	in := `
   471  route:
   472      receiver: team-X-mails
   473      group_interval: 0s
   474  
   475  receivers:
   476  - name: 'team-X-mails'
   477  `
   478  	_, err := Load(in)
   479  
   480  	expected := "group_interval cannot be zero"
   481  
   482  	if err == nil {
   483  		t.Fatalf("no error returned, expected:\n%q", expected)
   484  	}
   485  	if err.Error() != expected {
   486  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   487  	}
   488  }
   489  
   490  func TestRepeatIntervalIsGreaterThanZero(t *testing.T) {
   491  	in := `
   492  route:
   493      receiver: team-X-mails
   494      repeat_interval: 0s
   495  
   496  receivers:
   497  - name: 'team-X-mails'
   498  `
   499  	_, err := Load(in)
   500  
   501  	expected := "repeat_interval cannot be zero"
   502  
   503  	if err == nil {
   504  		t.Fatalf("no error returned, expected:\n%q", expected)
   505  	}
   506  	if err.Error() != expected {
   507  		t.Errorf("\nexpected:\n%q\ngot:\n%q", expected, err.Error())
   508  	}
   509  }
   510  
   511  func TestHideConfigSecrets(t *testing.T) {
   512  	c, err := LoadFile("testdata/conf.good.yml")
   513  	if err != nil {
   514  		t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   515  	}
   516  
   517  	// String method must not reveal authentication credentials.
   518  	s := c.String()
   519  	if strings.Count(s, "<secret>") != 13 || strings.Contains(s, "mysecret") {
   520  		t.Fatal("config's String method reveals authentication credentials.")
   521  	}
   522  }
   523  
   524  func TestJSONMarshal(t *testing.T) {
   525  	c, err := LoadFile("testdata/conf.good.yml")
   526  	if err != nil {
   527  		t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   528  	}
   529  
   530  	_, err = json.Marshal(c)
   531  	if err != nil {
   532  		t.Fatal("JSON Marshaling failed:", err)
   533  	}
   534  }
   535  
   536  func TestJSONMarshalSecret(t *testing.T) {
   537  	test := struct {
   538  		S Secret
   539  	}{
   540  		S: Secret("test"),
   541  	}
   542  
   543  	c, err := json.Marshal(test)
   544  	if err != nil {
   545  		t.Fatal(err)
   546  	}
   547  
   548  	// u003c -> "<"
   549  	// u003e -> ">"
   550  	require.Equal(t, "{\"S\":\"\\u003csecret\\u003e\"}", string(c), "Secret not properly elided.")
   551  }
   552  
   553  func TestMarshalSecretURL(t *testing.T) {
   554  	urlp, err := url.Parse("http://example.com/")
   555  	if err != nil {
   556  		t.Fatal(err)
   557  	}
   558  	u := &SecretURL{urlp}
   559  
   560  	c, err := json.Marshal(u)
   561  	if err != nil {
   562  		t.Fatal(err)
   563  	}
   564  	// u003c -> "<"
   565  	// u003e -> ">"
   566  	require.Equal(t, "\"\\u003csecret\\u003e\"", string(c), "SecretURL not properly elided in JSON.")
   567  	// Check that the marshaled data can be unmarshaled again.
   568  	out := &SecretURL{}
   569  	err = json.Unmarshal(c, out)
   570  	if err != nil {
   571  		t.Fatal(err)
   572  	}
   573  
   574  	c, err = yaml.Marshal(u)
   575  	if err != nil {
   576  		t.Fatal(err)
   577  	}
   578  	require.Equal(t, "<secret>\n", string(c), "SecretURL not properly elided in YAML.")
   579  	// Check that the marshaled data can be unmarshaled again.
   580  	out = &SecretURL{}
   581  	err = yaml.Unmarshal(c, &out)
   582  	if err != nil {
   583  		t.Fatal(err)
   584  	}
   585  }
   586  
   587  func TestUnmarshalSecretURL(t *testing.T) {
   588  	b := []byte(`"http://example.com/se cret"`)
   589  	var u SecretURL
   590  
   591  	err := json.Unmarshal(b, &u)
   592  	if err != nil {
   593  		t.Fatal(err)
   594  	}
   595  	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in JSON.")
   596  
   597  	err = yaml.Unmarshal(b, &u)
   598  	if err != nil {
   599  		t.Fatal(err)
   600  	}
   601  
   602  	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.")
   603  }
   604  
   605  func TestMarshalURL(t *testing.T) {
   606  	for name, tc := range map[string]struct {
   607  		input        *URL
   608  		expectedJSON string
   609  		expectedYAML string
   610  	}{
   611  		"url": {
   612  			input:        mustParseURL("http://example.com/"),
   613  			expectedJSON: "\"http://example.com/\"",
   614  			expectedYAML: "http://example.com/\n",
   615  		},
   616  
   617  		"wrapped nil value": {
   618  			input:        &URL{},
   619  			expectedJSON: "null",
   620  			expectedYAML: "null\n",
   621  		},
   622  
   623  		"wrapped empty URL": {
   624  			input:        &URL{&url.URL{}},
   625  			expectedJSON: "\"\"",
   626  			expectedYAML: "\"\"\n",
   627  		},
   628  	} {
   629  		t.Run(name, func(t *testing.T) {
   630  			j, err := json.Marshal(tc.input)
   631  			require.NoError(t, err)
   632  			require.Equal(t, tc.expectedJSON, string(j), "URL not properly marshaled into JSON.")
   633  
   634  			y, err := yaml.Marshal(tc.input)
   635  			require.NoError(t, err)
   636  			require.Equal(t, tc.expectedYAML, string(y), "URL not properly marshaled into YAML.")
   637  		})
   638  	}
   639  }
   640  
   641  func TestUnmarshalNilURL(t *testing.T) {
   642  	b := []byte(`null`)
   643  
   644  	{
   645  		var u URL
   646  		err := json.Unmarshal(b, &u)
   647  		require.Error(t, err, "unsupported scheme \"\" for URL")
   648  		require.Nil(t, nil, u.URL)
   649  	}
   650  
   651  	{
   652  		var u URL
   653  		err := yaml.Unmarshal(b, &u)
   654  		require.NoError(t, err)
   655  		require.Nil(t, nil, u.URL) // UnmarshalYAML is not even called when unmarshalling "null".
   656  	}
   657  }
   658  
   659  func TestUnmarshalEmptyURL(t *testing.T) {
   660  	b := []byte(`""`)
   661  
   662  	{
   663  		var u URL
   664  		err := json.Unmarshal(b, &u)
   665  		require.Error(t, err, "unsupported scheme \"\" for URL")
   666  		require.Equal(t, (*url.URL)(nil), u.URL)
   667  	}
   668  
   669  	{
   670  		var u URL
   671  		err := yaml.Unmarshal(b, &u)
   672  		require.Error(t, err, "unsupported scheme \"\" for URL")
   673  		require.Equal(t, (*url.URL)(nil), u.URL)
   674  	}
   675  }
   676  
   677  func TestUnmarshalURL(t *testing.T) {
   678  	b := []byte(`"http://example.com/a b"`)
   679  	var u URL
   680  
   681  	err := json.Unmarshal(b, &u)
   682  	if err != nil {
   683  		t.Fatal(err)
   684  	}
   685  	require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in JSON.")
   686  
   687  	err = yaml.Unmarshal(b, &u)
   688  	if err != nil {
   689  		t.Fatal(err)
   690  	}
   691  	require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in YAML.")
   692  }
   693  
   694  func TestUnmarshalInvalidURL(t *testing.T) {
   695  	for _, b := range [][]byte{
   696  		[]byte(`"://example.com"`),
   697  		[]byte(`"http:example.com"`),
   698  		[]byte(`"telnet://example.com"`),
   699  	} {
   700  		var u URL
   701  
   702  		err := json.Unmarshal(b, &u)
   703  		if err == nil {
   704  			t.Errorf("Expected an error unmarshaling %q from JSON", string(b))
   705  		}
   706  
   707  		err = yaml.Unmarshal(b, &u)
   708  		if err == nil {
   709  			t.Errorf("Expected an error unmarshaling %q from YAML", string(b))
   710  		}
   711  		t.Logf("%s", err)
   712  	}
   713  }
   714  
   715  func TestUnmarshalRelativeURL(t *testing.T) {
   716  	b := []byte(`"/home"`)
   717  	var u URL
   718  
   719  	err := json.Unmarshal(b, &u)
   720  	if err == nil {
   721  		t.Errorf("Expected an error parsing URL")
   722  	}
   723  
   724  	err = yaml.Unmarshal(b, &u)
   725  	if err == nil {
   726  		t.Errorf("Expected an error parsing URL")
   727  	}
   728  }
   729  
   730  func TestMarshalRegexpWithNilValue(t *testing.T) {
   731  	r := &Regexp{}
   732  
   733  	out, err := json.Marshal(r)
   734  	require.NoError(t, err)
   735  	require.Equal(t, "null", string(out))
   736  
   737  	out, err = yaml.Marshal(r)
   738  	require.NoError(t, err)
   739  	require.Equal(t, "null\n", string(out))
   740  }
   741  
   742  func TestUnmarshalEmptyRegexp(t *testing.T) {
   743  	b := []byte(`""`)
   744  
   745  	{
   746  		var re Regexp
   747  		err := json.Unmarshal(b, &re)
   748  		require.NoError(t, err)
   749  		require.Equal(t, regexp.MustCompile("^(?:)$"), re.Regexp)
   750  		require.Equal(t, "", re.original)
   751  	}
   752  
   753  	{
   754  		var re Regexp
   755  		err := yaml.Unmarshal(b, &re)
   756  		require.NoError(t, err)
   757  		require.Equal(t, regexp.MustCompile("^(?:)$"), re.Regexp)
   758  		require.Equal(t, "", re.original)
   759  	}
   760  }
   761  
   762  func TestUnmarshalNullRegexp(t *testing.T) {
   763  	input := []byte(`null`)
   764  
   765  	{
   766  		var re Regexp
   767  		err := json.Unmarshal(input, &re)
   768  		require.NoError(t, err)
   769  		require.Nil(t, nil, re.Regexp)
   770  		require.Equal(t, "", re.original)
   771  	}
   772  
   773  	{
   774  		var re Regexp
   775  		err := yaml.Unmarshal(input, &re) // Interestingly enough, unmarshalling `null` in YAML doesn't even call UnmarshalYAML.
   776  		require.NoError(t, err)
   777  		require.Nil(t, re.Regexp)
   778  		require.Equal(t, "", re.original)
   779  	}
   780  }
   781  
   782  func TestMarshalEmptyMatchers(t *testing.T) {
   783  	r := Matchers{}
   784  
   785  	out, err := json.Marshal(r)
   786  	require.NoError(t, err)
   787  	require.Equal(t, "[]", string(out))
   788  
   789  	out, err = yaml.Marshal(r)
   790  	require.NoError(t, err)
   791  	require.Equal(t, "[]\n", string(out))
   792  }
   793  
   794  func TestJSONUnmarshal(t *testing.T) {
   795  	c, err := LoadFile("testdata/conf.good.yml")
   796  	if err != nil {
   797  		t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   798  	}
   799  
   800  	_, err = json.Marshal(c)
   801  	if err != nil {
   802  		t.Fatal("JSON Marshaling failed:", err)
   803  	}
   804  }
   805  
   806  func TestMarshalIdempotency(t *testing.T) {
   807  	c, err := LoadFile("testdata/conf.good.yml")
   808  	if err != nil {
   809  		t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   810  	}
   811  
   812  	marshaled, err := yaml.Marshal(c)
   813  	if err != nil {
   814  		t.Fatal("YAML Marshaling failed:", err)
   815  	}
   816  
   817  	c = new(Config)
   818  	if err := yaml.Unmarshal(marshaled, c); err != nil {
   819  		t.Fatal("YAML Unmarshaling failed:", err)
   820  	}
   821  }
   822  
   823  func TestGroupByAllNotMarshaled(t *testing.T) {
   824  	in := `
   825  route:
   826      receiver: team-X-mails
   827      group_by: [...]
   828  
   829  receivers:
   830  - name: 'team-X-mails'
   831  `
   832  	c, err := Load(in)
   833  	if err != nil {
   834  		t.Fatal("load failed:", err)
   835  	}
   836  
   837  	dat, err := yaml.Marshal(c)
   838  	if err != nil {
   839  		t.Fatal("YAML Marshaling failed:", err)
   840  	}
   841  
   842  	if strings.Contains(string(dat), "groupbyall") {
   843  		t.Fatal("groupbyall found in config file")
   844  	}
   845  }
   846  
   847  func TestEmptyFieldsAndRegex(t *testing.T) {
   848  	boolFoo := true
   849  	regexpFoo := Regexp{
   850  		Regexp:   regexp.MustCompile("^(?:^(foo1|foo2|baz)$)$"),
   851  		original: "^(foo1|foo2|baz)$",
   852  	}
   853  
   854  	expectedConf := Config{
   855  		Global: &GlobalConfig{
   856  			HTTPConfig: &commoncfg.HTTPClientConfig{
   857  				FollowRedirects: true,
   858  				EnableHTTP2:     true,
   859  			},
   860  			ResolveTimeout:  model.Duration(5 * time.Minute),
   861  			SMTPSmarthost:   HostPort{Host: "localhost", Port: "25"},
   862  			SMTPFrom:        "alertmanager@example.org",
   863  			SlackAPIURL:     (*SecretURL)(mustParseURL("http://slack.example.com/")),
   864  			SMTPRequireTLS:  true,
   865  			PagerdutyURL:    mustParseURL("https://events.pagerduty.com/v2/enqueue"),
   866  			OpsGenieAPIURL:  mustParseURL("https://api.opsgenie.com/"),
   867  			WeChatAPIURL:    mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
   868  			VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
   869  			TelegramAPIUrl:  mustParseURL("https://api.telegram.org"),
   870  			WebexAPIURL:     mustParseURL("https://webexapis.com/v1/messages"),
   871  		},
   872  
   873  		Templates: []string{
   874  			"/etc/alertmanager/template/*.tmpl",
   875  		},
   876  		Route: &Route{
   877  			Receiver: "team-X-mails",
   878  			GroupBy: []model.LabelName{
   879  				"alertname",
   880  				"cluster",
   881  				"service",
   882  			},
   883  			GroupByStr: []string{
   884  				"alertname",
   885  				"cluster",
   886  				"service",
   887  			},
   888  			GroupByAll: false,
   889  			Routes: []*Route{
   890  				{
   891  					Receiver: "team-X-mails",
   892  					MatchRE: map[string]Regexp{
   893  						"service": regexpFoo,
   894  					},
   895  				},
   896  			},
   897  		},
   898  		Receivers: []*Receiver{
   899  			{
   900  				Name: "team-X-mails",
   901  				EmailConfigs: []*EmailConfig{
   902  					{
   903  						To:         "team-X+alerts@example.org",
   904  						From:       "alertmanager@example.org",
   905  						Smarthost:  HostPort{Host: "localhost", Port: "25"},
   906  						HTML:       "{{ template \"email.default.html\" . }}",
   907  						RequireTLS: &boolFoo,
   908  					},
   909  				},
   910  			},
   911  		},
   912  	}
   913  
   914  	// Load a non-empty configuration to ensure that all fields are overwritten.
   915  	// See https://github.com/prometheus/alertmanager/issues/1649.
   916  	_, err := LoadFile("testdata/conf.good.yml")
   917  	if err != nil {
   918  		t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   919  	}
   920  
   921  	config, err := LoadFile("testdata/conf.empty-fields.yml")
   922  	if err != nil {
   923  		t.Errorf("Error parsing %s: %s", "testdata/conf.empty-fields.yml", err)
   924  	}
   925  
   926  	configGot, err := yaml.Marshal(config)
   927  	if err != nil {
   928  		t.Fatal("YAML Marshaling failed:", err)
   929  	}
   930  
   931  	configExp, err := yaml.Marshal(expectedConf)
   932  	if err != nil {
   933  		t.Fatalf("%s", err)
   934  	}
   935  
   936  	if !reflect.DeepEqual(configGot, configExp) {
   937  		t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", "testdata/conf.empty-fields.yml", configGot, configExp)
   938  	}
   939  }
   940  
   941  func TestGlobalAndLocalHTTPConfig(t *testing.T) {
   942  	config, err := LoadFile("testdata/conf.http-config.good.yml")
   943  	if err != nil {
   944  		t.Fatalf("Error parsing %s: %s", "testdata/conf-http-config.good.yml", err)
   945  	}
   946  
   947  	if config.Global.HTTPConfig.FollowRedirects {
   948  		t.Fatalf("global HTTP config should not follow redirects")
   949  	}
   950  
   951  	if !config.Receivers[0].SlackConfigs[0].HTTPConfig.FollowRedirects {
   952  		t.Fatalf("global HTTP config should follow redirects")
   953  	}
   954  }
   955  
   956  func TestSMTPHello(t *testing.T) {
   957  	c, err := LoadFile("testdata/conf.good.yml")
   958  	if err != nil {
   959  		t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
   960  	}
   961  
   962  	const refValue = "host.example.org"
   963  	hostName := c.Global.SMTPHello
   964  	if hostName != refValue {
   965  		t.Errorf("Invalid SMTP Hello hostname: %s\nExpected: %s", hostName, refValue)
   966  	}
   967  }
   968  
   969  func TestSMTPBothPasswordAndFile(t *testing.T) {
   970  	_, err := LoadFile("testdata/conf.smtp-both-password-and-file.yml")
   971  	if err == nil {
   972  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.smtp-both-password-and-file.yml", err)
   973  	}
   974  	if err.Error() != "at most one of smtp_auth_password & smtp_auth_password_file must be configured" {
   975  		t.Errorf("Expected: %s\nGot: %s", "at most one of auth_password & auth_password_file must be configured", err.Error())
   976  	}
   977  }
   978  
   979  func TestSMTPNoUsernameOrPassword(t *testing.T) {
   980  	_, err := LoadFile("testdata/conf.smtp-no-username-or-password.yml")
   981  	if err != nil {
   982  		t.Fatalf("Error parsing %s: %s", "testdata/conf.smtp-no-username-or-password.yml", err)
   983  	}
   984  }
   985  
   986  func TestGlobalAndLocalSMTPPassword(t *testing.T) {
   987  	config, err := LoadFile("testdata/conf.smtp-password-global-and-local.yml")
   988  	if err != nil {
   989  		t.Fatalf("Error parsing %s: %s", "testdata/conf.smtp-password-global-and-local.yml", err)
   990  	}
   991  
   992  	require.Equal(t, "/tmp/globaluserpassword", config.Receivers[0].EmailConfigs[0].AuthPasswordFile, "first email should use password file /tmp/globaluserpassword")
   993  	require.Emptyf(t, config.Receivers[0].EmailConfigs[0].AuthPassword, "password field should be empty when file provided")
   994  
   995  	require.Equal(t, "/tmp/localuser1password", config.Receivers[0].EmailConfigs[1].AuthPasswordFile, "second email should use password file /tmp/localuser1password")
   996  	require.Emptyf(t, config.Receivers[0].EmailConfigs[1].AuthPassword, "password field should be empty when file provided")
   997  
   998  	require.Equal(t, Secret("mysecret"), config.Receivers[0].EmailConfigs[2].AuthPassword, "third email should use password mysecret")
   999  	require.Emptyf(t, config.Receivers[0].EmailConfigs[2].AuthPasswordFile, "file field should be empty when password provided")
  1000  }
  1001  
  1002  func TestGroupByAll(t *testing.T) {
  1003  	c, err := LoadFile("testdata/conf.group-by-all.yml")
  1004  	if err != nil {
  1005  		t.Fatalf("Error parsing %s: %s", "testdata/conf.group-by-all.yml", err)
  1006  	}
  1007  
  1008  	if !c.Route.GroupByAll {
  1009  		t.Errorf("Invalid group by all param: expected to by true")
  1010  	}
  1011  }
  1012  
  1013  func TestVictorOpsDefaultAPIKey(t *testing.T) {
  1014  	conf, err := LoadFile("testdata/conf.victorops-default-apikey.yml")
  1015  	if err != nil {
  1016  		t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey.yml", err)
  1017  	}
  1018  
  1019  	defaultKey := conf.Global.VictorOpsAPIKey
  1020  	overrideKey := Secret("qwe456")
  1021  	if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKey {
  1022  		t.Fatalf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, defaultKey)
  1023  	}
  1024  	if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKey {
  1025  		t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, string(overrideKey))
  1026  	}
  1027  }
  1028  
  1029  func TestVictorOpsDefaultAPIKeyFile(t *testing.T) {
  1030  	conf, err := LoadFile("testdata/conf.victorops-default-apikey-file.yml")
  1031  	if err != nil {
  1032  		t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey-file.yml", err)
  1033  	}
  1034  
  1035  	defaultKey := conf.Global.VictorOpsAPIKeyFile
  1036  	overrideKey := "/override_file"
  1037  	if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile {
  1038  		t.Fatalf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, defaultKey)
  1039  	}
  1040  	if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKeyFile {
  1041  		t.Errorf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, overrideKey)
  1042  	}
  1043  }
  1044  
  1045  func TestVictorOpsBothAPIKeyAndFile(t *testing.T) {
  1046  	_, err := LoadFile("testdata/conf.victorops-both-file-and-apikey.yml")
  1047  	if err == nil {
  1048  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-both-file-and-apikey.yml", err)
  1049  	}
  1050  	if err.Error() != "at most one of victorops_api_key & victorops_api_key_file must be configured" {
  1051  		t.Errorf("Expected: %s\nGot: %s", "at most one of victorops_api_key & victorops_api_key_file must be configured", err.Error())
  1052  	}
  1053  }
  1054  
  1055  func TestVictorOpsNoAPIKey(t *testing.T) {
  1056  	_, err := LoadFile("testdata/conf.victorops-no-apikey.yml")
  1057  	if err == nil {
  1058  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-no-apikey.yml", err)
  1059  	}
  1060  	if err.Error() != "no global VictorOps API Key set" {
  1061  		t.Errorf("Expected: %s\nGot: %s", "no global VictorOps API Key set", err.Error())
  1062  	}
  1063  }
  1064  
  1065  func TestOpsGenieDefaultAPIKey(t *testing.T) {
  1066  	conf, err := LoadFile("testdata/conf.opsgenie-default-apikey.yml")
  1067  	if err != nil {
  1068  		t.Fatalf("Error parsing %s: %s", "testdata/conf.opsgenie-default-apikey.yml", err)
  1069  	}
  1070  
  1071  	defaultKey := conf.Global.OpsGenieAPIKey
  1072  	if defaultKey != conf.Receivers[0].OpsGenieConfigs[0].APIKey {
  1073  		t.Fatalf("Invalid OpsGenie key: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKey, defaultKey)
  1074  	}
  1075  	if defaultKey == conf.Receivers[1].OpsGenieConfigs[0].APIKey {
  1076  		t.Errorf("Invalid OpsGenie key: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKey, "qwe456")
  1077  	}
  1078  }
  1079  
  1080  func TestOpsGenieDefaultAPIKeyFile(t *testing.T) {
  1081  	conf, err := LoadFile("testdata/conf.opsgenie-default-apikey-file.yml")
  1082  	if err != nil {
  1083  		t.Fatalf("Error parsing %s: %s", "testdata/conf.opsgenie-default-apikey-file.yml", err)
  1084  	}
  1085  
  1086  	defaultKey := conf.Global.OpsGenieAPIKeyFile
  1087  	if defaultKey != conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile {
  1088  		t.Fatalf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, defaultKey)
  1089  	}
  1090  	if defaultKey == conf.Receivers[1].OpsGenieConfigs[0].APIKeyFile {
  1091  		t.Errorf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, "/override_file")
  1092  	}
  1093  }
  1094  
  1095  func TestOpsGenieBothAPIKeyAndFile(t *testing.T) {
  1096  	_, err := LoadFile("testdata/conf.opsgenie-both-file-and-apikey.yml")
  1097  	if err == nil {
  1098  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-both-file-and-apikey.yml", err)
  1099  	}
  1100  	if err.Error() != "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured" {
  1101  		t.Errorf("Expected: %s\nGot: %s", "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured", err.Error())
  1102  	}
  1103  }
  1104  
  1105  func TestOpsGenieNoAPIKey(t *testing.T) {
  1106  	_, err := LoadFile("testdata/conf.opsgenie-no-apikey.yml")
  1107  	if err == nil {
  1108  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-no-apikey.yml", err)
  1109  	}
  1110  	if err.Error() != "no global OpsGenie API Key set either inline or in a file" {
  1111  		t.Errorf("Expected: %s\nGot: %s", "no global OpsGenie API Key set either inline or in a file", err.Error())
  1112  	}
  1113  }
  1114  
  1115  func TestOpsGenieDeprecatedTeamSpecified(t *testing.T) {
  1116  	_, err := LoadFile("testdata/conf.opsgenie-default-apikey-old-team.yml")
  1117  	if err == nil {
  1118  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-default-apikey-old-team.yml", err)
  1119  	}
  1120  
  1121  	const expectedErr = `yaml: unmarshal errors:
  1122    line 16: field teams not found in type config.plain`
  1123  	if err.Error() != expectedErr {
  1124  		t.Errorf("Expected: %s\nGot: %s", expectedErr, err.Error())
  1125  	}
  1126  }
  1127  
  1128  func TestSlackBothAPIURLAndFile(t *testing.T) {
  1129  	_, err := LoadFile("testdata/conf.slack-both-file-and-url.yml")
  1130  	if err == nil {
  1131  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.slack-both-file-and-url.yml", err)
  1132  	}
  1133  	if err.Error() != "at most one of slack_api_url & slack_api_url_file must be configured" {
  1134  		t.Errorf("Expected: %s\nGot: %s", "at most one of slack_api_url & slack_api_url_file must be configured", err.Error())
  1135  	}
  1136  }
  1137  
  1138  func TestSlackNoAPIURL(t *testing.T) {
  1139  	_, err := LoadFile("testdata/conf.slack-no-api-url.yml")
  1140  	if err == nil {
  1141  		t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.slack-no-api-url.yml", err)
  1142  	}
  1143  	if err.Error() != "no global Slack API URL set either inline or in a file" {
  1144  		t.Errorf("Expected: %s\nGot: %s", "no global Slack API URL set either inline or in a file", err.Error())
  1145  	}
  1146  }
  1147  
  1148  func TestSlackGlobalAPIURLFile(t *testing.T) {
  1149  	conf, err := LoadFile("testdata/conf.slack-default-api-url-file.yml")
  1150  	if err != nil {
  1151  		t.Fatalf("Error parsing %s: %s", "testdata/conf.slack-default-api-url-file.yml", err)
  1152  	}
  1153  
  1154  	// no override
  1155  	firstConfig := conf.Receivers[0].SlackConfigs[0]
  1156  	if firstConfig.APIURLFile != "/global_file" || firstConfig.APIURL != nil {
  1157  		t.Fatalf("Invalid Slack URL file: %s\nExpected: %s", firstConfig.APIURLFile, "/global_file")
  1158  	}
  1159  
  1160  	// override the file
  1161  	secondConfig := conf.Receivers[0].SlackConfigs[1]
  1162  	if secondConfig.APIURLFile != "/override_file" || secondConfig.APIURL != nil {
  1163  		t.Fatalf("Invalid Slack URL file: %s\nExpected: %s", secondConfig.APIURLFile, "/override_file")
  1164  	}
  1165  
  1166  	// override the global file with an inline URL
  1167  	thirdConfig := conf.Receivers[0].SlackConfigs[2]
  1168  	if thirdConfig.APIURL.String() != "http://mysecret.example.com/" || thirdConfig.APIURLFile != "" {
  1169  		t.Fatalf("Invalid Slack URL: %s\nExpected: %s", thirdConfig.APIURL.String(), "http://mysecret.example.com/")
  1170  	}
  1171  }
  1172  
  1173  func TestValidSNSConfig(t *testing.T) {
  1174  	_, err := LoadFile("testdata/conf.sns-topic-arn.yml")
  1175  	if err != nil {
  1176  		t.Fatalf("Error parsing %s: %s", "testdata/conf.sns-topic-arn.yml\"", err)
  1177  	}
  1178  }
  1179  
  1180  func TestInvalidSNSConfig(t *testing.T) {
  1181  	_, err := LoadFile("testdata/conf.sns-invalid.yml")
  1182  	if err == nil {
  1183  		t.Fatalf("expected error with missing fields on SNS config")
  1184  	}
  1185  	const expectedErr = `must provide either a Target ARN, Topic ARN, or Phone Number for SNS config`
  1186  	if err.Error() != expectedErr {
  1187  		t.Errorf("Expected: %s\nGot: %s", expectedErr, err.Error())
  1188  	}
  1189  }
  1190  
  1191  func TestUnmarshalHostPort(t *testing.T) {
  1192  	for _, tc := range []struct {
  1193  		in string
  1194  
  1195  		exp     HostPort
  1196  		jsonOut string
  1197  		yamlOut string
  1198  		err     bool
  1199  	}{
  1200  		{
  1201  			in:  `""`,
  1202  			exp: HostPort{},
  1203  			yamlOut: `""
  1204  `,
  1205  			jsonOut: `""`,
  1206  		},
  1207  		{
  1208  			in:  `"localhost:25"`,
  1209  			exp: HostPort{Host: "localhost", Port: "25"},
  1210  			yamlOut: `localhost:25
  1211  `,
  1212  			jsonOut: `"localhost:25"`,
  1213  		},
  1214  		{
  1215  			in:  `":25"`,
  1216  			exp: HostPort{Host: "", Port: "25"},
  1217  			yamlOut: `:25
  1218  `,
  1219  			jsonOut: `":25"`,
  1220  		},
  1221  		{
  1222  			in:  `"localhost"`,
  1223  			err: true,
  1224  		},
  1225  		{
  1226  			in:  `"localhost:"`,
  1227  			err: true,
  1228  		},
  1229  	} {
  1230  		tc := tc
  1231  		t.Run(tc.in, func(t *testing.T) {
  1232  			hp := HostPort{}
  1233  			err := yaml.Unmarshal([]byte(tc.in), &hp)
  1234  			if tc.err {
  1235  				require.Error(t, err)
  1236  				return
  1237  			}
  1238  			require.NoError(t, err)
  1239  			require.Equal(t, tc.exp, hp)
  1240  
  1241  			b, err := yaml.Marshal(&hp)
  1242  			require.NoError(t, err)
  1243  			require.Equal(t, tc.yamlOut, string(b))
  1244  
  1245  			b, err = json.Marshal(&hp)
  1246  			require.NoError(t, err)
  1247  			require.Equal(t, tc.jsonOut, string(b))
  1248  		})
  1249  	}
  1250  }
  1251  
  1252  func TestNilRegexp(t *testing.T) {
  1253  	for _, tc := range []struct {
  1254  		file   string
  1255  		errMsg string
  1256  	}{
  1257  		{
  1258  			file:   "testdata/conf.nil-match_re-route.yml",
  1259  			errMsg: "invalid_label",
  1260  		},
  1261  		{
  1262  			file:   "testdata/conf.nil-source_match_re-inhibition.yml",
  1263  			errMsg: "invalid_source_label",
  1264  		},
  1265  		{
  1266  			file:   "testdata/conf.nil-target_match_re-inhibition.yml",
  1267  			errMsg: "invalid_target_label",
  1268  		},
  1269  	} {
  1270  		t.Run(tc.file, func(t *testing.T) {
  1271  			_, err := os.Stat(tc.file)
  1272  			require.NoError(t, err)
  1273  
  1274  			_, err = LoadFile(tc.file)
  1275  			require.Error(t, err)
  1276  			require.Contains(t, err.Error(), tc.errMsg)
  1277  		})
  1278  	}
  1279  }
  1280  

View as plain text