mirror of
https://github.com/prymitive/karma
synced 2026-05-19 04:26:41 +00:00
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@ NAME := unsee
|
||||
VERSION := $(shell git describe --tags --always --dirty='-dev')
|
||||
|
||||
# Alertmanager instance used when running locally, points to mock data
|
||||
ALERTMANAGER_URI := https://raw.githubusercontent.com/cloudflare/unsee/master/mock/0.5
|
||||
ALERTMANAGER_URI := file://$(CURDIR)mock/0.5
|
||||
# Listen port when running locally
|
||||
PORT := 8080
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ silences. Endpoints in use:
|
||||
* ${ALERTMANAGER_URI}/api/v1/alerts/groups
|
||||
* ${ALERTMANAGER_URI}/api/v1/silences
|
||||
|
||||
Supported URI schemes:
|
||||
|
||||
* http://
|
||||
* https://
|
||||
* file://
|
||||
|
||||
`file://` scheme is only useful for testing purposes, it's used for `make run`
|
||||
target.
|
||||
|
||||
Example:
|
||||
|
||||
ALERTMANAGER_URI=https://alertmanager.example.com
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/unsee/alertmanager"
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/mock"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@@ -15,13 +16,15 @@ var testVersions = []string{"0.4", "0.5"}
|
||||
|
||||
func TestGetAlerts(t *testing.T) {
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
config.Config.AlertmanagerURI = "http://localhost"
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
for _, version := range testVersions {
|
||||
httpmock.Reset()
|
||||
mock.RegisterURL("api/v1/status", version, "status")
|
||||
mock.RegisterURL("api/v1/alerts/groups", version, "alerts/groups")
|
||||
mock.RegisterURL("http://localhost/api/v1/status", version, "status")
|
||||
mock.RegisterURL("http://localhost/api/v1/alerts/groups", version, "alerts/groups")
|
||||
|
||||
v := alertmanager.GetVersion()
|
||||
if !strings.HasPrefix(v, version) {
|
||||
@@ -40,13 +43,15 @@ func TestGetAlerts(t *testing.T) {
|
||||
|
||||
func TestGetSilences(t *testing.T) {
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
config.Config.AlertmanagerURI = "http://localhost"
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
for _, version := range testVersions {
|
||||
httpmock.Reset()
|
||||
mock.RegisterURL("api/v1/status", version, "status")
|
||||
mock.RegisterURL("api/v1/silences", version, "silences")
|
||||
mock.RegisterURL("http://localhost/api/v1/status", version, "status")
|
||||
mock.RegisterURL("http://localhost/api/v1/silences", version, "silences")
|
||||
|
||||
v := alertmanager.GetVersion()
|
||||
if !strings.HasPrefix(v, version) {
|
||||
|
||||
@@ -29,7 +29,7 @@ func GetVersion() string {
|
||||
return defaultVersion
|
||||
}
|
||||
ver := alertmanagerVersion{}
|
||||
err = transport.GetJSONFromURL(url, config.Config.AlertmanagerTimeout, &ver)
|
||||
err = transport.ReadJSON(url, config.Config.AlertmanagerTimeout, &ver)
|
||||
if err != nil {
|
||||
log.Errorf("%s request failed: %s", url, err.Error())
|
||||
return defaultVersion
|
||||
|
||||
@@ -62,7 +62,7 @@ func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
return groups, err
|
||||
}
|
||||
|
||||
err = transport.GetJSONFromURL(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
err = transport.ReadJSON(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
if err != nil {
|
||||
return groups, err
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (m SilenceMapper) GetSilences() ([]models.Silence, error) {
|
||||
|
||||
// Alertmanager 0.4 uses pagination for silences
|
||||
url = fmt.Sprintf("%s?limit=%d", url, math.MaxUint32)
|
||||
err = transport.GetJSONFromURL(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
err = transport.ReadJSON(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
if err != nil {
|
||||
return silences, err
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
return groups, err
|
||||
}
|
||||
|
||||
err = transport.GetJSONFromURL(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
err = transport.ReadJSON(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
if err != nil {
|
||||
return groups, err
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (m SilenceMapper) GetSilences() ([]models.Silence, error) {
|
||||
return silences, err
|
||||
}
|
||||
|
||||
err = transport.GetJSONFromURL(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
err = transport.ReadJSON(url, config.Config.AlertmanagerTimeout, &resp)
|
||||
if err != nil {
|
||||
return silences, err
|
||||
}
|
||||
|
||||
11
mock/mock.go
11
mock/mock.go
@@ -9,11 +9,16 @@ import (
|
||||
httpmock "gopkg.in/jarcoal/httpmock.v1"
|
||||
)
|
||||
|
||||
// RegisterURL for given url and return 200 status register mock http responder
|
||||
func RegisterURL(url string, version string, filename string) {
|
||||
// GetAbsoluteMockPath returns absolute path for given mock file
|
||||
func GetAbsoluteMockPath(filename string, version string) string {
|
||||
_, f, _, _ := runtime.Caller(0)
|
||||
cwd := filepath.Dir(f)
|
||||
fullPath := fmt.Sprintf("%s/%s/api/v1/%s", cwd, version, filename)
|
||||
return fmt.Sprintf("%s/%s/api/v1/%s", cwd, version, filename)
|
||||
}
|
||||
|
||||
// RegisterURL for given url and return 200 status register mock http responder
|
||||
func RegisterURL(url string, version string, filename string) {
|
||||
fullPath := GetAbsoluteMockPath(filename, version)
|
||||
mockJSON, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
16
transport/file.go
Normal file
16
transport/file.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type fileReader struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
func newFileReader(filname string) (*os.File, error) {
|
||||
log.Infof("Reading file '%s'", filname)
|
||||
return os.Open(filname)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package transport
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -11,27 +10,32 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetJSONFromURL allows to fetch Alertmanager data over HTTP transport and
|
||||
// decode it onto provided data structure.
|
||||
func GetJSONFromURL(url string, timeout time.Duration, target interface{}) error {
|
||||
log.Infof("GET %s", url)
|
||||
type httpReader struct {
|
||||
URL string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func newHTTPReader(url string, timeout time.Duration) (*io.ReadCloser, error) {
|
||||
hr := httpReader{URL: url, Timeout: timeout}
|
||||
|
||||
log.Infof("GET %s timeout=%s", hr.URL, hr.Timeout)
|
||||
|
||||
c := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequest("GET", hr.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Request to Alertmanager failed with %s", resp.Status)
|
||||
return nil, fmt.Errorf("Request to Alertmanager failed with %s", resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -41,12 +45,11 @@ func GetJSONFromURL(url string, timeout time.Duration, target interface{}) error
|
||||
case "gzip":
|
||||
reader, err = gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to decode gzipped content: %s", err.Error())
|
||||
return nil, fmt.Errorf("Failed to decode gzipped content: %s", err.Error())
|
||||
}
|
||||
defer reader.Close()
|
||||
default:
|
||||
reader = resp.Body
|
||||
}
|
||||
|
||||
return json.NewDecoder(reader).Decode(target)
|
||||
return &reader, nil
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package transport_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/transport"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
httpmock "gopkg.in/jarcoal/httpmock.v1"
|
||||
)
|
||||
|
||||
type mockJSONResponse struct {
|
||||
status string
|
||||
integer int
|
||||
yes bool
|
||||
no bool
|
||||
}
|
||||
|
||||
func TestGetJSONFromURL(t *testing.T) {
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
mockJSON := `{
|
||||
"response": "success",
|
||||
"integer": 123,
|
||||
"yes": true,
|
||||
"no": false
|
||||
}`
|
||||
httpmock.RegisterResponder("GET", "http://localhost/", httpmock.NewStringResponder(200, mockJSON))
|
||||
|
||||
response := mockJSONResponse{}
|
||||
err := transport.GetJSONFromURL("http://localhost/", time.Second, &response)
|
||||
if err != nil {
|
||||
t.Errorf("getJSONFromURL() failed: %s", err.Error())
|
||||
}
|
||||
|
||||
httpmock.RegisterResponder("GET", "http://localhost/404", httpmock.NewStringResponder(404, "Not found"))
|
||||
response = mockJSONResponse{}
|
||||
err = transport.GetJSONFromURL("http://localhost/404", time.Second, &response)
|
||||
if err == nil {
|
||||
t.Errorf("getJSONFromURL() on invalid url didn't return 404, response: %v", response)
|
||||
}
|
||||
}
|
||||
39
transport/transport.go
Normal file
39
transport/transport.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
func readFile(filename string, target interface{}) error {
|
||||
reader, err := newFileReader(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(reader).Decode(target)
|
||||
}
|
||||
|
||||
func readHTTP(url string, timeout time.Duration, target interface{}) error {
|
||||
reader, err := newHTTPReader(url, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(*reader).Decode(target)
|
||||
}
|
||||
|
||||
// ReadJSON using one of supported transports (file:// http://)
|
||||
func ReadJSON(uri string, timeout time.Duration, target interface{}) error {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Scheme == "file" {
|
||||
return readFile(u.Path, target)
|
||||
}
|
||||
if u.Scheme == "http" || u.Scheme == "https" {
|
||||
return readHTTP(u.String(), timeout, target)
|
||||
}
|
||||
return fmt.Errorf("Unsupported URI scheme '%s' in '%s'", u.Scheme, u)
|
||||
}
|
||||
88
transport/transport_test.go
Normal file
88
transport/transport_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package transport_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/mock"
|
||||
"github.com/cloudflare/unsee/transport"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
httpmock "gopkg.in/jarcoal/httpmock.v1"
|
||||
)
|
||||
|
||||
type transportTest struct {
|
||||
uri string
|
||||
timeout time.Duration
|
||||
failed bool
|
||||
}
|
||||
|
||||
var transportTests = []transportTest{
|
||||
transportTest{
|
||||
uri: "http://localhost/status",
|
||||
},
|
||||
transportTest{
|
||||
uri: "http://localhost/404",
|
||||
failed: true,
|
||||
},
|
||||
transportTest{
|
||||
uri: "http://localhost/invalid",
|
||||
failed: true,
|
||||
},
|
||||
transportTest{
|
||||
uri: "https://localhost/status",
|
||||
},
|
||||
transportTest{
|
||||
uri: "https://localhost/404",
|
||||
failed: true,
|
||||
},
|
||||
transportTest{
|
||||
uri: "https://localhost/invalid",
|
||||
failed: true,
|
||||
},
|
||||
transportTest{
|
||||
uri: fmt.Sprintf("file://%s", mock.GetAbsoluteMockPath("status", "0.4")),
|
||||
},
|
||||
transportTest{
|
||||
uri: "file:///non-existing-file.abcdef",
|
||||
failed: true,
|
||||
},
|
||||
transportTest{
|
||||
uri: "file://transport.go",
|
||||
failed: true,
|
||||
},
|
||||
}
|
||||
|
||||
type mockStatus struct {
|
||||
status string
|
||||
integer int
|
||||
yes bool
|
||||
no bool
|
||||
}
|
||||
|
||||
func TestFileReader(t *testing.T) {
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
mockJSON := `{
|
||||
"response": "success",
|
||||
"integer": 123,
|
||||
"yes": true,
|
||||
"no": false
|
||||
}`
|
||||
httpmock.RegisterResponder("GET", "http://localhost/status", httpmock.NewStringResponder(200, mockJSON))
|
||||
httpmock.RegisterResponder("GET", "http://localhost/404", httpmock.NewStringResponder(404, "404"))
|
||||
httpmock.RegisterResponder("GET", "http://localhost/invalid", httpmock.NewStringResponder(200, "bad json}{}"))
|
||||
httpmock.RegisterResponder("GET", "https://localhost/status", httpmock.NewStringResponder(200, mockJSON))
|
||||
httpmock.RegisterResponder("GET", "https://localhost/404", httpmock.NewStringResponder(404, "404"))
|
||||
httpmock.RegisterResponder("GET", "https://localhost/invalid", httpmock.NewStringResponder(200, "bad json}{}"))
|
||||
|
||||
for _, testCase := range transportTests {
|
||||
r := mockStatus{}
|
||||
err := transport.ReadJSON(testCase.uri, testCase.timeout, &r)
|
||||
if (err != nil) != testCase.failed {
|
||||
t.Errorf("[%s] Expected failure: %v, Read() failed: %v, error: %s", testCase.uri, testCase.failed, (err != nil), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user