diff --git a/.travis.yml b/.travis.yml index 2654a85bf..03928c1f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ defaults_go: &DEFAULTS_GO language: go - go: "1.9.2" + go: "1.10.1" cache: directories: - vendor diff --git a/Dockerfile b/Dockerfile index 679402480..9c603a8bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ RUN apk add --update make git COPY . /unsee RUN make -C /unsee webpack -FROM golang:1.9.2-alpine3.6 as go-builder +FROM golang:1.10.1-alpine as go-builder COPY --from=nodejs-builder /unsee /go/src/github.com/cloudflare/unsee ARG VERSION RUN apk add --update make git diff --git a/Gopkg.toml b/Gopkg.toml index 7e7c079b7..c414c74ca 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -19,6 +19,9 @@ # name = "github.com/x/y" # version = "2.4.0" +[prune] + go-tests = true + unused-packages = true [[constraint]] branch = "master" diff --git a/Makefile b/Makefile index 6dc904129..2016ae385 100644 --- a/Makefile +++ b/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 -MOCK_PATH := $(CURDIR)/internal/mock/0.13.0 +MOCK_PATH := $(CURDIR)/internal/mock/0.14.0 ALERTMANAGER_URI := "file://$(MOCK_PATH)" # Listen port when running locally PORT := 8080 @@ -49,25 +49,22 @@ endif touch $@ bindata_assetfs.go: .build/deps-build-go.ok .build/artifacts-bindata_assetfs.$(GO_BINDATA_MODE) .build/vendor.ok .build/artifacts-webpack.ok - go-bindata-assetfs $(GO_BINDATA_FLAGS) -prefix assets -nometadata assets/templates/... assets/static/dist/... + go-bindata-assetfs $(GO_BINDATA_FLAGS) -o bindata_assetfs.go -prefix assets -nometadata assets/templates/... assets/static/dist/... $(NAME): .build/deps-build-go.ok .build/vendor.ok bindata_assetfs.go $(SOURCES) go build -ldflags "-X main.version=$(VERSION)" .build/vendor.ok: .build/deps-build-go.ok Gopkg.lock Gopkg.toml dep ensure - dep prune touch $@ .PHONY: vendor vendor: .build/deps-build-go.ok dep ensure - dep prune .PHONY: vendor-update vendor-update: .build/deps-build-go.ok dep ensure -update - dep prune .PHONY: webpack webpack: .build/artifacts-webpack.ok diff --git a/api_test.go b/api_test.go index dd7b0455a..61e0d3d64 100644 --- a/api_test.go +++ b/api_test.go @@ -870,7 +870,10 @@ func testAlert(version string, t *testing.T, expectedAlert, gotAlert models.Aler for _, es := range expectedAM.Silences { foundSilence := false for _, gs := range gotAM.Silences { - if es.Comment == gs.Comment && es.CreatedBy == gs.CreatedBy && es.JiraID == gs.JiraID && es.JiraURL == es.JiraURL { + if es.Comment == gs.Comment && + es.CreatedBy == gs.CreatedBy && + es.JiraID == gs.JiraID && + es.JiraURL == gs.JiraURL { foundSilence = true } } @@ -891,11 +894,11 @@ func testAlert(version string, t *testing.T, expectedAlert, gotAlert models.Aler } func testAlertGroup(version string, t *testing.T, testCase groupTest, group models.AlertGroup) { - if testCase.hash != group.Hash { - // FIXME this is different per mock version due to startsAt / endsAt - // t.Errorf("[%s] Alert group.Hash mismatch, expected '%s' but got '%s' for group %v", - // version, testCase.hash, group.Hash, group.Labels) - } + //if testCase.hash != group.Hash { + // FIXME this is different per mock version due to startsAt / endsAt + // t.Errorf("[%s] Alert group.Hash mismatch, expected '%s' but got '%s' for group %v", + // version, testCase.hash, group.Hash, group.Labels) + //} if testCase.id != group.ID { t.Errorf("[%s] Alert group.ID mismatch, expected '%s' but got '%s' for group %v", version, testCase.id, group.ID, group.Labels) diff --git a/assets/static/base.css b/assets/static/base.css index cc740ddb5..b4dfd56eb 100644 --- a/assets/static/base.css +++ b/assets/static/base.css @@ -250,15 +250,19 @@ blockquote.silence-comment { margin-bottom: 2px; padding-left: 0; padding-right: 2px; - padding-top: 2px; + padding-top: 0; padding-bottom: 0; } +.silence-block.well { + border: 0; +} /* button is an icon inside a well, it gets white color make it dark gray by default, so it's visible but doesn't stand out and black on hover */ button.silence-delete { color: inherit; + margin-top: 2px; } button.silence-delete:hover, button.silence-delete:focus { @@ -270,7 +274,7 @@ button.silence-delete:focus { } .incident { - padding: 8px; + padding: 5px; } .incident>.panel>.panel-heading { diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS new file mode 100644 index 000000000..010b4c280 --- /dev/null +++ b/docs/CODEOWNERS @@ -0,0 +1,5 @@ +# CODEOWNERS file lists people and teams responsible for code in this +# repository. +# See https://help.github.com/articles/about-codeowners/ for details. + +* @prymitive diff --git a/internal/config/config.go b/internal/config/config.go index 55c63fab0..d763f3408 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -172,6 +172,7 @@ func (config *configSchema) LogValues() { URI: hideURLPassword(s.URI), Timeout: s.Timeout, TLS: s.TLS, + Proxy: s.Proxy, } servers = append(servers, server) } diff --git a/internal/mock/0.14.0/.ok b/internal/mock/0.14.0/.ok new file mode 100644 index 000000000..e69de29bb diff --git a/internal/mock/0.14.0/api/v1/alerts/groups b/internal/mock/0.14.0/api/v1/alerts/groups new file mode 100644 index 000000000..c46034a7d --- /dev/null +++ b/internal/mock/0.14.0/api/v1/alerts/groups @@ -0,0 +1,765 @@ +{ + "data": [ + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "alert": "Less than 10% disk space is free", + "dashboard": "http://localhost/dashboard.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "f87343c11c74a3f4", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Free_Disk_Space_Too_Low", + "cluster": "staging", + "instance": "server5", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-name", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Free_Disk_Space_Too_Low\"}", + "labels": { + "alertname": "Free_Disk_Space_Too_Low" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "help": "Example help annotation", + "summary": "Example summary", + "url": "http://localhost/example.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "54c2f185e49cfccb", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "HTTP_Probe_Failed", + "cluster": "dev", + "instance": "web1", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "9a32b09e-0c74-4f69-8ad9-600bdcc7b4cc" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "5cb0dd95e7f3d9c0", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "HTTP_Probe_Failed", + "cluster": "dev", + "instance": "web2", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-name", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"HTTP_Probe_Failed\"}", + "labels": { + "alertname": "HTTP_Probe_Failed" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "3bdb8b68bdce2ae0", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "prod", + "instance": "server2", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "24e4121619386f95", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server3", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "d9067fcc9686d942", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server4", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "5f1306dab6671183", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server5", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "0967807e4073b606", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server6", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "44497481566cd3c7", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server7", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2", + "f70e2eae-1d71-497b-9dd1-3d83b30aaf63" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "af9d8f2f0ccb3970", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server8", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary", + "url": "http://localhost/example.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "d0aee2649e71388b", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "prod", + "instance": "server1", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-name", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Host_Down\"}", + "labels": { + "alertname": "Host_Down" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "alert": "Memory usage exceeding threshold", + "dashboard": "http://localhost/dashboard.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "7d0b114ebf24f857", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Memory_Usage_Too_High", + "cluster": "prod", + "instance": "server2", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-name", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Memory_Usage_Too_High\"}", + "labels": { + "alertname": "Memory_Usage_Too_High" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "alert": "Less than 10% disk space is free", + "dashboard": "http://localhost/dashboard.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "f87343c11c74a3f4", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Free_Disk_Space_Too_Low", + "cluster": "staging", + "instance": "server5", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "service", + "alertname", + "cluster" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Free_Disk_Space_Too_Low\", cluster=\"staging\"}", + "labels": { + "alertname": "Free_Disk_Space_Too_Low", + "cluster": "staging" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "help": "Example help annotation", + "summary": "Example summary", + "url": "http://localhost/example.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "54c2f185e49cfccb", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "HTTP_Probe_Failed", + "cluster": "dev", + "instance": "web1", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "9a32b09e-0c74-4f69-8ad9-600bdcc7b4cc" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "5cb0dd95e7f3d9c0", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "HTTP_Probe_Failed", + "cluster": "dev", + "instance": "web2", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname", + "cluster", + "service" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"HTTP_Probe_Failed\", cluster=\"dev\"}", + "labels": { + "alertname": "HTTP_Probe_Failed", + "cluster": "dev" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "af9d8f2f0ccb3970", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server8", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "0967807e4073b606", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server6", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2" + ], + "state": "suppressed" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "44497481566cd3c7", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "dev", + "instance": "server7", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": null, + "silencedBy": [ + "1167d99e-60dd-4576-b514-64cf03891be2", + "f70e2eae-1d71-497b-9dd1-3d83b30aaf63" + ], + "state": "suppressed" + } + } + ], + "routeOpts": { + "groupBy": [ + "cluster", + "service", + "alertname" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Host_Down\", cluster=\"dev\"}", + "labels": { + "alertname": "Host_Down", + "cluster": "dev" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "summary": "Example summary", + "url": "http://localhost/example.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "d0aee2649e71388b", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "prod", + "instance": "server1", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "3bdb8b68bdce2ae0", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "prod", + "instance": "server2", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname", + "cluster", + "service" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Host_Down\", cluster=\"prod\"}", + "labels": { + "alertname": "Host_Down", + "cluster": "prod" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "24e4121619386f95", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server3", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "d9067fcc9686d942", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server4", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + }, + { + "annotations": { + "summary": "Example summary" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "5f1306dab6671183", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Host_Down", + "cluster": "staging", + "instance": "server5", + "job": "node_ping" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "service", + "alertname", + "cluster" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Host_Down\", cluster=\"staging\"}", + "labels": { + "alertname": "Host_Down", + "cluster": "staging" + } + }, + { + "blocks": [ + { + "alerts": [ + { + "annotations": { + "alert": "Memory usage exceeding threshold", + "dashboard": "http://localhost/dashboard.html" + }, + "endsAt": "0001-01-01T00:00:00Z", + "fingerprint": "7d0b114ebf24f857", + "generatorURL": "localhost/prometheus", + "labels": { + "alertname": "Memory_Usage_Too_High", + "cluster": "prod", + "instance": "server2", + "job": "node_exporter" + }, + "receivers": null, + "startsAt": "2018-02-13T22:41:17.691429964Z", + "status": { + "inhibitedBy": [], + "silencedBy": [], + "state": "active" + } + } + ], + "routeOpts": { + "groupBy": [ + "alertname", + "cluster", + "service" + ], + "groupInterval": 35000000000, + "groupWait": 15000000000, + "receiver": "by-cluster-service", + "repeatInterval": 3596400000000000 + } + } + ], + "groupKey": "{}/{alertname=~\"^(?:.*)$\"}:{alertname=\"Memory_Usage_Too_High\", cluster=\"prod\"}", + "labels": { + "alertname": "Memory_Usage_Too_High", + "cluster": "prod" + } + } + ], + "status": "success" +} diff --git a/internal/mock/0.14.0/api/v1/silences b/internal/mock/0.14.0/api/v1/silences new file mode 100644 index 000000000..34f366f06 --- /dev/null +++ b/internal/mock/0.14.0/api/v1/silences @@ -0,0 +1,64 @@ +{ + "data": [ + { + "comment": "Silenced instance", + "createdBy": "john@example.com", + "endsAt": "2063-01-01T00:00:00Z", + "id": "9a32b09e-0c74-4f69-8ad9-600bdcc7b4cc", + "matchers": [ + { + "isRegex": false, + "name": "instance", + "value": "web1" + } + ], + "startsAt": "2018-02-13T22:41:17.682678891Z", + "status": { + "state": "active" + }, + "updatedAt": "2018-02-13T22:41:17.682685244Z" + }, + { + "comment": "Silenced Host_Down alerts in the dev cluster", + "createdBy": "john@example.com", + "endsAt": "2063-01-01T00:00:00Z", + "id": "1167d99e-60dd-4576-b514-64cf03891be2", + "matchers": [ + { + "isRegex": false, + "name": "alertname", + "value": "Host_Down" + }, + { + "isRegex": false, + "name": "cluster", + "value": "dev" + } + ], + "startsAt": "2018-02-13T22:41:17.685938633Z", + "status": { + "state": "active" + }, + "updatedAt": "2018-02-13T22:41:17.685945586Z" + }, + { + "comment": "Silenced server7", + "createdBy": "john@example.com", + "endsAt": "2063-01-01T00:00:00Z", + "id": "f70e2eae-1d71-497b-9dd1-3d83b30aaf63", + "matchers": [ + { + "isRegex": false, + "name": "instance", + "value": "server7" + } + ], + "startsAt": "2018-02-13T22:41:17.688769461Z", + "status": { + "state": "active" + }, + "updatedAt": "2018-02-13T22:41:17.68877405Z" + } + ], + "status": "success" +} diff --git a/internal/mock/0.14.0/api/v1/status b/internal/mock/0.14.0/api/v1/status new file mode 100644 index 000000000..461935efb --- /dev/null +++ b/internal/mock/0.14.0/api/v1/status @@ -0,0 +1,98 @@ +{ + "data": { + "configJSON": { + "global": { + "hipchat_api_url": "https://api.hipchat.com/", + "opsgenie_api_url": "https://api.opsgenie.com/", + "pagerduty_url": "https://events.pagerduty.com/v2/enqueue", + "resolve_timeout": 300000000000, + "smtp_require_tls": true, + "victorops_api_url": "https://alert.victorops.com/integrations/generic/20131114/alert/", + "wechat_api_url": "https://qyapi.weixin.qq.com/cgi-bin/" + }, + "inhibit_rules": [ + { + "equal": [ + "alertname", + "cluster", + "service" + ], + "source_match": { + "severity": "critical" + }, + "target_match": { + "severity": "warning" + } + } + ], + "receivers": [ + { + "name": "default" + }, + { + "name": "by-cluster-service" + }, + { + "name": "by-name" + } + ], + "route": { + "group_by": [ + "alertname" + ], + "group_interval": 35000000000, + "group_wait": 15000000000, + "receiver": "default", + "repeat_interval": 3596400000000000, + "routes": [ + { + "continue": true, + "group_by": [ + "alertname", + "cluster", + "service" + ], + "match_re": { + "alertname": "^(?:.*)$" + }, + "receiver": "by-cluster-service" + }, + { + "continue": true, + "group_by": [ + "alertname" + ], + "match_re": { + "alertname": "^(?:.*)$" + }, + "receiver": "by-name" + } + ] + }, + "templates": null + }, + "configYAML": "global:\n resolve_timeout: 5m\n smtp_require_tls: true\n pagerduty_url: https://events.pagerduty.com/v2/enqueue\n hipchat_api_url: https://api.hipchat.com/\n opsgenie_api_url: https://api.opsgenie.com/\n wechat_api_url: https://qyapi.weixin.qq.com/cgi-bin/\n victorops_api_url: https://alert.victorops.com/integrations/generic/20131114/alert/\nroute:\n receiver: default\n group_by:\n - alertname\n routes:\n - receiver: by-cluster-service\n group_by:\n - alertname\n - cluster\n - service\n match_re:\n alertname: ^(?:.*)$\n continue: true\n - receiver: by-name\n group_by:\n - alertname\n match_re:\n alertname: ^(?:.*)$\n continue: true\n group_wait: 15s\n group_interval: 35s\n repeat_interval: 999h\ninhibit_rules:\n- source_match:\n severity: critical\n target_match:\n severity: warning\n equal:\n - alertname\n - cluster\n - service\nreceivers:\n- name: default\n- name: by-cluster-service\n- name: by-name\ntemplates: []\n", + "meshStatus": { + "connections": [], + "name": "02:42:ac:11:00:02", + "nickName": "f5aa481fb869", + "peers": [ + { + "name": "02:42:ac:11:00:02", + "nickName": "f5aa481fb869", + "uid": 10095986928128251540 + } + ] + }, + "uptime": "2018-02-13T22:41:02.274924341Z", + "versionInfo": { + "branch": "HEAD", + "buildDate": "20180213-08:16:42", + "buildUser": "root@37b6a49ebba9", + "goVersion": "go1.9.2", + "revision": "30af4d051b37ce817ea7e35b56c57a0e2ec9dbb0", + "version": "0.14.0" + } + }, + "status": "success" +} diff --git a/internal/mock/Makefile b/internal/mock/Makefile index 658f9d344..31671197b 100644 --- a/internal/mock/Makefile +++ b/internal/mock/Makefile @@ -3,7 +3,7 @@ DOCKER_IMAGE := prom/alertmanager DOCKER_ARGS := --name $(DOCKER_NAME) --rm -d -p 9093:9093 -v $(CURDIR)/alertmanager.yml:/etc/alertmanager/config.yml # list of Alertmanager versions to generate mock files for -VERSIONS := 0.4.0 0.4.1 0.4.2 0.5.0 0.5.1 0.6.0 0.6.2 0.7.0 0.7.1 0.8.0 0.9.0 0.9.1 0.10.0 0.11.0 0.12.0 0.13.0 +VERSIONS := 0.4.0 0.4.1 0.4.2 0.5.0 0.5.1 0.6.0 0.6.2 0.7.0 0.7.1 0.8.0 0.9.0 0.9.1 0.10.0 0.11.0 0.12.0 0.13.0 0.14.0 %/.ok: livemock.py $(eval VERSION := $(word 1, $(subst /, ,$@))) diff --git a/package-lock.json b/package-lock.json index f783bf587..d9c425a67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2281,7 +2281,7 @@ "requires": { "bootstrap": "3.3.7", "jquery": "3.2.1", - "moment": "2.19.1", + "moment": "2.21.0", "moment-timezone": "0.4.1" } }, @@ -6322,16 +6322,16 @@ "dev": true }, "moment": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz", - "integrity": "sha1-VtoaLRy/AdOLfhr8McELz6GSkWc=" + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "moment-timezone": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.4.1.tgz", "integrity": "sha1-gfWYw61eIs2teWtn7NjYjQ9bqgY=", "requires": { - "moment": "2.19.1" + "moment": "2.21.0" } }, "ms": { diff --git a/package.json b/package.json index 905ef95c6..3b5ecfe8b 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "loaders.css": "^0.1.2", "lru": "^3.1.0", "masonry-layout": "^4.2.0", - "moment": "^2.19.1", + "moment": "^2.21.0", "nprogress": "^0.2.0", "raven-js": "^3.19.1", "underscore": "^1.8.3" diff --git a/proxy.go b/proxy.go index a8e26d93f..4d3b43173 100644 --- a/proxy.go +++ b/proxy.go @@ -31,10 +31,21 @@ func NewAlertmanagerProxy(alertmanager *alertmanager.Alertmanager) (*httputil.Re Director: func(req *http.Request) { req.URL.Scheme = upstreamURL.Scheme req.URL.Host = upstreamURL.Host + + if upstreamURL.User.Username() != "" { + username := upstreamURL.User.Username() + password, _ := upstreamURL.User.Password() + req.SetBasicAuth(username, password) + } + // drop Accept-Encoding header so we always get uncompressed reponses from // upstream, there's a gzip middleware that's global so we don't want it // to gzip twice req.Header.Del("Accept-Encoding") + + // set hostname of proxied target + req.Host = upstreamURL.Host + log.Debugf("[%s] Proxy request for %s", alertmanager.Name, req.URL.Path) }, Transport: alertmanager.HTTPTransport, diff --git a/proxy_test.go b/proxy_test.go index 330bcc279..813140e3e 100644 --- a/proxy_test.go +++ b/proxy_test.go @@ -123,3 +123,102 @@ func TestProxy(t *testing.T) { } } } + +type proxyHeaderTest struct { + method string + localPath string + upstreamURI string + code int + alertmanagerURI string + alertmanagerHost string + authUser string + authPass string +} + +var proxyHeaderTests = []proxyHeaderTest{ + { + method: "POST", + localPath: "/proxy/alertmanager/dummy/api/v1/silences", + upstreamURI: "http://localhost:9093/api/v1/silences", + code: 200, + alertmanagerURI: "http://localhost:9093", + alertmanagerHost: "localhost:9093", + }, + { + method: "POST", + localPath: "/proxy/alertmanager/dummy/api/v1/silences", + upstreamURI: "http://alertmanager.example.com/api/v1/silences", + code: 200, + alertmanagerURI: "http://alertmanager.example.com", + alertmanagerHost: "alertmanager.example.com", + }, + { + method: "POST", + localPath: "/proxy/alertmanager/dummy/api/v1/silences", + upstreamURI: "http://alertmanager.example.com/api/v1/silences", + code: 200, + alertmanagerURI: "http://foo:bar@alertmanager.example.com", + alertmanagerHost: "alertmanager.example.com", + authUser: "foo", + authPass: "bar", + }, + { + method: "POST", + localPath: "/proxy/alertmanager/dummy/api/v1/silences", + upstreamURI: "http://alertmanager.example.com/api/v1/silences", + code: 200, + alertmanagerURI: "http://foo@alertmanager.example.com", + alertmanagerHost: "alertmanager.example.com", + authUser: "foo", + authPass: "", + }, +} + +func TestProxyHeaders(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + for _, testCase := range proxyHeaderTests { + r := ginTestEngine() + am, err := alertmanager.NewAlertmanager( + "dummy", + testCase.alertmanagerURI, + alertmanager.WithRequestTimeout(time.Second*5), + alertmanager.WithProxy(true), + ) + if err != nil { + t.Error(err) + } + setupRouterProxyHandlers(r, am) + + httpmock.Reset() + httpmock.RegisterResponder(testCase.method, testCase.upstreamURI, func(req *http.Request) (*http.Response, error) { + if req.Host != testCase.alertmanagerHost { + t.Errorf("req.Host is '%s' while '%s' was expected", req.Host, testCase.alertmanagerHost) + } + if req.Header.Get("Host") != "" { + t.Errorf("req.Header.Host is '%s' while '%s' was expected", req.Header.Get("Host"), testCase.alertmanagerHost) + } + if testCase.authUser != "" || testCase.authPass != "" { + user, password, _ := req.BasicAuth() + if testCase.authUser != "" && testCase.authUser != user { + t.Errorf("%s %s proxied to %s was expected to have Basic Auth user '%s', got '%s'", + testCase.method, testCase.localPath, testCase.upstreamURI, testCase.authUser, user) + } + if testCase.authPass != "" && testCase.authPass != password { + t.Errorf("%s %s proxied to %s was expected to have Basic Auth password '%s', got '%s'", + testCase.method, testCase.localPath, testCase.upstreamURI, testCase.authPass, password) + } + } + return httpmock.NewStringResponse(testCase.code, "ok"), nil + }) + + req, _ := http.NewRequest(testCase.method, testCase.localPath, nil) + resp := newCloseNotifyingRecorder() + r.ServeHTTP(resp, req) + if resp.Code != testCase.code { + t.Errorf("%s %s proxied to %s returned status %d while %d was expected", + testCase.method, testCase.localPath, testCase.upstreamURI, resp.Code, testCase.code) + } + } +}