feat(backend): add support for custom headers (#368)

This will allow the AlertManager upstreams to be sent user defined HTTP headers.
This commit is contained in:
Richard Maynard
2019-01-17 02:53:33 -06:00
committed by Łukasz Mierzwa
parent c6f392ebd9
commit ec14be0288
13 changed files with 49 additions and 14 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.build
.coverage
.tests
.vscode
bindata_assetfs.go
coverage.txt
karma

View File

@@ -41,6 +41,8 @@ alertmanager:
ca: string
cert: string
key: string
headers:
any: string
```
- `interval` - how often alerts should be refreshed, a string in
@@ -85,9 +87,12 @@ alertmanager:
TLS connections to this Alertmanager instance if it requires a TLS client
authentication.
Note that this option requires `tls:cert` to be also set.
- `headers` - a map with a list of key: values which are header: value.
These custom headers will be sent with every request to the alert manager
instance.
Example with two production Alertmanager instances running in HA mode and a
staging instance that is also proxied:
staging instance that is also proxied and requires a custom auth header:
```yaml
alertmanager:
@@ -107,6 +112,8 @@ alertmanager:
proxy: true
tls:
ca: /etc/ssl/staging-ca.crt
headers:
X-Auth-Token: aValidToken
- name: protected
uri: https://alertmanager-auth.prod.example.com
timeout: 20s

View File

@@ -5,6 +5,8 @@ alertmanager:
uri: http://localhost:9093
timeout: 10s
proxy: true
headers:
X-Auth-Test: some-token-or-other-string
- name: client-auth
uri: https://localhost:9093
timeout: 10s

View File

@@ -61,6 +61,8 @@ type Alertmanager struct {
status alertmanagerStatus
// metrics tracked per alertmanager instance
metrics alertmanagerMetrics
// headers to send with each AlertManager request
HTTPHeaders map[string]string
}
func (am *Alertmanager) fetchStatus() alertmanagerStatus {
@@ -80,7 +82,7 @@ func (am *Alertmanager) fetchStatus() alertmanagerStatus {
resp := alertmanagerStatusResponse{}
// read raw body from the source
source, err := am.reader.Read(url)
source, err := am.reader.Read(url, am.HTTPHeaders)
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, uri.SanitizeURI(url), err)
return status
@@ -157,7 +159,7 @@ func (am *Alertmanager) pullSilences(version string) error {
start := time.Now()
// read raw body from the source
source, err := am.reader.Read(url)
source, err := am.reader.Read(url, am.HTTPHeaders)
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, uri.SanitizeURI(url), err)
return err
@@ -222,7 +224,7 @@ func (am *Alertmanager) pullAlerts(version string) error {
start := time.Now()
// read raw body from the source
source, err := am.reader.Read(url)
source, err := am.reader.Read(url, am.HTTPHeaders)
if err != nil {
log.Errorf("[%s] %s request failed: %s", am.Name, uri.SanitizeURI(url), err)
return err

View File

@@ -31,6 +31,7 @@ func NewAlertmanager(name, upstreamURI string, opts ...Option) (*Alertmanager, e
colors: models.LabelsColorMap{},
autocomplete: []models.Autocomplete{},
knownLabels: []string{},
HTTPHeaders: map[string]string{},
metrics: alertmanagerMetrics{
errors: map[string]float64{
labelValueErrorsAlerts: 0,
@@ -48,7 +49,7 @@ func NewAlertmanager(name, upstreamURI string, opts ...Option) (*Alertmanager, e
}
var err error
am.reader, err = uri.NewReader(am.URI, am.RequestTimeout, am.HTTPTransport)
am.reader, err = uri.NewReader(am.URI, am.RequestTimeout, am.HTTPTransport, am.HTTPHeaders)
if err != nil {
return am, err
}
@@ -110,6 +111,15 @@ func WithRequestTimeout(timeout time.Duration) Option {
}
}
// WithHTTPHeaders option can be passed to NewAlertManager in order to set
// a map of headers that will be passed with every request
func WithHTTPHeaders(headers map[string]string) Option {
return func(am *Alertmanager) error {
am.HTTPHeaders = headers
return nil
}
}
// WithHTTPTransport option can be passed to NewAlertmanager in order to set
// a custom HTTP transport (http.RoundTripper implementation)
func WithHTTPTransport(httpTransport http.RoundTripper) Option {

View File

@@ -170,6 +170,7 @@ func (config *configSchema) Read() {
URI: v.GetString("alertmanager.uri"),
Timeout: v.GetDuration("alertmanager.timeout"),
Proxy: v.GetBool("alertmanager.proxy"),
Headers: make(map[string]string),
},
}
}

View File

@@ -64,6 +64,7 @@ func testReadConfig(t *testing.T) {
ca: ""
cert: ""
key: ""
headers: {}
annotations:
default:
hidden: true

View File

@@ -12,6 +12,7 @@ type alertmanagerConfig struct {
Cert string
Key string
}
Headers map[string]string
}
type jiraRule struct {

View File

@@ -46,7 +46,7 @@ func (r *FileURIReader) pathFromURI(uri string) (string, error) {
return absolutePath, nil
}
func (r *FileURIReader) Read(uri string) (io.ReadCloser, error) {
func (r *FileURIReader) Read(uri string, _ map[string]string) (io.ReadCloser, error) {
filename, err := r.pathFromURI(uri)
if err != nil {
return nil, err

View File

@@ -14,7 +14,7 @@ type HTTPURIReader struct {
client http.Client
}
func (r *HTTPURIReader) Read(uri string) (io.ReadCloser, error) {
func (r *HTTPURIReader) Read(uri string, headers map[string]string) (io.ReadCloser, error) {
log.Infof("GET %s timeout=%s", SanitizeURI(uri), r.client.Timeout)
request, err := http.NewRequest("GET", uri, nil)
@@ -23,6 +23,10 @@ func (r *HTTPURIReader) Read(uri string) (io.ReadCloser, error) {
}
request.Header.Add("Accept-Encoding", "gzip")
for header, value := range headers {
request.Header.Add(header, value)
}
resp, err := r.client.Do(request)
if err != nil {
return nil, err

View File

@@ -10,12 +10,12 @@ import (
// Reader reads from a specific URI schema
type Reader interface {
Read(string) (io.ReadCloser, error)
Read(string, map[string]string) (io.ReadCloser, error)
}
// NewReader creates an instance of URIReader that can handle URI schema
// for the passed uri string
func NewReader(uri string, timeout time.Duration, clientTransport http.RoundTripper) (Reader, error) {
func NewReader(uri string, timeout time.Duration, clientTransport http.RoundTripper, headers map[string]string) (Reader, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err

View File

@@ -34,11 +34,12 @@ type httpTransportTest struct {
useTLS bool
tlsConfig *tls.Config
failed bool
headers map[string]string
}
var httpTransportTests = []httpTransportTest{
{
// plain HTTP request, should work
// plain HTTP request, should work
},
{
// just enable TLS, will use proper RootCA certs so it should work
@@ -50,6 +51,9 @@ var httpTransportTests = []httpTransportTest{
tlsConfig: &tls.Config{RootCAs: x509.NewCertPool()},
failed: true,
},
{
headers: map[string]string{"X-Auth-Test": "tokenValue"},
},
}
type fileTransportTest struct {
@@ -57,6 +61,7 @@ type fileTransportTest struct {
failed bool
timeout time.Duration
size int64
headers map[string]string
}
var fileTransportTests = []fileTransportTest{
@@ -124,12 +129,12 @@ func TestHTTPReader(t *testing.T) {
tlsConfig = &tls.Config{RootCAs: caPool}
}
transp, err := uri.NewReader(amURI, testCase.timeout, &http.Transport{TLSClientConfig: tlsConfig})
transp, err := uri.NewReader(amURI, testCase.timeout, &http.Transport{TLSClientConfig: tlsConfig}, testCase.headers)
if err != nil {
t.Errorf("[%v] failed to create new HTTP transport: %s", testCase, err)
}
source, err := transp.Read(amURI)
source, err := transp.Read(amURI, testCase.headers)
if err != nil {
if !testCase.failed {
t.Errorf("[%v] unexpected failure while creating reader: %s", testCase, err)
@@ -152,12 +157,12 @@ func TestHTTPReader(t *testing.T) {
func TestFileReader(t *testing.T) {
//log.SetLevel(log.FatalLevel)
for _, testCase := range fileTransportTests {
transp, err := uri.NewReader(testCase.uri, testCase.timeout, &http.Transport{})
transp, err := uri.NewReader(testCase.uri, testCase.timeout, &http.Transport{}, testCase.headers)
if err != nil {
t.Errorf("[%v] failed to create new transport: %s", testCase, err)
}
source, err := transp.Read(testCase.uri)
source, err := transp.Read(testCase.uri, testCase.headers)
if err != nil {
if !testCase.failed {
t.Errorf("[%v] unexpected failure while creating reader: %s", testCase, err)

View File

@@ -116,6 +116,7 @@ func setupUpstreams() {
alertmanager.WithRequestTimeout(s.Timeout),
alertmanager.WithProxy(s.Proxy),
alertmanager.WithHTTPTransport(httpTransport), // we will pass a nil unless TLS.CA or TLS.Cert is set
alertmanager.WithHTTPHeaders(s.Headers),
)
if err != nil {
log.Fatalf("Failed to create Alertmanager '%s' with URI '%s': %s", s.Name, s.URI, err)