mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
feat(api): expose alertmanager headers in the API
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/prymitive/karma/internal/filters"
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
"github.com/prymitive/karma/internal/slices"
|
||||
"github.com/prymitive/karma/internal/uri"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -111,11 +112,20 @@ func getUpstreams() models.AlertmanagerAPISummary {
|
||||
Name: upstream.Name,
|
||||
URI: upstream.SanitizedURI(),
|
||||
PublicURI: upstream.PublicURI(),
|
||||
Headers: map[string]string{},
|
||||
Error: upstream.Error(),
|
||||
Version: upstream.Version(),
|
||||
Cluster: upstream.ClusterID(),
|
||||
ClusterMembers: members,
|
||||
}
|
||||
if !upstream.ProxyRequests {
|
||||
for k, v := range uri.HeadersForBasicAuth(u.PublicURI) {
|
||||
u.Headers[k] = v
|
||||
}
|
||||
for k, v := range upstream.HTTPHeaders {
|
||||
u.Headers[k] = v
|
||||
}
|
||||
}
|
||||
summary.Instances = append(summary.Instances, u)
|
||||
|
||||
summary.Counters.Total++
|
||||
|
||||
@@ -35,12 +35,12 @@ var uriTests = []uriTest{
|
||||
{
|
||||
rawURI: "http://user:pass@alertmanager.example.com",
|
||||
proxy: false,
|
||||
publicURI: "http://user:pass@alertmanager.example.com",
|
||||
publicURI: "http://alertmanager.example.com",
|
||||
},
|
||||
{
|
||||
rawURI: "https://user:pass@alertmanager.example.com/foo",
|
||||
proxy: false,
|
||||
publicURI: "https://user:pass@alertmanager.example.com/foo",
|
||||
publicURI: "https://alertmanager.example.com/foo",
|
||||
},
|
||||
{
|
||||
rawURI: "http://user:pass@alertmanager.example.com",
|
||||
|
||||
@@ -211,7 +211,7 @@ func (am *Alertmanager) PublicURI() string {
|
||||
if am.ExternalURI != "" {
|
||||
return am.ExternalURI
|
||||
}
|
||||
return am.URI
|
||||
return uri.WithoutUserinfo(am.URI)
|
||||
}
|
||||
|
||||
func (am *Alertmanager) pullAlerts(version string) error {
|
||||
|
||||
@@ -29,11 +29,12 @@ type AlertmanagerAPIStatus struct {
|
||||
URI string `json:"uri"`
|
||||
// this is URI client should use to talk to this Alertmanager, it might be
|
||||
// same as real or proxied URI
|
||||
PublicURI string `json:"publicURI"`
|
||||
Error string `json:"error"`
|
||||
Version string `json:"version"`
|
||||
Cluster string `json:"cluster"`
|
||||
ClusterMembers []string `json:"clusterMembers"`
|
||||
PublicURI string `json:"publicURI"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Error string `json:"error"`
|
||||
Version string `json:"version"`
|
||||
Cluster string `json:"cluster"`
|
||||
ClusterMembers []string `json:"clusterMembers"`
|
||||
}
|
||||
|
||||
// AlertmanagerAPICounters returns number of Alertmanager instances in each
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package uri
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
@@ -30,3 +31,35 @@ func SanitizeURI(s string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// HeadersForBasicAuth checks if the passed uri contains user & password
|
||||
// (http://user:pass@example.com) and if so generates headers for Basic Auth
|
||||
// based on
|
||||
func HeadersForBasicAuth(s string) map[string]string {
|
||||
headers := map[string]string{}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return headers
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
if password, pwdSet := u.User.Password(); pwdSet {
|
||||
auth := u.User.Username() + ":" + password
|
||||
headers["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
// WithoutUserinfo takes an URL and returns a copy of it with basic auth
|
||||
// stripped
|
||||
func WithoutUserinfo(s string) string {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
u.User = nil
|
||||
return u.String()
|
||||
}
|
||||
|
||||
+89
-13
@@ -7,35 +7,48 @@ import (
|
||||
)
|
||||
|
||||
type joinURLTest struct {
|
||||
base string
|
||||
sub string
|
||||
url string
|
||||
base string
|
||||
sub string
|
||||
url string
|
||||
isValid bool
|
||||
}
|
||||
|
||||
var joinURLTests = []joinURLTest{
|
||||
{
|
||||
base: "http://localhost",
|
||||
sub: "/sub",
|
||||
url: "http://localhost/sub",
|
||||
base: "http://localhost",
|
||||
sub: "/sub",
|
||||
url: "http://localhost/sub",
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
base: "http://localhost",
|
||||
sub: "/sub/",
|
||||
url: "http://localhost/sub",
|
||||
base: "http://localhost",
|
||||
sub: "/sub/",
|
||||
url: "http://localhost/sub",
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
base: "http://am.example.com",
|
||||
sub: "/api/v1/alerts",
|
||||
url: "http://am.example.com/api/v1/alerts",
|
||||
base: "http://am.example.com",
|
||||
sub: "/api/v1/alerts",
|
||||
url: "http://am.example.com/api/v1/alerts",
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
base: "%gh&%ij",
|
||||
sub: "/a + b",
|
||||
url: "",
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestJoinURL(t *testing.T) {
|
||||
for _, testCase := range joinURLTests {
|
||||
url, err := uri.JoinURL(testCase.base, testCase.sub)
|
||||
if err != nil {
|
||||
if err != nil && testCase.isValid {
|
||||
t.Errorf("joinURL(%v, %v) failed: %s", testCase.base, testCase.sub, err.Error())
|
||||
}
|
||||
if err == nil && !testCase.isValid {
|
||||
t.Errorf("expected error for '%s' and '%s' but got '%s'", testCase.base, testCase.sub, url)
|
||||
}
|
||||
if url != testCase.url {
|
||||
t.Errorf("Invalid joined url from '%s' + '%s', expected '%s', got '%s'", testCase.base, testCase.sub, testCase.url, url)
|
||||
}
|
||||
@@ -80,6 +93,10 @@ var sanitizeURITests = []sanitizeURITest{
|
||||
raw: "https://user:pass@alertmanager.example.com/foo",
|
||||
sanitized: "https://user:xxx@alertmanager.example.com/foo",
|
||||
},
|
||||
{
|
||||
raw: "%gh&%ij",
|
||||
sanitized: "%gh&%ij",
|
||||
},
|
||||
}
|
||||
|
||||
func TestSanitizedURI(t *testing.T) {
|
||||
@@ -91,3 +108,62 @@ func TestSanitizedURI(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeadersForBasicAuth(t *testing.T) {
|
||||
type headersTest struct {
|
||||
uri string
|
||||
isSet bool
|
||||
value string
|
||||
}
|
||||
testCases := []headersTest{
|
||||
{
|
||||
uri: "http://localhost.com",
|
||||
isSet: false,
|
||||
},
|
||||
{
|
||||
uri: "http://user@localhost.com",
|
||||
isSet: false,
|
||||
},
|
||||
{
|
||||
uri: "http://user:pass@localhost.com",
|
||||
isSet: true,
|
||||
value: "Basic dXNlcjpwYXNz",
|
||||
},
|
||||
{
|
||||
uri: "%gh&%ij",
|
||||
isSet: false,
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
headers := uri.HeadersForBasicAuth(test.uri)
|
||||
value, isSet := headers["Authorization"]
|
||||
if isSet != test.isSet {
|
||||
t.Errorf("[%s] expected Authorization header: %v, was set: %v", test.uri, test.isSet, isSet)
|
||||
}
|
||||
if value != test.value {
|
||||
t.Errorf("[%s] expected Authorization value: %s, value: %s", test.uri, test.value, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIWithoutUserinfo(t *testing.T) {
|
||||
type userinfoTest struct {
|
||||
uri string
|
||||
parsed string
|
||||
}
|
||||
testCases := []userinfoTest{
|
||||
{uri: "http://localhost", parsed: "http://localhost"},
|
||||
{uri: "http://localhost?foo=bar", parsed: "http://localhost?foo=bar"},
|
||||
{uri: "http://user@localhost", parsed: "http://localhost"},
|
||||
{uri: "http://user:pass@localhost", parsed: "http://localhost"},
|
||||
{uri: "http://user:pass@localhost?foo=bar#1", parsed: "http://localhost?foo=bar#1"},
|
||||
{uri: "%gh&%ij", parsed: "%gh&%ij"},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
parsed := uri.WithoutUserinfo(test.uri)
|
||||
if parsed != test.parsed {
|
||||
t.Errorf("'%s' got parsed as '%s', expected: '%s'", test.uri, parsed, test.parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user