Merge ~ubuntu-docker-images/ubuntu-docker-images/+git/prometheus-alertmanager:0.21-21.04-update into ~ubuntu-docker-images/ubuntu-docker-images/+git/prometheus-alertmanager:0.21-20.04

Proposed by Sergio Durigan Junior
Status: Merged
Merge reported by: Sergio Durigan Junior
Merged at revision: 9b02759d194cf26bc3685f3a0f1c6325e6968469
Proposed branch: ~ubuntu-docker-images/ubuntu-docker-images/+git/prometheus-alertmanager:0.21-21.04-update
Merge into: ~ubuntu-docker-images/ubuntu-docker-images/+git/prometheus-alertmanager:0.21-20.04
Diff against target: 627533 lines (+214/-733)
8 files modified
.gitignore (+1/-4)
Dockerfile (+32/-13)
HACKING.md (+1/-1)
Makefile (+26/-56)
README.md (+49/-366)
data/prometheus-alertmanager.yaml (+40/-0)
dev/null (+0/-293)
examples/alertmanager-deployment.yml (+65/-0)
Reviewer Review Type Date Requested Status
Bryce Harrington Approve
Lucas Kanashiro Approve
Canonical Server Pending
Review via email: mp+399038@code.launchpad.net

Description of the change

This new branch does a bunch of things:

- Get rid of the source code we maintain in-tree, because we won't be building from source anymore.
- Move everything from the oci/* directory to the root.
- Convert the Dockerfile to build from the snap.
- Update the docs.

The unit tests are passing locally.

To post a comment you must log in.
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

The diff is really big here, so I suggest fetching the branch and reviewing locally.

Revision history for this message
Lucas Kanashiro (lucaskanashiro) wrote :

There is a minor issue in the HACKING.md. Apart from that everything looks good:

- Unit tests are still passing
- Manifest file is present
- Web interface is working fine

review: Needs Fixing
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks. The HACKING.md issue was a bug in the template code, which I fixed.

Revision history for this message
Lucas Kanashiro (lucaskanashiro) wrote :

Awesome, +1.

review: Approve
Revision history for this message
Bryce Harrington (bryce) wrote :

I've not dealt with the snap-based images previously, so can't comment authoritatively on the snap logic, but the changes outside that LGTM.

I notice this drops the USER nobody line, which seems notable but isn't mentioned in the changelog entry, so just wanted to flag that in case it was unintended?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.circleci/config.yml b/.circleci/config.yml
2deleted file mode 100644
3index 8a626c5..0000000
4--- a/.circleci/config.yml
5+++ /dev/null
6@@ -1,138 +0,0 @@
7----
8-version: 2.1
9-
10-orbs:
11- prometheus: prometheus/prometheus@0.8.0
12- go: circleci/go@0.2.0
13-
14-jobs:
15- test_frontend:
16- # We need to use a machine executor because the front-end validation runs
17- # containers with mounted volumes which isn't supported with the docker
18- # executor (even with setup_remote_docker).
19- machine: true
20- steps:
21- - checkout
22- - run: sudo service docker restart
23- - run:
24- name: Remove existing Go installation
25- command: sudo rm -rf /usr/local/go
26- # Whenever the Go version is updated here, .promu.yml should also be updated.
27- - go/install:
28- version: "1.14"
29- - run:
30- name: Remove generated code
31- command: make clean
32- - run:
33- name: Generate front-end code
34- command: make all
35- working_directory: ~/project/ui/app
36- environment:
37- JUNIT_DIR: ~/test-results
38- - run:
39- name: Generate assets
40- command: make assets
41- - run:
42- name: Generate API v2 code
43- command: make apiv2
44- - run: git diff --exit-code
45- - store_test_results:
46- path: ~/test-results
47-
48- test:
49- docker:
50- # Whenever the Go version is updated here, .promu.yml should also be updated.
51- - image: circleci/golang:1.14
52- # maildev containers are for running the email tests against a "real" SMTP server.
53- # See notify/email_test.go for details.
54- - image: djfarrelly/maildev@sha256:624e0ec781e11c3531da83d9448f5861f258ee008c1b2da63b3248bfd680acfa
55- name: maildev-noauth
56- entrypoint: bin/maildev
57- command:
58- - -v
59- - image: djfarrelly/maildev@sha256:624e0ec781e11c3531da83d9448f5861f258ee008c1b2da63b3248bfd680acfa
60- name: maildev-auth
61- entrypoint: bin/maildev
62- command:
63- - -v
64- - --incoming-user
65- - user
66- - --incoming-pass
67- - pass
68-
69- environment:
70- EMAIL_NO_AUTH_CONFIG: /tmp/smtp_no_auth.yml
71- EMAIL_AUTH_CONFIG: /tmp/smtp_auth.yml
72-
73- steps:
74- - prometheus/setup_environment
75- - go/load-cache:
76- key: v1-go-mod
77- - run:
78- command: |
79- cat \<<EOF > $EMAIL_NO_AUTH_CONFIG
80- smarthost: maildev-noauth:1025
81- server: http://maildev-noauth:1080/
82- EOF
83- cat \<<EOF > $EMAIL_AUTH_CONFIG
84- smarthost: maildev-auth:1025
85- server: http://maildev-auth:1080/
86- username: user
87- password: pass
88- EOF
89- - run:
90- command: make
91- environment:
92- # By default Go uses GOMAXPROCS but a Circle CI executor has many
93- # cores (> 30) while the CPU and RAM resources are throttled. If we
94- # don't limit this to the number of allocated cores, the job is
95- # likely to get OOMed and killed.
96- GOOPTS: "-p 2"
97- - prometheus/check_proto:
98- version: "3.11.4"
99- - prometheus/store_artifact:
100- file: alertmanager
101- - prometheus/store_artifact:
102- file: amtool
103- - go/save-cache:
104- key: v1-go-mod
105- - store_test_results:
106- path: test-results
107-
108-workflows:
109- version: 2
110- alertmanager:
111- jobs:
112- - test_frontend:
113- filters:
114- tags:
115- only: /.*/
116- - test:
117- filters:
118- tags:
119- only: /.*/
120- - prometheus/build:
121- name: build
122- filters:
123- tags:
124- only: /.*/
125- - prometheus/publish_master:
126- context: org-context
127- requires:
128- - test_frontend
129- - test
130- - build
131- filters:
132- branches:
133- only: master
134- - prometheus/publish_release:
135- context: org-context
136- requires:
137- - test_frontend
138- - test
139- - build
140- filters:
141- tags:
142- only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
143- branches:
144- ignore: /.*/
145diff --git a/.dockerignore b/.dockerignore
146deleted file mode 100644
147index 6e6480b..0000000
148--- a/.dockerignore
149+++ /dev/null
150@@ -1,8 +0,0 @@
151-.build/
152-.tarballs/
153-
154-!.build/linux-amd64/
155-!.build/linux-armv7/
156-!.build/linux-arm64/
157-!.build/linux-ppc64le/
158-!.build/linux-s390x/
159diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
160deleted file mode 100644
161index ce79ffa..0000000
162--- a/.github/ISSUE_TEMPLATE.md
163+++ /dev/null
164@@ -1,47 +0,0 @@
165-<!--
166-
167- Please do *NOT* ask usage questions in Github issues.
168-
169- If your issue is not a feature request or bug report use:
170- https://groups.google.com/forum/#!forum/prometheus-users. If
171- you are unsure whether you hit a bug, search and ask in the
172- mailing list first.
173-
174- You can find more information at: https://prometheus.io/community/
175-
176--->
177-
178-**What did you do?**
179-
180-**What did you expect to see?**
181-
182-**What did you see instead? Under which circumstances?**
183-
184-**Environment**
185-
186-* System information:
187-
188- insert output of `uname -srm` here
189-
190-* Alertmanager version:
191-
192- insert output of `alertmanager --version` here
193-
194-* Prometheus version:
195-
196- insert output of `prometheus --version` here (if relevant to the issue)
197-
198-* Alertmanager configuration file:
199-```
200-insert configuration here
201-```
202-
203-* Prometheus configuration file:
204-```
205-insert configuration here (if relevant to the issue)
206-```
207-
208-* Logs:
209-```
210-insert Prometheus and Alertmanager logs relevant to the issue here
211-```
212diff --git a/.github/stale.yml b/.github/stale.yml
213deleted file mode 100644
214index 66a72af..0000000
215--- a/.github/stale.yml
216+++ /dev/null
217@@ -1,56 +0,0 @@
218-# Configuration for probot-stale - https://github.com/probot/stale
219-
220-# Number of days of inactivity before an Issue or Pull Request becomes stale
221-daysUntilStale: 60
222-
223-# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
224-# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
225-daysUntilClose: false
226-
227-# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
228-onlyLabels: []
229-
230-# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
231-exemptLabels:
232- - keepalive
233-
234-# Set to true to ignore issues in a project (defaults to false)
235-exemptProjects: false
236-
237-# Set to true to ignore issues in a milestone (defaults to false)
238-exemptMilestones: false
239-
240-# Set to true to ignore issues with an assignee (defaults to false)
241-exemptAssignees: false
242-
243-# Label to use when marking as stale
244-staleLabel: stale
245-
246-# Comment to post when marking as stale. Set to `false` to disable
247-markComment: false
248-
249-# Comment to post when removing the stale label.
250-# unmarkComment: >
251-# Your comment here.
252-
253-# Comment to post when closing a stale Issue or Pull Request.
254-# closeComment: >
255-# Your comment here.
256-
257-# Limit the number of actions per hour, from 1-30. Default is 30
258-limitPerRun: 30
259-
260-# Limit to only `issues` or `pulls`
261-only: pulls
262-
263-# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
264-# pulls:
265-# daysUntilStale: 30
266-# markComment: >
267-# This pull request has been automatically marked as stale because it has not had
268-# recent activity. It will be closed if no further activity occurs. Thank you
269-# for your contributions.
270-
271-# issues:
272-# exemptLabels:
273-# - confirmed
274diff --git a/.gitignore b/.gitignore
275index 205826a..0350652 100644
276--- a/.gitignore
277+++ b/.gitignore
278@@ -1,4 +1 @@
279-/*.snap
280-/parts/
281-/prime/
282-/stage/
283+templates/
284diff --git a/.golangci.yml b/.golangci.yml
285deleted file mode 100644
286index 1a05236..0000000
287--- a/.golangci.yml
288+++ /dev/null
289@@ -1,13 +0,0 @@
290-run:
291- modules-download-mode: vendor
292- deadline: 5m
293-
294-issues:
295- exclude-rules:
296- - path: _test.go
297- linters:
298- - errcheck
299-
300-linters-settings:
301- errcheck:
302- exclude: scripts/errcheck_excludes.txt
303diff --git a/.promu.yml b/.promu.yml
304deleted file mode 100644
305index 1c417c7..0000000
306--- a/.promu.yml
307+++ /dev/null
308@@ -1,48 +0,0 @@
309-go:
310- # Whenever the Go version is updated here, .travis.yml and
311- # .circle/config.yml should also be updated.
312- version: 1.14
313-repository:
314- path: github.com/prometheus/alertmanager
315-build:
316- binaries:
317- - name: alertmanager
318- path: ./cmd/alertmanager
319- - name: amtool
320- path: ./cmd/amtool
321- flags: -mod vendor -a -tags netgo
322- ldflags: |
323- -X github.com/prometheus/common/version.Version={{.Version}}
324- -X github.com/prometheus/common/version.Revision={{.Revision}}
325- -X github.com/prometheus/common/version.Branch={{.Branch}}
326- -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
327- -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
328-tarball:
329- files:
330- - examples/ha/alertmanager.yml
331- - LICENSE
332- - NOTICE
333-crossbuild:
334- platforms:
335- - linux/amd64
336- - linux/386
337- - darwin/amd64
338- - darwin/386
339- - windows/amd64
340- - windows/386
341- - freebsd/amd64
342- - freebsd/386
343- - openbsd/amd64
344- - openbsd/386
345- - netbsd/amd64
346- - netbsd/386
347- - dragonfly/amd64
348- - linux/arm
349- - linux/arm64
350- - freebsd/arm
351- - netbsd/arm
352- - linux/ppc64
353- - linux/ppc64le
354- - linux/mips64
355- - linux/mips64le
356- - linux/s390x
357diff --git a/CHANGELOG.md b/CHANGELOG.md
358deleted file mode 100644
359index 93dc0af..0000000
360--- a/CHANGELOG.md
361+++ /dev/null
362@@ -1,564 +0,0 @@
363-## 0.21.0 / 2020-06-16
364-
365-This release removes the HipChat integration as it is discontinued by Atlassian on June 30th 2020.
366-
367-* [CHANGE] [HipChat] Remove HipChat integration as it is end-of-life. #2282
368-* [CHANGE] [amtool] Remove default assignment of environment variables. #2161
369-* [CHANGE] [PagerDuty] Enforce 512KB event size limit. #2225
370-* [ENHANCEMENT] [amtool] Add `cluster` command to show cluster and peer statuses. #2256
371-* [ENHANCEMENT] Add redirection from `/` to the routes prefix when it isn't empty. #2235
372-* [ENHANCEMENT] [Webhook] Add `max_alerts` option to limit the number of alerts included in the payload. #2274
373-* [ENHANCEMENT] Improve logs for API v2, notifications and clustering. #2177 #2188 #2260 #2261 #2273
374-* [BUGFIX] Fix child routes not inheriting their parent route's grouping when `group_by: [...]`. #2154
375-* [BUGFIX] [UI] Fix the receiver selector in the Alerts page when the receiver name contains regular expression metacharacters such as `+`. #2090
376-* [BUGFIX] Fix error message about start and end time validation. #2173
377-* [BUGFIX] Fix a potential race condition in dispatcher. #2208
378-* [BUGFIX] [API v2] Return an empty array of peers when the clustering is disabled. #2203
379-* [BUGFIX] Fix the registration of `alertmanager_dispatcher_aggregation_groups` and `alertmanager_dispatcher_alert_processing_duration_seconds` metrics. #2200
380-* [BUGFIX] Always retry notifications with back-off. #2290
381-
382-## 0.20.0 / 2019-12-11
383-
384-* [CHANGE] Check that at least one silence matcher matches a non-empty string. #2081
385-* [ENHANCEMENT] [pagerduty] Check that PagerDuty keys aren't empty. #2085
386-* [ENHANCEMENT] [template] Add the `stringSlice` function. #2101
387-* [ENHANCEMENT] Add `alertmanager_dispatcher_aggregation_groups` and `alertmanager_dispatcher_alert_processing_duration_seconds` metrics. #2113
388-* [ENHANCEMENT] Log unused receivers. #2114
389-* [ENHANCEMENT] Add `alertmanager_receivers` metric. #2114
390-* [ENHANCEMENT] Add `alertmanager_integrations` metric. #2117
391-* [ENHANCEMENT] [email] Add Message-Id Header to outgoing emails. #2057
392-* [BUGFIX] Don't garbage-collect alerts from the store. #2040
393-* [BUGFIX] [ui] Disable the grammarly plugin on all textareas. #2061
394-* [BUGFIX] [config] Forbid nil regexp matchers. #2083
395-* [BUGFIX] [ui] Fix Silences UI when several filters are applied. #2075
396-
397-Contributors:
398-
399-* @CharlesJUDITH
400-* @NotAFile
401-* @Pger-Y
402-* @TheMeier
403-* @johncming
404-* @n33pm
405-* @ntk148v
406-* @oddlittlebird
407-* @perlun
408-* @qoops-1
409-* @roidelapluie
410-* @simonpasquier
411-* @stephenreddek
412-* @sylr
413-* @vrischmann
414-
415-## 0.19.0 / 2019-09-03
416-
417-* [CHANGE] Reject invalid external URLs at startup. #1960
418-* [CHANGE] Add Fingerprint to template data. #1945
419-* [CHANGE] Check Smarthost validity at config loading. #1957
420-* [ENHANCEMENT] Improve error messages for email receiver. #1953
421-* [ENHANCEMENT] Log error messages from OpsGenie API. #1965
422-* [ENHANCEMENT] Add the ability to configure Slack markdown field. #1967
423-* [ENHANCEMENT] Log warning when repeat_interval > retention. #1993
424-* [ENHANCEMENT] Add `alertmanager_cluster_enabled` metric. #1973
425-* [ENHANCEMENT] [ui] Recreate silence with previous comment. #1927
426-* [BUGFIX] [ui] Fix /api/v2/alerts/groups endpoint with similar alert groups. #1964
427-* [BUGFIX] Allow slashes in receivers. #2011
428-* [BUGFIX] [ui] Fix expand/collapse button with identical alert groups. #2012
429-
430-## 0.18.0 / 2019-07-08
431-
432-* [CHANGE] Remove quantile labels from Summary metrics. #1921
433-* [CHANGE] [OpsGenie] Move from the deprecated `teams` field in the configuration to `responders`. #1863
434-* [CHANGE] [ui] Collapse alert groups on the initial view. #1876
435-* [CHANGE] [Wechat] Set the default API secret to blank. #1888
436-* [CHANGE/BUGFIX] [PagerDuty] Fix embedding of images, the `text` field in the configuration has been renamed to `href`. #1931
437-* [ENHANCEMENT] Use persistent HTTP clients. #1904
438-* [ENHANCEMENT] Add `alertmanager_cluster_alive_messages_total`, `alertmanager_cluster_peer_info` and `alertmanager_cluster_pings_seconds` metrics. #1941
439-* [ENHANCEMENT] [api] Add missing metrics for API v2. #1902
440-* [ENHANCEMENT] [Slack] Log error message on retry errors. #1655
441-* [ENHANCEMENT] [ui] Allow to create silences from the alerts filter bar. #1911
442-* [ENHANCEMENT] [ui] Enable auto resize the textarea fields. #1893
443-* [BUGFIX] [amtool] Use scheme, authentication and base path from the URL if present. #1892 #1940
444-* [BUGFIX] [amtool] Support filtering alerts by receiver. #1915
445-* [BUGFIX] [api] Fix /api/v2/alerts with multiple receivers. #1948
446-* [BUGFIX] [PagerDuty] Truncate description to 1024 chars for PagerDuty v1. #1922
447-* [BUGFIX] [ui] Add filtering based off of "active" query param. #1879
448-
449-
450-## 0.17.0 / 2019-05-02
451-
452-This release includes changes to amtool which are not fully backwards
453-compatible with the previous amtool version (#1798) related to backup and
454-import of silences. If a backup of silences is created using a previous
455-version of amtool (v0.16.1 or earlier), it is possible that not all silences
456-can be correctly imported using a later version of amtool.
457-
458-Additionally, the groups endpoint that was dropped from api v1 has been added
459-to api v2. The default for viewing alerts in the UI now consumes from this
460-endpoint and displays alerts grouped according to the groups defined in the
461-running configuration. Custom grouping is still supported.
462-
463-This release has added two new flags that may need to be tweaked. For people
464-running with a lot of concurrent requests, consider increasing the value of
465-`--web.get-concurrency`. An increase in 503 errors indicates that the request
466-rate is exceeding the number of currently available workers. The other new
467-flag, --web.timeout, limits the time a request is allowed to run. The default
468-behavior is to not use a timeout.
469-
470-* [CHANGE] Modify the self-inhibition prevention semantics (#1873)
471-* [CHANGE] Make api/v2/status.cluster.{name,peers} properties optional for Alertmanager with disabled clustering (#1728)
472-* [FEATURE] Add groups endpoint to v2 api (#1791)
473-* [FEATURE] Optional timeout for HTTP requests (#1743)
474-* [ENHANCEMENT] Set HTTP headers to prevent asset caching (#1817)
475-* [ENHANCEMENT] API returns current silenced/inhibited state of alerts (#1733)
476-* [ENHANCEMENT] Configurable concurrency limit for GET requests (#1743)
477-* [ENHANCEMENT] Pushover notifier: support HTML, URL title and custom sounds (#1634)
478-* [ENHANCEMENT] Support adding custom fields to VictorOps notifications (#1420)
479-* [ENHANCEMENT] Migrate amtool CLI to API v2 (#1798)
480-* [ENHANCEMENT][ui] Default alert list view grouped by configured alert groups (#1864)
481-* [ENHANCEMENT][ui] Remove superfluous inhibited/silenced text, show inhibited status (#1698, #1862)
482-* [ENHANCEMENT][ui] Silence preview now shows already-muted alerts (#1776)
483-* [ENHANCEMENT][ui] Sort silences from api/v2 similarly to api/v1 (#1786)
484-* [BUGFIX] Trim PagerDuty message summary to 1024 chars (#1701)
485-* [BUGFIX] Add fix for race causing alerts to be dropped (#1843)
486-* [BUGFIX][ui] Correctly construct filter query string for api (#1869)
487-* [BUGFIX][ui] Do not display GroupByAll and GroupBy in marshaled config (#1665)
488-* [BUGFIX][ui] Respect regex setting when creating silences (#1697)
489-
490-## 0.16.2 / 2019-04-03
491-
492-Updating to v0.16.2 is recommended for all users using the Slack, Pagerduty,
493-Hipchat, Wechat, VictorOps and Pushover notifier, as connection errors could
494-leak secrets embedded in the notifier's URL to stdout.
495-
496-* [BUGFIX] Redact notifier URL from logs to not leak secrets embedded in the URL (#1822, #1825)
497-* [BUGFIX] Allow sending of unauthenticated SMTP requests when `smtp_auth_username` is not supplied (#1739)
498-
499-## 0.16.1 / 2019-01-31
500-
501-* [BUGFIX] Do not populate cluster info if clustering is disabled in API v2 (#1726)
502-
503-## 0.16.0 / 2019-01-17
504-
505-This release introduces a new API v2, fully generated via the OpenAPI project
506-[1]. At the same time with this release the previous API v1 is being
507-deprecated. API v1 will be removed with Alertmanager release v0.18.0.
508-
509-* [CHANGE] Deprecate API v1
510-* [CHANGE] Remove `api/v1/alerts/groups` GET endpoint (#1508 & #1525)
511-* [CHANGE] Revert Alertmanager working directory changes in Docker image back to `/alertmanager` (#1435)
512-* [CHANGE] Using the recommended label syntax for maintainer in Dockerfile (#1533)
513-* [CHANGE] Change `alertmanager_notifications_total` to count attempted notifications, not only successful ones (#1578)
514-* [CHANGE] Run as nobody inside container (#1586)
515-* [CHANGE] Support `w` for weeks when creating silences, remove `y` for year (#1620)
516-* [FEATURE] Introduce OpenAPI generated API v2 (#1352)
517-* [FEATURE] Lookup parts in strings using regexp.MatchString in templates (#1452)
518-* [FEATURE] Support image/thumb url in attachment in Slack notifier (#1506)
519-* [FEATURE] Support custom TLS certificates for the email notifier (#1528)
520-* [FEATURE] Add support for images and links in the PagerDuty notification config (#1559)
521-* [FEATURE] Add support for grouping by all labels (#1588)
522-* [FEATURE] [amtool] Add timeout support to amtool commands (#1471)
523-* [FEATURE] [amtool] Added `config routes` tools for visualization and testing routes (#1511)
524-* [FEATURE] [amtool] Support adding alerts using amtool (#1461)
525-* [ENHANCEMENT] Add support for --log.format (#1658)
526-* [ENHANCEMENT] Add CORS support to API v2 (#1667)
527-* [ENHANCEMENT] Support HTML, URL title and custom sounds for Pushover (#1634)
528-* [ENHANCEMENT] Update Alert compact view (#1698)
529-* [ENHANCEMENT] Support adding custom fields to VictorOps notifications (#1420)
530-* [ENHANCEMENT] Add help link in UI to Alertmanager documentation (#1522)
531-* [ENHANCEMENT] Enforce HTTP or HTTPS URLs in Alertmanager config (#1567)
532-* [ENHANCEMENT] Make OpsGenie API Key a templated string (#1594)
533-* [ENHANCEMENT] Add name, value and SlackConfirmationField to action in Slack notifier (#1557)
534-* [ENHANCEMENT] Show more alert information on silence form and silence view pages (#1601)
535-* [ENHANCEMENT] Add cluster peers DNS refresh job (#1428)
536-* [BUGFIX] Fix unmarshaling of secret URLs in config (#1663)
537-* [BUGFIX] Do not write groupbyall and groupby when marshaling config (#1665)
538-* [BUGFIX] Make a copy of firing alerts with EndsAt=0 when flushing (#1686)
539-* [BUGFIX] Respect regex matchers when recreating silences in UI (#1697)
540-* [BUGFIX] Change DefaultGlobalConfig to a function in Alertmanager configuration (#1656)
541-* [BUGFIX] Fix email template typo in alert-warning style (#1421)
542-* [BUGFIX] Fix silence redirect on silence creation UI page (#1548)
543-* [BUGFIX] Add missing `callback_id` parameter in Slack notifier (#1592)
544-* [BUGFIX] Throw error if no auth mechanism matches in email notifier (#1608)
545-* [BUGFIX] Use quoted-printable transfer encoding for the email notifier (#1609)
546-* [BUGFIX] Do not merge expired gossip messages (#1631)
547-* [BUGFIX] Fix "PLAIN" auth during notification via smtp-over-tls on port 465 (#1591)
548-* [BUGFIX] [amtool] Support for assuming first label is alertname in silence add and query (#1693)
549-* [BUGFIX] [amtool] Support assuming first label is alertname in alert query with matchers (#1575)
550-* [BUGFIX] [amtool] Fix config path check in amtool (#1538)
551-* [BUGFIX] [amtool] Fix rfc3339 example texts (#1526)
552-* [BUGFIX] [amtool] Fixed issue with loading path of a default configs (#1529)
553-
554-[1] https://github.com/prometheus/alertmanager#api
555-
556-## 0.15.3 / 2018-11-09
557-
558-* [BUGFIX] Fix alert merging supporting both empty and set EndsAt property for firing alerts send by Prometheus (#1611)
559-
560-## 0.15.2 / 2018-08-14
561-
562-* [ENHANCEMENT] [amtool] Add support for stdin to check-config (#1431)
563-* [ENHANCEMENT] Log PagerDuty v1 response on BadRequest (#1481)
564-* [BUGFIX] Correctly encode query strings in notifiers (#1516)
565-* [BUGFIX] Add cache control headers to the API responses to avoid IE caching (#1500)
566-* [BUGFIX] Avoid listener blocking on unsubscribe (#1482)
567-* [BUGFIX] Fix a bunch of unhandled errors (#1501)
568-* [BUGFIX] Update PagerDuty API V2 to send full details on resolve (#1483)
569-* [BUGFIX] Validate URLs at config load time (#1468)
570-* [BUGFIX] Fix Settle() interval (#1478)
571-* [BUGFIX] Fix email to be green if only none firing (#1475)
572-* [BUGFIX] Handle errors in notify (#1474)
573-* [BUGFIX] Fix templating of hipchat room id (#1463)
574-
575-## 0.15.1 / 2018-07-10
576-
577-* [BUGFIX] Fix email template typo in alert-warning style (#1421)
578-* [BUGFIX] Fix regression in Pager Duty config (#1455)
579-* [BUGFIX] Catch templating errors in Wechat Notify (#1436)
580-* [BUGFIX] Fail when no private address can be found for cluster (#1437)
581-* [BUGFIX] Make sure we don't miss the first pushPull when joining cluster (#1456)
582-* [BUGFIX] Fix concurrent read and write group error in dispatch (#1447)
583-
584-## 0.15.0 / 2018-06-22
585-
586-* [CHANGE] [amtool] Update silence add and update flags (#1298)
587-* [CHANGE] Replace deprecated InstrumentHandler() (#1302)
588-* [CHANGE] Validate Slack field config and only allow the necessary input (#1334)
589-* [CHANGE] Remove legacy alert ingest endpoint (#1362)
590-* [CHANGE] Move to memberlist as underlying gossip protocol including cluster flag changes from --mesh.xxx to --cluster.xxx (#1232)
591-* [CHANGE] Move Alertmanager working directory in Docker image to /etc/alertmanager (#1313)
592-* [BUGFIX/CHANGE] The default group by is no labels. (#1287)
593-* [FEATURE] [amtool] Filter alerts by receiver (#1402)
594-* [FEATURE] Wait for mesh to settle before sending alerts (#1209)
595-* [FEATURE] [amtool] Support basic auth in alertmanager url (#1279)
596-* [FEATURE] Make HTTP clients used for integrations configurable
597-* [ENHANCEMENT] Support receiving alerts with end time and zero start time
598-* [ENHANCEMENT] Sort dispatched alerts by job+instance (#1234)
599-* [ENHANCEMENT] Support alert query filters `active` and `unprocessed` (#1366)
600-* [ENHANCEMENT] [amtool] Expose alert query flags --active and --unprocessed (#1370)
601-* [ENHANCEMENT] Add Slack actions to notifications (#1355)
602-* [BUGFIX] Register nflog snapShotSize metric
603-* [BUGFIX] Sort alerts in correct order before flushing to notifiers (#1349)
604-* [BUGFIX] Don't reset initial wait timer if flush is in-progress (#1301)
605-* [BUGFIX] Fix resolved alerts still inhibiting (#1331)
606-* [BUGFIX] Template wechat config fields (#1356)
607-* [BUGFIX] Notify resolved alerts properly (#1408)
608-* [BUGFIX] Fix parsing for label values with commas (#1395)
609-* [BUGFIX] Hide sensitive Wechat configuration (#1253)
610-* [BUGFIX] Prepopulate matchers when recreating a silence (#1270)
611-* [BUGFIX] Fix wechat panic (#1293)
612-* [BUGFIX] Allow empty matchers in silences/filtering (#1289)
613-* [BUGFIX] Properly configure HTTP client for Wechat integration
614-
615-## 0.14.0 / 2018-02-12
616-
617-* [ENHANCEMENT] [amtool] Silence update support dwy suffixes to expire flag (#1197)
618-* [ENHANCEMENT] Allow templating PagerDuty receiver severity (#1214)
619-* [ENHANCEMENT] Include receiver name in failed notifications log messages (#1207)
620-* [ENHANCEMENT] Allow global opsgenie api key (#1208)
621-* [ENHANCEMENT] Add mesh metrics (#1225)
622-* [ENHANCEMENT] Add Class field to PagerDuty; add templating to PagerDuty-CEF fields (#1231)
623-* [BUGFIX] Don't notify of resolved alerts if none were reported firing (#1198)
624-* [BUGFIX] Notify only when new firing alerts are added (#1205)
625-* [BUGFIX] [mesh] Fix pending connections never set to established (#1204)
626-* [BUGFIX] Allow OpsGenie notifier to have empty team fields (#1224)
627-* [BUGFIX] Don't count alerts with EndTime in the future as resolved (#1233)
628-* [BUGFIX] Speed up re-rendering of Silence UI (#1235)
629-* [BUGFIX] Forbid 0 value for group_interval and repeat_interval (#1230)
630-* [BUGFIX] Fix WeChat agentid issue (#1229)
631-
632-## 0.13.0 / 2018-01-12
633-
634-* [CHANGE] Switch cmd/alertmanager to kingpin (#974)
635-* [CHANGE] [amtool] Switch amtool to kingpin (#976)
636-* [CHANGE] [amtool] silence query: --expired flag only shows expired silences (#1190)
637-* [CHANGE] Return config reload result from reload endpoint (#1180)
638-* [FEATURE] UI silence form is populated from location bar (#1148)
639-* [FEATURE] Add /-/healthy endpoint (#1159)
640-* [ENHANCEMENT] Instrument and log snapshot sizes on maintenance (#1155)
641-* [ENHANCEMENT] Make alertGC interval configurable (#1151)
642-* [ENHANCEMENT] Display mesh connections in the Status page (#1164)
643-* [BUGFIX] Template service keys for pagerduty notifier (#1182)
644-* [BUGFIX] Fix expire buttons on the silences page (#1171)
645-* [BUGFIX] Fix JavaScript error in MSIE due to endswith() usage (#1172)
646-* [BUGFIX] Correctly format UI error output (#1167)
647-
648-## 0.12.0 / 2017-12-15
649-
650-* [FEATURE] package amtool in docker container (#1127)
651-* [FEATURE] Add notify support for Chinese User wechat (#1059)
652-* [FEATURE] [amtool] Add a new `silence import` command (#1082)
653-* [FEATURE] [amtool] Add new command to update silence (#1123)
654-* [FEATURE] [amtool] Add ability to query for silences that will expire soon (#1120)
655-* [ENHANCEMENT] Template source field in PagerDuty alert payload (#1117)
656-* [ENHANCEMENT] Add footer field for slack messages (#1141)
657-* [ENHANCEMENT] Add Slack additional "fields" to notifications (#1135)
658-* [ENHANCEMENT] Adding check for webhook's URL formatting (#1129)
659-* [ENHANCEMENT] Let the browser remember the creator of a silence (#1112)
660-* [BUGFIX] Fix race in stopping inhibitor (#1118)
661-* [BUGFIX] Fix browser UI when entering negative duration (#1132)
662-
663-## 0.11.0 / 2017-11-16
664-
665-* [CHANGE] Make silence negative filtering consistent with alert filtering (#1095)
666-* [CHANGE] Change HipChat and OpsGenie api config names (#1087)
667-* [ENHANCEMENT] amtool: Allow 'd', 'w', 'y' time suffixes when creating silence (#1091)
668-* [ENHANCEMENT] Support OpsGenie Priority field (#1094)
669-* [BUGFIX] Fix UI when no silences are present (#1090)
670-* [BUGFIX] Fix OpsGenie Teams field (#1101)
671-* [BUGFIX] Fix OpsGenie Tags field (#1108)
672-
673-## 0.10.0 / 2017-11-09
674-
675-* [CHANGE] Prevent inhibiting alerts in the source of the inhibition (#1017)
676-* [ENHANCEMENT] Improve amtool check-config use and description text (#1016)
677-* [ENHANCEMENT] Add metrics about current silences and alerts (#998)
678-* [ENHANCEMENT] Sorted silences based on current status (#1015)
679-* [ENHANCEMENT] Add metric of alertmanager position in mesh (#1024)
680-* [ENHANCEMENT] Initialise notifications_total and notifications_failed_total (#1011)
681-* [ENHANCEMENT] Allow selectable matchers on silence view (#1030)
682-* [ENHANCEMENT] Allow template in victorops message_type field (#1038)
683-* [ENHANCEMENT] Optionally hide inhibited alerts in API response (#1039)
684-* [ENHANCEMENT] Toggle silenced and inhibited alerts in UI (#1049)
685-* [ENHANCEMENT] Fix pushover limits (title, message, url) (#1055)
686-* [ENHANCEMENT] Add limit to OpsGenie message (#1045)
687-* [ENHANCEMENT] Upgrade OpsGenie notifier to v2 API. (#1061)
688-* [ENHANCEMENT] Allow template in victorops routing_key field (#1083)
689-* [ENHANCEMENT] Add support for PagerDuty API v2 (#1054)
690-* [BUGFIX] Fix inhibit race (#1032)
691-* [BUGFIX] Fix segfault on amtool (#1031)
692-* [BUGFIX] Remove .WasInhibited and .WasSilenced fields of Alert type (#1026)
693-* [BUGFIX] nflog: Fix Log() crash when gossip is nil (#1064)
694-* [BUGFIX] Fix notifications for flapping alerts (#1071)
695-* [BUGFIX] Fix shutdown crash with nil mesh router (#1077)
696-* [BUGFIX] Fix negative matchers filtering (#1077)
697-
698-## 0.9.1 / 2017-09-29
699-* [BUGFIX] Fix -web.external-url regression in ui (#1008)
700-* [BUGFIX] Fix multipart email implementation (#1009)
701-
702-## 0.9.0 / 2017-09-28
703-* [ENHANCEMENT] Add current time to webhook message (#909)
704-* [ENHANCEMENT] Add link_names to slack notifier (#912)
705-* [ENHANCEMENT] Make ui labels selectable/highlightable (#932)
706-* [ENHANCEMENT] Make links in ui annotations selectable (#946)
707-* [ENHANCEMENT] Expose the alert's "fingerprint" (unique identifier) through API (#786)
708-* [ENHANCEMENT] Add README information for amtool (#939)
709-* [ENHANCEMENT] Use user-set logging option consistently throughout alertmanager (#968)
710-* [ENHANCEMENT] Sort alerts returned from API by their fingerprint (#969)
711-* [ENHANCEMENT] Add edit/delete silence buttons on silence page view (#970)
712-* [ENHANCEMENT] Add check-config subcommand to amtool (#978)
713-* [ENHANCEMENT] Add email notification text content support (#934)
714-* [ENHANCEMENT] Support passing binary name to make build target (#990)
715-* [ENHANCEMENT] Show total no. of silenced alerts in preview (#994)
716-* [ENHANCEMENT] Added confirmation dialog when expiring silences (#993)
717-* [BUGFIX] Fix crash when no mesh router is configured (#919)
718-* [BUGFIX] Render status page without mesh (#920)
719-* [BUGFIX] Exit amtool subcommands with non-zero error code (#938)
720-* [BUGFIX] Change mktemp invocation in makefile to work for macOS (#971)
721-* [BUGFIX] Add a mutex to silences.go:gossipData (#984)
722-* [BUGFIX] silences: avoid deadlock (#995)
723-* [BUGFIX] Ignore expired silences OnGossip (#999)
724-
725-## 0.8.0 / 2017-07-20
726-
727-* [FEATURE] Add ability to filter alerts by receiver in the UI (#890)
728-* [FEATURE] Add User-Agent for webhook requests (#893)
729-* [ENHANCEMENT] Add possibility to have a global victorops api_key (#897)
730-* [ENHANCEMENT] Add EntityDisplayName and improve StateMessage for Victorops
731- (#769)
732-* [ENHANCEMENT] Omit empty config fields and show regex upon re-marshaling to
733- elide secrets (#864)
734-* [ENHANCEMENT] Parse API error messages in UI (#866)
735-* [ENHANCEMENT] Enable sending mail via smtp port 465 (#704)
736-* [BUGFIX] Prevent duplicate notifications by sorting matchers (#882)
737-* [BUGFIX] Remove timeout for UI requests (#890)
738-* [BUGFIX] Update config file location of CLI in flag usage text (#895)
739-
740-## 0.7.1 / 2017-06-09
741-
742-* [BUGFIX] Fix filtering by label on Alert list and Silence list page
743-
744-## 0.7.0 / 2017-06-08
745-
746-* [CHANGE] Rewrite UI from scratch improving UX
747-* [CHANGE] Rename `config` to `configYAML` on `api/v1/status`
748-* [FEATURE] Add ability to update a silence on `api/v1/silences` POST endpoint (See #765)
749-* [FEATURE] Return alert status on `api/v1/alerts` GET endpoint
750-* [FEATURE] Serve silence state on `api/v1/silences` GET endpoint
751-* [FEATURE] Add ability to specify a route prefix
752-* [FEATURE] Add option to disable AM listening on mesh port
753-* [ENHANCEMENT] Add ability to specify `filter` string and `silenced` flag on `api/v1/alerts` GET endpoint
754-* [ENHANCEMENT] Update `cache-control` to prevent caching for web assets in general.
755-* [ENHANCEMENT] Serve web assets by alertmanager instead of external CDN (See #846)
756-* [ENHANCEMENT] Elide secrets in alertmanager config (See #840)
757-* [ENHANCEMENT] AMTool: Move config file to a more consistent location (See #843)
758-* [BUGFIX] Enable builds for Solaris/Illumos
759-* [BUGFIX] Load web assets based on url path (See #323)
760-
761-## 0.6.2 / 2017-05-09
762-
763-* [BUGFIX] Correctly link to silences from alert again
764-* [BUGFIX] Correctly hide silenced/show active alerts in UI again
765-* [BUGFIX] Fix regression of alerts not being displayed until first processing
766-* [BUGFIX] Fix internal usage of wrong lock for silence markers
767-* [BUGFIX] Adapt amtool's API parsing to recent API changes
768-* [BUGFIX] Correctly marshal regexes in config JSON response
769-* [CHANGE] Anchor silence regex matchers to be consistent with Prometheus
770-* [ENHANCEMENT] Error if root route is using `continue` keyword
771-
772-## 0.6.1 / 2017-04-28
773-
774-* [BUGFIX] Fix incorrectly serialized hash for notification providers.
775-* [ENHANCEMENT] Add processing status field to alerts.
776-* [FEATURE] Add config hash metric.
777-
778-## 0.6.0 / 2017-04-25
779-
780-* [BUGFIX] Add `groupKey` to `alerts/groups` endpoint https://github.com/prometheus/alertmanager/pull/576
781-* [BUGFIX] Only notify on firing alerts https://github.com/prometheus/alertmanager/pull/595
782-* [BUGFIX] Correctly marshal regex's in config for routing tree https://github.com/prometheus/alertmanager/pull/602
783-* [BUGFIX] Prevent panic when failing to load config https://github.com/prometheus/alertmanager/pull/607
784-* [BUGFIX] Prevent panic when alertmanager is started with an empty `-mesh.peer` https://github.com/prometheus/alertmanager/pull/726
785-* [CHANGE] Rename VictorOps config variables https://github.com/prometheus/alertmanager/pull/667
786-* [CHANGE] No longer generate releases for openbsd/arm https://github.com/prometheus/alertmanager/pull/732
787-* [ENHANCEMENT] Add `DELETE` as accepted CORS method https://github.com/prometheus/alertmanager/commit/0ecc59076ca6b4cbb63252fa7720a3d89d1c81d3
788-* [ENHANCEMENT] Switch to using `gogoproto` for protobuf https://github.com/prometheus/alertmanager/pull/715
789-* [ENHANCEMENT] Include notifier type in logs and errors https://github.com/prometheus/alertmanager/pull/702
790-* [FEATURE] Expose mesh peers on status page https://github.com/prometheus/alertmanager/pull/644
791-* [FEATURE] Add `reReplaceAll` template function https://github.com/prometheus/alertmanager/pull/639
792-* [FEATURE] Allow label-based filtering alerts/silences through API https://github.com/prometheus/alertmanager/pull/633
793-* [FEATURE] Add commandline tool for interacting with alertmanager https://github.com/prometheus/alertmanager/pull/636
794-
795-## 0.5.1 / 2016-11-24
796-
797-* [BUGFIX] Fix crash caused by race condition in silencing
798-* [ENHANCEMENT] Improve logging of API errors
799-* [ENHANCEMENT] Add metrics for the notification log
800-
801-## 0.5.0 / 2016-11-01
802-
803-This release requires a storage wipe. It contains fundamental internal
804-changes that came with implementing the high availability mode.
805-
806-* [FEATURE] Alertmanager clustering for high availability
807-* [FEATURE] Garbage collection of old silences and notification logs
808-* [CHANGE] New storage format
809-* [CHANGE] Stricter silence semantics for consistent historical view
810-
811-## 0.4.2 / 2016-09-02
812-
813-* [BUGFIX] Fix broken regex checkbox in silence form
814-* [BUGFIX] Simplify inconsistent silence update behavior
815-
816-## 0.4.1 / 2016-08-31
817-
818-* [BUGFIX] Wait for silence query to finish instead of showing error
819-* [BUGFIX] Fix sorting of silences
820-* [BUGFIX] Provide visual feedback after creating a silence
821-* [BUGFIX] Fix styling of silences
822-* [ENHANCEMENT] Provide cleaner API silence interface
823-
824-## 0.4.0 / 2016-08-23
825-
826-* [FEATURE] Silences are now paginated in the web ui
827-* [CHANGE] Failure to start on unparsed flags
828-
829-## 0.3.0 / 2016-07-07
830-
831-* [CHANGE] Alerts are purely in memory and no longer persistent across restarts
832-* [FEATURE] Add SMTP LOGIN authentication mechanism
833-
834-## 0.2.1 / 2016-06-23
835-
836-* [ENHANCEMENT] Allow inheritance of route receiver
837-* [ENHANCEMENT] Add silence cache to silence provider
838-* [BUGFIX] Fix HipChat room number in integration URL
839-
840-## 0.2.0 / 2016-06-17
841-
842-This release uses a new storage backend based on BoltDB. You have to backup
843-and wipe your former storage path to run it.
844-
845-* [CHANGE] Use BoltDB as data store.
846-* [CHANGE] Move SMTP authentication to configuration file
847-* [FEATURE] add /-/reload HTTP endpoint
848-* [FEATURE] Filter silenced alerts in web UI
849-* [ENHANCEMENT] reduce inhibition computation complexity
850-* [ENHANCEMENT] Add support for teams and tags in OpsGenie integration
851-* [BUGFIX] Handle OpsGenie responses correctly
852-* [BUGFIX] Fix Pushover queue length issue
853-* [BUGFIX] STARTTLS before querying auth mechanism in email integration
854-
855-## 0.1.1 / 2016-03-15
856-* [BUGFIX] Fix global database lock issue
857-* [ENHANCEMENT] Improve SQLite alerts index
858-* [ENHANCEMENT] Enable debug endpoint
859-
860-## 0.1.0 / 2016-02-23
861-This version is a full rewrite of the Alertmanager with a very different
862-feature set. Thus, there is no meaningful changelog.
863-
864-Changes with respect to 0.1.0-beta2:
865-* [CHANGE] Expose same data structure to templates and webhook
866-* [ENHANCEMENT] Show generator URL in default templates and web UI
867-* [ENHANCEMENT] Support for Slack icon_emoji field
868-* [ENHANCEMENT] Expose incident key to templates and webhook data
869-* [ENHANCEMENT] Allow markdown in Slack 'text' field
870-* [BUGFIX] Fixed database locking issue
871-
872-## 0.1.0-beta2 / 2016-02-03
873-* [BUGFIX] Properly set timeout for incoming alerts with fixed start time
874-* [ENHANCEMENT] Send source field in OpsGenie integration
875-* [ENHANCEMENT] Improved routing configuration validation
876-* [FEATURE] Basic instrumentation added
877-
878-## 0.1.0-beta1 / 2016-01-08
879-* [BUGFIX] Send full alert group state on each update. Fixes erroneous resolved notifications.
880-* [FEATURE] HipChat integration
881-* [CHANGE] Slack integration no longer sends resolved notifications by default
882-
883-## 0.1.0-beta0 / 2015-12-23
884-This version is a full rewrite of the Alertmanager with a very different
885-feature set. Thus, there is no meaningful changelog.
886-
887-## 0.0.4 / 2015-09-09
888-* [BUGFIX] Fix version info string in startup message.
889-* [BUGFIX] Fix Pushover notifications by setting the right priority level, as
890- well as required retry and expiry intervals.
891-* [FEATURE] Make it possible to link to individual alerts in the UI.
892-* [FEATURE] Rearrange alert columns in UI and allow expanding more alert details.
893-* [FEATURE] Add Amazon SNS notifications.
894-* [FEATURE] Add OpsGenie Webhook notifications.
895-* [FEATURE] Add `-web.external-url` flag to control the externally visible
896- Alertmanager URL.
897-* [FEATURE] Add runbook and alertmanager URLs to PagerDuty and email notifications.
898-* [FEATURE] Add a GET API to /api/alerts which pulls JSON formatted
899- AlertAggregates.
900-* [ENHANCEMENT] Sort alerts consistently in web UI.
901-* [ENHANCEMENT] Suggest to use email address as silence creator.
902-* [ENHANCEMENT] Make Slack timeout configurable.
903-* [ENHANCEMENT] Add channel name to error logging about Slack notifications.
904-* [ENHANCEMENT] Refactoring and tests for Flowdock notifications.
905-* [ENHANCEMENT] New Dockerfile using alpine-golang-make-onbuild base image.
906-* [CLEANUP] Add Docker instructions and other cleanups in README.md.
907-* [CLEANUP] Update Makefile.COMMON from prometheus/utils.
908-
909-## 0.0.3 / 2015-06-10
910-* [BUGFIX] Fix email template body writer being called with parameters in wrong order.
911-
912-## 0.0.2 / 2015-06-09
913-
914-* [BUGFIX] Fixed silences.json permissions in Docker image.
915-* [CHANGE] Changed case of API JSON properties to initial lower letter.
916-* [CHANGE] Migrated logging to use http://github.com/prometheus/log.
917-* [FEATURE] Flowdock notification support.
918-* [FEATURE] Slack notification support.
919-* [FEATURE] Generic webhook notification support.
920-* [FEATURE] Support for "@"-mentions in HipChat notifications.
921-* [FEATURE] Path prefix option to support reverse proxies.
922-* [ENHANCEMENT] Improved web redirection and 404 behavior.
923-* [CLEANUP] Updated compiled web assets from source.
924-* [CLEANUP] Updated fsnotify package to its new source location.
925-* [CLEANUP] Updates to README.md and AUTHORS.md.
926-* [CLEANUP] Various smaller cleanups and improvements.
927diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
928deleted file mode 100644
929index af2570c..0000000
930--- a/COPYRIGHT.txt
931+++ /dev/null
932@@ -1,12 +0,0 @@
933-Copyright Prometheus Team
934-Licensed under the Apache License, Version 2.0 (the "License");
935-you may not use this file except in compliance with the License.
936-You may obtain a copy of the License at
937-
938-http://www.apache.org/licenses/LICENSE-2.0
939-
940-Unless required by applicable law or agreed to in writing, software
941-distributed under the License is distributed on an "AS IS" BASIS,
942-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
943-See the License for the specific language governing permissions and
944-limitations under the License.
945diff --git a/Dockerfile b/Dockerfile
946index 7d98d53..2b5caa0 100644
947--- a/Dockerfile
948+++ b/Dockerfile
949@@ -1,21 +1,40 @@
950-ARG ARCH="amd64"
951-ARG OS="linux"
952-FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest
953-LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
954+FROM ubuntu:hirsute AS snap-installer
955
956-ARG ARCH="amd64"
957-ARG OS="linux"
958-COPY .build/${OS}-${ARCH}/amtool /bin/amtool
959-COPY .build/${OS}-${ARCH}/alertmanager /bin/alertmanager
960-COPY examples/ha/alertmanager.yml /etc/alertmanager/alertmanager.yml
961+RUN set -eux; \
962+ apt-get update; \
963+ DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y; \
964+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
965+ jq curl ca-certificates squashfs-tools; \
966+# taken from https://snapcraft.io/docs/build-on-docker
967+# Alternatively, we can install snapd, and issue `snap download prometheus-alertmanager`
968+ curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/prometheus-alertmanager?channel=edge' | jq '.download_url' -r) --output prometheus-alertmanager.snap; \
969+ mkdir -p /snap; \
970+ unsquashfs -d /snap/prometheus-alertmanager prometheus-alertmanager.snap
971
972-RUN mkdir -p /alertmanager && \
973- chown -R nobody:nogroup etc/alertmanager /alertmanager
974+FROM ubuntu:hirsute
975
976-USER nobody
977+ENV TZ UTC
978+
979+RUN set -eux; \
980+ apt-get update; \
981+ DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y; \
982+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
983+ tzdata; \
984+ rm -rf /var/lib/apt/lists/*; \
985+ mkdir -p /alertmanager
986+
987+COPY --from=snap-installer /snap/prometheus-alertmanager/bin/alertmanager /usr/bin/alertmanager
988+COPY --from=snap-installer /snap/prometheus-alertmanager/bin/amtool /usr/bin/amtool
989+COPY --from=snap-installer /snap/prometheus-alertmanager/etc/prometheus/alertmanager.yml.example /etc/alertmanager/alertmanager.yml
990+
991+# Copy the manifest files from the snap
992+COPY --from=snap-installer /snap/prometheus-alertmanager/snap/snapcraft.yaml /usr/share/rocks/
993+COPY --from=snap-installer /snap/prometheus-alertmanager/snap/manifest.yaml /usr/share/rocks/
994+
995+# Expose port, configure volume and define the entrypoint
996 EXPOSE 9093
997 VOLUME [ "/alertmanager" ]
998 WORKDIR /alertmanager
999-ENTRYPOINT [ "/bin/alertmanager" ]
1000+ENTRYPOINT [ "/usr/bin/alertmanager" ]
1001 CMD [ "--config.file=/etc/alertmanager/alertmanager.yml", \
1002 "--storage.path=/alertmanager" ]
1003diff --git a/oci/HACKING.md b/HACKING.md
1004similarity index 97%
1005rename from oci/HACKING.md
1006rename to HACKING.md
1007index 961e1f3..bf262b4 100644
1008--- a/oci/HACKING.md
1009+++ b/HACKING.md
1010@@ -7,7 +7,7 @@ In order to contribute to the Prometheus Alertmanager OCI image do the following
1011 * Build a new image with your changes. You can use the following command:
1012
1013 ```
1014-$ docker build -t ubuntu/prometheus-alertmanager:test -f oci/Dockerfile.ubuntu .
1015+$ docker build -t ubuntu/prometheus-alertmanager:test .
1016 ```
1017
1018 * Test the new image. Run it in some way that exercise your changes, you can also check th README.md file.
1019diff --git a/LICENSE b/LICENSE
1020deleted file mode 100644
1021index 261eeb9..0000000
1022--- a/LICENSE
1023+++ /dev/null
1024@@ -1,201 +0,0 @@
1025- Apache License
1026- Version 2.0, January 2004
1027- http://www.apache.org/licenses/
1028-
1029- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1030-
1031- 1. Definitions.
1032-
1033- "License" shall mean the terms and conditions for use, reproduction,
1034- and distribution as defined by Sections 1 through 9 of this document.
1035-
1036- "Licensor" shall mean the copyright owner or entity authorized by
1037- the copyright owner that is granting the License.
1038-
1039- "Legal Entity" shall mean the union of the acting entity and all
1040- other entities that control, are controlled by, or are under common
1041- control with that entity. For the purposes of this definition,
1042- "control" means (i) the power, direct or indirect, to cause the
1043- direction or management of such entity, whether by contract or
1044- otherwise, or (ii) ownership of fifty percent (50%) or more of the
1045- outstanding shares, or (iii) beneficial ownership of such entity.
1046-
1047- "You" (or "Your") shall mean an individual or Legal Entity
1048- exercising permissions granted by this License.
1049-
1050- "Source" form shall mean the preferred form for making modifications,
1051- including but not limited to software source code, documentation
1052- source, and configuration files.
1053-
1054- "Object" form shall mean any form resulting from mechanical
1055- transformation or translation of a Source form, including but
1056- not limited to compiled object code, generated documentation,
1057- and conversions to other media types.
1058-
1059- "Work" shall mean the work of authorship, whether in Source or
1060- Object form, made available under the License, as indicated by a
1061- copyright notice that is included in or attached to the work
1062- (an example is provided in the Appendix below).
1063-
1064- "Derivative Works" shall mean any work, whether in Source or Object
1065- form, that is based on (or derived from) the Work and for which the
1066- editorial revisions, annotations, elaborations, or other modifications
1067- represent, as a whole, an original work of authorship. For the purposes
1068- of this License, Derivative Works shall not include works that remain
1069- separable from, or merely link (or bind by name) to the interfaces of,
1070- the Work and Derivative Works thereof.
1071-
1072- "Contribution" shall mean any work of authorship, including
1073- the original version of the Work and any modifications or additions
1074- to that Work or Derivative Works thereof, that is intentionally
1075- submitted to Licensor for inclusion in the Work by the copyright owner
1076- or by an individual or Legal Entity authorized to submit on behalf of
1077- the copyright owner. For the purposes of this definition, "submitted"
1078- means any form of electronic, verbal, or written communication sent
1079- to the Licensor or its representatives, including but not limited to
1080- communication on electronic mailing lists, source code control systems,
1081- and issue tracking systems that are managed by, or on behalf of, the
1082- Licensor for the purpose of discussing and improving the Work, but
1083- excluding communication that is conspicuously marked or otherwise
1084- designated in writing by the copyright owner as "Not a Contribution."
1085-
1086- "Contributor" shall mean Licensor and any individual or Legal Entity
1087- on behalf of whom a Contribution has been received by Licensor and
1088- subsequently incorporated within the Work.
1089-
1090- 2. Grant of Copyright License. Subject to the terms and conditions of
1091- this License, each Contributor hereby grants to You a perpetual,
1092- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1093- copyright license to reproduce, prepare Derivative Works of,
1094- publicly display, publicly perform, sublicense, and distribute the
1095- Work and such Derivative Works in Source or Object form.
1096-
1097- 3. Grant of Patent License. Subject to the terms and conditions of
1098- this License, each Contributor hereby grants to You a perpetual,
1099- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1100- (except as stated in this section) patent license to make, have made,
1101- use, offer to sell, sell, import, and otherwise transfer the Work,
1102- where such license applies only to those patent claims licensable
1103- by such Contributor that are necessarily infringed by their
1104- Contribution(s) alone or by combination of their Contribution(s)
1105- with the Work to which such Contribution(s) was submitted. If You
1106- institute patent litigation against any entity (including a
1107- cross-claim or counterclaim in a lawsuit) alleging that the Work
1108- or a Contribution incorporated within the Work constitutes direct
1109- or contributory patent infringement, then any patent licenses
1110- granted to You under this License for that Work shall terminate
1111- as of the date such litigation is filed.
1112-
1113- 4. Redistribution. You may reproduce and distribute copies of the
1114- Work or Derivative Works thereof in any medium, with or without
1115- modifications, and in Source or Object form, provided that You
1116- meet the following conditions:
1117-
1118- (a) You must give any other recipients of the Work or
1119- Derivative Works a copy of this License; and
1120-
1121- (b) You must cause any modified files to carry prominent notices
1122- stating that You changed the files; and
1123-
1124- (c) You must retain, in the Source form of any Derivative Works
1125- that You distribute, all copyright, patent, trademark, and
1126- attribution notices from the Source form of the Work,
1127- excluding those notices that do not pertain to any part of
1128- the Derivative Works; and
1129-
1130- (d) If the Work includes a "NOTICE" text file as part of its
1131- distribution, then any Derivative Works that You distribute must
1132- include a readable copy of the attribution notices contained
1133- within such NOTICE file, excluding those notices that do not
1134- pertain to any part of the Derivative Works, in at least one
1135- of the following places: within a NOTICE text file distributed
1136- as part of the Derivative Works; within the Source form or
1137- documentation, if provided along with the Derivative Works; or,
1138- within a display generated by the Derivative Works, if and
1139- wherever such third-party notices normally appear. The contents
1140- of the NOTICE file are for informational purposes only and
1141- do not modify the License. You may add Your own attribution
1142- notices within Derivative Works that You distribute, alongside
1143- or as an addendum to the NOTICE text from the Work, provided
1144- that such additional attribution notices cannot be construed
1145- as modifying the License.
1146-
1147- You may add Your own copyright statement to Your modifications and
1148- may provide additional or different license terms and conditions
1149- for use, reproduction, or distribution of Your modifications, or
1150- for any such Derivative Works as a whole, provided Your use,
1151- reproduction, and distribution of the Work otherwise complies with
1152- the conditions stated in this License.
1153-
1154- 5. Submission of Contributions. Unless You explicitly state otherwise,
1155- any Contribution intentionally submitted for inclusion in the Work
1156- by You to the Licensor shall be under the terms and conditions of
1157- this License, without any additional terms or conditions.
1158- Notwithstanding the above, nothing herein shall supersede or modify
1159- the terms of any separate license agreement you may have executed
1160- with Licensor regarding such Contributions.
1161-
1162- 6. Trademarks. This License does not grant permission to use the trade
1163- names, trademarks, service marks, or product names of the Licensor,
1164- except as required for reasonable and customary use in describing the
1165- origin of the Work and reproducing the content of the NOTICE file.
1166-
1167- 7. Disclaimer of Warranty. Unless required by applicable law or
1168- agreed to in writing, Licensor provides the Work (and each
1169- Contributor provides its Contributions) on an "AS IS" BASIS,
1170- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1171- implied, including, without limitation, any warranties or conditions
1172- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
1173- PARTICULAR PURPOSE. You are solely responsible for determining the
1174- appropriateness of using or redistributing the Work and assume any
1175- risks associated with Your exercise of permissions under this License.
1176-
1177- 8. Limitation of Liability. In no event and under no legal theory,
1178- whether in tort (including negligence), contract, or otherwise,
1179- unless required by applicable law (such as deliberate and grossly
1180- negligent acts) or agreed to in writing, shall any Contributor be
1181- liable to You for damages, including any direct, indirect, special,
1182- incidental, or consequential damages of any character arising as a
1183- result of this License or out of the use or inability to use the
1184- Work (including but not limited to damages for loss of goodwill,
1185- work stoppage, computer failure or malfunction, or any and all
1186- other commercial damages or losses), even if such Contributor
1187- has been advised of the possibility of such damages.
1188-
1189- 9. Accepting Warranty or Additional Liability. While redistributing
1190- the Work or Derivative Works thereof, You may choose to offer,
1191- and charge a fee for, acceptance of support, warranty, indemnity,
1192- or other liability obligations and/or rights consistent with this
1193- License. However, in accepting such obligations, You may act only
1194- on Your own behalf and on Your sole responsibility, not on behalf
1195- of any other Contributor, and only if You agree to indemnify,
1196- defend, and hold each Contributor harmless for any liability
1197- incurred by, or claims asserted against, such Contributor by reason
1198- of your accepting any such warranty or additional liability.
1199-
1200- END OF TERMS AND CONDITIONS
1201-
1202- APPENDIX: How to apply the Apache License to your work.
1203-
1204- To apply the Apache License to your work, attach the following
1205- boilerplate notice, with the fields enclosed by brackets "[]"
1206- replaced with your own identifying information. (Don't include
1207- the brackets!) The text should be enclosed in the appropriate
1208- comment syntax for the file format. We also recommend that a
1209- file or class name and description of purpose be included on the
1210- same "printed page" as the copyright notice for easier
1211- identification within third-party archives.
1212-
1213- Copyright [yyyy] [name of copyright owner]
1214-
1215- Licensed under the Apache License, Version 2.0 (the "License");
1216- you may not use this file except in compliance with the License.
1217- You may obtain a copy of the License at
1218-
1219- http://www.apache.org/licenses/LICENSE-2.0
1220-
1221- Unless required by applicable law or agreed to in writing, software
1222- distributed under the License is distributed on an "AS IS" BASIS,
1223- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1224- See the License for the specific language governing permissions and
1225- limitations under the License.
1226diff --git a/MAINTAINERS.md b/MAINTAINERS.md
1227deleted file mode 100644
1228index e45f5e2..0000000
1229--- a/MAINTAINERS.md
1230+++ /dev/null
1231@@ -1 +0,0 @@
1232-* Simon Pasquier <pasquier.simon@gmail.com>
1233diff --git a/Makefile b/Makefile
1234index 3611908..b0bede0 100644
1235--- a/Makefile
1236+++ b/Makefile
1237@@ -1,56 +1,26 @@
1238-# Copyright 2015 The Prometheus Authors
1239-# Licensed under the Apache License, Version 2.0 (the "License");
1240-# you may not use this file except in compliance with the License.
1241-# You may obtain a copy of the License at
1242-#
1243-# http://www.apache.org/licenses/LICENSE-2.0
1244-#
1245-# Unless required by applicable law or agreed to in writing, software
1246-# distributed under the License is distributed on an "AS IS" BASIS,
1247-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1248-# See the License for the specific language governing permissions and
1249-# limitations under the License.
1250-
1251-# Needs to be defined before including Makefile.common to auto-generate targets
1252-DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le s390x
1253-
1254-include Makefile.common
1255-
1256-FRONTEND_DIR = $(BIN_DIR)/ui/app
1257-DOCKER_IMAGE_NAME ?= alertmanager
1258-
1259-STATICCHECK_IGNORE =
1260-
1261-.PHONY: build-all
1262-# Will build both the front-end as well as the back-end
1263-build-all: assets apiv2 build
1264-
1265-.PHONY: assets
1266-assets: asset/assets_vfsdata.go
1267-
1268-asset/assets_vfsdata.go: ui/app/script.js ui/app/index.html ui/app/lib template/default.tmpl
1269- GO111MODULE=$(GO111MODULE) $(GO) generate $(GOOPTS) ./asset
1270- @$(GOFMT) -w ./asset
1271-
1272-ui/app/script.js: $(shell find ui/app/src -iname *.elm) api/v2/openapi.yaml
1273- cd $(FRONTEND_DIR) && $(MAKE) script.js
1274-
1275-.PHONY: apiv2
1276-apiv2: api/v2/models api/v2/restapi api/v2/client
1277-
1278-SWAGGER = docker run \
1279- --user=$(shell id -u $(USER)):$(shell id -g $(USER)) \
1280- --rm \
1281- -v $(shell pwd):/go/src/github.com/prometheus/alertmanager \
1282- -w /go/src/github.com/prometheus/alertmanager quay.io/goswagger/swagger:v0.20.1
1283-
1284-api/v2/models api/v2/restapi api/v2/client: api/v2/openapi.yaml
1285- -rm -r api/v2/{client,models,restapi}
1286- $(SWAGGER) generate server -f api/v2/openapi.yaml --copyright-file=COPYRIGHT.txt --exclude-main -A alertmanager --target api/v2/
1287- $(SWAGGER) generate client -f api/v2/openapi.yaml --copyright-file=COPYRIGHT.txt --skip-models --target api/v2
1288-
1289-.PHONY: clean
1290-clean:
1291- - @rm -rf asset/assets_vfsdata.go \
1292- api/v2/models api/v2/restapi api/v2/client
1293- - @cd $(FRONTEND_DIR) && $(MAKE) clean
1294+RENDERDOWN ?= ../RenderDown/renderdown.py
1295+PYTHON ?= python3
1296+
1297+README_TEMPLATE = templates/README_DOCKERHUB.md
1298+
1299+HACKING_TEMPLATE = templates/HACKING_upstream.md
1300+
1301+all: all-doc
1302+
1303+all-doc: clean-doc readme
1304+
1305+get-templates:
1306+ wget -nv -e "robots=off" -nd -r -np -P templates https://git.launchpad.net/~canonical-server/ubuntu-docker-images/+git/templates/plain/templates/
1307+
1308+readme: get-templates
1309+ mv -v $(README_TEMPLATE) templates/README.md
1310+ mv -v $(HACKING_TEMPLATE) templates/HACKING.md
1311+ $(PYTHON) $(RENDERDOWN) templates/README.md > README.md
1312+ $(PYTHON) $(RENDERDOWN) templates/HACKING.md > HACKING.md
1313+
1314+clean: clean-doc
1315+
1316+clean-doc:
1317+ rm -frv templates/ README.md
1318+
1319+.PHONY: readme clean clean-doc all all-doc get-templates
1320diff --git a/Makefile.common b/Makefile.common
1321deleted file mode 100644
1322index b1dcf89..0000000
1323--- a/Makefile.common
1324+++ /dev/null
1325@@ -1,296 +0,0 @@
1326-# Copyright 2018 The Prometheus Authors
1327-# Licensed under the Apache License, Version 2.0 (the "License");
1328-# you may not use this file except in compliance with the License.
1329-# You may obtain a copy of the License at
1330-#
1331-# http://www.apache.org/licenses/LICENSE-2.0
1332-#
1333-# Unless required by applicable law or agreed to in writing, software
1334-# distributed under the License is distributed on an "AS IS" BASIS,
1335-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1336-# See the License for the specific language governing permissions and
1337-# limitations under the License.
1338-
1339-
1340-# A common Makefile that includes rules to be reused in different prometheus projects.
1341-# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository!
1342-
1343-# Example usage :
1344-# Create the main Makefile in the root project directory.
1345-# include Makefile.common
1346-# customTarget:
1347-# @echo ">> Running customTarget"
1348-#
1349-
1350-# Ensure GOBIN is not set during build so that promu is installed to the correct path
1351-unexport GOBIN
1352-
1353-GO ?= go
1354-GOFMT ?= $(GO)fmt
1355-FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
1356-GOOPTS ?=
1357-GOHOSTOS ?= $(shell $(GO) env GOHOSTOS)
1358-GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH)
1359-
1360-GO_VERSION ?= $(shell $(GO) version)
1361-GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
1362-PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
1363-
1364-GOVENDOR :=
1365-GO111MODULE :=
1366-ifeq (, $(PRE_GO_111))
1367- ifneq (,$(wildcard go.mod))
1368- # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI).
1369- GO111MODULE := on
1370-
1371- ifneq (,$(wildcard vendor))
1372- # Always use the local vendor/ directory to satisfy the dependencies.
1373- GOOPTS := $(GOOPTS) -mod=vendor
1374- endif
1375- endif
1376-else
1377- ifneq (,$(wildcard go.mod))
1378- ifneq (,$(wildcard vendor))
1379-$(warning This repository requires Go >= 1.11 because of Go modules)
1380-$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)')
1381- endif
1382- else
1383- # This repository isn't using Go modules (yet).
1384- GOVENDOR := $(FIRST_GOPATH)/bin/govendor
1385- endif
1386-endif
1387-PROMU := $(FIRST_GOPATH)/bin/promu
1388-pkgs = ./...
1389-
1390-ifeq (arm, $(GOHOSTARCH))
1391- GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM)
1392- GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM)
1393-else
1394- GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
1395-endif
1396-
1397-GOTEST := $(GO) test
1398-GOTEST_DIR :=
1399-ifneq ($(CIRCLE_JOB),)
1400-ifneq ($(shell which gotestsum),)
1401- GOTEST_DIR := test-results
1402- GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
1403-endif
1404-endif
1405-
1406-PROMU_VERSION ?= 0.5.0
1407-
1408-GOLANGCI_LINT :=
1409-GOLANGCI_LINT_OPTS ?=
1410-GOLANGCI_LINT_VERSION ?= v1.18.0
1411-# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
1412-# windows isn't included here because of the path separator being different.
1413-ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
1414- ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386))
1415- GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
1416- endif
1417-endif
1418-
1419-PREFIX ?= $(shell pwd)
1420-BIN_DIR ?= $(shell pwd)
1421-DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
1422-DOCKERFILE_PATH ?= ./Dockerfile
1423-DOCKERBUILD_CONTEXT ?= ./
1424-DOCKER_REPO ?= prom
1425-
1426-DOCKER_ARCHS ?= amd64
1427-
1428-BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
1429-PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
1430-TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS))
1431-
1432-ifeq ($(GOHOSTARCH),amd64)
1433- ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows))
1434- # Only supported on amd64
1435- test-flags := -race
1436- endif
1437-endif
1438-
1439-# This rule is used to forward a target like "build" to "common-build". This
1440-# allows a new "build" target to be defined in a Makefile which includes this
1441-# one and override "common-build" without override warnings.
1442-%: common-% ;
1443-
1444-.PHONY: common-all
1445-common-all: precheck style check_license lint unused build test
1446-
1447-.PHONY: common-style
1448-common-style:
1449- @echo ">> checking code style"
1450- @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
1451- if [ -n "$${fmtRes}" ]; then \
1452- echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
1453- echo "Please ensure you are using $$($(GO) version) for formatting code."; \
1454- exit 1; \
1455- fi
1456-
1457-.PHONY: common-check_license
1458-common-check_license:
1459- @echo ">> checking license header"
1460- @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
1461- awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
1462- done); \
1463- if [ -n "$${licRes}" ]; then \
1464- echo "license header checking failed:"; echo "$${licRes}"; \
1465- exit 1; \
1466- fi
1467-
1468-.PHONY: common-deps
1469-common-deps:
1470- @echo ">> getting dependencies"
1471-ifdef GO111MODULE
1472- GO111MODULE=$(GO111MODULE) $(GO) mod download
1473-else
1474- $(GO) get $(GOOPTS) -t ./...
1475-endif
1476-
1477-.PHONY: update-go-deps
1478-update-go-deps:
1479- @echo ">> updating Go dependencies"
1480- @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
1481- $(GO) get $$m; \
1482- done
1483- GO111MODULE=$(GO111MODULE) $(GO) mod tidy
1484-ifneq (,$(wildcard vendor))
1485- GO111MODULE=$(GO111MODULE) $(GO) mod vendor
1486-endif
1487-
1488-.PHONY: common-test-short
1489-common-test-short: $(GOTEST_DIR)
1490- @echo ">> running short tests"
1491- GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
1492-
1493-.PHONY: common-test
1494-common-test: $(GOTEST_DIR)
1495- @echo ">> running all tests"
1496- GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
1497-
1498-$(GOTEST_DIR):
1499- @mkdir -p $@
1500-
1501-.PHONY: common-format
1502-common-format:
1503- @echo ">> formatting code"
1504- GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs)
1505-
1506-.PHONY: common-vet
1507-common-vet:
1508- @echo ">> vetting code"
1509- GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
1510-
1511-.PHONY: common-lint
1512-common-lint: $(GOLANGCI_LINT)
1513-ifdef GOLANGCI_LINT
1514- @echo ">> running golangci-lint"
1515-ifdef GO111MODULE
1516-# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
1517-# Otherwise staticcheck might fail randomly for some reason not yet explained.
1518- GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
1519- GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
1520-else
1521- $(GOLANGCI_LINT) run $(pkgs)
1522-endif
1523-endif
1524-
1525-# For backward-compatibility.
1526-.PHONY: common-staticcheck
1527-common-staticcheck: lint
1528-
1529-.PHONY: common-unused
1530-common-unused: $(GOVENDOR)
1531-ifdef GOVENDOR
1532- @echo ">> running check for unused packages"
1533- @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
1534-else
1535-ifdef GO111MODULE
1536- @echo ">> running check for unused/missing packages in go.mod"
1537- GO111MODULE=$(GO111MODULE) $(GO) mod tidy
1538-ifeq (,$(wildcard vendor))
1539- @git diff --exit-code -- go.sum go.mod
1540-else
1541- @echo ">> running check for unused packages in vendor/"
1542- GO111MODULE=$(GO111MODULE) $(GO) mod vendor
1543- @git diff --exit-code -- go.sum go.mod vendor/
1544-endif
1545-endif
1546-endif
1547-
1548-.PHONY: common-build
1549-common-build: promu
1550- @echo ">> building binaries"
1551- GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
1552-
1553-.PHONY: common-tarball
1554-common-tarball: promu
1555- @echo ">> building release tarball"
1556- $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
1557-
1558-.PHONY: common-docker $(BUILD_DOCKER_ARCHS)
1559-common-docker: $(BUILD_DOCKER_ARCHS)
1560-$(BUILD_DOCKER_ARCHS): common-docker-%:
1561- docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
1562- -f $(DOCKERFILE_PATH) \
1563- --build-arg ARCH="$*" \
1564- --build-arg OS="linux" \
1565- $(DOCKERBUILD_CONTEXT)
1566-
1567-.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
1568-common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
1569-$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
1570- docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
1571-
1572-.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
1573-common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
1574-$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
1575- docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
1576-
1577-.PHONY: common-docker-manifest
1578-common-docker-manifest:
1579- DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG))
1580- DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)"
1581-
1582-.PHONY: promu
1583-promu: $(PROMU)
1584-
1585-$(PROMU):
1586- mkdir -p $(FIRST_GOPATH)/bin
1587- cp oci/promu/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu
1588-
1589-.PHONY: proto
1590-proto:
1591- @echo ">> generating code from proto files"
1592- @./scripts/genproto.sh
1593-
1594-ifdef GOLANGCI_LINT
1595-$(GOLANGCI_LINT):
1596- mkdir -p $(FIRST_GOPATH)/bin
1597- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \
1598- | sed -e '/install -d/d' \
1599- | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
1600-endif
1601-
1602-ifdef GOVENDOR
1603-.PHONY: $(GOVENDOR)
1604-$(GOVENDOR):
1605- GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
1606-endif
1607-
1608-.PHONY: precheck
1609-precheck::
1610-
1611-define PRECHECK_COMMAND_template =
1612-precheck:: $(1)_precheck
1613-
1614-PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1)))
1615-.PHONY: $(1)_precheck
1616-$(1)_precheck:
1617- @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \
1618- echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \
1619- exit 1; \
1620- fi
1621-endef
1622diff --git a/NOTICE b/NOTICE
1623deleted file mode 100644
1624index f5d0bbb..0000000
1625--- a/NOTICE
1626+++ /dev/null
1627@@ -1,18 +0,0 @@
1628-Prometheus Alertmanager
1629-Copyright 2013-2015 The Prometheus Authors
1630-
1631-This product includes software developed at
1632-SoundCloud Ltd. (http://soundcloud.com/).
1633-
1634-
1635-The following components are included in this product:
1636-
1637-Bootstrap
1638-http://getbootstrap.com
1639-Copyright 2011-2014 Twitter, Inc.
1640-Licensed under the MIT License
1641-
1642-bootstrap-datetimepicker.js
1643-http://www.eyecon.ro/bootstrap-datepicker
1644-Copyright 2012 Stefan Petre
1645-Licensed under the Apache License, Version 2.0
1646diff --git a/Procfile b/Procfile
1647deleted file mode 100644
1648index ab15cfc..0000000
1649--- a/Procfile
1650+++ /dev/null
1651@@ -1,5 +0,0 @@
1652-a1: ./alertmanager --log.level=debug --storage.path=$TMPDIR/a1 --web.listen-address=:9093 --cluster.listen-address=127.0.0.1:8001 --config.file=examples/ha/alertmanager.yml
1653-a2: ./alertmanager --log.level=debug --storage.path=$TMPDIR/a2 --web.listen-address=:9094 --cluster.listen-address=127.0.0.1:8002 --cluster.peer=127.0.0.1:8001 --config.file=examples/ha/alertmanager.yml
1654-a3: ./alertmanager --log.level=debug --storage.path=$TMPDIR/a3 --web.listen-address=:9095 --cluster.listen-address=127.0.0.1:8003 --cluster.peer=127.0.0.1:8001 --config.file=examples/ha/alertmanager.yml
1655-wh: go run ./examples/webhook/echo.go
1656-
1657diff --git a/README.md b/README.md
1658index c7a3683..021157d 100644
1659--- a/README.md
1660+++ b/README.md
1661@@ -1,406 +1,89 @@
1662-# Alertmanager [![CircleCI](https://circleci.com/gh/prometheus/alertmanager/tree/master.svg?style=shield)][circleci]
1663+# Prometheus Alertmanager | Ubuntu
1664
1665-[![Docker Repository on Quay](https://quay.io/repository/prometheus/alertmanager/status "Docker Repository on Quay")][quay]
1666-[![Docker Pulls](https://img.shields.io/docker/pulls/prom/alertmanager.svg?maxAge=604800)][hub]
1667+Current Prometheus Alertmanager Docker Image from Ubuntu. Receives security updates and rolls to newer Prometheus Alertmanager or Ubuntu LTS. This repository is exempted from per-user rate limits. For [LTS Docker Image](https://ubuntu.com/security/docker-images) versions of this image, see `lts/prometheus-alertmanager`.
1668
1669-The Alertmanager handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integrations such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts.
1670
1671-* [Documentation](http://prometheus.io/docs/alerting/alertmanager/)
1672+## About Prometheus Alertmanager
1673
1674-## Install
1675+The Alertmanager handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts. Read more on the [Prometheus Alertmanager website](https://prometheus.io/docs/alerting/latest/alertmanager/).
1676
1677-There are various ways of installing Alertmanager.
1678
1679-### Precompiled binaries
1680+## Tags and Architectures
1681+![LTS](https://assets.ubuntu.com/v1/0a5ff561-LTS%402x.png?h=17)
1682+Up to 5 years free security maintenance `from lts/prometheus-alertmanager`.
1683
1684-Precompiled binaries for released versions are available in the
1685-[*download* section](https://prometheus.io/download/)
1686-on [prometheus.io](https://prometheus.io). Using the latest production release binary
1687-is the recommended way of installing Alertmanager.
1688+![ESM](https://assets.ubuntu.com/v1/572f3fbd-ESM%402x.png?h=17)
1689+Up to 10 years customer security maintenance `from store/canonical/prometheus-alertmanager`.
1690
1691-### Docker images
1692+_Tags in italics are not available in ubuntu/prometheus-alertmanager but are shown here for completeness._
1693
1694-Docker images are available on [Quay.io](https://quay.io/repository/prometheus/alertmanager).
1695+| Channel Tag | | | Currently | Architectures |
1696+|---|---|---|---|---|
1697+| **`0.21-21.04_beta`** &nbsp;&nbsp; | | | Prometheus Alertmanager 0.21.0 on Ubuntu 21.04 | `amd64`, `arm64`, `ppc64el`, `s390x` |
1698+| _`track_risk`_ |
1699
1700-### Compiling the binary
1701+Channel tag shows the most stable channel for that track ordered `stable`, `candidate`, `beta`, `edge`. More risky channels are always implicitly available. So if `beta` is listed, you can also pull `edge`. If `candidate` is listed, you can pull `beta` and `edge`. When `stable` is listed, all four are available. Images are guaranteed to progress through the sequence `edge`, `beta`, `candidate` before `stable`.
1702
1703-You can either `go get` it:
1704
1705-```
1706-$ GO15VENDOREXPERIMENT=1 go get github.com/prometheus/alertmanager/cmd/...
1707-# cd $GOPATH/src/github.com/prometheus/alertmanager
1708-$ alertmanager --config.file=<your_file>
1709-```
1710-
1711-Or clone the repository and build manually:
1712-
1713-```
1714-$ mkdir -p $GOPATH/src/github.com/prometheus
1715-$ cd $GOPATH/src/github.com/prometheus
1716-$ git clone https://github.com/prometheus/alertmanager.git
1717-$ cd alertmanager
1718-$ make build
1719-$ ./alertmanager --config.file=<your_file>
1720-```
1721-
1722-You can also build just one of the binaries in this repo by passing a name to the build function:
1723-```
1724-$ make build BINARIES=amtool
1725-```
1726-
1727-## Example
1728-
1729-This is an example configuration that should cover most relevant aspects of the new YAML configuration format. The full documentation of the configuration can be found [here](https://prometheus.io/docs/alerting/configuration/).
1730-
1731-```yaml
1732-global:
1733- # The smarthost and SMTP sender used for mail notifications.
1734- smtp_smarthost: 'localhost:25'
1735- smtp_from: 'alertmanager@example.org'
1736-
1737-# The root route on which each incoming alert enters.
1738-route:
1739- # The root route must not have any matchers as it is the entry point for
1740- # all alerts. It needs to have a receiver configured so alerts that do not
1741- # match any of the sub-routes are sent to someone.
1742- receiver: 'team-X-mails'
1743-
1744- # The labels by which incoming alerts are grouped together. For example,
1745- # multiple alerts coming in for cluster=A and alertname=LatencyHigh would
1746- # be batched into a single group.
1747- #
1748- # To aggregate by all possible labels use '...' as the sole label name.
1749- # This effectively disables aggregation entirely, passing through all
1750- # alerts as-is. This is unlikely to be what you want, unless you have
1751- # a very low alert volume or your upstream notification system performs
1752- # its own grouping. Example: group_by: [...]
1753- group_by: ['alertname', 'cluster']
1754-
1755- # When a new group of alerts is created by an incoming alert, wait at
1756- # least 'group_wait' to send the initial notification.
1757- # This way ensures that you get multiple alerts for the same group that start
1758- # firing shortly after another are batched together on the first
1759- # notification.
1760- group_wait: 30s
1761-
1762- # When the first notification was sent, wait 'group_interval' to send a batch
1763- # of new alerts that started firing for that group.
1764- group_interval: 5m
1765-
1766- # If an alert has successfully been sent, wait 'repeat_interval' to
1767- # resend them.
1768- repeat_interval: 3h
1769-
1770- # All the above attributes are inherited by all child routes and can
1771- # overwritten on each.
1772-
1773- # The child route trees.
1774- routes:
1775- # This routes performs a regular expression match on alert labels to
1776- # catch alerts that are related to a list of services.
1777- - match_re:
1778- service: ^(foo1|foo2|baz)$
1779- receiver: team-X-mails
1780-
1781- # The service has a sub-route for critical alerts, any alerts
1782- # that do not match, i.e. severity != critical, fall-back to the
1783- # parent node and are sent to 'team-X-mails'
1784- routes:
1785- - match:
1786- severity: critical
1787- receiver: team-X-pager
1788-
1789- - match:
1790- service: files
1791- receiver: team-Y-mails
1792-
1793- routes:
1794- - match:
1795- severity: critical
1796- receiver: team-Y-pager
1797-
1798- # This route handles all alerts coming from a database service. If there's
1799- # no team to handle it, it defaults to the DB team.
1800- - match:
1801- service: database
1802-
1803- receiver: team-DB-pager
1804- # Also group alerts by affected database.
1805- group_by: [alertname, cluster, database]
1806-
1807- routes:
1808- - match:
1809- owner: team-X
1810- receiver: team-X-pager
1811-
1812- - match:
1813- owner: team-Y
1814- receiver: team-Y-pager
1815-
1816-
1817-# Inhibition rules allow to mute a set of alerts given that another alert is
1818-# firing.
1819-# We use this to mute any warning-level notifications if the same alert is
1820-# already critical.
1821-inhibit_rules:
1822-- source_match:
1823- severity: 'critical'
1824- target_match:
1825- severity: 'warning'
1826- # Apply inhibition if the alertname is the same.
1827- # CAUTION:
1828- # If all label names listed in `equal` are missing
1829- # from both the source and target alerts,
1830- # the inhibition rule will apply!
1831- equal: ['alertname']
1832-
1833-
1834-receivers:
1835-- name: 'team-X-mails'
1836- email_configs:
1837- - to: 'team-X+alerts@example.org, team-Y+alerts@example.org'
1838-
1839-- name: 'team-X-pager'
1840- email_configs:
1841- - to: 'team-X+alerts-critical@example.org'
1842- pagerduty_configs:
1843- - routing_key: <team-X-key>
1844-
1845-- name: 'team-Y-mails'
1846- email_configs:
1847- - to: 'team-Y+alerts@example.org'
1848-
1849-- name: 'team-Y-pager'
1850- pagerduty_configs:
1851- - routing_key: <team-Y-key>
1852-
1853-- name: 'team-DB-pager'
1854- pagerduty_configs:
1855- - routing_key: <team-DB-key>
1856-```
1857-
1858-## API
1859-
1860-The current Alertmanager API is version 2. This API is fully generated via the
1861-[OpenAPI project](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md)
1862-and [Go Swagger](https://github.com/go-swagger/go-swagger/) with the exception
1863-of the HTTP handlers themselves. The API specification can be found in
1864-[api/v2/openapi.yaml](api/v2/openapi.yaml). A HTML rendered version can be
1865-accessed [here](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/prometheus/alertmanager/master/api/v2/openapi.yaml).
1866-Clients can be easily generated via any OpenAPI generator for all major languages.
1867+## Usage
1868
1869-With the default config, endpoints are accessed under a `/api/v1` or `/api/v2` prefix.
1870-The v2 `/status` endpoint would be `/api/v2/status`. If `--web.route-prefix` is set then API routes are
1871-prefixed with that as well, so `--web.route-prefix=/alertmanager/` would
1872-relate to `/alertmanager/api/v2/status`.
1873-
1874-_API v2 is still under heavy development and thereby subject to change._
1875-
1876-## amtool
1877-
1878-`amtool` is a cli tool for interacting with the Alertmanager API. It is bundled with all releases of Alertmanager.
1879-
1880-### Install
1881-
1882-Alternatively you can install with:
1883-```
1884-go get github.com/prometheus/alertmanager/cmd/amtool
1885-```
1886-
1887-### Examples
1888-
1889-View all currently firing alerts:
1890-```
1891-$ amtool alert
1892-Alertname Starts At Summary
1893-Test_Alert 2017-08-02 18:30:18 UTC This is a testing alert!
1894-Test_Alert 2017-08-02 18:30:18 UTC This is a testing alert!
1895-Check_Foo_Fails 2017-08-02 18:30:18 UTC This is a testing alert!
1896-Check_Foo_Fails 2017-08-02 18:30:18 UTC This is a testing alert!
1897-```
1898-
1899-View all currently firing alerts with extended output:
1900-```
1901-$ amtool -o extended alert
1902-Labels Annotations Starts At Ends At Generator URL
1903-alertname="Test_Alert" instance="node0" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1904-alertname="Test_Alert" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1905-alertname="Check_Foo_Fails" instance="node0" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1906-alertname="Check_Foo_Fails" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1907-```
1908+Launch this image locally:
1909
1910-In addition to viewing alerts, you can use the rich query syntax provided by Alertmanager:
1911-```
1912-$ amtool -o extended alert query alertname="Test_Alert"
1913-Labels Annotations Starts At Ends At Generator URL
1914-alertname="Test_Alert" instance="node0" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1915-alertname="Test_Alert" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1916-
1917-$ amtool -o extended alert query instance=~".+1"
1918-Labels Annotations Starts At Ends At Generator URL
1919-alertname="Test_Alert" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1920-alertname="Check_Foo_Fails" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1921-
1922-$ amtool -o extended alert query alertname=~"Test.*" instance=~".+1"
1923-Labels Annotations Starts At Ends At Generator URL
1924-alertname="Test_Alert" instance="node1" link="https://example.com" summary="This is a testing alert!" 2017-08-02 18:31:24 UTC 0001-01-01 00:00:00 UTC http://my.testing.script.local
1925+```sh
1926+docker run -d --name prometheus-alertmanager-container -e TZ=UTC -p 30093:9093 ubuntu/prometheus-alertmanager:0.21-21.04_beta
1927 ```
1928+Access your Prometheus Alertmanager server at `localhost:30093`.
1929
1930-Silence an alert:
1931-```
1932-$ amtool silence add alertname=Test_Alert
1933-b3ede22e-ca14-4aa0-932c-ca2f3445f926
1934+#### Parameters
1935
1936-$ amtool silence add alertname="Test_Alert" instance=~".+0"
1937-e48cb58a-0b17-49ba-b734-3585139b1d25
1938-```
1939+| Parameter | Description |
1940+|---|---|
1941+| `-e TZ=UTC` | Timezone. |
1942+| `-p 30093:9093` | Expose Prometheus Alertmanager on `localhost:30093`. |
1943+| `-v /path/to/alertmanager.yml:/etc/prometheus/alertmanager.yml` | Local [configuration file](https://www.prometheus.io/docs/alerting/latest/configuration/) `alertmanager.yml` (try [this example](https://git.launchpad.net/~canonical-server/ubuntu-docker-images/+git/prometheus-alertmanager/plain/examples/config/alertmanager.yml?h=0.21-21.04)). |
1944+| `-v /path/to/persisted/data:/alertmanager` | Persist data instead of initializing a new database for each newly launched container. **Important note**: the directory you will be using to persist the data needs to belong to `nogroup:nobody`. You can run `chown nogroup:nobody <path_to_persist_data>` before launching your container. |
1945
1946-View silences:
1947-```
1948-$ amtool silence query
1949-ID Matchers Ends At Created By Comment
1950-b3ede22e-ca14-4aa0-932c-ca2f3445f926 alertname=Test_Alert 2017-08-02 19:54:50 UTC kellel
1951
1952-$ amtool silence query instance=~".+0"
1953-ID Matchers Ends At Created By Comment
1954-e48cb58a-0b17-49ba-b734-3585139b1d25 alertname=Test_Alert instance=~.+0 2017-08-02 22:41:39 UTC kellel
1955-```
1956+#### Testing/Debugging
1957
1958-Expire a silence:
1959-```
1960-$ amtool silence expire b3ede22e-ca14-4aa0-932c-ca2f3445f926
1961-```
1962+To debug the container:
1963
1964-Expire all silences matching a query:
1965+```sh
1966+docker logs -f prometheus-alertmanager-container
1967 ```
1968-$ amtool silence query instance=~".+0"
1969-ID Matchers Ends At Created By Comment
1970-e48cb58a-0b17-49ba-b734-3585139b1d25 alertname=Test_Alert instance=~.+0 2017-08-02 22:41:39 UTC kellel
1971-
1972-$ amtool silence expire $(amtool silence -q query instance=~".+0")
1973
1974-$ amtool silence query instance=~".+0"
1975+To get an interactive shell:
1976
1977+```sh
1978+docker exec -it prometheus-alertmanager-container /bin/bash
1979 ```
1980
1981-Expire all silences:
1982-```
1983-$ amtool silence expire $(amtool silence query -q)
1984-```
1985
1986-### Configuration
1987+## Deploy with Kubernetes
1988
1989-`amtool` allows a configuration file to specify some options for convenience. The default configuration file paths are `$HOME/.config/amtool/config.yml` or `/etc/amtool/config.yml`
1990+Works with any Kubernetes; if you don't have one, we recommend you [install MicroK8s](https://microk8s.io/) and `microk8s.enable dns storage` then `snap alias microk8s.kubectl kubectl`.
1991
1992-An example configuration file might look like the following:
1993+Download
1994+[alertmanager.yml](https://git.launchpad.net/~canonical-server/ubuntu-docker-images/+git/prometheus-alertmanager/plain/examples/config/alertmanager.yml?h=0.21-20.04) and
1995+[prometheus-alertmanager-deployment.yml](https://git.launchpad.net/~canonical-server/ubuntu-docker-images/+git/prometheus-alertmanager/plain/examples/alertmanager-deployment.yml?h=0.21-21.04) and set `containers.prometheus-alertmanager.image` in `prometheus-alertmanager-deployment.yml` to your chosen channel tag (e.g. `ubuntu/prometheus-alertmanager:0.21-21.04_beta`), then:
1996
1997+```sh
1998+kubectl create configmap prometheus-alertmanager-config --from-file=alertmanager=alertmanager.yml
1999+kubectl apply -f prometheus-alertmanager-deployment.yml
2000 ```
2001-# Define the path that `amtool` can find your `alertmanager` instance
2002-alertmanager.url: "http://localhost:9093"
2003-
2004-# Override the default author. (unset defaults to your username)
2005-author: me@example.com
2006-
2007-# Force amtool to give you an error if you don't include a comment on a silence
2008-comment_required: true
2009
2010-# Set a default output format. (unset defaults to simple)
2011-output: extended
2012+You will now be able to connect to the Prometheus Alertmanager server on `localhost:30093`.
2013
2014-# Set a default receiver
2015-receiver: team-X-pager
2016-```
2017-
2018-### Routes
2019+## Bugs and feature requests
2020
2021-`amtool` allows you to visualize the routes of your configuration in form of text tree view.
2022-Also you can use it to test the routing by passing it label set of an alert
2023-and it prints out all receivers the alert would match ordered and separated by `,`.
2024-(If you use `--verify.receivers` amtool returns error code 1 on mismatch)
2025+If you find a bug in our image or want to request a specific feature, please file a bug here:
2026
2027-Example of usage:
2028-```
2029-# View routing tree of remote Alertmanager
2030-$ amtool config routes --alertmanager.url=http://localhost:9090
2031+[https://bugs.launchpad.net/ubuntu-docker-images/+filebug](https://bugs.launchpad.net/ubuntu-docker-images/+filebug)
2032
2033-# Test if alert matches expected receiver
2034-$ amtool config routes test --config.file=doc/examples/simple.yml --tree --verify.receivers=team-X-pager service=database owner=team-X
2035-```
2036+Please title the bug "`prometheus-alertmanager: <issue summary>`". Make sure to include the digest of the image you are using, from:
2037
2038-## High Availability
2039-
2040-Alertmanager's high availability is in production use at many companies and is enabled by default.
2041-
2042-> Important: Both UDP and TCP are needed in alertmanager 0.15 and higher for the cluster to work.
2043-> - If you are using a firewall, make sure to whitelist the clustering port for both protocols.
2044-> - If you are running in a container, make sure to expose the clustering port for both protocols.
2045-
2046-To create a highly available cluster of the Alertmanager the instances need to
2047-be configured to communicate with each other. This is configured using the
2048-`--cluster.*` flags.
2049-
2050-- `--cluster.listen-address` string: cluster listen address (default "0.0.0.0:9094"; empty string disables HA mode)
2051-- `--cluster.advertise-address` string: cluster advertise address
2052-- `--cluster.peer` value: initial peers (repeat flag for each additional peer)
2053-- `--cluster.peer-timeout` value: peer timeout period (default "15s")
2054-- `--cluster.gossip-interval` value: cluster message propagation speed
2055- (default "200ms")
2056-- `--cluster.pushpull-interval` value: lower values will increase
2057- convergence speeds at expense of bandwidth (default "1m0s")
2058-- `--cluster.settle-timeout` value: maximum time to wait for cluster
2059- connections to settle before evaluating notifications.
2060-- `--cluster.tcp-timeout` value: timeout value for tcp connections, reads and writes (default "10s")
2061-- `--cluster.probe-timeout` value: time to wait for ack before marking node unhealthy
2062- (default "500ms")
2063-- `--cluster.probe-interval` value: interval between random node probes (default "1s")
2064-- `--cluster.reconnect-interval` value: interval between attempting to reconnect to lost peers (default "10s")
2065-- `--cluster.reconnect-timeout` value: length of time to attempt to reconnect to a lost peer (default: "6h0m0s")
2066-
2067-The chosen port in the `cluster.listen-address` flag is the port that needs to be
2068-specified in the `cluster.peer` flag of the other peers.
2069-
2070-The `cluster.advertise-address` flag is required if the instance doesn't have
2071-an IP address that is part of [RFC 6980](https://tools.ietf.org/html/rfc6890)
2072-with a default route.
2073-
2074-To start a cluster of three peers on your local machine use [`goreman`](https://github.com/mattn/goreman) and the
2075-Procfile within this repository.
2076-
2077- goreman start
2078-
2079-To point your Prometheus 1.4, or later, instance to multiple Alertmanagers, configure them
2080-in your `prometheus.yml` configuration file, for example:
2081-
2082-```yaml
2083-alerting:
2084- alertmanagers:
2085- - static_configs:
2086- - targets:
2087- - alertmanager1:9093
2088- - alertmanager2:9093
2089- - alertmanager3:9093
2090+```sh
2091+docker images --no-trunc --quiet ubuntu/prometheus-alertmanager:<tag>
2092 ```
2093
2094-> Important: Do not load balance traffic between Prometheus and its Alertmanagers, but instead point Prometheus to a list of all Alertmanagers. The Alertmanager implementation expects all alerts to be sent to all Alertmanagers to ensure high availability.
2095-
2096-### Turn off high availability
2097-
2098-If running Alertmanager in high availability mode is not desired, setting `--cluster.listen-address=` prevents Alertmanager from listening to incoming peer requests.
2099-
2100-## Contributing
2101-
2102-Check the [Prometheus contributing page](https://github.com/prometheus/prometheus/blob/master/CONTRIBUTING.md).
2103-
2104-To contribute to the user interface, refer to [ui/app/CONTRIBUTING.md](ui/app/CONTRIBUTING.md).
2105-
2106-## Architecture
2107-
2108-![](doc/arch.svg)
2109-
2110-## License
2111-
2112-Apache License 2.0, see [LICENSE](https://github.com/prometheus/alertmanager/blob/master/LICENSE).
2113
2114-[hub]: https://hub.docker.com/r/prom/alertmanager/
2115-[circleci]: https://circleci.com/gh/prometheus/alertmanager
2116-[quay]: https://quay.io/repository/prometheus/alertmanager
2117diff --git a/VERSION b/VERSION
2118deleted file mode 100644
2119index 8854156..0000000
2120--- a/VERSION
2121+++ /dev/null
2122@@ -1 +0,0 @@
2123-0.21.0
2124diff --git a/api/api.go b/api/api.go
2125deleted file mode 100644
2126index 6b4a883..0000000
2127--- a/api/api.go
2128+++ /dev/null
2129@@ -1,230 +0,0 @@
2130-// Copyright 2019 Prometheus Team
2131-// Licensed under the Apache License, Version 2.0 (the "License");
2132-// you may not use this file except in compliance with the License.
2133-// You may obtain a copy of the License at
2134-//
2135-// http://www.apache.org/licenses/LICENSE-2.0
2136-//
2137-// Unless required by applicable law or agreed to in writing, software
2138-// distributed under the License is distributed on an "AS IS" BASIS,
2139-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2140-// See the License for the specific language governing permissions and
2141-// limitations under the License.
2142-
2143-package api
2144-
2145-import (
2146- "errors"
2147- "fmt"
2148- "net/http"
2149- "runtime"
2150- "time"
2151-
2152- apiv1 "github.com/prometheus/alertmanager/api/v1"
2153- apiv2 "github.com/prometheus/alertmanager/api/v2"
2154- "github.com/prometheus/alertmanager/cluster"
2155- "github.com/prometheus/alertmanager/config"
2156- "github.com/prometheus/alertmanager/dispatch"
2157- "github.com/prometheus/alertmanager/provider"
2158- "github.com/prometheus/alertmanager/silence"
2159- "github.com/prometheus/alertmanager/types"
2160- "github.com/prometheus/client_golang/prometheus"
2161- "github.com/prometheus/common/model"
2162- "github.com/prometheus/common/route"
2163-
2164- "github.com/go-kit/kit/log"
2165-)
2166-
2167-// API represents all APIs of Alertmanager.
2168-type API struct {
2169- v1 *apiv1.API
2170- v2 *apiv2.API
2171- requestsInFlight prometheus.Gauge
2172- concurrencyLimitExceeded prometheus.Counter
2173- timeout time.Duration
2174- inFlightSem chan struct{}
2175-}
2176-
2177-// Options for the creation of an API object. Alerts, Silences, and StatusFunc
2178-// are mandatory to set. The zero value for everything else is a safe default.
2179-type Options struct {
2180- // Alerts to be used by the API. Mandatory.
2181- Alerts provider.Alerts
2182- // Silences to be used by the API. Mandatory.
2183- Silences *silence.Silences
2184- // StatusFunc is used be the API to retrieve the AlertStatus of an
2185- // alert. Mandatory.
2186- StatusFunc func(model.Fingerprint) types.AlertStatus
2187- // Peer from the gossip cluster. If nil, no clustering will be used.
2188- Peer *cluster.Peer
2189- // Timeout for all HTTP connections. The zero value (and negative
2190- // values) result in no timeout.
2191- Timeout time.Duration
2192- // Concurrency limit for GET requests. The zero value (and negative
2193- // values) result in a limit of GOMAXPROCS or 8, whichever is
2194- // larger. Status code 503 is served for GET requests that would exceed
2195- // the concurrency limit.
2196- Concurrency int
2197- // Logger is used for logging, if nil, no logging will happen.
2198- Logger log.Logger
2199- // Registry is used to register Prometheus metrics. If nil, no metrics
2200- // registration will happen.
2201- Registry prometheus.Registerer
2202- // GroupFunc returns a list of alert groups. The alerts are grouped
2203- // according to the current active configuration. Alerts returned are
2204- // filtered by the arguments provided to the function.
2205- GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string)
2206-}
2207-
2208-func (o Options) validate() error {
2209- if o.Alerts == nil {
2210- return errors.New("mandatory field Alerts not set")
2211- }
2212- if o.Silences == nil {
2213- return errors.New("mandatory field Silences not set")
2214- }
2215- if o.StatusFunc == nil {
2216- return errors.New("mandatory field StatusFunc not set")
2217- }
2218- if o.GroupFunc == nil {
2219- return errors.New("mandatory field GroupFunc not set")
2220- }
2221- return nil
2222-}
2223-
2224-// New creates a new API object combining all API versions. Note that an Update
2225-// call is also needed to get the APIs into an operational state.
2226-func New(opts Options) (*API, error) {
2227- if err := opts.validate(); err != nil {
2228- return nil, fmt.Errorf("invalid API options: %s", err)
2229- }
2230- l := opts.Logger
2231- if l == nil {
2232- l = log.NewNopLogger()
2233- }
2234- concurrency := opts.Concurrency
2235- if concurrency < 1 {
2236- concurrency = runtime.GOMAXPROCS(0)
2237- if concurrency < 8 {
2238- concurrency = 8
2239- }
2240- }
2241-
2242- v1 := apiv1.New(
2243- opts.Alerts,
2244- opts.Silences,
2245- opts.StatusFunc,
2246- opts.Peer,
2247- log.With(l, "version", "v1"),
2248- opts.Registry,
2249- )
2250-
2251- v2, err := apiv2.NewAPI(
2252- opts.Alerts,
2253- opts.GroupFunc,
2254- opts.StatusFunc,
2255- opts.Silences,
2256- opts.Peer,
2257- log.With(l, "version", "v2"),
2258- opts.Registry,
2259- )
2260-
2261- if err != nil {
2262- return nil, err
2263- }
2264-
2265- // TODO(beorn7): For now, this hardcodes the method="get" label. Other
2266- // methods should get the same instrumentation.
2267- requestsInFlight := prometheus.NewGauge(prometheus.GaugeOpts{
2268- Name: "alertmanager_http_requests_in_flight",
2269- Help: "Current number of HTTP requests being processed.",
2270- ConstLabels: prometheus.Labels{"method": "get"},
2271- })
2272- concurrencyLimitExceeded := prometheus.NewCounter(prometheus.CounterOpts{
2273- Name: "alertmanager_http_concurrency_limit_exceeded_total",
2274- Help: "Total number of times an HTTP request failed because the concurrency limit was reached.",
2275- ConstLabels: prometheus.Labels{"method": "get"},
2276- })
2277- if opts.Registry != nil {
2278- if err := opts.Registry.Register(requestsInFlight); err != nil {
2279- return nil, err
2280- }
2281- if err := opts.Registry.Register(concurrencyLimitExceeded); err != nil {
2282- return nil, err
2283- }
2284- }
2285-
2286- return &API{
2287- v1: v1,
2288- v2: v2,
2289- requestsInFlight: requestsInFlight,
2290- concurrencyLimitExceeded: concurrencyLimitExceeded,
2291- timeout: opts.Timeout,
2292- inFlightSem: make(chan struct{}, concurrency),
2293- }, nil
2294-}
2295-
2296-// Register all APIs. It registers APIv1 with the provided router directly. As
2297-// APIv2 works on the http.Handler level, this method also creates a new
2298-// http.ServeMux and then uses it to register both the provided router (to
2299-// handle "/") and APIv2 (to handle "<routePrefix>/api/v2"). The method returns
2300-// the newly created http.ServeMux. If a timeout has been set on construction of
2301-// API, it is enforced for all HTTP request going through this mux. The same is
2302-// true for the concurrency limit, with the exception that it is only applied to
2303-// GET requests.
2304-func (api *API) Register(r *route.Router, routePrefix string) *http.ServeMux {
2305- api.v1.Register(r.WithPrefix("/api/v1"))
2306-
2307- mux := http.NewServeMux()
2308- mux.Handle("/", api.limitHandler(r))
2309-
2310- apiPrefix := ""
2311- if routePrefix != "/" {
2312- apiPrefix = routePrefix
2313- }
2314- // TODO(beorn7): HTTP instrumentation is only in place for Router. Since
2315- // /api/v2 works on the Handler level, it is currently not instrumented
2316- // at all (with the exception of requestsInFlight, which is handled in
2317- // limitHandler below).
2318- mux.Handle(
2319- apiPrefix+"/api/v2/",
2320- api.limitHandler(http.StripPrefix(apiPrefix+"/api/v2", api.v2.Handler)),
2321- )
2322-
2323- return mux
2324-}
2325-
2326-// Update config and resolve timeout of each API. APIv2 also needs
2327-// setAlertStatus to be updated.
2328-func (api *API) Update(cfg *config.Config, setAlertStatus func(model.LabelSet)) {
2329- api.v1.Update(cfg)
2330- api.v2.Update(cfg, setAlertStatus)
2331-}
2332-
2333-func (api *API) limitHandler(h http.Handler) http.Handler {
2334- concLimiter := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
2335- if req.Method == http.MethodGet { // Only limit concurrency of GETs.
2336- select {
2337- case api.inFlightSem <- struct{}{}: // All good, carry on.
2338- api.requestsInFlight.Inc()
2339- defer func() {
2340- <-api.inFlightSem
2341- api.requestsInFlight.Dec()
2342- }()
2343- default:
2344- api.concurrencyLimitExceeded.Inc()
2345- http.Error(rsp, fmt.Sprintf(
2346- "Limit of concurrent GET requests reached (%d), try again later.\n", cap(api.inFlightSem),
2347- ), http.StatusServiceUnavailable)
2348- return
2349- }
2350- }
2351- h.ServeHTTP(rsp, req)
2352- })
2353- if api.timeout <= 0 {
2354- return concLimiter
2355- }
2356- return http.TimeoutHandler(concLimiter, api.timeout, fmt.Sprintf(
2357- "Exceeded configured timeout of %v.\n", api.timeout,
2358- ))
2359-}
2360diff --git a/api/metrics/metrics.go b/api/metrics/metrics.go
2361deleted file mode 100644
2362index 483569a..0000000
2363--- a/api/metrics/metrics.go
2364+++ /dev/null
2365@@ -1,54 +0,0 @@
2366-// Copyright 2019 Prometheus Team
2367-// Licensed under the Apache License, Version 2.0 (the "License");
2368-// you may not use this file except in compliance with the License.
2369-// You may obtain a copy of the License at
2370-//
2371-// http://www.apache.org/licenses/LICENSE-2.0
2372-//
2373-// Unless required by applicable law or agreed to in writing, software
2374-// distributed under the License is distributed on an "AS IS" BASIS,
2375-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2376-// See the License for the specific language governing permissions and
2377-// limitations under the License.
2378-
2379-package metrics
2380-
2381-import "github.com/prometheus/client_golang/prometheus"
2382-
2383-// Alerts stores metrics for alerts which are common across all API versions.
2384-type Alerts struct {
2385- firing prometheus.Counter
2386- resolved prometheus.Counter
2387- invalid prometheus.Counter
2388-}
2389-
2390-// NewAlerts returns an *Alerts struct for the given API version.
2391-func NewAlerts(version string, r prometheus.Registerer) *Alerts {
2392- numReceivedAlerts := prometheus.NewCounterVec(prometheus.CounterOpts{
2393- Name: "alertmanager_alerts_received_total",
2394- Help: "The total number of received alerts.",
2395- ConstLabels: prometheus.Labels{"version": version},
2396- }, []string{"status"})
2397- numInvalidAlerts := prometheus.NewCounter(prometheus.CounterOpts{
2398- Name: "alertmanager_alerts_invalid_total",
2399- Help: "The total number of received alerts that were invalid.",
2400- ConstLabels: prometheus.Labels{"version": version},
2401- })
2402- if r != nil {
2403- r.MustRegister(numReceivedAlerts, numInvalidAlerts)
2404- }
2405- return &Alerts{
2406- firing: numReceivedAlerts.WithLabelValues("firing"),
2407- resolved: numReceivedAlerts.WithLabelValues("resolved"),
2408- invalid: numInvalidAlerts,
2409- }
2410-}
2411-
2412-// Firing returns a counter of firing alerts.
2413-func (a *Alerts) Firing() prometheus.Counter { return a.firing }
2414-
2415-// Resolved returns a counter of resolved alerts.
2416-func (a *Alerts) Resolved() prometheus.Counter { return a.resolved }
2417-
2418-// Invalid returns a counter of invalid alerts.
2419-func (a *Alerts) Invalid() prometheus.Counter { return a.invalid }
2420diff --git a/api/v1/api.go b/api/v1/api.go
2421deleted file mode 100644
2422index 3a14664..0000000
2423--- a/api/v1/api.go
2424+++ /dev/null
2425@@ -1,797 +0,0 @@
2426-// Copyright 2015 Prometheus Team
2427-// Licensed under the Apache License, Version 2.0 (the "License");
2428-// you may not use this file except in compliance with the License.
2429-// You may obtain a copy of the License at
2430-//
2431-// http://www.apache.org/licenses/LICENSE-2.0
2432-//
2433-// Unless required by applicable law or agreed to in writing, software
2434-// distributed under the License is distributed on an "AS IS" BASIS,
2435-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2436-// See the License for the specific language governing permissions and
2437-// limitations under the License.
2438-
2439-package v1
2440-
2441-import (
2442- "encoding/json"
2443- "errors"
2444- "fmt"
2445- "net/http"
2446- "regexp"
2447- "sort"
2448- "sync"
2449- "time"
2450-
2451- "github.com/go-kit/kit/log"
2452- "github.com/go-kit/kit/log/level"
2453- "github.com/prometheus/client_golang/prometheus"
2454- "github.com/prometheus/common/model"
2455- "github.com/prometheus/common/route"
2456- "github.com/prometheus/common/version"
2457-
2458- "github.com/prometheus/alertmanager/api/metrics"
2459- "github.com/prometheus/alertmanager/cluster"
2460- "github.com/prometheus/alertmanager/config"
2461- "github.com/prometheus/alertmanager/dispatch"
2462- "github.com/prometheus/alertmanager/pkg/labels"
2463- "github.com/prometheus/alertmanager/provider"
2464- "github.com/prometheus/alertmanager/silence"
2465- "github.com/prometheus/alertmanager/silence/silencepb"
2466- "github.com/prometheus/alertmanager/types"
2467-)
2468-
2469-var corsHeaders = map[string]string{
2470- "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin",
2471- "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
2472- "Access-Control-Allow-Origin": "*",
2473- "Access-Control-Expose-Headers": "Date",
2474- "Cache-Control": "no-cache, no-store, must-revalidate",
2475-}
2476-
2477-// Alert is the API representation of an alert, which is a regular alert
2478-// annotated with silencing and inhibition info.
2479-type Alert struct {
2480- *model.Alert
2481- Status types.AlertStatus `json:"status"`
2482- Receivers []string `json:"receivers"`
2483- Fingerprint string `json:"fingerprint"`
2484-}
2485-
2486-// Enables cross-site script calls.
2487-func setCORS(w http.ResponseWriter) {
2488- for h, v := range corsHeaders {
2489- w.Header().Set(h, v)
2490- }
2491-}
2492-
2493-// API provides registration of handlers for API routes.
2494-type API struct {
2495- alerts provider.Alerts
2496- silences *silence.Silences
2497- config *config.Config
2498- route *dispatch.Route
2499- uptime time.Time
2500- peer *cluster.Peer
2501- logger log.Logger
2502- m *metrics.Alerts
2503-
2504- getAlertStatus getAlertStatusFn
2505-
2506- mtx sync.RWMutex
2507-}
2508-
2509-type getAlertStatusFn func(model.Fingerprint) types.AlertStatus
2510-
2511-// New returns a new API.
2512-func New(
2513- alerts provider.Alerts,
2514- silences *silence.Silences,
2515- sf getAlertStatusFn,
2516- peer *cluster.Peer,
2517- l log.Logger,
2518- r prometheus.Registerer,
2519-) *API {
2520- if l == nil {
2521- l = log.NewNopLogger()
2522- }
2523-
2524- return &API{
2525- alerts: alerts,
2526- silences: silences,
2527- getAlertStatus: sf,
2528- uptime: time.Now(),
2529- peer: peer,
2530- logger: l,
2531- m: metrics.NewAlerts("v1", r),
2532- }
2533-}
2534-
2535-// Register registers the API handlers under their correct routes
2536-// in the given router.
2537-func (api *API) Register(r *route.Router) {
2538- wrap := func(f http.HandlerFunc) http.HandlerFunc {
2539- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2540- setCORS(w)
2541- f(w, r)
2542- })
2543- }
2544-
2545- r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {}))
2546-
2547- r.Get("/status", wrap(api.status))
2548- r.Get("/receivers", wrap(api.receivers))
2549-
2550- r.Get("/alerts", wrap(api.listAlerts))
2551- r.Post("/alerts", wrap(api.addAlerts))
2552-
2553- r.Get("/silences", wrap(api.listSilences))
2554- r.Post("/silences", wrap(api.setSilence))
2555- r.Get("/silence/:sid", wrap(api.getSilence))
2556- r.Del("/silence/:sid", wrap(api.delSilence))
2557-}
2558-
2559-// Update sets the configuration string to a new value.
2560-func (api *API) Update(cfg *config.Config) {
2561- api.mtx.Lock()
2562- defer api.mtx.Unlock()
2563-
2564- api.config = cfg
2565- api.route = dispatch.NewRoute(cfg.Route, nil)
2566-}
2567-
2568-type errorType string
2569-
2570-const (
2571- errorInternal errorType = "server_error"
2572- errorBadData errorType = "bad_data"
2573-)
2574-
2575-type apiError struct {
2576- typ errorType
2577- err error
2578-}
2579-
2580-func (e *apiError) Error() string {
2581- return fmt.Sprintf("%s: %s", e.typ, e.err)
2582-}
2583-
2584-func (api *API) receivers(w http.ResponseWriter, req *http.Request) {
2585- api.mtx.RLock()
2586- defer api.mtx.RUnlock()
2587-
2588- receivers := make([]string, 0, len(api.config.Receivers))
2589- for _, r := range api.config.Receivers {
2590- receivers = append(receivers, r.Name)
2591- }
2592-
2593- api.respond(w, receivers)
2594-}
2595-
2596-func (api *API) status(w http.ResponseWriter, req *http.Request) {
2597- api.mtx.RLock()
2598-
2599- var status = struct {
2600- ConfigYAML string `json:"configYAML"`
2601- ConfigJSON *config.Config `json:"configJSON"`
2602- VersionInfo map[string]string `json:"versionInfo"`
2603- Uptime time.Time `json:"uptime"`
2604- ClusterStatus *clusterStatus `json:"clusterStatus"`
2605- }{
2606- ConfigYAML: api.config.String(),
2607- ConfigJSON: api.config,
2608- VersionInfo: map[string]string{
2609- "version": version.Version,
2610- "revision": version.Revision,
2611- "branch": version.Branch,
2612- "buildUser": version.BuildUser,
2613- "buildDate": version.BuildDate,
2614- "goVersion": version.GoVersion,
2615- },
2616- Uptime: api.uptime,
2617- ClusterStatus: getClusterStatus(api.peer),
2618- }
2619-
2620- api.mtx.RUnlock()
2621-
2622- api.respond(w, status)
2623-}
2624-
2625-type peerStatus struct {
2626- Name string `json:"name"`
2627- Address string `json:"address"`
2628-}
2629-
2630-type clusterStatus struct {
2631- Name string `json:"name"`
2632- Status string `json:"status"`
2633- Peers []peerStatus `json:"peers"`
2634-}
2635-
2636-func getClusterStatus(p *cluster.Peer) *clusterStatus {
2637- if p == nil {
2638- return nil
2639- }
2640- s := &clusterStatus{Name: p.Name(), Status: p.Status()}
2641-
2642- for _, n := range p.Peers() {
2643- s.Peers = append(s.Peers, peerStatus{
2644- Name: n.Name,
2645- Address: n.Address(),
2646- })
2647- }
2648- return s
2649-}
2650-
2651-func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
2652- var (
2653- err error
2654- receiverFilter *regexp.Regexp
2655- // Initialize result slice to prevent api returning `null` when there
2656- // are no alerts present
2657- res = []*Alert{}
2658- matchers = []*labels.Matcher{}
2659- ctx = r.Context()
2660-
2661- showActive, showInhibited bool
2662- showSilenced, showUnprocessed bool
2663- )
2664-
2665- getBoolParam := func(name string) (bool, error) {
2666- v := r.FormValue(name)
2667- if v == "" {
2668- return true, nil
2669- }
2670- if v == "false" {
2671- return false, nil
2672- }
2673- if v != "true" {
2674- err := fmt.Errorf("parameter %q can either be 'true' or 'false', not %q", name, v)
2675- api.respondError(w, apiError{
2676- typ: errorBadData,
2677- err: err,
2678- }, nil)
2679- return false, err
2680- }
2681- return true, nil
2682- }
2683-
2684- if filter := r.FormValue("filter"); filter != "" {
2685- matchers, err = labels.ParseMatchers(filter)
2686- if err != nil {
2687- api.respondError(w, apiError{
2688- typ: errorBadData,
2689- err: err,
2690- }, nil)
2691- return
2692- }
2693- }
2694-
2695- showActive, err = getBoolParam("active")
2696- if err != nil {
2697- return
2698- }
2699-
2700- showSilenced, err = getBoolParam("silenced")
2701- if err != nil {
2702- return
2703- }
2704-
2705- showInhibited, err = getBoolParam("inhibited")
2706- if err != nil {
2707- return
2708- }
2709-
2710- showUnprocessed, err = getBoolParam("unprocessed")
2711- if err != nil {
2712- return
2713- }
2714-
2715- if receiverParam := r.FormValue("receiver"); receiverParam != "" {
2716- receiverFilter, err = regexp.Compile("^(?:" + receiverParam + ")$")
2717- if err != nil {
2718- api.respondError(w, apiError{
2719- typ: errorBadData,
2720- err: fmt.Errorf(
2721- "failed to parse receiver param: %s",
2722- receiverParam,
2723- ),
2724- }, nil)
2725- return
2726- }
2727- }
2728-
2729- alerts := api.alerts.GetPending()
2730- defer alerts.Close()
2731-
2732- api.mtx.RLock()
2733- for a := range alerts.Next() {
2734- if err = alerts.Err(); err != nil {
2735- break
2736- }
2737- if err = ctx.Err(); err != nil {
2738- break
2739- }
2740-
2741- routes := api.route.Match(a.Labels)
2742- receivers := make([]string, 0, len(routes))
2743- for _, r := range routes {
2744- receivers = append(receivers, r.RouteOpts.Receiver)
2745- }
2746-
2747- if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
2748- continue
2749- }
2750-
2751- if !alertMatchesFilterLabels(&a.Alert, matchers) {
2752- continue
2753- }
2754-
2755- // Continue if the alert is resolved.
2756- if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) {
2757- continue
2758- }
2759-
2760- status := api.getAlertStatus(a.Fingerprint())
2761-
2762- if !showActive && status.State == types.AlertStateActive {
2763- continue
2764- }
2765-
2766- if !showUnprocessed && status.State == types.AlertStateUnprocessed {
2767- continue
2768- }
2769-
2770- if !showSilenced && len(status.SilencedBy) != 0 {
2771- continue
2772- }
2773-
2774- if !showInhibited && len(status.InhibitedBy) != 0 {
2775- continue
2776- }
2777-
2778- alert := &Alert{
2779- Alert: &a.Alert,
2780- Status: status,
2781- Receivers: receivers,
2782- Fingerprint: a.Fingerprint().String(),
2783- }
2784-
2785- res = append(res, alert)
2786- }
2787- api.mtx.RUnlock()
2788-
2789- if err != nil {
2790- api.respondError(w, apiError{
2791- typ: errorInternal,
2792- err: err,
2793- }, nil)
2794- return
2795- }
2796- sort.Slice(res, func(i, j int) bool {
2797- return res[i].Fingerprint < res[j].Fingerprint
2798- })
2799- api.respond(w, res)
2800-}
2801-
2802-func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool {
2803- for _, r := range receivers {
2804- if filter.MatchString(r) {
2805- return true
2806- }
2807- }
2808-
2809- return false
2810-}
2811-
2812-func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool {
2813- sms := make(map[string]string)
2814- for name, value := range a.Labels {
2815- sms[string(name)] = string(value)
2816- }
2817- return matchFilterLabels(matchers, sms)
2818-}
2819-
2820-func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) {
2821- var alerts []*types.Alert
2822- if err := api.receive(r, &alerts); err != nil {
2823- api.respondError(w, apiError{
2824- typ: errorBadData,
2825- err: err,
2826- }, nil)
2827- return
2828- }
2829-
2830- api.insertAlerts(w, r, alerts...)
2831-}
2832-
2833-func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
2834- now := time.Now()
2835-
2836- api.mtx.RLock()
2837- resolveTimeout := time.Duration(api.config.Global.ResolveTimeout)
2838- api.mtx.RUnlock()
2839-
2840- for _, alert := range alerts {
2841- alert.UpdatedAt = now
2842-
2843- // Ensure StartsAt is set.
2844- if alert.StartsAt.IsZero() {
2845- if alert.EndsAt.IsZero() {
2846- alert.StartsAt = now
2847- } else {
2848- alert.StartsAt = alert.EndsAt
2849- }
2850- }
2851- // If no end time is defined, set a timeout after which an alert
2852- // is marked resolved if it is not updated.
2853- if alert.EndsAt.IsZero() {
2854- alert.Timeout = true
2855- alert.EndsAt = now.Add(resolveTimeout)
2856- }
2857- if alert.EndsAt.After(time.Now()) {
2858- api.m.Firing().Inc()
2859- } else {
2860- api.m.Resolved().Inc()
2861- }
2862- }
2863-
2864- // Make a best effort to insert all alerts that are valid.
2865- var (
2866- validAlerts = make([]*types.Alert, 0, len(alerts))
2867- validationErrs = &types.MultiError{}
2868- )
2869- for _, a := range alerts {
2870- removeEmptyLabels(a.Labels)
2871-
2872- if err := a.Validate(); err != nil {
2873- validationErrs.Add(err)
2874- api.m.Invalid().Inc()
2875- continue
2876- }
2877- validAlerts = append(validAlerts, a)
2878- }
2879- if err := api.alerts.Put(validAlerts...); err != nil {
2880- api.respondError(w, apiError{
2881- typ: errorInternal,
2882- err: err,
2883- }, nil)
2884- return
2885- }
2886-
2887- if validationErrs.Len() > 0 {
2888- api.respondError(w, apiError{
2889- typ: errorBadData,
2890- err: validationErrs,
2891- }, nil)
2892- return
2893- }
2894-
2895- api.respond(w, nil)
2896-}
2897-
2898-func removeEmptyLabels(ls model.LabelSet) {
2899- for k, v := range ls {
2900- if string(v) == "" {
2901- delete(ls, k)
2902- }
2903- }
2904-}
2905-
2906-func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
2907- var sil types.Silence
2908- if err := api.receive(r, &sil); err != nil {
2909- api.respondError(w, apiError{
2910- typ: errorBadData,
2911- err: err,
2912- }, nil)
2913- return
2914- }
2915-
2916- // This is an API only validation, it cannot be done internally
2917- // because the expired silence is semantically important.
2918- // But one should not be able to create expired silences, that
2919- // won't have any use.
2920- if sil.Expired() {
2921- api.respondError(w, apiError{
2922- typ: errorBadData,
2923- err: errors.New("start time must not be equal to end time"),
2924- }, nil)
2925- return
2926- }
2927-
2928- if sil.EndsAt.Before(time.Now()) {
2929- api.respondError(w, apiError{
2930- typ: errorBadData,
2931- err: errors.New("end time can't be in the past"),
2932- }, nil)
2933- return
2934- }
2935-
2936- psil, err := silenceToProto(&sil)
2937- if err != nil {
2938- api.respondError(w, apiError{
2939- typ: errorBadData,
2940- err: err,
2941- }, nil)
2942- return
2943- }
2944-
2945- sid, err := api.silences.Set(psil)
2946- if err != nil {
2947- api.respondError(w, apiError{
2948- typ: errorBadData,
2949- err: err,
2950- }, nil)
2951- return
2952- }
2953-
2954- api.respond(w, struct {
2955- SilenceID string `json:"silenceId"`
2956- }{
2957- SilenceID: sid,
2958- })
2959-}
2960-
2961-func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
2962- sid := route.Param(r.Context(), "sid")
2963-
2964- sils, _, err := api.silences.Query(silence.QIDs(sid))
2965- if err != nil || len(sils) == 0 {
2966- http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
2967- return
2968- }
2969- sil, err := silenceFromProto(sils[0])
2970- if err != nil {
2971- api.respondError(w, apiError{
2972- typ: errorInternal,
2973- err: err,
2974- }, nil)
2975- return
2976- }
2977-
2978- api.respond(w, sil)
2979-}
2980-
2981-func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
2982- sid := route.Param(r.Context(), "sid")
2983-
2984- if err := api.silences.Expire(sid); err != nil {
2985- api.respondError(w, apiError{
2986- typ: errorBadData,
2987- err: err,
2988- }, nil)
2989- return
2990- }
2991- api.respond(w, nil)
2992-}
2993-
2994-func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
2995- psils, _, err := api.silences.Query()
2996- if err != nil {
2997- api.respondError(w, apiError{
2998- typ: errorInternal,
2999- err: err,
3000- }, nil)
3001- return
3002- }
3003-
3004- matchers := []*labels.Matcher{}
3005- if filter := r.FormValue("filter"); filter != "" {
3006- matchers, err = labels.ParseMatchers(filter)
3007- if err != nil {
3008- api.respondError(w, apiError{
3009- typ: errorBadData,
3010- err: err,
3011- }, nil)
3012- return
3013- }
3014- }
3015-
3016- sils := []*types.Silence{}
3017- for _, ps := range psils {
3018- s, err := silenceFromProto(ps)
3019- if err != nil {
3020- api.respondError(w, apiError{
3021- typ: errorInternal,
3022- err: err,
3023- }, nil)
3024- return
3025- }
3026-
3027- if !silenceMatchesFilterLabels(s, matchers) {
3028- continue
3029- }
3030- sils = append(sils, s)
3031- }
3032-
3033- var active, pending, expired []*types.Silence
3034-
3035- for _, s := range sils {
3036- switch s.Status.State {
3037- case types.SilenceStateActive:
3038- active = append(active, s)
3039- case types.SilenceStatePending:
3040- pending = append(pending, s)
3041- case types.SilenceStateExpired:
3042- expired = append(expired, s)
3043- }
3044- }
3045-
3046- sort.Slice(active, func(i int, j int) bool {
3047- return active[i].EndsAt.Before(active[j].EndsAt)
3048- })
3049- sort.Slice(pending, func(i int, j int) bool {
3050- return pending[i].StartsAt.Before(pending[j].EndsAt)
3051- })
3052- sort.Slice(expired, func(i int, j int) bool {
3053- return expired[i].EndsAt.After(expired[j].EndsAt)
3054- })
3055-
3056- // Initialize silences explicitly to an empty list (instead of nil)
3057- // So that it does not get converted to "null" in JSON.
3058- silences := []*types.Silence{}
3059- silences = append(silences, active...)
3060- silences = append(silences, pending...)
3061- silences = append(silences, expired...)
3062-
3063- api.respond(w, silences)
3064-}
3065-
3066-func silenceMatchesFilterLabels(s *types.Silence, matchers []*labels.Matcher) bool {
3067- sms := make(map[string]string)
3068- for _, m := range s.Matchers {
3069- sms[m.Name] = m.Value
3070- }
3071-
3072- return matchFilterLabels(matchers, sms)
3073-}
3074-
3075-func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
3076- for _, m := range matchers {
3077- v, prs := sms[m.Name]
3078- switch m.Type {
3079- case labels.MatchNotRegexp, labels.MatchNotEqual:
3080- if string(m.Value) == "" && prs {
3081- continue
3082- }
3083- if !m.Matches(string(v)) {
3084- return false
3085- }
3086- default:
3087- if string(m.Value) == "" && !prs {
3088- continue
3089- }
3090- if !prs || !m.Matches(string(v)) {
3091- return false
3092- }
3093- }
3094- }
3095-
3096- return true
3097-}
3098-
3099-func silenceToProto(s *types.Silence) (*silencepb.Silence, error) {
3100- sil := &silencepb.Silence{
3101- Id: s.ID,
3102- StartsAt: s.StartsAt,
3103- EndsAt: s.EndsAt,
3104- UpdatedAt: s.UpdatedAt,
3105- Comment: s.Comment,
3106- CreatedBy: s.CreatedBy,
3107- }
3108- for _, m := range s.Matchers {
3109- matcher := &silencepb.Matcher{
3110- Name: m.Name,
3111- Pattern: m.Value,
3112- Type: silencepb.Matcher_EQUAL,
3113- }
3114- if m.IsRegex {
3115- matcher.Type = silencepb.Matcher_REGEXP
3116- }
3117- sil.Matchers = append(sil.Matchers, matcher)
3118- }
3119- return sil, nil
3120-}
3121-
3122-func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) {
3123- sil := &types.Silence{
3124- ID: s.Id,
3125- StartsAt: s.StartsAt,
3126- EndsAt: s.EndsAt,
3127- UpdatedAt: s.UpdatedAt,
3128- Status: types.SilenceStatus{
3129- State: types.CalcSilenceState(s.StartsAt, s.EndsAt),
3130- },
3131- Comment: s.Comment,
3132- CreatedBy: s.CreatedBy,
3133- }
3134- for _, m := range s.Matchers {
3135- matcher := &types.Matcher{
3136- Name: m.Name,
3137- Value: m.Pattern,
3138- }
3139- switch m.Type {
3140- case silencepb.Matcher_EQUAL:
3141- case silencepb.Matcher_REGEXP:
3142- matcher.IsRegex = true
3143- default:
3144- return nil, fmt.Errorf("unknown matcher type")
3145- }
3146- sil.Matchers = append(sil.Matchers, matcher)
3147- }
3148-
3149- return sil, nil
3150-}
3151-
3152-type status string
3153-
3154-const (
3155- statusSuccess status = "success"
3156- statusError status = "error"
3157-)
3158-
3159-type response struct {
3160- Status status `json:"status"`
3161- Data interface{} `json:"data,omitempty"`
3162- ErrorType errorType `json:"errorType,omitempty"`
3163- Error string `json:"error,omitempty"`
3164-}
3165-
3166-func (api *API) respond(w http.ResponseWriter, data interface{}) {
3167- w.Header().Set("Content-Type", "application/json")
3168- w.WriteHeader(200)
3169-
3170- b, err := json.Marshal(&response{
3171- Status: statusSuccess,
3172- Data: data,
3173- })
3174- if err != nil {
3175- level.Error(api.logger).Log("msg", "Error marshaling JSON", "err", err)
3176- return
3177- }
3178-
3179- if _, err := w.Write(b); err != nil {
3180- level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
3181- }
3182-}
3183-
3184-func (api *API) respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
3185- w.Header().Set("Content-Type", "application/json")
3186-
3187- switch apiErr.typ {
3188- case errorBadData:
3189- w.WriteHeader(http.StatusBadRequest)
3190- case errorInternal:
3191- w.WriteHeader(http.StatusInternalServerError)
3192- default:
3193- panic(fmt.Sprintf("unknown error type %q", apiErr.Error()))
3194- }
3195-
3196- b, err := json.Marshal(&response{
3197- Status: statusError,
3198- ErrorType: apiErr.typ,
3199- Error: apiErr.err.Error(),
3200- Data: data,
3201- })
3202- if err != nil {
3203- return
3204- }
3205- level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error())
3206-
3207- if _, err := w.Write(b); err != nil {
3208- level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
3209- }
3210-}
3211-
3212-func (api *API) receive(r *http.Request, v interface{}) error {
3213- dec := json.NewDecoder(r.Body)
3214- defer r.Body.Close()
3215-
3216- err := dec.Decode(v)
3217- if err != nil {
3218- level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err)
3219- return err
3220- }
3221- return nil
3222-}
3223diff --git a/api/v1/api_test.go b/api/v1/api_test.go
3224deleted file mode 100644
3225index d25f362..0000000
3226--- a/api/v1/api_test.go
3227+++ /dev/null
3228@@ -1,581 +0,0 @@
3229-// Copyright 2018 Prometheus Team
3230-// Licensed under the Apache License, Version 2.0 (the "License");
3231-// you may not use this file except in compliance with the License.
3232-// You may obtain a copy of the License at
3233-//
3234-// http://www.apache.org/licenses/LICENSE-2.0
3235-//
3236-// Unless required by applicable law or agreed to in writing, software
3237-// distributed under the License is distributed on an "AS IS" BASIS,
3238-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3239-// See the License for the specific language governing permissions and
3240-// limitations under the License.
3241-
3242-package v1
3243-
3244-import (
3245- "bytes"
3246- "encoding/json"
3247- "errors"
3248- "fmt"
3249- "io/ioutil"
3250- "net/http"
3251- "net/http/httptest"
3252- "regexp"
3253- "testing"
3254- "time"
3255-
3256- "github.com/prometheus/common/model"
3257- "github.com/stretchr/testify/require"
3258-
3259- "github.com/prometheus/alertmanager/config"
3260- "github.com/prometheus/alertmanager/dispatch"
3261- "github.com/prometheus/alertmanager/pkg/labels"
3262- "github.com/prometheus/alertmanager/provider"
3263- "github.com/prometheus/alertmanager/types"
3264-)
3265-
3266-// fakeAlerts is a struct implementing the provider.Alerts interface for tests.
3267-type fakeAlerts struct {
3268- fps map[model.Fingerprint]int
3269- alerts []*types.Alert
3270- err error
3271-}
3272-
3273-func newFakeAlerts(alerts []*types.Alert, withErr bool) *fakeAlerts {
3274- fps := make(map[model.Fingerprint]int)
3275- for i, a := range alerts {
3276- fps[a.Fingerprint()] = i
3277- }
3278- f := &fakeAlerts{
3279- alerts: alerts,
3280- fps: fps,
3281- }
3282- if withErr {
3283- f.err = errors.New("error occurred")
3284- }
3285- return f
3286-}
3287-
3288-func (f *fakeAlerts) Subscribe() provider.AlertIterator { return nil }
3289-func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
3290-func (f *fakeAlerts) Put(alerts ...*types.Alert) error {
3291- return f.err
3292-}
3293-func (f *fakeAlerts) GetPending() provider.AlertIterator {
3294- ch := make(chan *types.Alert)
3295- done := make(chan struct{})
3296- go func() {
3297- defer close(ch)
3298- for _, a := range f.alerts {
3299- ch <- a
3300- }
3301- }()
3302- return provider.NewAlertIterator(ch, done, f.err)
3303-}
3304-
3305-func newGetAlertStatus(f *fakeAlerts) func(model.Fingerprint) types.AlertStatus {
3306- return func(fp model.Fingerprint) types.AlertStatus {
3307- status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}}
3308-
3309- i, ok := f.fps[fp]
3310- if !ok {
3311- return status
3312- }
3313- alert := f.alerts[i]
3314- switch alert.Labels["state"] {
3315- case "active":
3316- status.State = types.AlertStateActive
3317- case "unprocessed":
3318- status.State = types.AlertStateUnprocessed
3319- case "suppressed":
3320- status.State = types.AlertStateSuppressed
3321- }
3322- if alert.Labels["silenced_by"] != "" {
3323- status.SilencedBy = append(status.SilencedBy, string(alert.Labels["silenced_by"]))
3324- }
3325- if alert.Labels["inhibited_by"] != "" {
3326- status.InhibitedBy = append(status.InhibitedBy, string(alert.Labels["inhibited_by"]))
3327- }
3328- return status
3329- }
3330-}
3331-
3332-func TestAddAlerts(t *testing.T) {
3333- now := func(offset int) time.Time {
3334- return time.Now().Add(time.Duration(offset) * time.Second)
3335- }
3336-
3337- for i, tc := range []struct {
3338- start, end time.Time
3339- err bool
3340- code int
3341- }{
3342- {time.Time{}, time.Time{}, false, 200},
3343- {now(0), time.Time{}, false, 200},
3344- {time.Time{}, now(-1), false, 200},
3345- {time.Time{}, now(0), false, 200},
3346- {time.Time{}, now(1), false, 200},
3347- {now(-2), now(-1), false, 200},
3348- {now(1), now(2), false, 200},
3349- {now(1), now(0), false, 400},
3350- {now(0), time.Time{}, true, 500},
3351- } {
3352- alerts := []model.Alert{{
3353- StartsAt: tc.start,
3354- EndsAt: tc.end,
3355- Labels: model.LabelSet{"label1": "test1"},
3356- Annotations: model.LabelSet{"annotation1": "some text"},
3357- }}
3358- b, err := json.Marshal(&alerts)
3359- if err != nil {
3360- t.Errorf("Unexpected error %v", err)
3361- }
3362-
3363- alertsProvider := newFakeAlerts([]*types.Alert{}, tc.err)
3364- api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
3365- defaultGlobalConfig := config.DefaultGlobalConfig()
3366- route := config.Route{}
3367- api.Update(&config.Config{
3368- Global: &defaultGlobalConfig,
3369- Route: &route,
3370- })
3371-
3372- r, err := http.NewRequest("POST", "/api/v1/alerts", bytes.NewReader(b))
3373- w := httptest.NewRecorder()
3374- if err != nil {
3375- t.Errorf("Unexpected error %v", err)
3376- }
3377-
3378- api.addAlerts(w, r)
3379- res := w.Result()
3380- body, _ := ioutil.ReadAll(res.Body)
3381-
3382- require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, StartsAt %v, EndsAt %v, Response: %s", i, tc.start, tc.end, string(body)))
3383- }
3384-}
3385-
3386-func TestListAlerts(t *testing.T) {
3387- now := time.Now()
3388- alerts := []*types.Alert{
3389- &types.Alert{
3390- Alert: model.Alert{
3391- Labels: model.LabelSet{"state": "active", "alertname": "alert1"},
3392- StartsAt: now.Add(-time.Minute),
3393- },
3394- },
3395- &types.Alert{
3396- Alert: model.Alert{
3397- Labels: model.LabelSet{"state": "unprocessed", "alertname": "alert2"},
3398- StartsAt: now.Add(-time.Minute),
3399- },
3400- },
3401- &types.Alert{
3402- Alert: model.Alert{
3403- Labels: model.LabelSet{"state": "suppressed", "silenced_by": "abc", "alertname": "alert3"},
3404- StartsAt: now.Add(-time.Minute),
3405- },
3406- },
3407- &types.Alert{
3408- Alert: model.Alert{
3409- Labels: model.LabelSet{"state": "suppressed", "inhibited_by": "abc", "alertname": "alert4"},
3410- StartsAt: now.Add(-time.Minute),
3411- },
3412- },
3413- &types.Alert{
3414- Alert: model.Alert{
3415- Labels: model.LabelSet{"alertname": "alert5"},
3416- StartsAt: now.Add(-2 * time.Minute),
3417- EndsAt: now.Add(-time.Minute),
3418- },
3419- },
3420- }
3421-
3422- for i, tc := range []struct {
3423- err bool
3424- params map[string]string
3425-
3426- code int
3427- anames []string
3428- }{
3429- {
3430- false,
3431- map[string]string{},
3432- 200,
3433- []string{"alert1", "alert2", "alert3", "alert4"},
3434- },
3435- {
3436- false,
3437- map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
3438- 200,
3439- []string{"alert1", "alert2", "alert3", "alert4"},
3440- },
3441- {
3442- false,
3443- map[string]string{"active": "false", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
3444- 200,
3445- []string{"alert2", "alert3", "alert4"},
3446- },
3447- {
3448- false,
3449- map[string]string{"active": "true", "unprocessed": "false", "silenced": "true", "inhibited": "true"},
3450- 200,
3451- []string{"alert1", "alert3", "alert4"},
3452- },
3453- {
3454- false,
3455- map[string]string{"active": "true", "unprocessed": "true", "silenced": "false", "inhibited": "true"},
3456- 200,
3457- []string{"alert1", "alert2", "alert4"},
3458- },
3459- {
3460- false,
3461- map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "false"},
3462- 200,
3463- []string{"alert1", "alert2", "alert3"},
3464- },
3465- {
3466- false,
3467- map[string]string{"filter": "{alertname=\"alert3\""},
3468- 200,
3469- []string{"alert3"},
3470- },
3471- {
3472- false,
3473- map[string]string{"filter": "{alertname"},
3474- 400,
3475- []string{},
3476- },
3477- {
3478- false,
3479- map[string]string{"receiver": "other"},
3480- 200,
3481- []string{},
3482- },
3483- {
3484- false,
3485- map[string]string{"active": "invalid"},
3486- 400,
3487- []string{},
3488- },
3489- {
3490- true,
3491- map[string]string{},
3492- 500,
3493- []string{},
3494- },
3495- } {
3496- alertsProvider := newFakeAlerts(alerts, tc.err)
3497- api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
3498- api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil)
3499-
3500- r, err := http.NewRequest("GET", "/api/v1/alerts", nil)
3501- if err != nil {
3502- t.Fatalf("Unexpected error %v", err)
3503- }
3504- q := r.URL.Query()
3505- for k, v := range tc.params {
3506- q.Add(k, v)
3507- }
3508- r.URL.RawQuery = q.Encode()
3509- w := httptest.NewRecorder()
3510-
3511- api.listAlerts(w, r)
3512- body, _ := ioutil.ReadAll(w.Result().Body)
3513-
3514- var res response
3515- err = json.Unmarshal(body, &res)
3516- if err != nil {
3517- t.Fatalf("Unexpected error %v", err)
3518- }
3519-
3520- require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
3521- if w.Code != 200 {
3522- continue
3523- }
3524-
3525- // Data needs to be serialized/deserialized to be converted to the real type.
3526- b, err := json.Marshal(res.Data)
3527- if err != nil {
3528- t.Fatalf("Unexpected error %v", err)
3529- }
3530- retAlerts := []*Alert{}
3531- err = json.Unmarshal(b, &retAlerts)
3532- if err != nil {
3533- t.Fatalf("Unexpected error %v", err)
3534- }
3535-
3536- anames := []string{}
3537- for _, a := range retAlerts {
3538- name, ok := a.Labels["alertname"]
3539- if ok {
3540- anames = append(anames, string(name))
3541- }
3542- }
3543- require.Equal(t, tc.anames, anames, fmt.Sprintf("test case: %d, alert names are not equal", i))
3544- }
3545-}
3546-
3547-func TestAlertFiltering(t *testing.T) {
3548- type test struct {
3549- alert *model.Alert
3550- msg string
3551- expected bool
3552- }
3553-
3554- // Equal
3555- equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
3556- if err != nil {
3557- t.Errorf("Unexpected error %v", err)
3558- }
3559-
3560- tests := []test{
3561- {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=test1", true},
3562- {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=test2", false},
3563- {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=test2", false},
3564- }
3565-
3566- for _, test := range tests {
3567- actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{equal})
3568- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3569- require.Equal(t, test.expected, actual, msg)
3570- }
3571-
3572- // Not Equal
3573- notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
3574- if err != nil {
3575- t.Errorf("Unexpected error %v", err)
3576- }
3577-
3578- tests = []test{
3579- {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!=test1", false},
3580- {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!=test2", true},
3581- {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!=test2", true},
3582- }
3583-
3584- for _, test := range tests {
3585- actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{notEqual})
3586- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3587- require.Equal(t, test.expected, actual, msg)
3588- }
3589-
3590- // Regexp Equal
3591- regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
3592- if err != nil {
3593- t.Errorf("Unexpected error %v", err)
3594- }
3595-
3596- tests = []test{
3597- {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=~test1", true},
3598- {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=~test2", true},
3599- {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=~test2", false},
3600- }
3601-
3602- for _, test := range tests {
3603- actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpEqual})
3604- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3605- require.Equal(t, test.expected, actual, msg)
3606- }
3607-
3608- // Regexp Not Equal
3609- regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
3610- if err != nil {
3611- t.Errorf("Unexpected error %v", err)
3612- }
3613-
3614- tests = []test{
3615- {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!~test1", false},
3616- {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!~test2", false},
3617- {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!~test2", true},
3618- }
3619-
3620- for _, test := range tests {
3621- actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpNotEqual})
3622- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3623- require.Equal(t, test.expected, actual, msg)
3624- }
3625-}
3626-
3627-func TestSilenceFiltering(t *testing.T) {
3628- type test struct {
3629- silence *types.Silence
3630- msg string
3631- expected bool
3632- }
3633-
3634- // Equal
3635- equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
3636- if err != nil {
3637- t.Errorf("Unexpected error %v", err)
3638- }
3639-
3640- tests := []test{
3641- {
3642- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
3643- "label1=test1",
3644- true,
3645- },
3646- {
3647- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
3648- "label1=test2",
3649- false,
3650- },
3651- {
3652- &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
3653- "label2=test2",
3654- false,
3655- },
3656- }
3657-
3658- for _, test := range tests {
3659- actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{equal})
3660- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3661- require.Equal(t, test.expected, actual, msg)
3662- }
3663-
3664- // Not Equal
3665- notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
3666- if err != nil {
3667- t.Errorf("Unexpected error %v", err)
3668- }
3669-
3670- tests = []test{
3671- {
3672- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
3673- "label1!=test1",
3674- false,
3675- },
3676- {
3677- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
3678- "label1!=test2",
3679- true,
3680- },
3681- {
3682- &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
3683- "label2!=test2",
3684- true,
3685- },
3686- }
3687-
3688- for _, test := range tests {
3689- actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{notEqual})
3690- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3691- require.Equal(t, test.expected, actual, msg)
3692- }
3693-
3694- // Regexp Equal
3695- regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
3696- if err != nil {
3697- t.Errorf("Unexpected error %v", err)
3698- }
3699-
3700- tests = []test{
3701- {
3702- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
3703- "label1=~test1",
3704- true,
3705- },
3706- {
3707- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
3708- "label1=~test2",
3709- true,
3710- },
3711- {
3712- &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
3713- "label2=~test2",
3714- false,
3715- },
3716- }
3717-
3718- for _, test := range tests {
3719- actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpEqual})
3720- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3721- require.Equal(t, test.expected, actual, msg)
3722- }
3723-
3724- // Regexp Not Equal
3725- regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
3726- if err != nil {
3727- t.Errorf("Unexpected error %v", err)
3728- }
3729-
3730- tests = []test{
3731- {
3732- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
3733- "label1!~test1",
3734- false,
3735- },
3736- {
3737- &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
3738- "label1!~test2",
3739- false,
3740- },
3741- {
3742- &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
3743- "label2!~test2",
3744- true,
3745- },
3746- }
3747-
3748- for _, test := range tests {
3749- actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpNotEqual})
3750- msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
3751- require.Equal(t, test.expected, actual, msg)
3752- }
3753-}
3754-
3755-func TestReceiversMatchFilter(t *testing.T) {
3756- receivers := []string{"pagerduty", "slack", "pushover"}
3757-
3758- filter, err := regexp.Compile(fmt.Sprintf("^(?:%s)$", "push.*"))
3759- if err != nil {
3760- t.Errorf("Unexpected error %v", err)
3761- }
3762- require.True(t, receiversMatchFilter(receivers, filter))
3763-
3764- filter, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", "push"))
3765- if err != nil {
3766- t.Errorf("Unexpected error %v", err)
3767- }
3768- require.False(t, receiversMatchFilter(receivers, filter))
3769-}
3770-
3771-func TestMatchFilterLabels(t *testing.T) {
3772- testCases := []struct {
3773- matcher labels.MatchType
3774- expected bool
3775- }{
3776- {labels.MatchEqual, true},
3777- {labels.MatchRegexp, true},
3778- {labels.MatchNotEqual, false},
3779- {labels.MatchNotRegexp, false},
3780- }
3781-
3782- for _, tc := range testCases {
3783- l, err := labels.NewMatcher(tc.matcher, "foo", "")
3784- require.NoError(t, err)
3785- sms := map[string]string{
3786- "baz": "bar",
3787- }
3788- ls := []*labels.Matcher{l}
3789-
3790- require.Equal(t, tc.expected, matchFilterLabels(ls, sms))
3791-
3792- l, err = labels.NewMatcher(tc.matcher, "foo", "")
3793- require.NoError(t, err)
3794- sms = map[string]string{
3795- "baz": "bar",
3796- "foo": "quux",
3797- }
3798- ls = []*labels.Matcher{l}
3799- require.NotEqual(t, tc.expected, matchFilterLabels(ls, sms))
3800- }
3801-}
3802-
3803-func newMatcher(labelSet model.LabelSet) types.Matchers {
3804- matchers := make([]*types.Matcher, 0, len(labelSet))
3805- for key, val := range labelSet {
3806- matchers = append(matchers, types.NewMatcher(key, string(val)))
3807- }
3808- return matchers
3809-}
3810diff --git a/api/v2/api.go b/api/v2/api.go
3811deleted file mode 100644
3812index 6cde197..0000000
3813--- a/api/v2/api.go
3814+++ /dev/null
3815@@ -1,798 +0,0 @@
3816-// Copyright 2018 Prometheus Team
3817-// Licensed under the Apache License, Version 2.0 (the "License");
3818-// you may not use this file except in compliance with the License.
3819-// You may obtain a copy of the License at
3820-//
3821-// http://www.apache.org/licenses/LICENSE-2.0
3822-//
3823-// Unless required by applicable law or agreed to in writing, software
3824-// distributed under the License is distributed on an "AS IS" BASIS,
3825-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3826-// See the License for the specific language governing permissions and
3827-// limitations under the License.
3828-
3829-package v2
3830-
3831-import (
3832- "fmt"
3833- "net/http"
3834- "regexp"
3835- "sort"
3836- "sync"
3837- "time"
3838-
3839- "github.com/go-kit/kit/log"
3840- "github.com/go-kit/kit/log/level"
3841- "github.com/go-openapi/loads"
3842- "github.com/go-openapi/runtime/middleware"
3843- "github.com/go-openapi/strfmt"
3844- "github.com/prometheus/client_golang/prometheus"
3845- prometheus_model "github.com/prometheus/common/model"
3846- "github.com/prometheus/common/version"
3847- "github.com/rs/cors"
3848-
3849- "github.com/prometheus/alertmanager/api/metrics"
3850- open_api_models "github.com/prometheus/alertmanager/api/v2/models"
3851- "github.com/prometheus/alertmanager/api/v2/restapi"
3852- "github.com/prometheus/alertmanager/api/v2/restapi/operations"
3853- alert_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert"
3854- alertgroup_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup"
3855- general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
3856- receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver"
3857- silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence"
3858- "github.com/prometheus/alertmanager/cluster"
3859- "github.com/prometheus/alertmanager/config"
3860- "github.com/prometheus/alertmanager/dispatch"
3861- "github.com/prometheus/alertmanager/pkg/labels"
3862- "github.com/prometheus/alertmanager/provider"
3863- "github.com/prometheus/alertmanager/silence"
3864- "github.com/prometheus/alertmanager/silence/silencepb"
3865- "github.com/prometheus/alertmanager/types"
3866-)
3867-
3868-// API represents an Alertmanager API v2
3869-type API struct {
3870- peer *cluster.Peer
3871- silences *silence.Silences
3872- alerts provider.Alerts
3873- alertGroups groupsFn
3874- getAlertStatus getAlertStatusFn
3875- uptime time.Time
3876-
3877- // mtx protects alertmanagerConfig, setAlertStatus and route.
3878- mtx sync.RWMutex
3879- // resolveTimeout represents the default resolve timeout that an alert is
3880- // assigned if no end time is specified.
3881- alertmanagerConfig *config.Config
3882- route *dispatch.Route
3883- setAlertStatus setAlertStatusFn
3884-
3885- logger log.Logger
3886- m *metrics.Alerts
3887-
3888- Handler http.Handler
3889-}
3890-
3891-type groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
3892-type getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus
3893-type setAlertStatusFn func(prometheus_model.LabelSet)
3894-
3895-// NewAPI returns a new Alertmanager API v2
3896-func NewAPI(
3897- alerts provider.Alerts,
3898- gf groupsFn,
3899- sf getAlertStatusFn,
3900- silences *silence.Silences,
3901- peer *cluster.Peer,
3902- l log.Logger,
3903- r prometheus.Registerer,
3904-) (*API, error) {
3905- api := API{
3906- alerts: alerts,
3907- getAlertStatus: sf,
3908- alertGroups: gf,
3909- peer: peer,
3910- silences: silences,
3911- logger: l,
3912- m: metrics.NewAlerts("v2", r),
3913- uptime: time.Now(),
3914- }
3915-
3916- // Load embedded swagger file.
3917- swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
3918- if err != nil {
3919- return nil, fmt.Errorf("failed to load embedded swagger file: %v", err.Error())
3920- }
3921-
3922- // Create new service API.
3923- openAPI := operations.NewAlertmanagerAPI(swaggerSpec)
3924-
3925- // Skip the redoc middleware, only serving the OpenAPI specification and
3926- // the API itself via RoutesHandler. See:
3927- // https://github.com/go-swagger/go-swagger/issues/1779
3928- openAPI.Middleware = func(b middleware.Builder) http.Handler {
3929- return middleware.Spec("", swaggerSpec.Raw(), openAPI.Context().RoutesHandler(b))
3930- }
3931-
3932- openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler)
3933- openAPI.AlertPostAlertsHandler = alert_ops.PostAlertsHandlerFunc(api.postAlertsHandler)
3934- openAPI.AlertgroupGetAlertGroupsHandler = alertgroup_ops.GetAlertGroupsHandlerFunc(api.getAlertGroupsHandler)
3935- openAPI.GeneralGetStatusHandler = general_ops.GetStatusHandlerFunc(api.getStatusHandler)
3936- openAPI.ReceiverGetReceiversHandler = receiver_ops.GetReceiversHandlerFunc(api.getReceiversHandler)
3937- openAPI.SilenceDeleteSilenceHandler = silence_ops.DeleteSilenceHandlerFunc(api.deleteSilenceHandler)
3938- openAPI.SilenceGetSilenceHandler = silence_ops.GetSilenceHandlerFunc(api.getSilenceHandler)
3939- openAPI.SilenceGetSilencesHandler = silence_ops.GetSilencesHandlerFunc(api.getSilencesHandler)
3940- openAPI.SilencePostSilencesHandler = silence_ops.PostSilencesHandlerFunc(api.postSilencesHandler)
3941-
3942- handleCORS := cors.Default().Handler
3943- api.Handler = handleCORS(openAPI.Serve(nil))
3944-
3945- return &api, nil
3946-}
3947-
3948-func (api *API) requestLogger(req *http.Request) log.Logger {
3949- return log.With(api.logger, "path", req.URL.Path, "method", req.Method)
3950-}
3951-
3952-// Update sets the API struct members that may change between reloads of alertmanager.
3953-func (api *API) Update(cfg *config.Config, setAlertStatus setAlertStatusFn) {
3954- api.mtx.Lock()
3955- defer api.mtx.Unlock()
3956-
3957- api.alertmanagerConfig = cfg
3958- api.route = dispatch.NewRoute(cfg.Route, nil)
3959- api.setAlertStatus = setAlertStatus
3960-}
3961-
3962-func (api *API) getStatusHandler(params general_ops.GetStatusParams) middleware.Responder {
3963- api.mtx.RLock()
3964- defer api.mtx.RUnlock()
3965-
3966- original := api.alertmanagerConfig.String()
3967- uptime := strfmt.DateTime(api.uptime)
3968-
3969- status := open_api_models.ClusterStatusStatusDisabled
3970-
3971- resp := open_api_models.AlertmanagerStatus{
3972- Uptime: &uptime,
3973- VersionInfo: &open_api_models.VersionInfo{
3974- Version: &version.Version,
3975- Revision: &version.Revision,
3976- Branch: &version.Branch,
3977- BuildUser: &version.BuildUser,
3978- BuildDate: &version.BuildDate,
3979- GoVersion: &version.GoVersion,
3980- },
3981- Config: &open_api_models.AlertmanagerConfig{
3982- Original: &original,
3983- },
3984- Cluster: &open_api_models.ClusterStatus{
3985- Status: &status,
3986- Peers: []*open_api_models.PeerStatus{},
3987- },
3988- }
3989-
3990- // If alertmanager cluster feature is disabled, then api.peers == nil.
3991- if api.peer != nil {
3992- status := api.peer.Status()
3993-
3994- peers := []*open_api_models.PeerStatus{}
3995- for _, n := range api.peer.Peers() {
3996- address := n.Address()
3997- peers = append(peers, &open_api_models.PeerStatus{
3998- Name: &n.Name,
3999- Address: &address,
4000- })
4001- }
4002-
4003- sort.Slice(peers, func(i, j int) bool {
4004- return *peers[i].Name < *peers[j].Name
4005- })
4006-
4007- resp.Cluster = &open_api_models.ClusterStatus{
4008- Name: api.peer.Name(),
4009- Status: &status,
4010- Peers: peers,
4011- }
4012- }
4013-
4014- return general_ops.NewGetStatusOK().WithPayload(&resp)
4015-}
4016-
4017-func (api *API) getReceiversHandler(params receiver_ops.GetReceiversParams) middleware.Responder {
4018- api.mtx.RLock()
4019- defer api.mtx.RUnlock()
4020-
4021- receivers := make([]*open_api_models.Receiver, 0, len(api.alertmanagerConfig.Receivers))
4022- for _, r := range api.alertmanagerConfig.Receivers {
4023- receivers = append(receivers, &open_api_models.Receiver{Name: &r.Name})
4024- }
4025-
4026- return receiver_ops.NewGetReceiversOK().WithPayload(receivers)
4027-}
4028-
4029-func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Responder {
4030- var (
4031- receiverFilter *regexp.Regexp
4032- // Initialize result slice to prevent api returning `null` when there
4033- // are no alerts present
4034- res = open_api_models.GettableAlerts{}
4035- ctx = params.HTTPRequest.Context()
4036-
4037- logger = api.requestLogger(params.HTTPRequest)
4038- )
4039-
4040- matchers, err := parseFilter(params.Filter)
4041- if err != nil {
4042- level.Error(logger).Log("msg", "Failed to parse matchers", "err", err)
4043- return alertgroup_ops.NewGetAlertGroupsBadRequest().WithPayload(err.Error())
4044- }
4045-
4046- if params.Receiver != nil {
4047- receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$")
4048- if err != nil {
4049- level.Error(logger).Log("msg", "Failed to compile receiver regex", "err", err)
4050- return alert_ops.
4051- NewGetAlertsBadRequest().
4052- WithPayload(
4053- fmt.Sprintf("failed to parse receiver param: %v", err.Error()),
4054- )
4055- }
4056- }
4057-
4058- alerts := api.alerts.GetPending()
4059- defer alerts.Close()
4060-
4061- alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
4062- now := time.Now()
4063-
4064- api.mtx.RLock()
4065- for a := range alerts.Next() {
4066- if err = alerts.Err(); err != nil {
4067- break
4068- }
4069- if err = ctx.Err(); err != nil {
4070- break
4071- }
4072-
4073- routes := api.route.Match(a.Labels)
4074- receivers := make([]string, 0, len(routes))
4075- for _, r := range routes {
4076- receivers = append(receivers, r.RouteOpts.Receiver)
4077- }
4078-
4079- if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
4080- continue
4081- }
4082-
4083- if !alertFilter(a, now) {
4084- continue
4085- }
4086-
4087- alert := alertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers)
4088-
4089- res = append(res, alert)
4090- }
4091- api.mtx.RUnlock()
4092-
4093- if err != nil {
4094- level.Error(logger).Log("msg", "Failed to get alerts", "err", err)
4095- return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
4096- }
4097- sort.Slice(res, func(i, j int) bool {
4098- return *res[i].Fingerprint < *res[j].Fingerprint
4099- })
4100-
4101- return alert_ops.NewGetAlertsOK().WithPayload(res)
4102-}
4103-
4104-func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder {
4105- logger := api.requestLogger(params.HTTPRequest)
4106-
4107- alerts := openAPIAlertsToAlerts(params.Alerts)
4108- now := time.Now()
4109-
4110- api.mtx.RLock()
4111- resolveTimeout := time.Duration(api.alertmanagerConfig.Global.ResolveTimeout)
4112- api.mtx.RUnlock()
4113-
4114- for _, alert := range alerts {
4115- alert.UpdatedAt = now
4116-
4117- // Ensure StartsAt is set.
4118- if alert.StartsAt.IsZero() {
4119- if alert.EndsAt.IsZero() {
4120- alert.StartsAt = now
4121- } else {
4122- alert.StartsAt = alert.EndsAt
4123- }
4124- }
4125- // If no end time is defined, set a timeout after which an alert
4126- // is marked resolved if it is not updated.
4127- if alert.EndsAt.IsZero() {
4128- alert.Timeout = true
4129- alert.EndsAt = now.Add(resolveTimeout)
4130- }
4131- if alert.EndsAt.After(time.Now()) {
4132- api.m.Firing().Inc()
4133- } else {
4134- api.m.Resolved().Inc()
4135- }
4136- }
4137-
4138- // Make a best effort to insert all alerts that are valid.
4139- var (
4140- validAlerts = make([]*types.Alert, 0, len(alerts))
4141- validationErrs = &types.MultiError{}
4142- )
4143- for _, a := range alerts {
4144- removeEmptyLabels(a.Labels)
4145-
4146- if err := a.Validate(); err != nil {
4147- validationErrs.Add(err)
4148- api.m.Invalid().Inc()
4149- continue
4150- }
4151- validAlerts = append(validAlerts, a)
4152- }
4153- if err := api.alerts.Put(validAlerts...); err != nil {
4154- level.Error(logger).Log("msg", "Failed to create alerts", "err", err)
4155- return alert_ops.NewPostAlertsInternalServerError().WithPayload(err.Error())
4156- }
4157-
4158- if validationErrs.Len() > 0 {
4159- level.Error(logger).Log("msg", "Failed to validate alerts", "err", validationErrs.Error())
4160- return alert_ops.NewPostAlertsBadRequest().WithPayload(validationErrs.Error())
4161- }
4162-
4163- return alert_ops.NewPostAlertsOK()
4164-}
4165-
4166-func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams) middleware.Responder {
4167- logger := api.requestLogger(params.HTTPRequest)
4168-
4169- matchers, err := parseFilter(params.Filter)
4170- if err != nil {
4171- level.Error(logger).Log("msg", "Failed to parse matchers", "err", err)
4172- return alertgroup_ops.NewGetAlertGroupsBadRequest().WithPayload(err.Error())
4173- }
4174-
4175- var receiverFilter *regexp.Regexp
4176- if params.Receiver != nil {
4177- receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$")
4178- if err != nil {
4179- level.Error(logger).Log("msg", "Failed to compile receiver regex", "err", err)
4180- return alertgroup_ops.
4181- NewGetAlertGroupsBadRequest().
4182- WithPayload(
4183- fmt.Sprintf("failed to parse receiver param: %v", err.Error()),
4184- )
4185- }
4186- }
4187-
4188- rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
4189- return func(r *dispatch.Route) bool {
4190- receiver := r.RouteOpts.Receiver
4191- if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
4192- return false
4193- }
4194- return true
4195- }
4196- }(receiverFilter)
4197-
4198- af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
4199- alertGroups, allReceivers := api.alertGroups(rf, af)
4200-
4201- res := make(open_api_models.AlertGroups, 0, len(alertGroups))
4202-
4203- for _, alertGroup := range alertGroups {
4204- ag := &open_api_models.AlertGroup{
4205- Receiver: &open_api_models.Receiver{Name: &alertGroup.Receiver},
4206- Labels: modelLabelSetToAPILabelSet(alertGroup.Labels),
4207- Alerts: make([]*open_api_models.GettableAlert, 0, len(alertGroup.Alerts)),
4208- }
4209-
4210- for _, alert := range alertGroup.Alerts {
4211- fp := alert.Fingerprint()
4212- receivers := allReceivers[fp]
4213- status := api.getAlertStatus(fp)
4214- apiAlert := alertToOpenAPIAlert(alert, status, receivers)
4215- ag.Alerts = append(ag.Alerts, apiAlert)
4216- }
4217- res = append(res, ag)
4218- }
4219-
4220- return alertgroup_ops.NewGetAlertGroupsOK().WithPayload(res)
4221-}
4222-
4223-func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, active bool) func(a *types.Alert, now time.Time) bool {
4224- return func(a *types.Alert, now time.Time) bool {
4225- if !a.EndsAt.IsZero() && a.EndsAt.Before(now) {
4226- return false
4227- }
4228-
4229- // Set alert's current status based on its label set.
4230- api.setAlertStatus(a.Labels)
4231-
4232- // Get alert's current status after seeing if it is suppressed.
4233- status := api.getAlertStatus(a.Fingerprint())
4234-
4235- if !active && status.State == types.AlertStateActive {
4236- return false
4237- }
4238-
4239- if !silenced && len(status.SilencedBy) != 0 {
4240- return false
4241- }
4242-
4243- if !inhibited && len(status.InhibitedBy) != 0 {
4244- return false
4245- }
4246-
4247- return alertMatchesFilterLabels(&a.Alert, matchers)
4248- }
4249-}
4250-
4251-func alertToOpenAPIAlert(alert *types.Alert, status types.AlertStatus, receivers []string) *open_api_models.GettableAlert {
4252- startsAt := strfmt.DateTime(alert.StartsAt)
4253- updatedAt := strfmt.DateTime(alert.UpdatedAt)
4254- endsAt := strfmt.DateTime(alert.EndsAt)
4255-
4256- apiReceivers := make([]*open_api_models.Receiver, 0, len(receivers))
4257- for i := range receivers {
4258- apiReceivers = append(apiReceivers, &open_api_models.Receiver{Name: &receivers[i]})
4259- }
4260-
4261- fp := alert.Fingerprint().String()
4262- state := string(status.State)
4263- aa := &open_api_models.GettableAlert{
4264- Alert: open_api_models.Alert{
4265- GeneratorURL: strfmt.URI(alert.GeneratorURL),
4266- Labels: modelLabelSetToAPILabelSet(alert.Labels),
4267- },
4268- Annotations: modelLabelSetToAPILabelSet(alert.Annotations),
4269- StartsAt: &startsAt,
4270- UpdatedAt: &updatedAt,
4271- EndsAt: &endsAt,
4272- Fingerprint: &fp,
4273- Receivers: apiReceivers,
4274- Status: &open_api_models.AlertStatus{
4275- State: &state,
4276- SilencedBy: status.SilencedBy,
4277- InhibitedBy: status.InhibitedBy,
4278- },
4279- }
4280-
4281- if aa.Status.SilencedBy == nil {
4282- aa.Status.SilencedBy = []string{}
4283- }
4284-
4285- if aa.Status.InhibitedBy == nil {
4286- aa.Status.InhibitedBy = []string{}
4287- }
4288-
4289- return aa
4290-}
4291-
4292-func openAPIAlertsToAlerts(apiAlerts open_api_models.PostableAlerts) []*types.Alert {
4293- alerts := []*types.Alert{}
4294- for _, apiAlert := range apiAlerts {
4295- alert := types.Alert{
4296- Alert: prometheus_model.Alert{
4297- Labels: apiLabelSetToModelLabelSet(apiAlert.Labels),
4298- Annotations: apiLabelSetToModelLabelSet(apiAlert.Annotations),
4299- StartsAt: time.Time(apiAlert.StartsAt),
4300- EndsAt: time.Time(apiAlert.EndsAt),
4301- GeneratorURL: string(apiAlert.GeneratorURL),
4302- },
4303- }
4304- alerts = append(alerts, &alert)
4305- }
4306-
4307- return alerts
4308-}
4309-
4310-func removeEmptyLabels(ls prometheus_model.LabelSet) {
4311- for k, v := range ls {
4312- if string(v) == "" {
4313- delete(ls, k)
4314- }
4315- }
4316-}
4317-
4318-func modelLabelSetToAPILabelSet(modelLabelSet prometheus_model.LabelSet) open_api_models.LabelSet {
4319- apiLabelSet := open_api_models.LabelSet{}
4320- for key, value := range modelLabelSet {
4321- apiLabelSet[string(key)] = string(value)
4322- }
4323-
4324- return apiLabelSet
4325-}
4326-
4327-func apiLabelSetToModelLabelSet(apiLabelSet open_api_models.LabelSet) prometheus_model.LabelSet {
4328- modelLabelSet := prometheus_model.LabelSet{}
4329- for key, value := range apiLabelSet {
4330- modelLabelSet[prometheus_model.LabelName(key)] = prometheus_model.LabelValue(value)
4331- }
4332-
4333- return modelLabelSet
4334-}
4335-
4336-func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool {
4337- for _, r := range receivers {
4338- if filter.MatchString(r) {
4339- return true
4340- }
4341- }
4342-
4343- return false
4344-}
4345-
4346-func alertMatchesFilterLabels(a *prometheus_model.Alert, matchers []*labels.Matcher) bool {
4347- sms := make(map[string]string)
4348- for name, value := range a.Labels {
4349- sms[string(name)] = string(value)
4350- }
4351- return matchFilterLabels(matchers, sms)
4352-}
4353-
4354-func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
4355- for _, m := range matchers {
4356- v, prs := sms[m.Name]
4357- switch m.Type {
4358- case labels.MatchNotRegexp, labels.MatchNotEqual:
4359- if m.Value == "" && prs {
4360- continue
4361- }
4362- if !m.Matches(v) {
4363- return false
4364- }
4365- default:
4366- if m.Value == "" && !prs {
4367- continue
4368- }
4369- if !prs || !m.Matches(v) {
4370- return false
4371- }
4372- }
4373- }
4374-
4375- return true
4376-}
4377-
4378-func (api *API) getSilencesHandler(params silence_ops.GetSilencesParams) middleware.Responder {
4379- logger := api.requestLogger(params.HTTPRequest)
4380-
4381- matchers := []*labels.Matcher{}
4382- if params.Filter != nil {
4383- for _, matcherString := range params.Filter {
4384- matcher, err := labels.ParseMatcher(matcherString)
4385- if err != nil {
4386- level.Error(logger).Log("msg", "Failed to parse matchers", "err", err)
4387- return alert_ops.NewGetAlertsBadRequest().WithPayload(err.Error())
4388- }
4389-
4390- matchers = append(matchers, matcher)
4391- }
4392- }
4393-
4394- psils, _, err := api.silences.Query()
4395- if err != nil {
4396- level.Error(logger).Log("msg", "Failed to get silences", "err", err)
4397- return silence_ops.NewGetSilencesInternalServerError().WithPayload(err.Error())
4398- }
4399-
4400- sils := open_api_models.GettableSilences{}
4401- for _, ps := range psils {
4402- silence, err := gettableSilenceFromProto(ps)
4403- if err != nil {
4404- level.Error(logger).Log("msg", "Failed to unmarshal silence from proto", "err", err)
4405- return silence_ops.NewGetSilencesInternalServerError().WithPayload(err.Error())
4406- }
4407- if !gettableSilenceMatchesFilterLabels(silence, matchers) {
4408- continue
4409- }
4410- sils = append(sils, &silence)
4411- }
4412-
4413- sortSilences(sils)
4414-
4415- return silence_ops.NewGetSilencesOK().WithPayload(sils)
4416-}
4417-
4418-var (
4419- silenceStateOrder = map[types.SilenceState]int{
4420- types.SilenceStateActive: 1,
4421- types.SilenceStatePending: 2,
4422- types.SilenceStateExpired: 3,
4423- }
4424-)
4425-
4426-// sortSilences sorts first according to the state "active, pending, expired"
4427-// then by end time or start time depending on the state.
4428-// active silences should show the next to expire first
4429-// pending silences are ordered based on which one starts next
4430-// expired are ordered based on which one expired most recently
4431-func sortSilences(sils open_api_models.GettableSilences) {
4432- sort.Slice(sils, func(i, j int) bool {
4433- state1 := types.SilenceState(*sils[i].Status.State)
4434- state2 := types.SilenceState(*sils[j].Status.State)
4435- if state1 != state2 {
4436- return silenceStateOrder[state1] < silenceStateOrder[state2]
4437- }
4438- switch state1 {
4439- case types.SilenceStateActive:
4440- endsAt1 := time.Time(*sils[i].Silence.EndsAt)
4441- endsAt2 := time.Time(*sils[j].Silence.EndsAt)
4442- return endsAt1.Before(endsAt2)
4443- case types.SilenceStatePending:
4444- startsAt1 := time.Time(*sils[i].Silence.StartsAt)
4445- startsAt2 := time.Time(*sils[j].Silence.StartsAt)
4446- return startsAt1.Before(startsAt2)
4447- case types.SilenceStateExpired:
4448- endsAt1 := time.Time(*sils[i].Silence.EndsAt)
4449- endsAt2 := time.Time(*sils[j].Silence.EndsAt)
4450- return endsAt1.After(endsAt2)
4451- }
4452- return false
4453- })
4454-}
4455-
4456-func gettableSilenceMatchesFilterLabels(s open_api_models.GettableSilence, matchers []*labels.Matcher) bool {
4457- sms := make(map[string]string)
4458- for _, m := range s.Matchers {
4459- sms[*m.Name] = *m.Value
4460- }
4461-
4462- return matchFilterLabels(matchers, sms)
4463-}
4464-
4465-func (api *API) getSilenceHandler(params silence_ops.GetSilenceParams) middleware.Responder {
4466- logger := api.requestLogger(params.HTTPRequest)
4467-
4468- sils, _, err := api.silences.Query(silence.QIDs(params.SilenceID.String()))
4469- if err != nil {
4470- level.Error(logger).Log("msg", "Failed to get silence by id", "err", err, "id", params.SilenceID.String())
4471- return silence_ops.NewGetSilenceInternalServerError().WithPayload(err.Error())
4472- }
4473-
4474- if len(sils) == 0 {
4475- level.Error(logger).Log("msg", "Failed to find silence", "err", err, "id", params.SilenceID.String())
4476- return silence_ops.NewGetSilenceNotFound()
4477- }
4478-
4479- sil, err := gettableSilenceFromProto(sils[0])
4480- if err != nil {
4481- level.Error(logger).Log("msg", "Failed to convert unmarshal from proto", "err", err)
4482- return silence_ops.NewGetSilenceInternalServerError().WithPayload(err.Error())
4483- }
4484-
4485- return silence_ops.NewGetSilenceOK().WithPayload(&sil)
4486-}
4487-
4488-func (api *API) deleteSilenceHandler(params silence_ops.DeleteSilenceParams) middleware.Responder {
4489- logger := api.requestLogger(params.HTTPRequest)
4490-
4491- sid := params.SilenceID.String()
4492- if err := api.silences.Expire(sid); err != nil {
4493- level.Error(logger).Log("msg", "Failed to expire silence", "err", err)
4494- return silence_ops.NewDeleteSilenceInternalServerError().WithPayload(err.Error())
4495- }
4496- return silence_ops.NewDeleteSilenceOK()
4497-}
4498-
4499-func gettableSilenceFromProto(s *silencepb.Silence) (open_api_models.GettableSilence, error) {
4500- start := strfmt.DateTime(s.StartsAt)
4501- end := strfmt.DateTime(s.EndsAt)
4502- updated := strfmt.DateTime(s.UpdatedAt)
4503- state := string(types.CalcSilenceState(s.StartsAt, s.EndsAt))
4504- sil := open_api_models.GettableSilence{
4505- Silence: open_api_models.Silence{
4506- StartsAt: &start,
4507- EndsAt: &end,
4508- Comment: &s.Comment,
4509- CreatedBy: &s.CreatedBy,
4510- },
4511- ID: &s.Id,
4512- UpdatedAt: &updated,
4513- Status: &open_api_models.SilenceStatus{
4514- State: &state,
4515- },
4516- }
4517-
4518- for _, m := range s.Matchers {
4519- matcher := &open_api_models.Matcher{
4520- Name: &m.Name,
4521- Value: &m.Pattern,
4522- }
4523- switch m.Type {
4524- case silencepb.Matcher_EQUAL:
4525- f := false
4526- matcher.IsRegex = &f
4527- case silencepb.Matcher_REGEXP:
4528- t := true
4529- matcher.IsRegex = &t
4530- default:
4531- return sil, fmt.Errorf(
4532- "unknown matcher type for matcher '%v' in silence '%v'",
4533- m.Name,
4534- s.Id,
4535- )
4536- }
4537- sil.Matchers = append(sil.Matchers, matcher)
4538- }
4539-
4540- return sil, nil
4541-}
4542-
4543-func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middleware.Responder {
4544- logger := api.requestLogger(params.HTTPRequest)
4545-
4546- sil, err := postableSilenceToProto(params.Silence)
4547- if err != nil {
4548- level.Error(logger).Log("msg", "Failed to marshal silence to proto", "err", err)
4549- return silence_ops.NewPostSilencesBadRequest().WithPayload(
4550- fmt.Sprintf("failed to convert API silence to internal silence: %v", err.Error()),
4551- )
4552- }
4553-
4554- if sil.StartsAt.After(sil.EndsAt) || sil.StartsAt.Equal(sil.EndsAt) {
4555- msg := "Failed to create silence: start time must be before end time"
4556- level.Error(logger).Log("msg", msg, "starts_at", sil.StartsAt, "ends_at", sil.EndsAt)
4557- return silence_ops.NewPostSilencesBadRequest().WithPayload(msg)
4558- }
4559-
4560- if sil.EndsAt.Before(time.Now()) {
4561- msg := "Failed to create silence: end time can't be in the past"
4562- level.Error(logger).Log("msg", msg, "ends_at", sil.EndsAt)
4563- return silence_ops.NewPostSilencesBadRequest().WithPayload(msg)
4564- }
4565-
4566- sid, err := api.silences.Set(sil)
4567- if err != nil {
4568- level.Error(logger).Log("msg", "Failed to create silence", "err", err)
4569- if err == silence.ErrNotFound {
4570- return silence_ops.NewPostSilencesNotFound().WithPayload(err.Error())
4571- }
4572- return silence_ops.NewPostSilencesBadRequest().WithPayload(err.Error())
4573- }
4574-
4575- return silence_ops.NewPostSilencesOK().WithPayload(&silence_ops.PostSilencesOKBody{
4576- SilenceID: sid,
4577- })
4578-}
4579-
4580-func postableSilenceToProto(s *open_api_models.PostableSilence) (*silencepb.Silence, error) {
4581- sil := &silencepb.Silence{
4582- Id: s.ID,
4583- StartsAt: time.Time(*s.StartsAt),
4584- EndsAt: time.Time(*s.EndsAt),
4585- Comment: *s.Comment,
4586- CreatedBy: *s.CreatedBy,
4587- }
4588- for _, m := range s.Matchers {
4589- matcher := &silencepb.Matcher{
4590- Name: *m.Name,
4591- Pattern: *m.Value,
4592- Type: silencepb.Matcher_EQUAL,
4593- }
4594- if *m.IsRegex {
4595- matcher.Type = silencepb.Matcher_REGEXP
4596- }
4597- sil.Matchers = append(sil.Matchers, matcher)
4598- }
4599- return sil, nil
4600-}
4601-
4602-func parseFilter(filter []string) ([]*labels.Matcher, error) {
4603- matchers := make([]*labels.Matcher, 0, len(filter))
4604- for _, matcherString := range filter {
4605- matcher, err := labels.ParseMatcher(matcherString)
4606- if err != nil {
4607- return nil, err
4608- }
4609-
4610- matchers = append(matchers, matcher)
4611- }
4612- return matchers, nil
4613-}
4614diff --git a/api/v2/api_test.go b/api/v2/api_test.go
4615deleted file mode 100644
4616index 4cb4b74..0000000
4617--- a/api/v2/api_test.go
4618+++ /dev/null
4619@@ -1,173 +0,0 @@
4620-// Copyright 2019 Prometheus Team
4621-// Licensed under the Apache License, Version 2.0 (the "License");
4622-// you may not use this file except in compliance with the License.
4623-// You may obtain a copy of the License at
4624-//
4625-// http://www.apache.org/licenses/LICENSE-2.0
4626-//
4627-// Unless required by applicable law or agreed to in writing, software
4628-// distributed under the License is distributed on an "AS IS" BASIS,
4629-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4630-// See the License for the specific language governing permissions and
4631-// limitations under the License.
4632-
4633-package v2
4634-
4635-import (
4636- "strconv"
4637- "testing"
4638- "time"
4639-
4640- "github.com/go-openapi/strfmt"
4641- "github.com/prometheus/common/model"
4642- "github.com/stretchr/testify/require"
4643-
4644- open_api_models "github.com/prometheus/alertmanager/api/v2/models"
4645- general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
4646- "github.com/prometheus/alertmanager/config"
4647- "github.com/prometheus/alertmanager/types"
4648-)
4649-
4650-// If api.peers == nil, Alertmanager cluster feature is disabled. Make sure to
4651-// not try to access properties of peer, which would trigger a nil pointer
4652-// dereference.
4653-func TestGetStatusHandlerWithNilPeer(t *testing.T) {
4654- api := API{
4655- uptime: time.Now(),
4656- peer: nil,
4657- alertmanagerConfig: &config.Config{},
4658- }
4659-
4660- // Test ensures this method call does not panic.
4661- status := api.getStatusHandler(general_ops.GetStatusParams{}).(*general_ops.GetStatusOK)
4662-
4663- c := status.Payload.Cluster
4664-
4665- if c == nil || c.Status == nil {
4666- t.Fatal("expected cluster status not to be nil, violating the openapi specification")
4667- }
4668-
4669- if c.Peers == nil {
4670- t.Fatal("expected cluster peers to be not nil when api.peer is nil, violating the openapi specification")
4671- }
4672- if len(c.Peers) != 0 {
4673- t.Fatal("expected cluster peers to be empty when api.peer is nil, violating the openapi specification")
4674- }
4675-
4676- if c.Name != "" {
4677- t.Fatal("expected cluster name to be empty, violating the openapi specification")
4678- }
4679-}
4680-
4681-func assertEqualStrings(t *testing.T, expected string, actual string) {
4682- if expected != actual {
4683- t.Fatal("expected: ", expected, ", actual: ", actual)
4684- }
4685-}
4686-
4687-var (
4688- testComment = "comment"
4689- createdBy = "test"
4690-)
4691-
4692-func gettableSilence(id string, state string,
4693- updatedAt string, start string, end string,
4694-) *open_api_models.GettableSilence {
4695-
4696- updAt, err := strfmt.ParseDateTime(updatedAt)
4697- if err != nil {
4698- panic(err)
4699- }
4700- strAt, err := strfmt.ParseDateTime(start)
4701- if err != nil {
4702- panic(err)
4703- }
4704- endAt, err := strfmt.ParseDateTime(end)
4705- if err != nil {
4706- panic(err)
4707- }
4708- return &open_api_models.GettableSilence{
4709- Silence: open_api_models.Silence{
4710- StartsAt: &strAt,
4711- EndsAt: &endAt,
4712- Comment: &testComment,
4713- CreatedBy: &createdBy,
4714- },
4715- ID: &id,
4716- UpdatedAt: &updAt,
4717- Status: &open_api_models.SilenceStatus{
4718- State: &state,
4719- },
4720- }
4721-}
4722-
4723-func TestGetSilencesHandler(t *testing.T) {
4724-
4725- updateTime := "2019-01-01T12:00:00+00:00"
4726- silences := []*open_api_models.GettableSilence{
4727- gettableSilence("silence-6-expired", "expired", updateTime,
4728- "2019-01-01T12:00:00+00:00", "2019-01-01T11:00:00+00:00"),
4729- gettableSilence("silence-1-active", "active", updateTime,
4730- "2019-01-01T12:00:00+00:00", "2019-01-01T13:00:00+00:00"),
4731- gettableSilence("silence-7-expired", "expired", updateTime,
4732- "2019-01-01T12:00:00+00:00", "2019-01-01T10:00:00+00:00"),
4733- gettableSilence("silence-5-expired", "expired", updateTime,
4734- "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
4735- gettableSilence("silence-0-active", "active", updateTime,
4736- "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
4737- gettableSilence("silence-4-pending", "pending", updateTime,
4738- "2019-01-01T13:00:00+00:00", "2019-01-01T12:00:00+00:00"),
4739- gettableSilence("silence-3-pending", "pending", updateTime,
4740- "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
4741- gettableSilence("silence-2-active", "active", updateTime,
4742- "2019-01-01T12:00:00+00:00", "2019-01-01T14:00:00+00:00"),
4743- }
4744- sortSilences(open_api_models.GettableSilences(silences))
4745-
4746- for i, sil := range silences {
4747- assertEqualStrings(t, "silence-"+strconv.Itoa(i)+"-"+*sil.Status.State, *sil.ID)
4748- }
4749-}
4750-
4751-func convertDateTime(ts time.Time) *strfmt.DateTime {
4752- dt := strfmt.DateTime(ts)
4753- return &dt
4754-}
4755-
4756-func TestAlertToOpenAPIAlert(t *testing.T) {
4757- var (
4758- start = time.Now().Add(-time.Minute)
4759- updated = time.Now()
4760- active = "active"
4761- fp = "0223b772b51c29e1"
4762- receivers = []string{"receiver1", "receiver2"}
4763-
4764- alert = &types.Alert{
4765- Alert: model.Alert{
4766- Labels: model.LabelSet{"severity": "critical", "alertname": "alert1"},
4767- StartsAt: start,
4768- },
4769- UpdatedAt: updated,
4770- }
4771- )
4772- openAPIAlert := alertToOpenAPIAlert(alert, types.AlertStatus{State: types.AlertStateActive}, receivers)
4773- require.Equal(t, &open_api_models.GettableAlert{
4774- Annotations: open_api_models.LabelSet{},
4775- Alert: open_api_models.Alert{
4776- Labels: open_api_models.LabelSet{"severity": "critical", "alertname": "alert1"},
4777- },
4778- Status: &open_api_models.AlertStatus{
4779- State: &active,
4780- InhibitedBy: []string{},
4781- SilencedBy: []string{},
4782- },
4783- StartsAt: convertDateTime(start),
4784- EndsAt: convertDateTime(time.Time{}),
4785- UpdatedAt: convertDateTime(updated),
4786- Fingerprint: &fp,
4787- Receivers: []*open_api_models.Receiver{
4788- &open_api_models.Receiver{Name: &receivers[0]},
4789- &open_api_models.Receiver{Name: &receivers[1]},
4790- },
4791- }, openAPIAlert)
4792-}
4793diff --git a/api/v2/client/alert/alert_client.go b/api/v2/client/alert/alert_client.go
4794deleted file mode 100644
4795index 7d299c4..0000000
4796--- a/api/v2/client/alert/alert_client.go
4797+++ /dev/null
4798@@ -1,114 +0,0 @@
4799-// Code generated by go-swagger; DO NOT EDIT.
4800-
4801-// Copyright Prometheus Team
4802-// Licensed under the Apache License, Version 2.0 (the "License");
4803-// you may not use this file except in compliance with the License.
4804-// You may obtain a copy of the License at
4805-//
4806-// http://www.apache.org/licenses/LICENSE-2.0
4807-//
4808-// Unless required by applicable law or agreed to in writing, software
4809-// distributed under the License is distributed on an "AS IS" BASIS,
4810-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4811-// See the License for the specific language governing permissions and
4812-// limitations under the License.
4813-//
4814-
4815-package alert
4816-
4817-// This file was generated by the swagger tool.
4818-// Editing this file might prove futile when you re-run the swagger generate command
4819-
4820-import (
4821- "fmt"
4822-
4823- "github.com/go-openapi/runtime"
4824-
4825- strfmt "github.com/go-openapi/strfmt"
4826-)
4827-
4828-// New creates a new alert API client.
4829-func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
4830- return &Client{transport: transport, formats: formats}
4831-}
4832-
4833-/*
4834-Client for alert API
4835-*/
4836-type Client struct {
4837- transport runtime.ClientTransport
4838- formats strfmt.Registry
4839-}
4840-
4841-/*
4842-GetAlerts Get a list of alerts
4843-*/
4844-func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) {
4845- // TODO: Validate the params before sending
4846- if params == nil {
4847- params = NewGetAlertsParams()
4848- }
4849-
4850- result, err := a.transport.Submit(&runtime.ClientOperation{
4851- ID: "getAlerts",
4852- Method: "GET",
4853- PathPattern: "/alerts",
4854- ProducesMediaTypes: []string{"application/json"},
4855- ConsumesMediaTypes: []string{"application/json"},
4856- Schemes: []string{"http"},
4857- Params: params,
4858- Reader: &GetAlertsReader{formats: a.formats},
4859- Context: params.Context,
4860- Client: params.HTTPClient,
4861- })
4862- if err != nil {
4863- return nil, err
4864- }
4865- success, ok := result.(*GetAlertsOK)
4866- if ok {
4867- return success, nil
4868- }
4869- // unexpected success response
4870- // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
4871- msg := fmt.Sprintf("unexpected success response for getAlerts: API contract not enforced by server. Client expected to get an error, but got: %T", result)
4872- panic(msg)
4873-}
4874-
4875-/*
4876-PostAlerts Create new Alerts
4877-*/
4878-func (a *Client) PostAlerts(params *PostAlertsParams) (*PostAlertsOK, error) {
4879- // TODO: Validate the params before sending
4880- if params == nil {
4881- params = NewPostAlertsParams()
4882- }
4883-
4884- result, err := a.transport.Submit(&runtime.ClientOperation{
4885- ID: "postAlerts",
4886- Method: "POST",
4887- PathPattern: "/alerts",
4888- ProducesMediaTypes: []string{"application/json"},
4889- ConsumesMediaTypes: []string{"application/json"},
4890- Schemes: []string{"http"},
4891- Params: params,
4892- Reader: &PostAlertsReader{formats: a.formats},
4893- Context: params.Context,
4894- Client: params.HTTPClient,
4895- })
4896- if err != nil {
4897- return nil, err
4898- }
4899- success, ok := result.(*PostAlertsOK)
4900- if ok {
4901- return success, nil
4902- }
4903- // unexpected success response
4904- // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
4905- msg := fmt.Sprintf("unexpected success response for postAlerts: API contract not enforced by server. Client expected to get an error, but got: %T", result)
4906- panic(msg)
4907-}
4908-
4909-// SetTransport changes the transport on the client
4910-func (a *Client) SetTransport(transport runtime.ClientTransport) {
4911- a.transport = transport
4912-}
4913diff --git a/api/v2/client/alert/get_alerts_parameters.go b/api/v2/client/alert/get_alerts_parameters.go
4914deleted file mode 100644
4915index cba0387..0000000
4916--- a/api/v2/client/alert/get_alerts_parameters.go
4917+++ /dev/null
4918@@ -1,350 +0,0 @@
4919-// Code generated by go-swagger; DO NOT EDIT.
4920-
4921-// Copyright Prometheus Team
4922-// Licensed under the Apache License, Version 2.0 (the "License");
4923-// you may not use this file except in compliance with the License.
4924-// You may obtain a copy of the License at
4925-//
4926-// http://www.apache.org/licenses/LICENSE-2.0
4927-//
4928-// Unless required by applicable law or agreed to in writing, software
4929-// distributed under the License is distributed on an "AS IS" BASIS,
4930-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4931-// See the License for the specific language governing permissions and
4932-// limitations under the License.
4933-//
4934-
4935-package alert
4936-
4937-// This file was generated by the swagger tool.
4938-// Editing this file might prove futile when you re-run the swagger generate command
4939-
4940-import (
4941- "context"
4942- "net/http"
4943- "time"
4944-
4945- "github.com/go-openapi/errors"
4946- "github.com/go-openapi/runtime"
4947- cr "github.com/go-openapi/runtime/client"
4948- "github.com/go-openapi/swag"
4949-
4950- strfmt "github.com/go-openapi/strfmt"
4951-)
4952-
4953-// NewGetAlertsParams creates a new GetAlertsParams object
4954-// with the default values initialized.
4955-func NewGetAlertsParams() *GetAlertsParams {
4956- var (
4957- activeDefault = bool(true)
4958- inhibitedDefault = bool(true)
4959- silencedDefault = bool(true)
4960- unprocessedDefault = bool(true)
4961- )
4962- return &GetAlertsParams{
4963- Active: &activeDefault,
4964- Inhibited: &inhibitedDefault,
4965- Silenced: &silencedDefault,
4966- Unprocessed: &unprocessedDefault,
4967-
4968- timeout: cr.DefaultTimeout,
4969- }
4970-}
4971-
4972-// NewGetAlertsParamsWithTimeout creates a new GetAlertsParams object
4973-// with the default values initialized, and the ability to set a timeout on a request
4974-func NewGetAlertsParamsWithTimeout(timeout time.Duration) *GetAlertsParams {
4975- var (
4976- activeDefault = bool(true)
4977- inhibitedDefault = bool(true)
4978- silencedDefault = bool(true)
4979- unprocessedDefault = bool(true)
4980- )
4981- return &GetAlertsParams{
4982- Active: &activeDefault,
4983- Inhibited: &inhibitedDefault,
4984- Silenced: &silencedDefault,
4985- Unprocessed: &unprocessedDefault,
4986-
4987- timeout: timeout,
4988- }
4989-}
4990-
4991-// NewGetAlertsParamsWithContext creates a new GetAlertsParams object
4992-// with the default values initialized, and the ability to set a context for a request
4993-func NewGetAlertsParamsWithContext(ctx context.Context) *GetAlertsParams {
4994- var (
4995- activeDefault = bool(true)
4996- inhibitedDefault = bool(true)
4997- silencedDefault = bool(true)
4998- unprocessedDefault = bool(true)
4999- )
5000- return &GetAlertsParams{
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: