├── .circleci └── config.yml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── ci.yml │ ├── container_description.yml │ ├── golangci-lint.yml │ ├── mixin.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .promu.yml ├── .yamllint ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── COPYRIGHT.txt ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── Makefile.common ├── NOTICE ├── Procfile ├── README.md ├── RELEASE.md ├── SECURITY.md ├── VERSION ├── api ├── api.go ├── metrics │ └── metrics.go ├── v1_deprecation_router.go └── v2 │ ├── api.go │ ├── api_test.go │ ├── client │ ├── alert │ │ ├── alert_client.go │ │ ├── get_alerts_parameters.go │ │ ├── get_alerts_responses.go │ │ ├── post_alerts_parameters.go │ │ └── post_alerts_responses.go │ ├── alertgroup │ │ ├── alertgroup_client.go │ │ ├── get_alert_groups_parameters.go │ │ └── get_alert_groups_responses.go │ ├── alertmanager_api_client.go │ ├── general │ │ ├── general_client.go │ │ ├── get_status_parameters.go │ │ └── get_status_responses.go │ ├── receiver │ │ ├── get_receivers_parameters.go │ │ ├── get_receivers_responses.go │ │ └── receiver_client.go │ └── silence │ │ ├── delete_silence_parameters.go │ │ ├── delete_silence_responses.go │ │ ├── get_silence_parameters.go │ │ ├── get_silence_responses.go │ │ ├── get_silences_parameters.go │ │ ├── get_silences_responses.go │ │ ├── post_silences_parameters.go │ │ ├── post_silences_responses.go │ │ └── silence_client.go │ ├── compat.go │ ├── models │ ├── alert.go │ ├── alert_group.go │ ├── alert_groups.go │ ├── alert_status.go │ ├── alertmanager_config.go │ ├── alertmanager_status.go │ ├── cluster_status.go │ ├── gettable_alert.go │ ├── gettable_alerts.go │ ├── gettable_silence.go │ ├── gettable_silences.go │ ├── label_set.go │ ├── matcher.go │ ├── matchers.go │ ├── peer_status.go │ ├── postable_alert.go │ ├── postable_alerts.go │ ├── postable_silence.go │ ├── receiver.go │ ├── silence.go │ ├── silence_status.go │ └── version_info.go │ ├── openapi.yaml │ ├── restapi │ ├── configure_alertmanager.go │ ├── doc.go │ ├── embedded_spec.go │ ├── operations │ │ ├── alert │ │ │ ├── get_alerts.go │ │ │ ├── get_alerts_parameters.go │ │ │ ├── get_alerts_responses.go │ │ │ ├── get_alerts_urlbuilder.go │ │ │ ├── post_alerts.go │ │ │ ├── post_alerts_parameters.go │ │ │ ├── post_alerts_responses.go │ │ │ └── post_alerts_urlbuilder.go │ │ ├── alertgroup │ │ │ ├── get_alert_groups.go │ │ │ ├── get_alert_groups_parameters.go │ │ │ ├── get_alert_groups_responses.go │ │ │ └── get_alert_groups_urlbuilder.go │ │ ├── alertmanager_api.go │ │ ├── general │ │ │ ├── get_status.go │ │ │ ├── get_status_parameters.go │ │ │ ├── get_status_responses.go │ │ │ └── get_status_urlbuilder.go │ │ ├── receiver │ │ │ ├── get_receivers.go │ │ │ ├── get_receivers_parameters.go │ │ │ ├── get_receivers_responses.go │ │ │ └── get_receivers_urlbuilder.go │ │ └── silence │ │ │ ├── delete_silence.go │ │ │ ├── delete_silence_parameters.go │ │ │ ├── delete_silence_responses.go │ │ │ ├── delete_silence_urlbuilder.go │ │ │ ├── get_silence.go │ │ │ ├── get_silence_parameters.go │ │ │ ├── get_silence_responses.go │ │ │ ├── get_silence_urlbuilder.go │ │ │ ├── get_silences.go │ │ │ ├── get_silences_parameters.go │ │ │ ├── get_silences_responses.go │ │ │ ├── get_silences_urlbuilder.go │ │ │ ├── post_silences.go │ │ │ ├── post_silences_parameters.go │ │ │ ├── post_silences_responses.go │ │ │ └── post_silences_urlbuilder.go │ └── server.go │ └── testing.go ├── asset ├── asset.go ├── asset_generate.go ├── assets_vfsdata.go └── doc.go ├── cli ├── alert.go ├── alert_add.go ├── alert_query.go ├── check_config.go ├── check_config_test.go ├── cluster.go ├── config.go ├── config │ ├── config.go │ ├── config_test.go │ ├── http_config.go │ └── testdata │ │ ├── amtool.bad.yml │ │ ├── amtool.good1.yml │ │ ├── amtool.good2.yml │ │ ├── http_config.bad.yml │ │ ├── http_config.basic_auth.good.yml │ │ └── http_config.good.yml ├── format │ ├── format.go │ ├── format_extended.go │ ├── format_json.go │ ├── format_simple.go │ └── sort.go ├── root.go ├── routing.go ├── silence.go ├── silence_add.go ├── silence_expire.go ├── silence_import.go ├── silence_query.go ├── silence_update.go ├── template.go ├── template_render.go ├── test_routing.go ├── test_routing_test.go ├── testdata │ ├── conf.bad.yml │ ├── conf.good.yml │ ├── conf.routing-reverted.yml │ └── conf.routing.yml └── utils.go ├── cluster ├── advertise.go ├── advertise_test.go ├── channel.go ├── channel_test.go ├── cluster.go ├── cluster_test.go ├── clusterpb │ ├── cluster.pb.go │ └── cluster.proto ├── connection_pool.go ├── delegate.go ├── testdata │ ├── certs │ │ ├── ca-config.json │ │ ├── ca-csr.json │ │ ├── ca-key.pem │ │ ├── ca.csr │ │ ├── ca.pem │ │ ├── node1-csr.json │ │ ├── node1-key.pem │ │ ├── node1.csr │ │ ├── node1.pem │ │ ├── node2-csr.json │ │ ├── node2-key.pem │ │ ├── node2.csr │ │ └── node2.pem │ ├── empty_tls_config.yml │ ├── tls_config_node1.yml │ ├── tls_config_node2.yml │ ├── tls_config_with_missing_client.yml │ └── tls_config_with_missing_server.yml ├── tls_config.go ├── tls_connection.go ├── tls_connection_test.go ├── tls_transport.go └── tls_transport_test.go ├── cmd ├── alertmanager │ ├── main.go │ └── main_test.go └── amtool │ ├── README.md │ └── main.go ├── config ├── config.go ├── config_test.go ├── coordinator.go ├── coordinator_test.go ├── notifiers.go ├── notifiers_test.go ├── receiver │ ├── receiver.go │ └── receiver_test.go └── testdata │ ├── conf.empty-fields.yml │ ├── conf.good.yml │ ├── conf.group-by-all.yml │ ├── conf.http-config.good.yml │ ├── conf.inhibit-equal-utf8.yml │ ├── conf.inhibit-equal.yml │ ├── conf.nil-match_re-route.yml │ ├── conf.nil-source_match_re-inhibition.yml │ ├── conf.nil-target_match_re-inhibition.yml │ ├── conf.opsgenie-both-file-and-apikey.yml │ ├── conf.opsgenie-default-apikey-file.yml │ ├── conf.opsgenie-default-apikey-old-team.yml │ ├── conf.opsgenie-default-apikey.yml │ ├── conf.opsgenie-no-apikey.yml │ ├── conf.rocketchat-both-token-and-tokenfile.yml │ ├── conf.rocketchat-both-tokenid-and-tokenidfile.yml │ ├── conf.rocketchat-default-token-file.yml │ ├── conf.rocketchat-default-token.yml │ ├── conf.rocketchat-no-token.yml │ ├── conf.slack-both-file-and-url.yml │ ├── conf.slack-default-api-url-file.yml │ ├── conf.slack-no-api-url.yml │ ├── conf.smtp-both-password-and-file.yml │ ├── conf.smtp-no-username-or-password.yml │ ├── conf.smtp-password-global-and-local.yml │ ├── conf.sns-invalid.yml │ ├── conf.sns-topic-arn.yml │ ├── conf.victorops-both-file-and-apikey.yml │ ├── conf.victorops-default-apikey-file.yml │ ├── conf.victorops-default-apikey.yml │ └── conf.victorops-no-apikey.yml ├── dispatch ├── dispatch.go ├── dispatch_test.go ├── route.go └── route_test.go ├── doc ├── alertmanager-mixin │ ├── .gitignore │ ├── .lint │ ├── Makefile │ ├── README.md │ ├── alerts.jsonnet │ ├── alerts.libsonnet │ ├── config.libsonnet │ ├── dashboards.jsonnet │ ├── dashboards.libsonnet │ ├── dashboards │ │ └── overview.libsonnet │ ├── jsonnetfile.json │ ├── jsonnetfile.lock.json │ └── mixin.libsonnet ├── arch.svg ├── arch.xml ├── design │ └── secure-cluster-traffic.md └── examples │ └── simple.yml ├── docs ├── alertmanager.md ├── alerts_api.md ├── configuration.md ├── https.md ├── index.md ├── management_api.md ├── notification_examples.md ├── notifications.md └── overview.md ├── examples ├── ha │ ├── alertmanager.yml │ ├── send_alerts.sh │ └── tls │ │ ├── Makefile │ │ ├── Procfile │ │ ├── README.md │ │ ├── certs │ │ ├── ca-config.json │ │ ├── ca-csr.json │ │ ├── ca-key.pem │ │ ├── ca.csr │ │ ├── ca.pem │ │ ├── node1-csr.json │ │ ├── node1-key.pem │ │ ├── node1.csr │ │ ├── node1.pem │ │ ├── node2-csr.json │ │ ├── node2-key.pem │ │ ├── node2.csr │ │ └── node2.pem │ │ ├── tls_config_node1.yml │ │ └── tls_config_node2.yml └── webhook │ └── echo.go ├── featurecontrol ├── featurecontrol.go └── featurecontrol_test.go ├── go.mod ├── go.sum ├── inhibit ├── inhibit.go ├── inhibit_bench_test.go └── inhibit_test.go ├── matcher ├── compat │ ├── parse.go │ └── parse_test.go ├── compliance │ └── compliance_test.go └── parse │ ├── bench_test.go │ ├── fuzz_test.go │ ├── lexer.go │ ├── lexer_test.go │ ├── parse.go │ ├── parse_test.go │ └── token.go ├── nflog ├── nflog.go ├── nflog_test.go └── nflogpb │ ├── nflog.pb.go │ ├── nflog.proto │ ├── set.go │ └── set_test.go ├── notify ├── discord │ ├── discord.go │ └── discord_test.go ├── email │ ├── email.go │ ├── email_test.go │ └── testdata │ │ ├── auth.yml │ │ └── noauth.yml ├── jira │ ├── jira.go │ ├── jira_test.go │ └── types.go ├── msteams │ ├── msteams.go │ └── msteams_test.go ├── msteamsv2 │ ├── msteamsv2.go │ └── msteamsv2_test.go ├── notify.go ├── notify_test.go ├── opsgenie │ ├── api_key_file │ ├── opsgenie.go │ └── opsgenie_test.go ├── pagerduty │ ├── pagerduty.go │ └── pagerduty_test.go ├── pushover │ ├── pushover.go │ └── pushover_test.go ├── rocketchat │ ├── rocketchat.go │ └── rocketchat_test.go ├── slack │ ├── slack.go │ └── slack_test.go ├── sns │ ├── sns.go │ └── sns_test.go ├── telegram │ ├── telegram.go │ └── telegram_test.go ├── test │ └── test.go ├── util.go ├── util_test.go ├── victorops │ ├── victorops.go │ └── victorops_test.go ├── webex │ ├── webex.go │ └── webex_test.go ├── webhook │ ├── webhook.go │ └── webhook_test.go └── wechat │ ├── wechat.go │ └── wechat_test.go ├── pkg ├── README.md ├── labels │ ├── matcher.go │ ├── matcher_test.go │ ├── parse.go │ └── parse_test.go └── modtimevfs │ └── modtimevfs.go ├── provider ├── mem │ ├── mem.go │ └── mem_test.go └── provider.go ├── scripts ├── compress_assets.sh ├── genproto.sh ├── package_assets.sh └── tools.go ├── silence ├── silence.go ├── silence_bench_test.go ├── silence_test.go └── silencepb │ ├── silence.pb.go │ └── silence.proto ├── store ├── store.go └── store_test.go ├── template ├── Dockerfile ├── Makefile ├── default.tmpl ├── email.html ├── email.tmpl ├── inline-css.js ├── template.go └── template_test.go ├── test ├── cli │ ├── acceptance.go │ ├── acceptance │ │ └── cli_test.go │ ├── collector.go │ └── mock.go └── with_api_v2 │ ├── acceptance.go │ ├── acceptance │ ├── api_test.go │ ├── cluster_test.go │ ├── inhibit_test.go │ ├── send_test.go │ ├── silence_test.go │ ├── utf8_test.go │ └── web_test.go │ ├── collector.go │ └── mock.go ├── timeinterval ├── timeinterval.go └── timeinterval_test.go ├── types ├── types.go └── types_test.go └── ui ├── Dockerfile ├── app ├── .gitignore ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── elm.json ├── favicon.ico ├── index.html ├── lib │ ├── bootstrap-4.0.0-alpha.6-dist │ │ └── css │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ ├── elm-datepicker │ │ └── css │ │ │ └── elm-datepicker.css │ └── font-awesome-4.7.0 │ │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── review │ ├── elm.json │ └── src │ │ └── ReviewConfig.elm ├── src │ ├── Alerts │ │ └── Api.elm │ ├── Data │ │ ├── Alert.elm │ │ ├── AlertGroup.elm │ │ ├── AlertStatus.elm │ │ ├── AlertmanagerConfig.elm │ │ ├── AlertmanagerStatus.elm │ │ ├── ClusterStatus.elm │ │ ├── GettableAlert.elm │ │ ├── GettableSilence.elm │ │ ├── InlineResponse200.elm │ │ ├── Matcher.elm │ │ ├── PeerStatus.elm │ │ ├── PostableAlert.elm │ │ ├── PostableSilence.elm │ │ ├── Receiver.elm │ │ ├── Silence.elm │ │ ├── SilenceStatus.elm │ │ └── VersionInfo.elm │ ├── DateTime.elm │ ├── Main.elm │ ├── Parsing.elm │ ├── Silences │ │ ├── Api.elm │ │ ├── Decoders.elm │ │ └── Types.elm │ ├── Status │ │ ├── Api.elm │ │ └── Types.elm │ ├── Types.elm │ ├── Updates.elm │ ├── Utils │ │ ├── Api.elm │ │ ├── Date.elm │ │ ├── DateTimePicker │ │ │ ├── Types.elm │ │ │ ├── Updates.elm │ │ │ ├── Utils.elm │ │ │ └── Views.elm │ │ ├── Filter.elm │ │ ├── FormValidation.elm │ │ ├── Keyboard.elm │ │ ├── List.elm │ │ ├── Match.elm │ │ ├── String.elm │ │ ├── Types.elm │ │ └── Views.elm │ ├── Views.elm │ └── Views │ │ ├── AlertList │ │ ├── AlertView.elm │ │ ├── Parsing.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── FilterBar │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── GroupBar │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── NavBar │ │ ├── Types.elm │ │ └── Views.elm │ │ ├── NotFound │ │ └── Views.elm │ │ ├── ReceiverBar │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── Settings │ │ ├── Parsing.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── Shared │ │ ├── Alert.elm │ │ ├── AlertCompact.elm │ │ ├── AlertListCompact.elm │ │ ├── Dialog.elm │ │ ├── SilencePreview.elm │ │ └── Types.elm │ │ ├── SilenceForm │ │ ├── Parsing.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── SilenceList │ │ ├── Parsing.elm │ │ ├── SilenceView.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ ├── SilenceView │ │ ├── Parsing.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm │ │ └── Status │ │ ├── Parsing.elm │ │ ├── Types.elm │ │ ├── Updates.elm │ │ └── Views.elm └── tests │ ├── Filter.elm │ ├── Helpers.elm │ ├── Match.elm │ └── StringUtils.elm ├── react-app ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── dist │ ├── 959.js.gz │ ├── 959.js.map.gz │ ├── 97.js.gz │ ├── 97.js.map.gz │ ├── index.html.gz │ ├── main.js.gz │ └── main.js.map.gz ├── embed.go ├── embed.go.tmpl ├── package-lock.json ├── package.json ├── src │ ├── App.tsx │ ├── Router.tsx │ ├── client │ │ └── am-client.ts │ ├── components │ │ └── navbar.tsx │ ├── index.html │ ├── index.tsx │ ├── utils │ │ ├── fetch.ts │ │ └── url-builder.ts │ └── views │ │ └── ViewStatus.tsx ├── tsconfig.json ├── ui.go ├── web.go ├── webpack.common.ts ├── webpack.dev.ts └── webpack.prod.ts └── web.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .tarballs/ 3 | 4 | !.build/linux-amd64/ 5 | !.build/linux-armv7/ 6 | !.build/linux-arm64/ 7 | !.build/linux-ppc64le/ 8 | !.build/linux-s390x/ 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | **What did you do?** 15 | 16 | **What did you expect to see?** 17 | 18 | **What did you see instead? Under which circumstances?** 19 | 20 | **Environment** 21 | 22 | * System information: 23 | 24 | insert output of `uname -srm` here 25 | 26 | * Alertmanager version: 27 | 28 | insert output of `alertmanager --version` here (repeat for each alertmanager 29 | version in your cluster, if relevant to the issue) 30 | 31 | * Prometheus version: 32 | 33 | insert output of `prometheus --version` here (repeat for each prometheus 34 | version in your cluster, if relevant to the issue) 35 | 36 | * Alertmanager configuration file: 37 | ``` 38 | insert configuration here 39 | ``` 40 | 41 | * Prometheus configuration file: 42 | ``` 43 | insert configuration here (if relevant to the issue) 44 | ``` 45 | 46 | * Logs: 47 | ``` 48 | insert Prometheus and Alertmanager logs relevant to the issue here 49 | ``` 50 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | open-pull-requests-limit: 20 6 | schedule: 7 | interval: "monthly" 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | - package-ecosystem: "npm" 13 | directory: "/ui/react-app" 14 | open-pull-requests-limit: 20 15 | schedule: 16 | interval: "monthly" 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: false 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - keepalive 16 | 17 | # Set to true to ignore issues in a project (defaults to false) 18 | exemptProjects: false 19 | 20 | # Set to true to ignore issues in a milestone (defaults to false) 21 | exemptMilestones: false 22 | 23 | # Set to true to ignore issues with an assignee (defaults to false) 24 | exemptAssignees: false 25 | 26 | # Label to use when marking as stale 27 | staleLabel: stale 28 | 29 | # Comment to post when marking as stale. Set to `false` to disable 30 | markComment: false 31 | 32 | # Comment to post when removing the stale label. 33 | # unmarkComment: > 34 | # Your comment here. 35 | 36 | # Comment to post when closing a stale Issue or Pull Request. 37 | # closeComment: > 38 | # Your comment here. 39 | 40 | # Limit the number of actions per hour, from 1-30. Default is 30 41 | limitPerRun: 30 42 | 43 | # Limit to only `issues` or `pulls` 44 | only: pulls 45 | 46 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 47 | # pulls: 48 | # daysUntilStale: 30 49 | # markComment: > 50 | # This pull request has been automatically marked as stale because it has not had 51 | # recent activity. It will be closed if no further activity occurs. Thank you 52 | # for your contributions. 53 | 54 | # issues: 55 | # exemptLabels: 56 | # - confirmed 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | on: # yamllint disable-line rule:truthy 4 | pull_request: 5 | workflow_call: 6 | jobs: 7 | test_frontend: 8 | name: Test alertmanager frontend 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 12 | - run: make clean 13 | - run: make all 14 | working-directory: ./ui/app 15 | - run: make assets 16 | - run: make apiv2 17 | - run: git diff --exit-code 18 | 19 | build: 20 | name: Build Alertmanager for common architectures 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | thread: [0, 1, 2] 25 | steps: 26 | - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 27 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 28 | - uses: ./.github/promci/actions/build 29 | with: 30 | promu_opts: "-p linux/amd64 -p windows/amd64 -p linux/arm64 -p darwin/amd64 -p darwin/arm64 -p linux/386" 31 | parallelism: 3 32 | thread: ${{ matrix.thread }} 33 | 34 | test: 35 | name: Test 36 | runs-on: ubuntu-latest 37 | # Whenever the Go version is updated here, .promu.yml 38 | # should also be updated. 39 | container: 40 | image: quay.io/prometheus/golang-builder:1.24-base 41 | steps: 42 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 43 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 44 | - uses: ./.github/promci/actions/setup_environment 45 | - run: make 46 | - run: git diff --exit-code 47 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This action is synced from https://github.com/prometheus/prometheus 3 | name: golangci-lint 4 | on: 5 | push: 6 | paths: 7 | - "go.sum" 8 | - "go.mod" 9 | - "**.go" 10 | - "scripts/errcheck_excludes.txt" 11 | - ".github/workflows/golangci-lint.yml" 12 | - ".golangci.yml" 13 | pull_request: 14 | 15 | permissions: # added using https://github.com/step-security/secure-repo 16 | contents: read 17 | 18 | jobs: 19 | golangci: 20 | permissions: 21 | contents: read # for actions/checkout to fetch code 22 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | - name: Install Go 29 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 30 | with: 31 | go-version: 1.24.x 32 | - name: Install snmp_exporter/generator dependencies 33 | run: sudo apt-get update && sudo apt-get -y install libsnmp-dev 34 | if: github.repository == 'prometheus/snmp_exporter' 35 | - name: Lint 36 | uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 37 | with: 38 | args: --verbose 39 | version: v1.64.6 40 | -------------------------------------------------------------------------------- /.github/workflows/mixin.yml: -------------------------------------------------------------------------------- 1 | name: mixin 2 | on: 3 | pull_request: 4 | paths: 5 | - "doc/alertmanager-mixin/**" 6 | 7 | jobs: 8 | mixin: 9 | name: mixin-lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 14 | - name: install Go 15 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 16 | with: 17 | go-version: 1.24.x 18 | # pin the mixtool version until https://github.com/monitoring-mixins/mixtool/issues/135 is merged. 19 | - run: go install github.com/monitoring-mixins/mixtool/cmd/mixtool@2282201396b69055bb0f92f187049027a16d2130 20 | - run: go install github.com/google/go-jsonnet/cmd/jsonnetfmt@latest 21 | - run: go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest 22 | - run: make -C doc/alertmanager-mixin lint 23 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | ci: 9 | name: Run ci 10 | uses: ./.github/workflows/ci.yml 11 | 12 | build: 13 | name: Build Alertmanager for all architectures 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | thread: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 18 | needs: ci 19 | steps: 20 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 21 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 22 | - uses: ./.github/promci/actions/build 23 | with: 24 | parallelism: 12 25 | thread: ${{ matrix.thread }} 26 | publish_main: 27 | name: Publish main branch artefacts 28 | runs-on: ubuntu-latest 29 | needs: build 30 | steps: 31 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 32 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 33 | - uses: ./.github/promci/actions/publish_main 34 | with: 35 | docker_hub_login: ${{ secrets.docker_hub_login }} 36 | docker_hub_password: ${{ secrets.docker_hub_password }} 37 | quay_io_login: ${{ secrets.quay_io_login }} 38 | quay_io_password: ${{ secrets.quay_io_password }} 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | tags: 6 | - v* 7 | jobs: 8 | ci: 9 | name: Run ci 10 | uses: ./.github/workflows/ci.yml 11 | 12 | build: 13 | name: Build Alertmanager for all architectures 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | thread: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 18 | needs: ci 19 | steps: 20 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 21 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 22 | - uses: ./.github/promci/actions/build 23 | with: 24 | parallelism: 12 25 | thread: ${{ matrix.thread }} 26 | publish_release: 27 | name: Publish release artefacts 28 | runs-on: ubuntu-latest 29 | needs: build 30 | steps: 31 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 32 | - uses: prometheus/promci@c3c93a50d581b928af720f0134b2b2dad32a6c41 # v0.4.6 33 | - uses: ./.github/promci/actions/publish_release 34 | with: 35 | docker_hub_login: ${{ secrets.docker_hub_login }} 36 | docker_hub_password: ${{ secrets.docker_hub_password }} 37 | quay_io_login: ${{ secrets.quay_io_login }} 38 | quay_io_password: ${{ secrets.quay_io_password }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data/ 2 | /alertmanager 3 | /amtool 4 | *.yml 5 | *.yaml 6 | /.build 7 | /.release 8 | /.tarballs 9 | /vendor 10 | 11 | !.golangci.yml 12 | !/cli/testdata/*.yml 13 | !/cli/config/testdata/*.yml 14 | !/cluster/testdata/*.yml 15 | !/config/testdata/*.yml 16 | !/examples/ha/tls/*.yml 17 | !/notify/email/testdata/*.yml 18 | !/doc/examples/simple.yml 19 | !/circle.yml 20 | !/.travis.yml 21 | !/.promu.yml 22 | !/api/v2/openapi.yaml 23 | !.github/workflows/*.yml 24 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | go: 2 | # Whenever the Go version is updated here, 3 | # .circle/config.yml should also be updated. 4 | version: 1.24 5 | repository: 6 | path: github.com/prometheus/alertmanager 7 | build: 8 | binaries: 9 | - name: alertmanager 10 | path: ./cmd/alertmanager 11 | - name: amtool 12 | path: ./cmd/amtool 13 | tags: 14 | all: 15 | - netgo 16 | windows: [] 17 | ldflags: | 18 | -X github.com/prometheus/common/version.Version={{.Version}} 19 | -X github.com/prometheus/common/version.Revision={{.Revision}} 20 | -X github.com/prometheus/common/version.Branch={{.Branch}} 21 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 22 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 23 | tarball: 24 | files: 25 | - examples/ha/alertmanager.yml 26 | - LICENSE 27 | - NOTICE 28 | crossbuild: 29 | platforms: 30 | - darwin 31 | - dragonfly 32 | - freebsd 33 | - illumos 34 | - linux 35 | - netbsd 36 | - openbsd 37 | - windows 38 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | ignore: | 4 | **/node_modules 5 | 6 | rules: 7 | braces: 8 | max-spaces-inside: 1 9 | level: error 10 | brackets: 11 | max-spaces-inside: 1 12 | level: error 13 | commas: disable 14 | comments: disable 15 | comments-indentation: disable 16 | document-start: disable 17 | indentation: 18 | spaces: consistent 19 | indent-sequences: consistent 20 | key-duplicates: 21 | ignore: | 22 | config/testdata/section_key_dup.bad.yml 23 | line-length: disable 24 | truthy: 25 | check-keys: false 26 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Prometheus Community Code of Conduct 2 | 3 | Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Copyright Prometheus Team 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARCH="amd64" 2 | ARG OS="linux" 3 | FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest 4 | LABEL maintainer="The Prometheus Authors " 5 | LABEL org.opencontainers.image.source="https://github.com/prometheus/alertmanager" 6 | 7 | ARG ARCH="amd64" 8 | ARG OS="linux" 9 | COPY .build/${OS}-${ARCH}/amtool /bin/amtool 10 | COPY .build/${OS}-${ARCH}/alertmanager /bin/alertmanager 11 | COPY examples/ha/alertmanager.yml /etc/alertmanager/alertmanager.yml 12 | 13 | RUN mkdir -p /alertmanager && \ 14 | chown -R nobody:nobody /etc/alertmanager /alertmanager 15 | 16 | USER nobody 17 | EXPOSE 9093 18 | VOLUME [ "/alertmanager" ] 19 | WORKDIR /alertmanager 20 | ENTRYPOINT [ "/bin/alertmanager" ] 21 | CMD [ "--config.file=/etc/alertmanager/alertmanager.yml", \ 22 | "--storage.path=/alertmanager" ] 23 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Simon Pasquier @simonpasquier 2 | * Andrey Kuzmin @w0rm 3 | * Josue Abreu @gotjosh 4 | * George Robinson @grobinson-grafana 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Prometheus Alertmanager 2 | Copyright 2013-2015 The Prometheus Authors 3 | 4 | This product includes software developed at 5 | SoundCloud Ltd. (http://soundcloud.com/). 6 | 7 | 8 | The following components are included in this product: 9 | 10 | Bootstrap 11 | http://getbootstrap.com 12 | Copyright 2011-2014 Twitter, Inc. 13 | Licensed under the MIT License 14 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | 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 2 | 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 3 | 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 4 | wh: go run ./examples/webhook/echo.go 5 | 6 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | This page describes the release process and the currently planned schedule for upcoming releases as well as the respective release shepherd. Release shepherds are chosen on a voluntary basis. 3 | 4 | ## Release Schedule 5 | 6 | Release cadence of first pre-releases being cut is 12 weeks. 7 | 8 | | release series | date (year-month-day) | release shepherd | 9 | |----------------|-----------------------|-------------------------------| 10 | | v0.26 | 2023-08-23 | Josh Abreu (Github: @gotjosh) | 11 | | v0.27 | 2024-02-28 | Josh Abreu (Github: @gotjosh) | 12 | | v0.28 | 2024-05-28 | Josh Abreu (Github: @gotjosh) | 13 | 14 | If you are interested in volunteering please create a pull request against the [prometheus/alertmanager](https://github.com/prometheus/alertmanager) repository and propose yourself for the release of your choice. 15 | 16 | If you'd like to know more about the shepherd responsibilities or the release instructions please [refer to the `RELEASE.MD`](https://github.com/prometheus/prometheus/blob/main/RELEASE.md) in [prometheus/prometheus](https://github.com/prometheus/prometheus). 17 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a security issue 2 | 3 | The Prometheus security policy, including how to report vulnerabilities, can be 4 | found here: 5 | 6 | 7 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.28.1 2 | -------------------------------------------------------------------------------- /api/v2/models/label_set.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | // Copyright Prometheus Team 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package models 18 | 19 | // This file was generated by the swagger tool. 20 | // Editing this file might prove futile when you re-run the swagger generate command 21 | 22 | import ( 23 | "context" 24 | 25 | "github.com/go-openapi/strfmt" 26 | ) 27 | 28 | // LabelSet label set 29 | // 30 | // swagger:model labelSet 31 | type LabelSet map[string]string 32 | 33 | // Validate validates this label set 34 | func (m LabelSet) Validate(formats strfmt.Registry) error { 35 | return nil 36 | } 37 | 38 | // ContextValidate validates this label set based on context it is used 39 | func (m LabelSet) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /api/v2/restapi/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | // Copyright Prometheus Team 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Package restapi Alertmanager API 17 | // 18 | // API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 19 | // Schemes: 20 | // http 21 | // Host: localhost 22 | // BasePath: /api/v2/ 23 | // Version: 0.0.1 24 | // License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html 25 | // 26 | // Consumes: 27 | // - application/json 28 | // 29 | // Produces: 30 | // - application/json 31 | // 32 | // swagger:meta 33 | package restapi 34 | -------------------------------------------------------------------------------- /asset/asset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build dev 15 | // +build dev 16 | 17 | package asset 18 | 19 | import ( 20 | "net/http" 21 | "os" 22 | "strings" 23 | 24 | "github.com/shurcooL/httpfs/filter" 25 | "github.com/shurcooL/httpfs/union" 26 | ) 27 | 28 | var static http.FileSystem = filter.Keep( 29 | http.Dir("../ui/app"), 30 | func(path string, fi os.FileInfo) bool { 31 | return path == "/" || 32 | path == "/script.js" || 33 | path == "/index.html" || 34 | path == "/favicon.ico" || 35 | strings.HasPrefix(path, "/lib") 36 | }, 37 | ) 38 | 39 | var templates http.FileSystem = filter.Keep( 40 | http.Dir("../template"), 41 | func(path string, fi os.FileInfo) bool { 42 | return path == "/" || path == "/default.tmpl" || path == "/email.tmpl" 43 | }, 44 | ) 45 | 46 | // Assets contains the project's assets. 47 | var Assets http.FileSystem = union.New(map[string]http.FileSystem{ 48 | "/templates": templates, 49 | "/static": static, 50 | }) 51 | -------------------------------------------------------------------------------- /asset/asset_generate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build ignore 15 | // +build ignore 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | "time" 22 | 23 | "github.com/shurcooL/vfsgen" 24 | 25 | "github.com/prometheus/alertmanager/asset" 26 | "github.com/prometheus/alertmanager/pkg/modtimevfs" 27 | ) 28 | 29 | func main() { 30 | fs := modtimevfs.New(asset.Assets, time.Unix(1, 0)) 31 | err := vfsgen.Generate(fs, vfsgen.Options{ 32 | PackageName: "asset", 33 | BuildTags: "!dev", 34 | VariableName: "Assets", 35 | }) 36 | if err != nil { 37 | log.Fatalln(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /asset/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Package asset provides the assets via a virtual filesystem. 15 | package asset 16 | 17 | import ( 18 | // The blank import is to make go modules happy. 19 | _ "github.com/shurcooL/vfsgen" 20 | ) 21 | 22 | //go:generate go run -tags=dev asset_generate.go 23 | -------------------------------------------------------------------------------- /cli/alert.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "github.com/alecthomas/kingpin/v2" 18 | ) 19 | 20 | func configureAlertCmd(app *kingpin.Application) { 21 | alertCmd := app.Command("alert", "Add or query alerts.").PreAction(requireAlertManagerURL) 22 | configureQueryAlertsCmd(alertCmd) 23 | configureAddAlertCmd(alertCmd) 24 | } 25 | -------------------------------------------------------------------------------- /cli/check_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | func TestCheckConfig(t *testing.T) { 21 | err := CheckConfig([]string{"testdata/conf.good.yml"}) 22 | if err != nil { 23 | t.Fatalf("checking valid config file failed with: %v", err) 24 | } 25 | 26 | err = CheckConfig([]string{"testdata/conf.bad.yml"}) 27 | if err == nil { 28 | t.Fatalf("failed to detect invalid file.") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cli/cluster.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | 20 | "github.com/alecthomas/kingpin/v2" 21 | 22 | "github.com/prometheus/alertmanager/cli/format" 23 | ) 24 | 25 | const clusterHelp = `View cluster status and peers.` 26 | 27 | // configureClusterCmd represents the cluster command. 28 | func configureClusterCmd(app *kingpin.Application) { 29 | clusterCmd := app.Command("cluster", clusterHelp) 30 | clusterCmd.Command("show", clusterHelp).Default().Action(execWithTimeout(showStatus)).PreAction(requireAlertManagerURL) 31 | } 32 | 33 | func showStatus(ctx context.Context, _ *kingpin.ParseContext) error { 34 | alertManagerStatus, err := getRemoteAlertmanagerConfigStatus(ctx, alertmanagerURL) 35 | if err != nil { 36 | return err 37 | } 38 | formatter, found := format.Formatters[output] 39 | if !found { 40 | return errors.New("unknown output formatter") 41 | } 42 | return formatter.FormatClusterStatus(alertManagerStatus.Cluster) 43 | } 44 | -------------------------------------------------------------------------------- /cli/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | 20 | kingpin "github.com/alecthomas/kingpin/v2" 21 | 22 | "github.com/prometheus/alertmanager/cli/format" 23 | ) 24 | 25 | const configHelp = `View current config. 26 | 27 | The amount of output is controlled by the output selection flag: 28 | - Simple: Print just the running config 29 | - Extended: Print the running config as well as uptime and all version info 30 | - Json: Print entire config object as json 31 | ` 32 | 33 | // configureConfigCmd represents the config command. 34 | func configureConfigCmd(app *kingpin.Application) { 35 | configCmd := app.Command("config", configHelp) 36 | configCmd.Command("show", configHelp).Default().Action(execWithTimeout(queryConfig)).PreAction(requireAlertManagerURL) 37 | configureRoutingCmd(configCmd) 38 | } 39 | 40 | func queryConfig(ctx context.Context, _ *kingpin.ParseContext) error { 41 | status, err := getRemoteAlertmanagerConfigStatus(ctx, alertmanagerURL) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | formatter, found := format.Formatters[output] 47 | if !found { 48 | return errors.New("unknown output formatter") 49 | } 50 | 51 | return formatter.FormatConfig(status) 52 | } 53 | -------------------------------------------------------------------------------- /cli/config/http_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package config 15 | 16 | import ( 17 | "os" 18 | "path/filepath" 19 | 20 | promconfig "github.com/prometheus/common/config" 21 | "gopkg.in/yaml.v2" 22 | ) 23 | 24 | // LoadHTTPConfigFile returns HTTPClientConfig for the given http_config file. 25 | func LoadHTTPConfigFile(filename string) (*promconfig.HTTPClientConfig, error) { 26 | b, err := os.ReadFile(filename) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | httpConfig := &promconfig.HTTPClientConfig{} 32 | err = yaml.UnmarshalStrict(b, httpConfig) 33 | if err != nil { 34 | return nil, err 35 | } 36 | httpConfig.SetDirectory(filepath.Dir(filepath.Dir(filename))) 37 | 38 | return httpConfig, nil 39 | } 40 | -------------------------------------------------------------------------------- /cli/config/testdata/amtool.bad.yml: -------------------------------------------------------------------------------- 1 | BAD 2 | -------------------------------------------------------------------------------- /cli/config/testdata/amtool.good1.yml: -------------------------------------------------------------------------------- 1 | id: id1 2 | url: url1 3 | -------------------------------------------------------------------------------- /cli/config/testdata/amtool.good2.yml: -------------------------------------------------------------------------------- 1 | old-id: id2 2 | url: url2 3 | -------------------------------------------------------------------------------- /cli/config/testdata/http_config.bad.yml: -------------------------------------------------------------------------------- 1 | authorization: 2 | type: Basic 3 | -------------------------------------------------------------------------------- /cli/config/testdata/http_config.basic_auth.good.yml: -------------------------------------------------------------------------------- 1 | basic_auth: 2 | username: user 3 | password: password 4 | -------------------------------------------------------------------------------- /cli/config/testdata/http_config.good.yml: -------------------------------------------------------------------------------- 1 | authorization: 2 | type: Bearer 3 | credentials: theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo 4 | proxy_url: "http://remote.host" 5 | -------------------------------------------------------------------------------- /cli/format/format_json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package format 15 | 16 | import ( 17 | "encoding/json" 18 | "io" 19 | "os" 20 | 21 | "github.com/prometheus/alertmanager/api/v2/models" 22 | ) 23 | 24 | type JSONFormatter struct { 25 | writer io.Writer 26 | } 27 | 28 | func init() { 29 | Formatters["json"] = &JSONFormatter{writer: os.Stdout} 30 | } 31 | 32 | func (formatter *JSONFormatter) SetOutput(writer io.Writer) { 33 | formatter.writer = writer 34 | } 35 | 36 | func (formatter *JSONFormatter) FormatSilences(silences []models.GettableSilence) error { 37 | enc := json.NewEncoder(formatter.writer) 38 | return enc.Encode(silences) 39 | } 40 | 41 | func (formatter *JSONFormatter) FormatAlerts(alerts []*models.GettableAlert) error { 42 | enc := json.NewEncoder(formatter.writer) 43 | return enc.Encode(alerts) 44 | } 45 | 46 | func (formatter *JSONFormatter) FormatConfig(status *models.AlertmanagerStatus) error { 47 | enc := json.NewEncoder(formatter.writer) 48 | return enc.Encode(status) 49 | } 50 | 51 | func (formatter *JSONFormatter) FormatClusterStatus(status *models.ClusterStatus) error { 52 | enc := json.NewEncoder(formatter.writer) 53 | return enc.Encode(status) 54 | } 55 | -------------------------------------------------------------------------------- /cli/format/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package format 15 | 16 | import ( 17 | "bytes" 18 | "net" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/prometheus/alertmanager/api/v2/models" 23 | ) 24 | 25 | type ByEndAt []models.GettableSilence 26 | 27 | func (s ByEndAt) Len() int { return len(s) } 28 | func (s ByEndAt) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 29 | func (s ByEndAt) Less(i, j int) bool { 30 | return time.Time(*s[i].Silence.EndsAt).Before(time.Time(*s[j].Silence.EndsAt)) 31 | } 32 | 33 | type ByStartsAt []*models.GettableAlert 34 | 35 | func (s ByStartsAt) Len() int { return len(s) } 36 | func (s ByStartsAt) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 37 | func (s ByStartsAt) Less(i, j int) bool { 38 | return time.Time(*s[i].StartsAt).Before(time.Time(*s[j].StartsAt)) 39 | } 40 | 41 | type ByAddress []*models.PeerStatus 42 | 43 | func (s ByAddress) Len() int { return len(s) } 44 | func (s ByAddress) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 45 | func (s ByAddress) Less(i, j int) bool { 46 | ip1, port1, _ := net.SplitHostPort(*s[i].Address) 47 | ip2, port2, _ := net.SplitHostPort(*s[j].Address) 48 | if ip1 == ip2 { 49 | p1, _ := strconv.Atoi(port1) 50 | p2, _ := strconv.Atoi(port2) 51 | return p1 < p2 52 | } 53 | return bytes.Compare(net.ParseIP(ip1), net.ParseIP(ip2)) < 0 54 | } 55 | -------------------------------------------------------------------------------- /cli/silence.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "github.com/alecthomas/kingpin/v2" 18 | ) 19 | 20 | // configureSilenceCmd represents the silence command. 21 | func configureSilenceCmd(app *kingpin.Application) { 22 | silenceCmd := app.Command("silence", "Add, expire or view silences. For more information and additional flags see query help").PreAction(requireAlertManagerURL) 23 | configureSilenceAddCmd(silenceCmd) 24 | configureSilenceExpireCmd(silenceCmd) 25 | configureSilenceImportCmd(silenceCmd) 26 | configureSilenceQueryCmd(silenceCmd) 27 | configureSilenceUpdateCmd(silenceCmd) 28 | } 29 | -------------------------------------------------------------------------------- /cli/silence_expire.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | 20 | "github.com/alecthomas/kingpin/v2" 21 | "github.com/go-openapi/strfmt" 22 | 23 | "github.com/prometheus/alertmanager/api/v2/client/silence" 24 | ) 25 | 26 | type silenceExpireCmd struct { 27 | ids []string 28 | } 29 | 30 | func configureSilenceExpireCmd(cc *kingpin.CmdClause) { 31 | var ( 32 | c = &silenceExpireCmd{} 33 | expireCmd = cc.Command("expire", "expire an alertmanager silence") 34 | ) 35 | expireCmd.Arg("silence-ids", "Ids of silences to expire").StringsVar(&c.ids) 36 | expireCmd.Action(execWithTimeout(c.expire)) 37 | } 38 | 39 | func (c *silenceExpireCmd) expire(ctx context.Context, _ *kingpin.ParseContext) error { 40 | if len(c.ids) < 1 { 41 | return errors.New("no silence IDs specified") 42 | } 43 | 44 | amclient := NewAlertmanagerClient(alertmanagerURL) 45 | 46 | for _, id := range c.ids { 47 | params := silence.NewDeleteSilenceParams().WithContext(ctx) 48 | params.SilenceID = strfmt.UUID(id) 49 | _, err := amclient.Silence.DeleteSilence(params) 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cli/template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cli 15 | 16 | import ( 17 | "github.com/alecthomas/kingpin/v2" 18 | ) 19 | 20 | // configureTemplateCmd represents the template command. 21 | func configureTemplateCmd(app *kingpin.Application) { 22 | templateCmd := app.Command("template", "Render template files.") 23 | configureTemplateRenderCmd(templateCmd) 24 | } 25 | -------------------------------------------------------------------------------- /cli/testdata/conf.bad.yml: -------------------------------------------------------------------------------- 1 | BAD 2 | -------------------------------------------------------------------------------- /cli/testdata/conf.good.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | 4 | templates: 5 | - '/etc/alertmanager/template/*.tmpl' 6 | 7 | route: 8 | receiver: default 9 | 10 | receivers: 11 | - name: default 12 | -------------------------------------------------------------------------------- /cli/testdata/conf.routing-reverted.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | 4 | templates: 5 | - '/etc/alertmanager/template/*.tmpl' 6 | 7 | route: 8 | receiver: default 9 | routes: 10 | - match: 11 | test: 2 12 | receiver: test2 13 | continue: true 14 | - match_re: 15 | test: ^[12]$ 16 | receiver: test1 17 | continue: true 18 | 19 | receivers: 20 | - name: default 21 | - name: test1 22 | - name: test2 23 | -------------------------------------------------------------------------------- /cli/testdata/conf.routing.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | 4 | templates: 5 | - '/etc/alertmanager/template/*.tmpl' 6 | 7 | route: 8 | receiver: default 9 | routes: 10 | - match_re: 11 | test: ^[12]$ 12 | receiver: test1 13 | continue: true 14 | - match: 15 | test: 2 16 | receiver: test2 17 | 18 | receivers: 19 | - name: default 20 | - name: test1 21 | - name: test2 22 | -------------------------------------------------------------------------------- /cluster/clusterpb/cluster.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package clusterpb; 4 | import "gogoproto/gogo.proto"; 5 | 6 | option (gogoproto.marshaler_all) = true; 7 | option (gogoproto.sizer_all) = true; 8 | option (gogoproto.unmarshaler_all) = true; 9 | option (gogoproto.goproto_getters_all) = false; 10 | 11 | message Part { 12 | string key = 1; 13 | bytes data = 2; 14 | } 15 | 16 | message FullState { 17 | repeated Part parts = 1 [(gogoproto.nullable) = false]; 18 | } 19 | 20 | message MemberlistMessage { 21 | string version = 1; 22 | enum Kind { 23 | STREAM = 0; 24 | PACKET = 1; 25 | } 26 | Kind kind = 2; 27 | string from_addr = 3; 28 | bytes msg = 4; 29 | } 30 | -------------------------------------------------------------------------------- /cluster/testdata/certs/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "876000h" 5 | }, 6 | "profiles": { 7 | "massl": { 8 | "usages": ["signing", "key encipherment", "server auth", "client auth"], 9 | "expiry": "876000h" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cluster/testdata/certs/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "massl", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "massl", 12 | "OU": "VIC", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /cluster/testdata/certs/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLp 3 | nZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCb 4 | ChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8u 5 | hEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToC 6 | va+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6 7 | rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQABAoIBAQCwcL1vXUq7W4UD 8 | OaRtbWrQ0dk0ETBnxT/E0y33fRJ8GZovWM2EXSVEuukSP+uEQ5elNYeWqo0fi3cT 9 | ruvJSnMw9xPyXVDq+4C8slW3R1TqTK683VzvUizM4KC5qIyCpn1KBbgHrh6E7Sp1 10 | e4cIuaawVN3qIg5qThmx2YA4nBIcEt68q9cpy3NgEe+EQf44zM/St+y8kSkDUOVw 11 | fNKX0WfZ/hPL1TAYpWiIgSf+m/V3d/1l/scvMYONcuSjXSORCyoeAWYtOQgf78wW 12 | 9j3kiBTaqDYCUZFnY/ltlZrm8ltAaKVJ0MmPKjVh8GJBXZp9fSVU8Y4ZIZRSeuBA 13 | OoStHGAdAoGBAMluMIE33hGny2V0dNzW23D84eXQK38AsdP632jQeuzxBknItg45 14 | qAfzh8F8W10DQnSv5tj0bmOHfo0mG09bu9eo5nLLINOE7Ju/7ly/76RNJNJ4ADjx 15 | JKZi/PpvfP+s/fzel0X3OPgA+CJKzUHuqlU4V9BLc7focZAYtaM2w7rHAoGBAOzU 16 | eXpapkqYhbYRcsrVV57nZV0rLzsLVJBpJg2zC8un95ALrr0rlZfuPJfOCY/uuS1w 17 | f8ixRz2MkRWGreLHy35NB4GV0sF9VPn1jMp9SuBNvO0JRUMWuDAdVe8SCjXadrOh 18 | +m3yKJSkFKDchglUYnZKV1skgA/b9jjjnu2fvd0TAoGAVUTnFZxvzmuIp78fxWjS 19 | 5ka23hE8iHvjy4e00WsHzovNjKiBoQ35Orx16ItbJcm+dSUNhSQcItf104yhHPwJ 20 | Tab7PvcMQ15OxzP9lJfPu27Iuqv/9Bro1+Kpkt5lPNqffk9AHGcmX54RbHrb3yBI 21 | TOEYE14Nc3nbsRM0uQ3y13sCgYB5Om4QZpSWvKo9P4M+NqTKb3JglblwhOU9osVa 22 | 39ra3dkIgCJrLQM/KTEVF9+nMLDThLG0fqKT6/9cQHuECXet6Co+d/3RE6HK7Zmr 23 | ESWh2ckqoMM2i0uvPWT+ooJdfL2kR/bUDtAc/jyc9yUZY3ufR4Cd4/o1pAfOqR1y 24 | T4G1xwKBgQChE4VWawCVg2qanRjvZcdNk0zpZx4dxqqKYq/VHuSfjNLQixIZsgXT 25 | xx9BHuORn6c/nurqEStLwN3BzbpPU/j6YjMUmTslSH2sKhHwWNYGBZC52aJiOOda 26 | Bz6nAkihG0n2PjYt2T84w6FWHgLJuSsmiEVJcb+AOdyKh1MlzJiwMQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cluster/testdata/certs/ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMx 4 | DjAMBgNVBAMTBW1hc3NsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 5 | uljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM 6 | 9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7 7 | BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPX 8 | Kw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/ 9 | BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6rBANYsfojvyCXuyt 10 | Wnj04mvdAWwmFh0hhq+nxQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAJFmooMt 11 | TocElxCb3DGJTRUXxr4DqcATASIX35a2wV3MmPqUHHXr6BQkO/FRho66EsZf3DE/ 12 | mumou01K+KByxgsmw04CACjSeZ2t/g6pAsDCKrx/BwL3tAo09lG2Y2Ah0BND2Cta 13 | EZpTliU2MimZlk7UZb8VIXh2Tx56fZRoHLzO4U4+FY8ZR+tspxPRM7hLg/aUqA5D 14 | zGj6kByX8aYjxsmQokP4rx/w2mz6vwt4cZ1pXwr0RderkMIh9Har/0k9X1WIAP61 15 | PNQx74qnaq+icjtN2+8gvJE/CJL/wfcwW6kQwEtX1xsTpnzyFaRoYpSPQrvkCtiW 16 | +WzgnOh7RvKyAYI= 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /cluster/testdata/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDlDCCAnygAwIBAgIUetcc8yLasSAxvyUWtgTVKR6jfBIwDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMB4XDTIxMDUwNTE2MTYwMFoXDTI2MDUwNDE2MTYwMFowYjELMAkGA1UE 6 | BhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxDjAM 7 | BgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMTBW1hc3NsMIIBIjAN 8 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9db 9 | gwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13Q 10 | roK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbh 11 | NnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ 12 | 7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgb 13 | xBgy9MZQEot88d1T2XH6rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQAB 14 | o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU 15 | d4DTElKq6gnGYDJZgJvC+4flrZAwDQYJKoZIhvcNAQELBQADggEBAHYaMxcyVdFP 16 | QZJHDP6x+A1hHeZy/kaL2VPzUIOgt3ONEvjhsJblFjLaTWwXFJwxVHXRsQjqrURj 17 | dD2VIkMbCJM4WN8odi6PdHscL0DwHKzwZDapQxsOUYcPBbR2EsBPRyWSoJmBnEyZ 18 | 0TANtyUcZNWc7/C4GG66SWypu1YRdtcaDiEOVkdetGWuo5bw1J7B7isQ4J4a3+Qn 19 | iTKId/7wWBY95JS2xJuOeEjk/ty74ruSapk2Aip1GRSRXMDtD0XeO5dpxc6eIU3C 20 | jbRpUGqCdSAfzxQTlxQAMEKdgRlt7lzMFX/PAXagDOMC9D/6APn5fquEV2d9wMWs 21 | 0N9UCwKftAU= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node1-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "system:server", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "system:node1", 12 | "OU": "massl", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA1b9bm4rvDtpYsqgtCC52+L535d4/Q2O10fWD2i2CfRXXfYJQ 3 | 5cr4AV2iqScFsJSs7KwyQde/c4VWj/vEA2/SJHZFBlknKdCcrgHVebrvnzm6Guze 4 | ICutZSKocFXy9Kw+YmWuA64nHVfmSCKG07GhXhEsLsSCn4PTDYOiGAUm1GdSDxUp 5 | 8yUXec13Eb20mld0xE9kQnCnEWRnxMXtQJXoz9lpLc7DgXtN6nCXSG/CqdDPOduU 6 | nmseaxyAGpAFnUmxcqUuYAJUQ1hUOJhk0RVSsLTmu+FGdOxk79AxmmKQ2z9l/GuA 7 | VikVJGTxY4jRPezxHQ3bdqzzCIdJxTxLinftZQIDAQABAoIBADpxQtvphemau8vF 8 | feKRycfDVEcOmF+VoL4SkgWSke4fjbbsbbAW6e59qp7zY3PfgtSHVIp6Mgek+oEN 9 | xo9mAKAlkkPlFncxadWN/M921FPF1ePMxgMnzhYr/sAQUAikG76NrKGm+VzljrpE 10 | bnbtR4DP0zPKWSjCQ2+bgTNuHSrPwUtEngVT6ugjfWU1RitlvjTsZ9hSuOSBlS7P 11 | rjbQGaEh53PraDut8PIlF4wIF+nLeERFP/a6DC8Btpbv9P50YRosag6yU/G+OYX9 12 | spvBPvRJGrubslKnNRz9AcjbVd3QhL+Tm7mV7iakK918jLWb95Ro4WW+9lT6IAi6 13 | xRSOr9UCgYEA5wI3JhKkYa4PST7ALqmJSDkPH8+tctiEx+ovmnqBufFuLWFoO/rc 14 | EOYslnaZM3UVCnhrFv7+LxezSI5DyQu8dBEzf0RMICvXUNBkGC7ZJQL428fjXPhX 15 | 8mZIoJ0ol4hbamr8yTYlK0vGTwqN1bDj71w6NszuN4ecN1cKNWsMbnMCgYEA7N8Y 16 | MzHWNijMr7xZ1lXl4Ye9+kp3aTUjUYBBaxFr4bQ8y0fH48lzq3qOso5wgvp0DKYo 17 | uemD5QKbo81LKoTRLa+nxPq0UqKm9FiSWmnrcxMuph989oZ1ZFHA2B+nvbuMTF8J 18 | 8sESclTSbgkG87DpycJOUwG3XAcXM+80pXuzJscCgYB+Dzxu/09KqoRW8PJIxGVQ 19 | zypMrrS07iiPO2FcyCtQf8oi43vQ91Ttt91vAisZ5HNl8k5mDyJAKovANToSVOAy 20 | 6kwSz/9GswXdaMqmU7JVOyj4Lj0JN9AuS9ioJPrIrjVMfjORzYU8+i2uZlD94niP 21 | 3uE5lF0OWmdJ36qHefIftwKBgQDcPQZcO19H1iGS2FbTYeSvEK5ENM7YRG8FTXIF 22 | 4hnjrtjDzYb+tYVWEErznFrifYo/ZJMDYSqgWQ9reusDqqBvkR41mUDmgJMpJ91U 23 | MZ2YzmIWVbqz4QrvbtAWY0Bsuh/VtpwiWQAUy+coJj6PgJOvY3m91h+tcm5RfHz/ 24 | zIcjawKBgA6kDcOLOnWcvhP3XwtW5dcWlNuBBNEKna+kIT/5Vlrh91y2w7F54DNK 25 | i0w5CZCpbTugJmZ67XLHnfongC7e2vAQ3atoT96RU4mf9614qs9LMtGAbnuCLB8+ 26 | sT2rnaZKtzr83ensbYkbBxP/zmPBfFQ9FKcIYIA7En8zAIr2T3vJ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node1.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMTEOMAwGA1UE 4 | CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQDVv1ubiu8O2liyqC0ILnb4vnfl3j9DY7XR9YPaLYJ9 6 | Fdd9glDlyvgBXaKpJwWwlKzsrDJB179zhVaP+8QDb9IkdkUGWScp0JyuAdV5uu+f 7 | Oboa7N4gK61lIqhwVfL0rD5iZa4DricdV+ZIIobTsaFeESwuxIKfg9MNg6IYBSbU 8 | Z1IPFSnzJRd5zXcRvbSaV3TET2RCcKcRZGfExe1AlejP2WktzsOBe03qcJdIb8Kp 9 | 0M8525Seax5rHIAakAWdSbFypS5gAlRDWFQ4mGTRFVKwtOa74UZ07GTv0DGaYpDb 10 | P2X8a4BWKRUkZPFjiNE97PEdDdt2rPMIh0nFPEuKd+1lAgMBAAGgLTArBgkqhkiG 11 | 9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B 12 | AQsFAAOCAQEAW/tTyJaBfWtbC9hYUmhh8lxUztv2+WT4xaR/jdQ46sk/87vKuwI6 13 | 4AkkGfiPLLqgW3xbQOwk5/ynRabttbsgTUHt744RtRFLzfcQKEBZoNPvrfHvmDil 14 | YqHIOx2SJ5hzIBwVlVSBn50hdSSED1Ip22DaU8GukzuacB8+2rhg3MOWJbKVt5aR 15 | 03H4XkAynLS1FHNOraDIv1eT58D3l4hanrNOZIa0xAuChd25qLO/JHvU/3wccGUA 16 | KNg3vGOy2Q8qVBrTFLn+yQHuOr/wSupXESO1jiI/h+txsBQnZ6oYfZnVJ+7o3Oln 17 | 3Hguw77aYeTAeZQPPbmJbDLegLG0ZC6RmA== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAjCCAuqgAwIBAgIUbYMGwSgQF8iRZ5xmhflInj8VZ0owDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD 6 | VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV 7 | MBMGA1UEChMMc3lzdGVtOm5vZGUxMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN 8 | c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANW/ 9 | W5uK7w7aWLKoLQgudvi+d+XeP0NjtdH1g9otgn0V132CUOXK+AFdoqknBbCUrOys 10 | MkHXv3OFVo/7xANv0iR2RQZZJynQnK4B1Xm67585uhrs3iArrWUiqHBV8vSsPmJl 11 | rgOuJx1X5kgihtOxoV4RLC7Egp+D0w2DohgFJtRnUg8VKfMlF3nNdxG9tJpXdMRP 12 | ZEJwpxFkZ8TF7UCV6M/ZaS3Ow4F7Tepwl0hvwqnQzznblJ5rHmscgBqQBZ1JsXKl 13 | LmACVENYVDiYZNEVUrC05rvhRnTsZO/QMZpikNs/ZfxrgFYpFSRk8WOI0T3s8R0N 14 | 23as8wiHScU8S4p37WUCAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l 15 | BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE 16 | FGprx5v+KrO4DeOtA6kps4BL/zKyMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb 17 | wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF 18 | AAOCAQEAmWTdMLyWOrNAS0uY+u3FUV3Hm50xF1PfxbT6wK1hu6vH6B63E0o9K2/1 19 | U25Ie8Y2IzFocKMvbqC+mrY56G0bWoUlMONhthYqm8uTKtjlFO33A9I7WIT9Tw+B 20 | nnwZZO7+Ljkd30qSzBinCjrIEx31Vq2pr54ungd8+wK8nfz/zdZnJcqxcN9zvCXB 21 | GTE8yCuqGWKk/oDuIzVjr73U0QaWi+vThqJtBjhOIWQHHVJwbIyhuYzUaivgZPYB 22 | 8eKXWk4JH3eAcq5z5koNGyCcZd/k4WnvxZYxNBAkoQ6AWVfEMGOCaRjD1FTnMbpG 23 | BW79ndJqLmn8OH+DeCnSWhTWxAgg+Q== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node2-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "system:server", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "system:node2", 12 | "OU": "massl", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node2-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtCtzT9vhRMTbhAg/pm8eBn+4IvVQeVqnHoEon9IKIx5fyvqS 3 | Q6Ui3xSik9kJq5FSAa1mScajJwfB1o6ycaSP6n+Q88Py4v7q65n0stCHoJCH0uPw 4 | MQyEhwX7nNilV9C4UZTyZ2StDdAjmMBHiN81EJAqH2d4Xtgrd/IIWhljSXm+aPbu 5 | QjSz8BtR/7+MswrCdlJ8y6gWi020kt6GSHjmaxI1jStGvBxxksK86v3J97wfNwWY 6 | 7GJi70uBrvO0pk5bYckDzUTKeN1QGvBnZ8uDXs7pPysvftJr85GzX0iE9YLMDxO3 7 | qc/PlwCdxM8H6gHTTkLPizGZtpMF9Z497pW9YQIDAQABAoIBAFfQwdCPxHmnVbNB 8 | 7fwqNsFGKTLozMOJeuE0ZN+ZGZXKbTha70WHTLrcrO1RIRR9rTHiGXQmHEmez0zL 9 | mpAnfHn4mWcm/9DCHTCehpVNbH3HVFxm+yB9EG9bbCsjsVtfASfKaGgauvp7k44V 10 | UgiVeqDLE6zg2tunk3BQCOAZdbpOiXrdvoZiGx2Q4SMLPfzmfIyH4BUT836pLTmp 11 | o6/yNiFqQWfCgjeEAOQor4TcdzYIT+3wP51HfAjhZKMIvmjwL16ov1/QpmWRD4ni 12 | 4svzYpeMYpl5OrZkKeDS4ZIQBGjxk+fzPmfFUbfVRSI2gDORsah8HoRVI4LnwKWn 13 | 7kQDv0ECgYEA6V+KVb8bPzCZNbroEZFdug6YtT4yv5Mj3/kpMTIvA3vtu02v8e7F 14 | O56yT43QfUZA0Ar37O0HQ6mbpPsRE5RSr70i40RR+slMZVHX/AQViG7oQJGBijPt 15 | 1tFdLnb+1wSON3jYt2975Kw2IfgOXprWtEmL5zGuplEUjx9Lbdf1HjkCgYEAxaNe 16 | XgXdAiWFoY4Qq6xBRO/WNZCdn3Ysqx6snCtDRilxeNyDoE/6x2Ma9/NRBtIiulAb 17 | s09vDRfJKLbzocUhIn8BQ+GkbAS/A6+x2vcuGhK3F84xqZdbrCqvqdJS8K824jug 18 | vUCfCBJlyNRDz8kEsN5odLM1xkij93Jv23HvGGkCgYEAptcz6ctfalSPI9eEs5KO 19 | REbNK73UwBssaaISreYnsED4G5EVuUuvW8k/xxomtHj2OwWsa4ilSd1GtbL8aVf/ 20 | qT35ZCrixP0GjeTuGXC+CDTp+8dKqggoAAzbpi1SUVwjZEsT/EhKdZgcdzqE42Ol 21 | HWz7BQUCzEpo/U0tOtFKnxkCgYEAi05Vy8wyNbsg7/jlAzyNXPv4bxUaJTX00kDy 22 | xbkw2BmKI/i6xprZVwUiEzdsG3SuicjBXahVzFLBtXMPUy1R57DBwYkgjgriYMTM 23 | hlzIIBSk/aCXHMTVFwuXegoH8CJwexIwgHU2I0hkeiQ0EBfOuKRr2CYhdzvoZxhA 24 | g9tQ/lECgYAjPYoXfNI3rHCWUmaD5eDJZpE0xuJeiiy5auojykdAc7vVapNaIyMK 25 | G3EaU44RtXcSwH19TlH9UCm3MH1QiIwaBOzGcKj3Ut6ZyFKuWDUk4yqvps3uZU/h 26 | h16Tp49Ja7/4LY1uuEngg1KMEiWgk5jiU7G0H9zrtEiTj9c3FDKDvg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node2.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMjEOMAwGA1UE 4 | CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQC0K3NP2+FExNuECD+mbx4Gf7gi9VB5WqcegSif0goj 6 | Hl/K+pJDpSLfFKKT2QmrkVIBrWZJxqMnB8HWjrJxpI/qf5Dzw/Li/urrmfSy0Ieg 7 | kIfS4/AxDISHBfuc2KVX0LhRlPJnZK0N0COYwEeI3zUQkCofZ3he2Ct38ghaGWNJ 8 | eb5o9u5CNLPwG1H/v4yzCsJ2UnzLqBaLTbSS3oZIeOZrEjWNK0a8HHGSwrzq/cn3 9 | vB83BZjsYmLvS4Gu87SmTlthyQPNRMp43VAa8Gdny4Nezuk/Ky9+0mvzkbNfSIT1 10 | gswPE7epz8+XAJ3EzwfqAdNOQs+LMZm2kwX1nj3ulb1hAgMBAAGgLTArBgkqhkiG 11 | 9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B 12 | AQsFAAOCAQEARh0Pi36mNmyprU4j25GWNqQYCJ6cBGnaPeiwr8/F3rsGsF4LTQdP 13 | xW2oBrEWyYRidNCkSMrPkcSiXu1Loy9APwSAXgJZWMYy0Ccdbd3P7dtGNOZkKaLA 14 | QKntGA5E1YAbzNhlt7NviGpqZ49K2aOgcGBTnDZ7xDzmg4uo3tcHgzOCwarYZT8l 15 | qVpc3jAyxRBOrxVKPZNFb4hAFvUm8k6/Etn5n4otN0JT3KGewbfQY50CxW5ShK52 16 | QCs2PmFMYHHmG11FD3W755MxzhL6UmMy20GUgWWthGmR1LugcBgDtWO/7bqqC9tT 17 | XYDTDJ1j0g3Y0cvy2+kltrams4lGE3xs6g== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /cluster/testdata/certs/node2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAjCCAuqgAwIBAgIUex5xEYsDJPUg8idU0Sql2ixGdTwwDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD 6 | VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV 7 | MBMGA1UEChMMc3lzdGVtOm5vZGUyMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN 8 | c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQr 9 | c0/b4UTE24QIP6ZvHgZ/uCL1UHlapx6BKJ/SCiMeX8r6kkOlIt8UopPZCauRUgGt 10 | ZknGoycHwdaOsnGkj+p/kPPD8uL+6uuZ9LLQh6CQh9Lj8DEMhIcF+5zYpVfQuFGU 11 | 8mdkrQ3QI5jAR4jfNRCQKh9neF7YK3fyCFoZY0l5vmj27kI0s/AbUf+/jLMKwnZS 12 | fMuoFotNtJLehkh45msSNY0rRrwccZLCvOr9yfe8HzcFmOxiYu9Lga7ztKZOW2HJ 13 | A81EynjdUBrwZ2fLg17O6T8rL37Sa/ORs19IhPWCzA8Tt6nPz5cAncTPB+oB005C 14 | z4sxmbaTBfWePe6VvWECAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l 15 | BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE 16 | FDNgivphLRqKzV8n29GJq6S2I+CQMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb 17 | wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF 18 | AAOCAQEAnNG3nzycALGf+N8PuG4sUIkD+SYA1nOEgfD2KiGNyuTYHhGgFXTw8KzB 19 | olH05VidldBvC0+pl5EqZAp9qdzpw6Z5Mb0gdoZY6TeKDUo022G3BHLMUGLp8y+i 20 | KE6+awwgdJZ6vPbdnWAh7VM/HCUrGIIPmLFan13j/2RiMfaDxdMAowPmbVc8MLgA 21 | JHI6pPo8D1DacEvMM09qGtwQEUoREOWJ/SzTWl1nc/IAS1yOL1LCyKLcoj/HWqjG 22 | 3LXficQ7rf+Cpn1GnrKwMziT0OLDLxOs/+5d3nFSLxqF1lpykhPPkmHOHnuY8sMX 23 | Qdndn9QILdp5GNvqiVNQYcQa/gOb6g== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /cluster/testdata/empty_tls_config.yml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cluster/testdata/tls_config_node1.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: "certs/node1.pem" 3 | key_file: "certs/node1-key.pem" 4 | client_ca_file: "certs/ca.pem" 5 | client_auth_type: "VerifyClientCertIfGiven" 6 | tls_client_config: 7 | cert_file: "certs/node1.pem" 8 | key_file: "certs/node1-key.pem" 9 | ca_file: "certs/ca.pem" 10 | -------------------------------------------------------------------------------- /cluster/testdata/tls_config_node2.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: "certs/node2.pem" 3 | key_file: "certs/node2-key.pem" 4 | client_ca_file: "certs/ca.pem" 5 | client_auth_type: "VerifyClientCertIfGiven" 6 | tls_client_config: 7 | cert_file: "certs/node2.pem" 8 | key_file: "certs/node2-key.pem" 9 | ca_file: "certs/ca.pem" 10 | -------------------------------------------------------------------------------- /cluster/testdata/tls_config_with_missing_client.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: "certs/node2.pem" 3 | key_file: "certs/node2-key.pem" 4 | client_ca_file: "certs/ca.pem" 5 | client_auth_type: "VerifyClientCertIfGiven" 6 | -------------------------------------------------------------------------------- /cluster/testdata/tls_config_with_missing_server.yml: -------------------------------------------------------------------------------- 1 | tls_client_config: 2 | cert_file: "certs/node1.pem" 3 | key_file: "certs/node1-key.pem" 4 | ca_file: "certs/ca.pem" 5 | -------------------------------------------------------------------------------- /cluster/tls_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package cluster 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "path/filepath" 20 | 21 | "github.com/prometheus/common/config" 22 | "github.com/prometheus/exporter-toolkit/web" 23 | "gopkg.in/yaml.v2" 24 | ) 25 | 26 | type TLSTransportConfig struct { 27 | TLSServerConfig *web.TLSConfig `yaml:"tls_server_config"` 28 | TLSClientConfig *config.TLSConfig `yaml:"tls_client_config"` 29 | } 30 | 31 | func GetTLSTransportConfig(configPath string) (*TLSTransportConfig, error) { 32 | if configPath == "" { 33 | return nil, nil 34 | } 35 | 36 | bytes, err := os.ReadFile(configPath) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | cfg := &TLSTransportConfig{ 42 | TLSClientConfig: &config.TLSConfig{}, 43 | } 44 | if err := yaml.UnmarshalStrict(bytes, cfg); err != nil { 45 | return nil, err 46 | } 47 | 48 | if cfg.TLSServerConfig == nil { 49 | return nil, fmt.Errorf("missing 'tls_server_config' entry in the TLS configuration") 50 | } 51 | 52 | cfg.TLSServerConfig.SetDirectory(filepath.Dir(configPath)) 53 | cfg.TLSClientConfig.SetDirectory(filepath.Dir(configPath)) 54 | 55 | return cfg, nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/amtool/README.md: -------------------------------------------------------------------------------- 1 | # Generating amtool artifacts 2 | 3 | Amtool comes with the option to create a number of ease-of-use artifacts that can be created. 4 | 5 | ## Shell completion 6 | 7 | A bash completion script can be generated by calling `amtool --completion-script-bash`. 8 | 9 | The bash completion file can be added to `/etc/bash_completion.d/`. 10 | 11 | ## Man pages 12 | 13 | A man page can be generated by calling `amtool --help-man`. 14 | 15 | Man pages can be added to the man directory of your choice 16 | 17 | amtool --help-man > /usr/local/share/man/man1/amtool.1 18 | sudo mandb 19 | 20 | Then you should be able to view the man pages as expected. 21 | -------------------------------------------------------------------------------- /cmd/amtool/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "github.com/prometheus/alertmanager/cli" 17 | 18 | func main() { 19 | cli.Execute() 20 | } 21 | -------------------------------------------------------------------------------- /config/testdata/conf.empty-fields.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | smtp_from: 'alertmanager@example.org' 4 | smtp_auth_username: '' 5 | smtp_auth_password: '' 6 | smtp_hello: '' 7 | slack_api_url: 'https://slack.com/webhook' 8 | templates: 9 | - '/etc/alertmanager/template/*.tmpl' 10 | route: 11 | group_by: ['alertname', 'cluster', 'service'] 12 | receiver: team-X-mails 13 | routes: 14 | - match_re: 15 | service: ^(foo1|foo2|baz)$ 16 | receiver: team-X-mails 17 | receivers: 18 | - name: 'team-X-mails' 19 | email_configs: 20 | - to: 'team-X+alerts@example.org' 21 | -------------------------------------------------------------------------------- /config/testdata/conf.group-by-all.yml: -------------------------------------------------------------------------------- 1 | route: 2 | group_by: ['...'] 3 | group_wait: 30s 4 | group_interval: 5m 5 | repeat_interval: 3h 6 | receiver: team-X 7 | receivers: 8 | - name: 'team-X' 9 | -------------------------------------------------------------------------------- /config/testdata/conf.http-config.good.yml: -------------------------------------------------------------------------------- 1 | global: 2 | slack_api_url: 'https://slack.com/webhook' 3 | http_config: 4 | follow_redirects: false 5 | route: 6 | receiver: team-X-slack 7 | receivers: 8 | - name: 'team-X-slack' 9 | slack_configs: 10 | - http_config: 11 | proxy_url: foo 12 | -------------------------------------------------------------------------------- /config/testdata/conf.inhibit-equal-utf8.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: test 3 | receivers: 4 | - name: test 5 | inhibit_rules: 6 | - source_matchers: 7 | - foo=bar 8 | target_matchers: 9 | - bar=baz 10 | equal: ['qux🙂', 'corge'] 11 | -------------------------------------------------------------------------------- /config/testdata/conf.inhibit-equal.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: test 3 | receivers: 4 | - name: test 5 | inhibit_rules: 6 | - source_matchers: 7 | - foo=bar 8 | target_matchers: 9 | - bar=baz 10 | equal: ['qux', 'corge'] 11 | -------------------------------------------------------------------------------- /config/testdata/conf.nil-match_re-route.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: empty 3 | routes: 4 | - match_re: 5 | invalid_label: 6 | receiver: empty 7 | receivers: 8 | - name: empty 9 | -------------------------------------------------------------------------------- /config/testdata/conf.nil-source_match_re-inhibition.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: empty 3 | receivers: 4 | - name: empty 5 | inhibit_rules: 6 | - source_match_re: 7 | invalid_source_label: 8 | target_match_re: 9 | severity: critical 10 | -------------------------------------------------------------------------------- /config/testdata/conf.nil-target_match_re-inhibition.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: empty 3 | receivers: 4 | - name: empty 5 | inhibit_rules: 6 | - source_match: 7 | severity: critical 8 | target_match_re: 9 | invalid_target_label: 10 | -------------------------------------------------------------------------------- /config/testdata/conf.opsgenie-both-file-and-apikey.yml: -------------------------------------------------------------------------------- 1 | global: 2 | opsgenie_api_key: asd132 3 | opsgenie_api_key_file: '/global_file' 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: escalation-Y-opsgenie 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-opsgenie 14 | receivers: 15 | - name: 'team-X-opsgenie' 16 | opsgenie_configs: 17 | - responders: 18 | - name: 'team-X' 19 | type: 'team' 20 | - name: 'escalation-Y-opsgenie' 21 | opsgenie_configs: 22 | - responders: 23 | - name: 'escalation-Y' 24 | type: 'escalation' 25 | api_key: qwe456 26 | -------------------------------------------------------------------------------- /config/testdata/conf.opsgenie-default-apikey-file.yml: -------------------------------------------------------------------------------- 1 | global: 2 | opsgenie_api_key_file: '/global_file' 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: escalation-Y-opsgenie 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-opsgenie 13 | receivers: 14 | - name: 'team-X-opsgenie' 15 | opsgenie_configs: 16 | - responders: 17 | - name: 'team-X' 18 | type: 'team' 19 | - name: 'escalation-Y-opsgenie' 20 | opsgenie_configs: 21 | - responders: 22 | - name: 'escalation-Y' 23 | type: 'escalation' 24 | api_key_file: /override_file 25 | -------------------------------------------------------------------------------- /config/testdata/conf.opsgenie-default-apikey-old-team.yml: -------------------------------------------------------------------------------- 1 | global: 2 | opsgenie_api_key: asd132 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: escalation-Y-opsgenie 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-opsgenie 13 | receivers: 14 | - name: 'team-X-opsgenie' 15 | opsgenie_configs: 16 | - teams: 'team-X' 17 | -------------------------------------------------------------------------------- /config/testdata/conf.opsgenie-default-apikey.yml: -------------------------------------------------------------------------------- 1 | global: 2 | opsgenie_api_key: asd132 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: escalation-Y-opsgenie 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-opsgenie 13 | receivers: 14 | - name: 'team-X-opsgenie' 15 | opsgenie_configs: 16 | - responders: 17 | - name: 'team-X' 18 | type: 'team' 19 | - name: 'escalation-Y-opsgenie' 20 | opsgenie_configs: 21 | - responders: 22 | - name: 'escalation-Y' 23 | type: 'escalation' 24 | api_key: qwe456 25 | -------------------------------------------------------------------------------- /config/testdata/conf.opsgenie-no-apikey.yml: -------------------------------------------------------------------------------- 1 | route: 2 | group_by: ['alertname', 'cluster', 'service'] 3 | group_wait: 30s 4 | group_interval: 5m 5 | repeat_interval: 3h 6 | receiver: team-X-opsgenie 7 | routes: 8 | - match: 9 | service: foo 10 | receiver: team-X-opsgenie 11 | receivers: 12 | - name: 'team-X-opsgenie' 13 | opsgenie_configs: 14 | - responders: 15 | - name: 'team-X' 16 | type: 'team' 17 | -------------------------------------------------------------------------------- /config/testdata/conf.rocketchat-both-token-and-tokenfile.yml: -------------------------------------------------------------------------------- 1 | global: 2 | rocketchat_token_file: /global_file 3 | rocketchat_token: token123 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: team-Y-rocketchat 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-rocketchat 14 | receivers: 15 | - name: 'team-X-rocketchat' 16 | rocketchat_configs: 17 | - channel: '#team-X' 18 | - name: 'team-Y-rocketchat' 19 | rocketchat_configs: 20 | - channel: '#team-Y' 21 | -------------------------------------------------------------------------------- /config/testdata/conf.rocketchat-both-tokenid-and-tokenidfile.yml: -------------------------------------------------------------------------------- 1 | global: 2 | rocketchat_token_id_file: /global_file 3 | rocketchat_token_id: id123 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: team-Y-rocketchat 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-rocketchat 14 | receivers: 15 | - name: 'team-X-rocketchat' 16 | rocketchat_configs: 17 | - channel: '#team-X' 18 | - name: 'team-Y-rocketchat' 19 | rocketchat_configs: 20 | - channel: '#team-Y' 21 | -------------------------------------------------------------------------------- /config/testdata/conf.rocketchat-default-token-file.yml: -------------------------------------------------------------------------------- 1 | global: 2 | rocketchat_token_file: /global_file 3 | rocketchat_token_id_file: /etc/alertmanager/rocketchat_token_id 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: team-Y-rocketchat 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-rocketchat 14 | receivers: 15 | - name: 'team-X-rocketchat' 16 | rocketchat_configs: 17 | - channel: '#team-X' 18 | - name: 'team-Y-rocketchat' 19 | rocketchat_configs: 20 | - channel: '#team-Y' 21 | token_file: /override_file 22 | token_id_file: /override_file 23 | -------------------------------------------------------------------------------- /config/testdata/conf.rocketchat-default-token.yml: -------------------------------------------------------------------------------- 1 | global: 2 | rocketchat_token: token123 3 | rocketchat_token_id: id123 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: team-Y-rocketchat 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-rocketchat 14 | receivers: 15 | - name: 'team-X-rocketchat' 16 | rocketchat_configs: 17 | - channel: '#team-X' 18 | - name: 'team-Y-rocketchat' 19 | rocketchat_configs: 20 | - channel: '#team-Y' 21 | token: token456 22 | token_id: id456 23 | -------------------------------------------------------------------------------- /config/testdata/conf.rocketchat-no-token.yml: -------------------------------------------------------------------------------- 1 | global: 2 | rocketchat_token_id: id123 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: team-Y-rocketchat 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-rocketchat 13 | receivers: 14 | - name: 'team-X-rocketchat' 15 | rocketchat_configs: 16 | - channel: '#team-X' 17 | - name: 'team-Y-rocketchat' 18 | rocketchat_configs: 19 | - channel: '#team-Y' 20 | -------------------------------------------------------------------------------- /config/testdata/conf.slack-both-file-and-url.yml: -------------------------------------------------------------------------------- 1 | global: 2 | slack_api_url: "http://mysecret.example.com/" 3 | slack_api_url_file: '/global_file' 4 | route: 5 | receiver: 'slack-notifications' 6 | group_by: [alertname, datacenter, app] 7 | receivers: 8 | - name: 'slack-notifications' 9 | slack_configs: 10 | - channel: '#alerts1' 11 | text: 'test' 12 | -------------------------------------------------------------------------------- /config/testdata/conf.slack-default-api-url-file.yml: -------------------------------------------------------------------------------- 1 | global: 2 | slack_api_url_file: '/global_file' 3 | route: 4 | receiver: 'slack-notifications' 5 | group_by: [alertname, datacenter, app] 6 | receivers: 7 | - name: 'slack-notifications' 8 | slack_configs: 9 | # Use global 10 | - channel: '#alerts1' 11 | text: 'test' 12 | # Override global with other file 13 | - channel: '#alerts2' 14 | text: 'test' 15 | api_url_file: '/override_file' 16 | # Override global with inline URL 17 | - channel: '#alerts3' 18 | text: 'test' 19 | api_url: 'http://mysecret.example.com/' 20 | -------------------------------------------------------------------------------- /config/testdata/conf.slack-no-api-url.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: 'slack-notifications' 3 | group_by: [alertname, datacenter, app] 4 | receivers: 5 | - name: 'slack-notifications' 6 | slack_configs: 7 | - channel: '#alerts' 8 | text: 'test' 9 | -------------------------------------------------------------------------------- /config/testdata/conf.smtp-both-password-and-file.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | smtp_from: 'alertmanager@example.org' 4 | smtp_auth_username: 'alertmanager' 5 | smtp_auth_password: "multiline\nmysecret" 6 | smtp_auth_password_file: "/tmp/global" 7 | smtp_hello: "host.example.org" 8 | route: 9 | receiver: 'email-notifications' 10 | receivers: 11 | - name: 'email-notifications' 12 | email_configs: 13 | - to: 'one@example.org' 14 | -------------------------------------------------------------------------------- /config/testdata/conf.smtp-no-username-or-password.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | smtp_from: 'alertmanager@example.org' 4 | smtp_hello: "host.example.org" 5 | route: 6 | receiver: 'email-notifications' 7 | receivers: 8 | - name: 'email-notifications' 9 | email_configs: 10 | - to: 'one@example.org' 11 | -------------------------------------------------------------------------------- /config/testdata/conf.smtp-password-global-and-local.yml: -------------------------------------------------------------------------------- 1 | global: 2 | smtp_smarthost: 'localhost:25' 3 | smtp_from: 'alertmanager@example.org' 4 | smtp_auth_username: 'globaluser' 5 | smtp_auth_password_file: '/tmp/globaluserpassword' 6 | smtp_hello: "host.example.org" 7 | route: 8 | receiver: 'email-notifications' 9 | receivers: 10 | - name: 'email-notifications' 11 | email_configs: 12 | # Use global 13 | - to: 'one@example.org' 14 | # Override global with other file 15 | - to: 'two@example.org' 16 | auth_username: 'localuser1' 17 | auth_password_file: '/tmp/localuser1password' 18 | # Override global with inline password 19 | - to: 'three@example.org' 20 | auth_username: 'localuser2' 21 | auth_password: 'mysecret' 22 | -------------------------------------------------------------------------------- /config/testdata/conf.sns-invalid.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: 'sns-api-notifications' 3 | group_by: [alertname] 4 | receivers: 5 | - name: 'sns-api-notifications' 6 | sns_configs: 7 | - api_url: https://sns.us-east-2.amazonaws.com 8 | sigv4: 9 | region: us-east-2 10 | access_key: access_key 11 | secret_key: secret_ket 12 | attributes: 13 | severity: Sev2 14 | -------------------------------------------------------------------------------- /config/testdata/conf.sns-topic-arn.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: 'sns-api-notifications' 3 | group_by: [alertname] 4 | receivers: 5 | - name: 'sns-api-notifications' 6 | sns_configs: 7 | - api_url: https://sns.us-east-2.amazonaws.com 8 | topic_arn: arn:aws:sns:us-east-2:123456789012:My-Topic 9 | sigv4: 10 | region: us-east-2 11 | access_key: access_key 12 | secret_key: secret_ket 13 | attributes: 14 | severity: Sev2 15 | -------------------------------------------------------------------------------- /config/testdata/conf.victorops-both-file-and-apikey.yml: -------------------------------------------------------------------------------- 1 | global: 2 | victorops_api_key: asd132 3 | victorops_api_key_file: '/global_file' 4 | route: 5 | group_by: ['alertname', 'cluster', 'service'] 6 | group_wait: 30s 7 | group_interval: 5m 8 | repeat_interval: 3h 9 | receiver: team-Y-victorops 10 | routes: 11 | - match: 12 | service: foo 13 | receiver: team-X-victorops 14 | receivers: 15 | - name: 'team-X-victorops' 16 | victorops_configs: 17 | - routing_key: 'team-X' 18 | - name: 'team-Y-victorops' 19 | victorops_configs: 20 | - routing_key: 'team-Y' 21 | api_key: qwe456 22 | -------------------------------------------------------------------------------- /config/testdata/conf.victorops-default-apikey-file.yml: -------------------------------------------------------------------------------- 1 | global: 2 | victorops_api_key_file: '/global_file' 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: team-Y-victorops 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-victorops 13 | receivers: 14 | - name: 'team-X-victorops' 15 | victorops_configs: 16 | - routing_key: 'team-X' 17 | - name: 'team-Y-victorops' 18 | victorops_configs: 19 | - routing_key: 'team-Y' 20 | api_key_file: /override_file 21 | -------------------------------------------------------------------------------- /config/testdata/conf.victorops-default-apikey.yml: -------------------------------------------------------------------------------- 1 | global: 2 | victorops_api_key: asd132 3 | route: 4 | group_by: ['alertname', 'cluster', 'service'] 5 | group_wait: 30s 6 | group_interval: 5m 7 | repeat_interval: 3h 8 | receiver: team-Y-victorops 9 | routes: 10 | - match: 11 | service: foo 12 | receiver: team-X-victorops 13 | receivers: 14 | - name: 'team-X-victorops' 15 | victorops_configs: 16 | - routing_key: 'team-X' 17 | - name: 'team-Y-victorops' 18 | victorops_configs: 19 | - routing_key: 'team-Y' 20 | api_key: qwe456 21 | -------------------------------------------------------------------------------- /config/testdata/conf.victorops-no-apikey.yml: -------------------------------------------------------------------------------- 1 | route: 2 | group_by: ['alertname', 'cluster', 'service'] 3 | group_wait: 30s 4 | group_interval: 5m 5 | repeat_interval: 3h 6 | receiver: team-X-victorops 7 | routes: 8 | - match: 9 | service: foo 10 | receiver: team-X-victorops 11 | receivers: 12 | - name: 'team-X-victorops' 13 | victorops_configs: 14 | - routing_key: 'team-X' 15 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | dashboards_out 3 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/.lint: -------------------------------------------------------------------------------- 1 | exclusions: 2 | target-instance-rule: 3 | reason: no need to have every query contains two matchers within every selector - `{job=~"$job", instance=~"$instance"}` 4 | template-job-rule: 5 | entries: 6 | - dashboard: Alertmanager / Overview 7 | reason: multi-select is not always required 8 | template-instance-rule: 9 | entries: 10 | - dashboard: Alertmanager / Overview 11 | reason: multi-select is not always required 12 | panel-units-rule: 13 | entries: 14 | - dashboard: Alertmanager / Overview 15 | reason: Dashboard does not benefit from specific unit specification. -------------------------------------------------------------------------------- /doc/alertmanager-mixin/Makefile: -------------------------------------------------------------------------------- 1 | JSONNET_FMT := jsonnetfmt -n 2 --max-blank-lines 1 --string-style s --comment-style s 2 | ALERTMANAGER_ALERTS := alertmanager_alerts.yaml 3 | 4 | default: vendor build dashboards_out 5 | 6 | all: fmt build 7 | 8 | vendor: 9 | jb install 10 | 11 | fmt: 12 | find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \ 13 | xargs -n 1 -- $(JSONNET_FMT) -i 14 | 15 | lint: build 16 | find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \ 17 | while read f; do \ 18 | $(JSONNET_FMT) "$$f" | diff -u "$$f" -; \ 19 | done 20 | 21 | mixtool lint mixin.libsonnet 22 | 23 | dashboards_out: mixin.libsonnet config.libsonnet $(wildcard dashboards/*) 24 | @mkdir -p dashboards_out 25 | jsonnet -J vendor -m dashboards_out dashboards.jsonnet 26 | 27 | build: vendor 28 | mixtool generate alerts mixin.libsonnet > $(ALERTMANAGER_ALERTS) 29 | 30 | clean: 31 | rm -rf $(ALERTMANAGER_ALERTS) 32 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/README.md: -------------------------------------------------------------------------------- 1 | # Alertmanager Mixin 2 | 3 | The Alertmanager Mixin is a set of configurable, reusable, and extensible 4 | alerts (and eventually dashboards) for Alertmanager. 5 | 6 | The alerts are designed to monitor a cluster of Alertmanager instances. To make 7 | them work as expected, the Prometheus server the alerts are evaluated on has to 8 | scrape all Alertmanager instances of the cluster, even if those instances are 9 | distributed over different locations. All Alertmanager instances in the same 10 | Alertmanager cluster must have the same `job` label. In turn, if monitoring 11 | multiple different Alertmanager clusters, instances from different clusters 12 | must have a different `job` label. 13 | 14 | The most basic use of the Alertmanager Mixin is to create a YAML file with the 15 | alerts from it. To do so, you need to have `jsonnetfmt` and `mixtool` installed. If you have a working Go development environment, it's 16 | easiest to run the following: 17 | 18 | ```bash 19 | $ go get github.com/monitoring-mixins/mixtool/cmd/mixtool 20 | $ go get github.com/google/go-jsonnet/cmd/jsonnetfmt 21 | ``` 22 | 23 | Edit `config.libsonnet` to match your environment and then build 24 | `alertmanager_alerts.yaml` with the alerts by running: 25 | 26 | ```bash 27 | $ make build 28 | ``` 29 | 30 | For instructions on more advanced uses of mixins, see https://github.com/monitoring-mixins/docs. 31 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/alerts.jsonnet: -------------------------------------------------------------------------------- 1 | std.manifestYamlDoc((import 'mixin.libsonnet').prometheusAlerts) 2 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/dashboards.jsonnet: -------------------------------------------------------------------------------- 1 | local dashboards = (import 'mixin.libsonnet').grafanaDashboards; 2 | 3 | { 4 | [name]: dashboards[name] 5 | for name in std.objectFields(dashboards) 6 | } 7 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/dashboards.libsonnet: -------------------------------------------------------------------------------- 1 | (import './dashboards/overview.libsonnet') 2 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/jsonnetfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/grafana/grafonnet.git", 8 | "subdir": "gen/grafonnet-latest" 9 | } 10 | }, 11 | "version": "main" 12 | } 13 | ], 14 | "legacyImports": false 15 | } 16 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/jsonnetfile.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/grafana/grafonnet.git", 8 | "subdir": "gen/grafonnet-latest" 9 | } 10 | }, 11 | "version": "1ce5aec95ce32336fe47c8881361847c475b5254", 12 | "sum": "64fMUPI3frXGj4X1FqFd1t7r04w3CUSmXaDcJ23EYbQ=" 13 | }, 14 | { 15 | "source": { 16 | "git": { 17 | "remote": "https://github.com/grafana/grafonnet.git", 18 | "subdir": "gen/grafonnet-v11.1.0" 19 | } 20 | }, 21 | "version": "1ce5aec95ce32336fe47c8881361847c475b5254", 22 | "sum": "41w7p/rwrNsITqNHMXtGSJAfAyKmnflg6rFhKBduUxM=" 23 | }, 24 | { 25 | "source": { 26 | "git": { 27 | "remote": "https://github.com/jsonnet-libs/docsonnet.git", 28 | "subdir": "doc-util" 29 | } 30 | }, 31 | "version": "6ac6c69685b8c29c54515448eaca583da2d88150", 32 | "sum": "BrAL/k23jq+xy9oA7TWIhUx07dsA/QLm3g7ktCwe//U=" 33 | }, 34 | { 35 | "source": { 36 | "git": { 37 | "remote": "https://github.com/jsonnet-libs/xtd.git", 38 | "subdir": "" 39 | } 40 | }, 41 | "version": "63d430b69a95741061c2f7fc9d84b1a778511d9c", 42 | "sum": "qiZi3axUSXCVzKUF83zSAxklwrnitMmrDK4XAfjPMdE=" 43 | } 44 | ], 45 | "legacyImports": false 46 | } 47 | -------------------------------------------------------------------------------- /doc/alertmanager-mixin/mixin.libsonnet: -------------------------------------------------------------------------------- 1 | (import 'config.libsonnet') + 2 | (import 'alerts.libsonnet') + 3 | (import 'dashboards.libsonnet') 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Alerting 3 | sort_rank: 7 4 | nav_icon: bell-o 5 | --- 6 | -------------------------------------------------------------------------------- /docs/management_api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Management API 3 | sort_rank: 9 4 | --- 5 | 6 | # Management API 7 | 8 | Alertmanager provides a set of management API to ease automation and integrations. 9 | 10 | 11 | ### Health check 12 | 13 | ``` 14 | GET /-/healthy 15 | HEAD /-/healthy 16 | ``` 17 | 18 | This endpoint always returns 200 and should be used to check Alertmanager health. 19 | 20 | 21 | ### Readiness check 22 | 23 | ``` 24 | GET /-/ready 25 | HEAD /-/ready 26 | ``` 27 | 28 | This endpoint returns 200 when Alertmanager is ready to serve traffic (i.e. respond to queries). 29 | 30 | 31 | ### Reload 32 | 33 | ``` 34 | POST /-/reload 35 | ``` 36 | 37 | This endpoint triggers a reload of the Alertmanager configuration file. 38 | 39 | An alternative way to trigger a configuration reload is by sending a `SIGHUP` to the Alertmanager process. 40 | -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Alerting overview 3 | sort_rank: 1 4 | nav_icon: sliders 5 | --- 6 | 7 | # Alerting Overview 8 | 9 | Alerting with Prometheus is separated into two parts. Alerting rules in 10 | Prometheus servers send alerts to an Alertmanager. The [Alertmanager](alertmanager.md) 11 | then manages those alerts, including silencing, inhibition, aggregation and 12 | sending out notifications via methods such as email, on-call notification systems, and chat platforms. 13 | 14 | The main steps to setting up alerting and notifications are: 15 | 16 | * Setup and [configure](configuration.md) the Alertmanager 17 | * [Configure Prometheus](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config) to talk to the Alertmanager 18 | * Create [alerting rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) in Prometheus 19 | -------------------------------------------------------------------------------- /examples/ha/alertmanager.yml: -------------------------------------------------------------------------------- 1 | route: 2 | group_by: ['alertname'] 3 | group_wait: 30s 4 | group_interval: 5m 5 | repeat_interval: 1h 6 | receiver: 'web.hook' 7 | receivers: 8 | - name: 'web.hook' 9 | webhook_configs: 10 | - url: 'http://127.0.0.1:5001/' 11 | inhibit_rules: 12 | - source_match: 13 | severity: 'critical' 14 | target_match: 15 | severity: 'warning' 16 | equal: ['alertname', 'dev', 'instance'] 17 | -------------------------------------------------------------------------------- /examples/ha/send_alerts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | alerts1='[ 3 | { 4 | "labels": { 5 | "alertname": "DiskRunningFull", 6 | "dev": "sda1", 7 | "instance": "example1" 8 | }, 9 | "annotations": { 10 | "info": "The disk sda1 is running full", 11 | "summary": "please check the instance example1" 12 | } 13 | }, 14 | { 15 | "labels": { 16 | "alertname": "DiskRunningFull", 17 | "dev": "sda2", 18 | "instance": "example1" 19 | }, 20 | "annotations": { 21 | "info": "The disk sda2 is running full", 22 | "summary": "please check the instance example1", 23 | "runbook": "the following link http://test-url should be clickable" 24 | } 25 | }, 26 | { 27 | "labels": { 28 | "alertname": "DiskRunningFull", 29 | "dev": "sda1", 30 | "instance": "example2" 31 | }, 32 | "annotations": { 33 | "info": "The disk sda1 is running full", 34 | "summary": "please check the instance example2" 35 | } 36 | }, 37 | { 38 | "labels": { 39 | "alertname": "DiskRunningFull", 40 | "dev": "sdb2", 41 | "instance": "example2" 42 | }, 43 | "annotations": { 44 | "info": "The disk sdb2 is running full", 45 | "summary": "please check the instance example2" 46 | } 47 | }, 48 | { 49 | "labels": { 50 | "alertname": "DiskRunningFull", 51 | "dev": "sda1", 52 | "instance": "example3", 53 | "severity": "critical" 54 | } 55 | }, 56 | { 57 | "labels": { 58 | "alertname": "DiskRunningFull", 59 | "dev": "sda1", 60 | "instance": "example3", 61 | "severity": "warning" 62 | } 63 | } 64 | ]' 65 | curl -XPOST -d"$alerts1" http://localhost:9093/api/v1/alerts 66 | curl -XPOST -d"$alerts1" http://localhost:9094/api/v1/alerts 67 | curl -XPOST -d"$alerts1" http://localhost:9095/api/v1/alerts 68 | -------------------------------------------------------------------------------- /examples/ha/tls/Makefile: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/wolfeidau/golang-massl/ 2 | 3 | .PHONY: start 4 | start: 5 | goreman start 6 | 7 | .PHONY: gen-certs 8 | gen-certs: certs/ca.pem certs/node1.pem certs/node1-key.pem certs/node2.pem certs/node2-key.pem 9 | 10 | certs/ca.pem certs/ca-key.pem: certs/ca-csr.json 11 | cd certs; cfssl gencert -initca ca-csr.json | cfssljson -bare ca 12 | 13 | certs/node1.pem certs/node1-key.pem: certs/ca-config.json certs/ca.pem certs/ca-key.pem certs/node1-csr.json 14 | cd certs; cfssl gencert \ 15 | -ca=ca.pem \ 16 | -ca-key=ca-key.pem \ 17 | -config=ca-config.json \ 18 | -hostname=localhost,127.0.0.1 \ 19 | -profile=massl node1-csr.json | cfssljson -bare node1 20 | 21 | certs/node2.pem certs/node2-key.pem: certs/ca-config.json certs/ca.pem certs/ca-key.pem certs/node2-csr.json 22 | cd certs; cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=ca-config.json \ 26 | -hostname=localhost,127.0.0.1 \ 27 | -profile=massl node2-csr.json | cfssljson -bare node2 28 | -------------------------------------------------------------------------------- /examples/ha/tls/Procfile: -------------------------------------------------------------------------------- 1 | a1: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a1 --web.listen-address=:9093 --cluster.listen-address=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml 2 | 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=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml 3 | 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=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml 4 | a4: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a4 --web.listen-address=:9096 --cluster.listen-address=127.0.0.1:8004 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml 5 | a5: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a5 --web.listen-address=:9097 --cluster.listen-address=127.0.0.1:8005 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml 6 | a6: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a6 --web.listen-address=:9098 --cluster.listen-address=127.0.0.1:8006 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml 7 | wh: go run ../../webhook/echo.go 8 | -------------------------------------------------------------------------------- /examples/ha/tls/README.md: -------------------------------------------------------------------------------- 1 | # TLS Transport Config Example 2 | 3 | ## Usage 4 | 1. Install dependencies: 5 | 1. `go install github.com/cloudflare/cfssl/cmd/cfssl` 6 | 2. `go install github.com/mattn/goreman` 7 | 2. Build Alertmanager (root of repository): 8 | 1. `go mod download` 9 | 1. `make build`. 10 | 2. `make start` (inside this directory). 11 | 12 | ## Testing 13 | 1. Start the cluster (as explained above) 14 | 2. Navigate to one of the Alertmanager instances at `localhost:9093`. 15 | 3. Create a silence. 16 | 4. Navigate to the other Alertmanager instance at `localhost:9094`. 17 | 5. Observe that the silence created in the other Alertmanager instance has been synchronized over to this instance. 18 | 6. Repeat. 19 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "876000h" 5 | }, 6 | "profiles": { 7 | "massl": { 8 | "usages": ["signing", "key encipherment", "server auth", "client auth"], 9 | "expiry": "876000h" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "massl", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "massl", 12 | "OU": "VIC", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /examples/ha/tls/certs/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLp 3 | nZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCb 4 | ChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8u 5 | hEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToC 6 | va+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6 7 | rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQABAoIBAQCwcL1vXUq7W4UD 8 | OaRtbWrQ0dk0ETBnxT/E0y33fRJ8GZovWM2EXSVEuukSP+uEQ5elNYeWqo0fi3cT 9 | ruvJSnMw9xPyXVDq+4C8slW3R1TqTK683VzvUizM4KC5qIyCpn1KBbgHrh6E7Sp1 10 | e4cIuaawVN3qIg5qThmx2YA4nBIcEt68q9cpy3NgEe+EQf44zM/St+y8kSkDUOVw 11 | fNKX0WfZ/hPL1TAYpWiIgSf+m/V3d/1l/scvMYONcuSjXSORCyoeAWYtOQgf78wW 12 | 9j3kiBTaqDYCUZFnY/ltlZrm8ltAaKVJ0MmPKjVh8GJBXZp9fSVU8Y4ZIZRSeuBA 13 | OoStHGAdAoGBAMluMIE33hGny2V0dNzW23D84eXQK38AsdP632jQeuzxBknItg45 14 | qAfzh8F8W10DQnSv5tj0bmOHfo0mG09bu9eo5nLLINOE7Ju/7ly/76RNJNJ4ADjx 15 | JKZi/PpvfP+s/fzel0X3OPgA+CJKzUHuqlU4V9BLc7focZAYtaM2w7rHAoGBAOzU 16 | eXpapkqYhbYRcsrVV57nZV0rLzsLVJBpJg2zC8un95ALrr0rlZfuPJfOCY/uuS1w 17 | f8ixRz2MkRWGreLHy35NB4GV0sF9VPn1jMp9SuBNvO0JRUMWuDAdVe8SCjXadrOh 18 | +m3yKJSkFKDchglUYnZKV1skgA/b9jjjnu2fvd0TAoGAVUTnFZxvzmuIp78fxWjS 19 | 5ka23hE8iHvjy4e00WsHzovNjKiBoQ35Orx16ItbJcm+dSUNhSQcItf104yhHPwJ 20 | Tab7PvcMQ15OxzP9lJfPu27Iuqv/9Bro1+Kpkt5lPNqffk9AHGcmX54RbHrb3yBI 21 | TOEYE14Nc3nbsRM0uQ3y13sCgYB5Om4QZpSWvKo9P4M+NqTKb3JglblwhOU9osVa 22 | 39ra3dkIgCJrLQM/KTEVF9+nMLDThLG0fqKT6/9cQHuECXet6Co+d/3RE6HK7Zmr 23 | ESWh2ckqoMM2i0uvPWT+ooJdfL2kR/bUDtAc/jyc9yUZY3ufR4Cd4/o1pAfOqR1y 24 | T4G1xwKBgQChE4VWawCVg2qanRjvZcdNk0zpZx4dxqqKYq/VHuSfjNLQixIZsgXT 25 | xx9BHuORn6c/nurqEStLwN3BzbpPU/j6YjMUmTslSH2sKhHwWNYGBZC52aJiOOda 26 | Bz6nAkihG0n2PjYt2T84w6FWHgLJuSsmiEVJcb+AOdyKh1MlzJiwMQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMx 4 | DjAMBgNVBAMTBW1hc3NsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 5 | uljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM 6 | 9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7 7 | BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPX 8 | Kw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/ 9 | BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6rBANYsfojvyCXuyt 10 | Wnj04mvdAWwmFh0hhq+nxQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAJFmooMt 11 | TocElxCb3DGJTRUXxr4DqcATASIX35a2wV3MmPqUHHXr6BQkO/FRho66EsZf3DE/ 12 | mumou01K+KByxgsmw04CACjSeZ2t/g6pAsDCKrx/BwL3tAo09lG2Y2Ah0BND2Cta 13 | EZpTliU2MimZlk7UZb8VIXh2Tx56fZRoHLzO4U4+FY8ZR+tspxPRM7hLg/aUqA5D 14 | zGj6kByX8aYjxsmQokP4rx/w2mz6vwt4cZ1pXwr0RderkMIh9Har/0k9X1WIAP61 15 | PNQx74qnaq+icjtN2+8gvJE/CJL/wfcwW6kQwEtX1xsTpnzyFaRoYpSPQrvkCtiW 16 | +WzgnOh7RvKyAYI= 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDlDCCAnygAwIBAgIUetcc8yLasSAxvyUWtgTVKR6jfBIwDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMB4XDTIxMDUwNTE2MTYwMFoXDTI2MDUwNDE2MTYwMFowYjELMAkGA1UE 6 | BhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxDjAM 7 | BgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMTBW1hc3NsMIIBIjAN 8 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9db 9 | gwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13Q 10 | roK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbh 11 | NnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ 12 | 7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgb 13 | xBgy9MZQEot88d1T2XH6rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQAB 14 | o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU 15 | d4DTElKq6gnGYDJZgJvC+4flrZAwDQYJKoZIhvcNAQELBQADggEBAHYaMxcyVdFP 16 | QZJHDP6x+A1hHeZy/kaL2VPzUIOgt3ONEvjhsJblFjLaTWwXFJwxVHXRsQjqrURj 17 | dD2VIkMbCJM4WN8odi6PdHscL0DwHKzwZDapQxsOUYcPBbR2EsBPRyWSoJmBnEyZ 18 | 0TANtyUcZNWc7/C4GG66SWypu1YRdtcaDiEOVkdetGWuo5bw1J7B7isQ4J4a3+Qn 19 | iTKId/7wWBY95JS2xJuOeEjk/ty74ruSapk2Aip1GRSRXMDtD0XeO5dpxc6eIU3C 20 | jbRpUGqCdSAfzxQTlxQAMEKdgRlt7lzMFX/PAXagDOMC9D/6APn5fquEV2d9wMWs 21 | 0N9UCwKftAU= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node1-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "system:server", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "system:node1", 12 | "OU": "massl", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA1b9bm4rvDtpYsqgtCC52+L535d4/Q2O10fWD2i2CfRXXfYJQ 3 | 5cr4AV2iqScFsJSs7KwyQde/c4VWj/vEA2/SJHZFBlknKdCcrgHVebrvnzm6Guze 4 | ICutZSKocFXy9Kw+YmWuA64nHVfmSCKG07GhXhEsLsSCn4PTDYOiGAUm1GdSDxUp 5 | 8yUXec13Eb20mld0xE9kQnCnEWRnxMXtQJXoz9lpLc7DgXtN6nCXSG/CqdDPOduU 6 | nmseaxyAGpAFnUmxcqUuYAJUQ1hUOJhk0RVSsLTmu+FGdOxk79AxmmKQ2z9l/GuA 7 | VikVJGTxY4jRPezxHQ3bdqzzCIdJxTxLinftZQIDAQABAoIBADpxQtvphemau8vF 8 | feKRycfDVEcOmF+VoL4SkgWSke4fjbbsbbAW6e59qp7zY3PfgtSHVIp6Mgek+oEN 9 | xo9mAKAlkkPlFncxadWN/M921FPF1ePMxgMnzhYr/sAQUAikG76NrKGm+VzljrpE 10 | bnbtR4DP0zPKWSjCQ2+bgTNuHSrPwUtEngVT6ugjfWU1RitlvjTsZ9hSuOSBlS7P 11 | rjbQGaEh53PraDut8PIlF4wIF+nLeERFP/a6DC8Btpbv9P50YRosag6yU/G+OYX9 12 | spvBPvRJGrubslKnNRz9AcjbVd3QhL+Tm7mV7iakK918jLWb95Ro4WW+9lT6IAi6 13 | xRSOr9UCgYEA5wI3JhKkYa4PST7ALqmJSDkPH8+tctiEx+ovmnqBufFuLWFoO/rc 14 | EOYslnaZM3UVCnhrFv7+LxezSI5DyQu8dBEzf0RMICvXUNBkGC7ZJQL428fjXPhX 15 | 8mZIoJ0ol4hbamr8yTYlK0vGTwqN1bDj71w6NszuN4ecN1cKNWsMbnMCgYEA7N8Y 16 | MzHWNijMr7xZ1lXl4Ye9+kp3aTUjUYBBaxFr4bQ8y0fH48lzq3qOso5wgvp0DKYo 17 | uemD5QKbo81LKoTRLa+nxPq0UqKm9FiSWmnrcxMuph989oZ1ZFHA2B+nvbuMTF8J 18 | 8sESclTSbgkG87DpycJOUwG3XAcXM+80pXuzJscCgYB+Dzxu/09KqoRW8PJIxGVQ 19 | zypMrrS07iiPO2FcyCtQf8oi43vQ91Ttt91vAisZ5HNl8k5mDyJAKovANToSVOAy 20 | 6kwSz/9GswXdaMqmU7JVOyj4Lj0JN9AuS9ioJPrIrjVMfjORzYU8+i2uZlD94niP 21 | 3uE5lF0OWmdJ36qHefIftwKBgQDcPQZcO19H1iGS2FbTYeSvEK5ENM7YRG8FTXIF 22 | 4hnjrtjDzYb+tYVWEErznFrifYo/ZJMDYSqgWQ9reusDqqBvkR41mUDmgJMpJ91U 23 | MZ2YzmIWVbqz4QrvbtAWY0Bsuh/VtpwiWQAUy+coJj6PgJOvY3m91h+tcm5RfHz/ 24 | zIcjawKBgA6kDcOLOnWcvhP3XwtW5dcWlNuBBNEKna+kIT/5Vlrh91y2w7F54DNK 25 | i0w5CZCpbTugJmZ67XLHnfongC7e2vAQ3atoT96RU4mf9614qs9LMtGAbnuCLB8+ 26 | sT2rnaZKtzr83ensbYkbBxP/zmPBfFQ9FKcIYIA7En8zAIr2T3vJ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node1.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMTEOMAwGA1UE 4 | CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQDVv1ubiu8O2liyqC0ILnb4vnfl3j9DY7XR9YPaLYJ9 6 | Fdd9glDlyvgBXaKpJwWwlKzsrDJB179zhVaP+8QDb9IkdkUGWScp0JyuAdV5uu+f 7 | Oboa7N4gK61lIqhwVfL0rD5iZa4DricdV+ZIIobTsaFeESwuxIKfg9MNg6IYBSbU 8 | Z1IPFSnzJRd5zXcRvbSaV3TET2RCcKcRZGfExe1AlejP2WktzsOBe03qcJdIb8Kp 9 | 0M8525Seax5rHIAakAWdSbFypS5gAlRDWFQ4mGTRFVKwtOa74UZ07GTv0DGaYpDb 10 | P2X8a4BWKRUkZPFjiNE97PEdDdt2rPMIh0nFPEuKd+1lAgMBAAGgLTArBgkqhkiG 11 | 9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B 12 | AQsFAAOCAQEAW/tTyJaBfWtbC9hYUmhh8lxUztv2+WT4xaR/jdQ46sk/87vKuwI6 13 | 4AkkGfiPLLqgW3xbQOwk5/ynRabttbsgTUHt744RtRFLzfcQKEBZoNPvrfHvmDil 14 | YqHIOx2SJ5hzIBwVlVSBn50hdSSED1Ip22DaU8GukzuacB8+2rhg3MOWJbKVt5aR 15 | 03H4XkAynLS1FHNOraDIv1eT58D3l4hanrNOZIa0xAuChd25qLO/JHvU/3wccGUA 16 | KNg3vGOy2Q8qVBrTFLn+yQHuOr/wSupXESO1jiI/h+txsBQnZ6oYfZnVJ+7o3Oln 17 | 3Hguw77aYeTAeZQPPbmJbDLegLG0ZC6RmA== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAjCCAuqgAwIBAgIUbYMGwSgQF8iRZ5xmhflInj8VZ0owDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD 6 | VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV 7 | MBMGA1UEChMMc3lzdGVtOm5vZGUxMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN 8 | c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANW/ 9 | W5uK7w7aWLKoLQgudvi+d+XeP0NjtdH1g9otgn0V132CUOXK+AFdoqknBbCUrOys 10 | MkHXv3OFVo/7xANv0iR2RQZZJynQnK4B1Xm67585uhrs3iArrWUiqHBV8vSsPmJl 11 | rgOuJx1X5kgihtOxoV4RLC7Egp+D0w2DohgFJtRnUg8VKfMlF3nNdxG9tJpXdMRP 12 | ZEJwpxFkZ8TF7UCV6M/ZaS3Ow4F7Tepwl0hvwqnQzznblJ5rHmscgBqQBZ1JsXKl 13 | LmACVENYVDiYZNEVUrC05rvhRnTsZO/QMZpikNs/ZfxrgFYpFSRk8WOI0T3s8R0N 14 | 23as8wiHScU8S4p37WUCAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l 15 | BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE 16 | FGprx5v+KrO4DeOtA6kps4BL/zKyMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb 17 | wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF 18 | AAOCAQEAmWTdMLyWOrNAS0uY+u3FUV3Hm50xF1PfxbT6wK1hu6vH6B63E0o9K2/1 19 | U25Ie8Y2IzFocKMvbqC+mrY56G0bWoUlMONhthYqm8uTKtjlFO33A9I7WIT9Tw+B 20 | nnwZZO7+Ljkd30qSzBinCjrIEx31Vq2pr54ungd8+wK8nfz/zdZnJcqxcN9zvCXB 21 | GTE8yCuqGWKk/oDuIzVjr73U0QaWi+vThqJtBjhOIWQHHVJwbIyhuYzUaivgZPYB 22 | 8eKXWk4JH3eAcq5z5koNGyCcZd/k4WnvxZYxNBAkoQ6AWVfEMGOCaRjD1FTnMbpG 23 | BW79ndJqLmn8OH+DeCnSWhTWxAgg+Q== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node2-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "system:server", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "AU", 10 | "L": "Melbourne", 11 | "O": "system:node2", 12 | "OU": "massl", 13 | "ST": "Victoria" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node2-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtCtzT9vhRMTbhAg/pm8eBn+4IvVQeVqnHoEon9IKIx5fyvqS 3 | Q6Ui3xSik9kJq5FSAa1mScajJwfB1o6ycaSP6n+Q88Py4v7q65n0stCHoJCH0uPw 4 | MQyEhwX7nNilV9C4UZTyZ2StDdAjmMBHiN81EJAqH2d4Xtgrd/IIWhljSXm+aPbu 5 | QjSz8BtR/7+MswrCdlJ8y6gWi020kt6GSHjmaxI1jStGvBxxksK86v3J97wfNwWY 6 | 7GJi70uBrvO0pk5bYckDzUTKeN1QGvBnZ8uDXs7pPysvftJr85GzX0iE9YLMDxO3 7 | qc/PlwCdxM8H6gHTTkLPizGZtpMF9Z497pW9YQIDAQABAoIBAFfQwdCPxHmnVbNB 8 | 7fwqNsFGKTLozMOJeuE0ZN+ZGZXKbTha70WHTLrcrO1RIRR9rTHiGXQmHEmez0zL 9 | mpAnfHn4mWcm/9DCHTCehpVNbH3HVFxm+yB9EG9bbCsjsVtfASfKaGgauvp7k44V 10 | UgiVeqDLE6zg2tunk3BQCOAZdbpOiXrdvoZiGx2Q4SMLPfzmfIyH4BUT836pLTmp 11 | o6/yNiFqQWfCgjeEAOQor4TcdzYIT+3wP51HfAjhZKMIvmjwL16ov1/QpmWRD4ni 12 | 4svzYpeMYpl5OrZkKeDS4ZIQBGjxk+fzPmfFUbfVRSI2gDORsah8HoRVI4LnwKWn 13 | 7kQDv0ECgYEA6V+KVb8bPzCZNbroEZFdug6YtT4yv5Mj3/kpMTIvA3vtu02v8e7F 14 | O56yT43QfUZA0Ar37O0HQ6mbpPsRE5RSr70i40RR+slMZVHX/AQViG7oQJGBijPt 15 | 1tFdLnb+1wSON3jYt2975Kw2IfgOXprWtEmL5zGuplEUjx9Lbdf1HjkCgYEAxaNe 16 | XgXdAiWFoY4Qq6xBRO/WNZCdn3Ysqx6snCtDRilxeNyDoE/6x2Ma9/NRBtIiulAb 17 | s09vDRfJKLbzocUhIn8BQ+GkbAS/A6+x2vcuGhK3F84xqZdbrCqvqdJS8K824jug 18 | vUCfCBJlyNRDz8kEsN5odLM1xkij93Jv23HvGGkCgYEAptcz6ctfalSPI9eEs5KO 19 | REbNK73UwBssaaISreYnsED4G5EVuUuvW8k/xxomtHj2OwWsa4ilSd1GtbL8aVf/ 20 | qT35ZCrixP0GjeTuGXC+CDTp+8dKqggoAAzbpi1SUVwjZEsT/EhKdZgcdzqE42Ol 21 | HWz7BQUCzEpo/U0tOtFKnxkCgYEAi05Vy8wyNbsg7/jlAzyNXPv4bxUaJTX00kDy 22 | xbkw2BmKI/i6xprZVwUiEzdsG3SuicjBXahVzFLBtXMPUy1R57DBwYkgjgriYMTM 23 | hlzIIBSk/aCXHMTVFwuXegoH8CJwexIwgHU2I0hkeiQ0EBfOuKRr2CYhdzvoZxhA 24 | g9tQ/lECgYAjPYoXfNI3rHCWUmaD5eDJZpE0xuJeiiy5auojykdAc7vVapNaIyMK 25 | G3EaU44RtXcSwH19TlH9UCm3MH1QiIwaBOzGcKj3Ut6ZyFKuWDUk4yqvps3uZU/h 26 | h16Tp49Ja7/4LY1uuEngg1KMEiWgk5jiU7G0H9zrtEiTj9c3FDKDvg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node2.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw 3 | EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMjEOMAwGA1UE 4 | CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQC0K3NP2+FExNuECD+mbx4Gf7gi9VB5WqcegSif0goj 6 | Hl/K+pJDpSLfFKKT2QmrkVIBrWZJxqMnB8HWjrJxpI/qf5Dzw/Li/urrmfSy0Ieg 7 | kIfS4/AxDISHBfuc2KVX0LhRlPJnZK0N0COYwEeI3zUQkCofZ3he2Ct38ghaGWNJ 8 | eb5o9u5CNLPwG1H/v4yzCsJ2UnzLqBaLTbSS3oZIeOZrEjWNK0a8HHGSwrzq/cn3 9 | vB83BZjsYmLvS4Gu87SmTlthyQPNRMp43VAa8Gdny4Nezuk/Ky9+0mvzkbNfSIT1 10 | gswPE7epz8+XAJ3EzwfqAdNOQs+LMZm2kwX1nj3ulb1hAgMBAAGgLTArBgkqhkiG 11 | 9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B 12 | AQsFAAOCAQEARh0Pi36mNmyprU4j25GWNqQYCJ6cBGnaPeiwr8/F3rsGsF4LTQdP 13 | xW2oBrEWyYRidNCkSMrPkcSiXu1Loy9APwSAXgJZWMYy0Ccdbd3P7dtGNOZkKaLA 14 | QKntGA5E1YAbzNhlt7NviGpqZ49K2aOgcGBTnDZ7xDzmg4uo3tcHgzOCwarYZT8l 15 | qVpc3jAyxRBOrxVKPZNFb4hAFvUm8k6/Etn5n4otN0JT3KGewbfQY50CxW5ShK52 16 | QCs2PmFMYHHmG11FD3W755MxzhL6UmMy20GUgWWthGmR1LugcBgDtWO/7bqqC9tT 17 | XYDTDJ1j0g3Y0cvy2+kltrams4lGE3xs6g== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /examples/ha/tls/certs/node2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAjCCAuqgAwIBAgIUex5xEYsDJPUg8idU0Sql2ixGdTwwDQYJKoZIhvcNAQEL 3 | BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN 4 | ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT 5 | BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD 6 | VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV 7 | MBMGA1UEChMMc3lzdGVtOm5vZGUyMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN 8 | c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQr 9 | c0/b4UTE24QIP6ZvHgZ/uCL1UHlapx6BKJ/SCiMeX8r6kkOlIt8UopPZCauRUgGt 10 | ZknGoycHwdaOsnGkj+p/kPPD8uL+6uuZ9LLQh6CQh9Lj8DEMhIcF+5zYpVfQuFGU 11 | 8mdkrQ3QI5jAR4jfNRCQKh9neF7YK3fyCFoZY0l5vmj27kI0s/AbUf+/jLMKwnZS 12 | fMuoFotNtJLehkh45msSNY0rRrwccZLCvOr9yfe8HzcFmOxiYu9Lga7ztKZOW2HJ 13 | A81EynjdUBrwZ2fLg17O6T8rL37Sa/ORs19IhPWCzA8Tt6nPz5cAncTPB+oB005C 14 | z4sxmbaTBfWePe6VvWECAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l 15 | BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE 16 | FDNgivphLRqKzV8n29GJq6S2I+CQMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb 17 | wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF 18 | AAOCAQEAnNG3nzycALGf+N8PuG4sUIkD+SYA1nOEgfD2KiGNyuTYHhGgFXTw8KzB 19 | olH05VidldBvC0+pl5EqZAp9qdzpw6Z5Mb0gdoZY6TeKDUo022G3BHLMUGLp8y+i 20 | KE6+awwgdJZ6vPbdnWAh7VM/HCUrGIIPmLFan13j/2RiMfaDxdMAowPmbVc8MLgA 21 | JHI6pPo8D1DacEvMM09qGtwQEUoREOWJ/SzTWl1nc/IAS1yOL1LCyKLcoj/HWqjG 22 | 3LXficQ7rf+Cpn1GnrKwMziT0OLDLxOs/+5d3nFSLxqF1lpykhPPkmHOHnuY8sMX 23 | Qdndn9QILdp5GNvqiVNQYcQa/gOb6g== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /examples/ha/tls/tls_config_node1.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: "certs/node1.pem" 3 | key_file: "certs/node1-key.pem" 4 | client_ca_file: "certs/ca.pem" 5 | client_auth_type: "VerifyClientCertIfGiven" 6 | tls_client_config: 7 | cert_file: "certs/node1.pem" 8 | key_file: "certs/node1-key.pem" 9 | ca_file: "certs/ca.pem" 10 | -------------------------------------------------------------------------------- /examples/ha/tls/tls_config_node2.yml: -------------------------------------------------------------------------------- 1 | tls_server_config: 2 | cert_file: "certs/node2.pem" 3 | key_file: "certs/node2-key.pem" 4 | client_ca_file: "certs/ca.pem" 5 | client_auth_type: "VerifyClientCertIfGiven" 6 | tls_client_config: 7 | cert_file: "certs/node2.pem" 8 | key_file: "certs/node2-key.pem" 9 | ca_file: "certs/ca.pem" 10 | -------------------------------------------------------------------------------- /examples/webhook/echo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "bytes" 18 | "encoding/json" 19 | "io" 20 | "log" 21 | "net/http" 22 | ) 23 | 24 | func main() { 25 | log.Fatal(http.ListenAndServe(":5001", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | b, err := io.ReadAll(r.Body) 27 | if err != nil { 28 | panic(err) 29 | } 30 | defer r.Body.Close() 31 | var buf bytes.Buffer 32 | if err := json.Indent(&buf, b, " >", " "); err != nil { 33 | panic(err) 34 | } 35 | log.Println(buf.String()) 36 | }))) 37 | } 38 | -------------------------------------------------------------------------------- /featurecontrol/featurecontrol_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package featurecontrol 15 | 16 | import ( 17 | "errors" 18 | "strings" 19 | "testing" 20 | 21 | "github.com/prometheus/common/promslog" 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestFlags(t *testing.T) { 26 | tc := []struct { 27 | name string 28 | featureFlags string 29 | err error 30 | }{ 31 | { 32 | name: "with only valid feature flags", 33 | featureFlags: FeatureReceiverNameInMetrics, 34 | }, 35 | { 36 | name: "with only invalid feature flags", 37 | featureFlags: "somethingsomething", 38 | err: errors.New("Unknown option 'somethingsomething' for --enable-feature"), 39 | }, 40 | { 41 | name: "with both, valid and invalid feature flags", 42 | featureFlags: strings.Join([]string{FeatureReceiverNameInMetrics, "somethingbad"}, ","), 43 | err: errors.New("Unknown option 'somethingbad' for --enable-feature"), 44 | }, 45 | } 46 | 47 | for _, tt := range tc { 48 | t.Run(tt.name, func(t *testing.T) { 49 | fc, err := NewFlags(promslog.NewNopLogger(), tt.featureFlags) 50 | if tt.err != nil { 51 | require.EqualError(t, err, tt.err.Error()) 52 | } else { 53 | require.NoError(t, err) 54 | require.NotNil(t, fc) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /matcher/parse/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package parse 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | const ( 21 | simpleExample = "{foo=\"bar\"}" 22 | complexExample = "{foo=\"bar\",bar=~\"[a-zA-Z0-9+]\"}" 23 | ) 24 | 25 | func BenchmarkParseSimple(b *testing.B) { 26 | for i := 0; i < b.N; i++ { 27 | if _, err := Matchers(simpleExample); err != nil { 28 | b.Fatal(err) 29 | } 30 | } 31 | } 32 | 33 | func BenchmarkParseComplex(b *testing.B) { 34 | for i := 0; i < b.N; i++ { 35 | if _, err := Matchers(complexExample); err != nil { 36 | b.Fatal(err) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /matcher/parse/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package parse 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | // FuzzParse fuzz tests the parser to see if we can make it panic. 21 | func FuzzParse(f *testing.F) { 22 | f.Add("{foo=bar,bar=~[a-zA-Z]+,baz!=qux,qux!~[0-9]+") 23 | f.Fuzz(func(t *testing.T, s string) { 24 | matchers, err := Matchers(s) 25 | if matchers != nil && err != nil { 26 | t.Errorf("Unexpected matchers and err: %v %s", matchers, err) 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /nflog/nflogpb/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package nflogpb 15 | 16 | // IsFiringSubset returns whether the given subset is a subset of the alerts 17 | // that were firing at the time of the last notification. 18 | func (m *Entry) IsFiringSubset(subset map[uint64]struct{}) bool { 19 | set := map[uint64]struct{}{} 20 | for i := range m.FiringAlerts { 21 | set[m.FiringAlerts[i]] = struct{}{} 22 | } 23 | 24 | return isSubset(set, subset) 25 | } 26 | 27 | // IsResolvedSubset returns whether the given subset is a subset of the alerts 28 | // that were resolved at the time of the last notification. 29 | func (m *Entry) IsResolvedSubset(subset map[uint64]struct{}) bool { 30 | set := map[uint64]struct{}{} 31 | for i := range m.ResolvedAlerts { 32 | set[m.ResolvedAlerts[i]] = struct{}{} 33 | } 34 | 35 | return isSubset(set, subset) 36 | } 37 | 38 | func isSubset(set, subset map[uint64]struct{}) bool { 39 | for k := range subset { 40 | _, exists := set[k] 41 | if !exists { 42 | return false 43 | } 44 | } 45 | 46 | return true 47 | } 48 | -------------------------------------------------------------------------------- /notify/email/testdata/auth.yml: -------------------------------------------------------------------------------- 1 | smarthost: localhost:1026 2 | server: http://localhost:1081/ 3 | username: user 4 | password: pass 5 | -------------------------------------------------------------------------------- /notify/email/testdata/noauth.yml: -------------------------------------------------------------------------------- 1 | smarthost: localhost:1025 2 | server: http://localhost:1080/ 3 | -------------------------------------------------------------------------------- /notify/opsgenie/api_key_file: -------------------------------------------------------------------------------- 1 | my_secret_api_key 2 | 3 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 | The `pkg` directory is deprecated. 2 | Please do not add new packages to this directory. 3 | Existing packages will be moved elsewhere eventually. 4 | -------------------------------------------------------------------------------- /pkg/modtimevfs/modtimevfs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // Package modtimevfs implements a virtual file system that returns a fixed 15 | // modification time for all files and directories. 16 | package modtimevfs 17 | 18 | import ( 19 | "net/http" 20 | "os" 21 | "time" 22 | ) 23 | 24 | type timefs struct { 25 | fs http.FileSystem 26 | t time.Time 27 | } 28 | 29 | // New returns a file system that returns constant modification time for all files. 30 | func New(fs http.FileSystem, t time.Time) http.FileSystem { 31 | return &timefs{fs: fs, t: t} 32 | } 33 | 34 | type file struct { 35 | http.File 36 | os.FileInfo 37 | t time.Time 38 | } 39 | 40 | func (t *timefs) Open(name string) (http.File, error) { 41 | f, err := t.fs.Open(name) 42 | if err != nil { 43 | return f, err 44 | } 45 | defer func() { 46 | if err != nil { 47 | f.Close() 48 | } 49 | }() 50 | 51 | fstat, err := f.Stat() 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return &file{f, fstat, t.t}, nil 57 | } 58 | 59 | // Stat implements the http.File interface. 60 | func (f *file) Stat() (os.FileInfo, error) { 61 | return f, nil 62 | } 63 | 64 | // ModTime implements the os.FileInfo interface. 65 | func (f *file) ModTime() time.Time { 66 | return f.t 67 | } 68 | -------------------------------------------------------------------------------- /scripts/compress_assets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # compress static assets 4 | 5 | set -euo pipefail 6 | 7 | cd ui/react-app 8 | cp embed.go.tmpl embed.go 9 | 10 | GZIP_OPTS="-fkn" 11 | # gzip option '-k' may not always exist in the latest gzip available on different distros. 12 | if ! gzip -k -h &>/dev/null; then GZIP_OPTS="-fn"; fi 13 | 14 | dist="dist" 15 | 16 | if ! [[ -d "${dist}" ]]; then 17 | mkdir -p ${dist} 18 | echo " 19 | 20 | 21 | 22 | Node 23 | 24 | 25 | 26 | 27 | 28 |
29 |

This is the default index, looks like you forget to generate the react app before generating the golang endpoint.

30 |
31 | 32 | " > ${dist}/index.html 33 | fi 34 | 35 | find dist -type f -name '*.gz' -delete 36 | find dist -type f -exec gzip $GZIP_OPTS '{}' \; -print0 | xargs -0 -I % echo %.gz | sort | xargs echo //go:embed >> embed.go 37 | echo var embedFS embed.FS >> embed.go 38 | -------------------------------------------------------------------------------- /scripts/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Generate all protobuf bindings. 4 | # Run from repository root. 5 | # 6 | # Initial script taken from etcd under the Apache 2.0 license 7 | # File: https://github.com/coreos/etcd/blob/78a5eb79b510eb497deddd1a76f5153bc4b202d2/scripts/genproto.sh 8 | 9 | set -e 10 | set -u 11 | 12 | if ! [[ "$0" =~ "scripts/genproto.sh" ]]; then 13 | echo "must be run from repository root" 14 | exit 255 15 | fi 16 | 17 | if ! [[ $(protoc --version) =~ "3.15.8" ]]; then 18 | echo "could not find protoc 3.15.8, is it installed + in PATH?" 19 | exit 255 20 | fi 21 | 22 | echo "installing plugins" 23 | 24 | # Since we run go mod download, the go.sum will change. 25 | # Make a backup. 26 | cp go.sum go.sum.bak 27 | 28 | go mod download 29 | 30 | INSTALL_PKGS="golang.org/x/tools/cmd/goimports github.com/gogo/protobuf/protoc-gen-gogofast" 31 | for pkg in ${INSTALL_PKGS}; do 32 | go install "$pkg" 33 | done 34 | 35 | GOGOPROTO_ROOT="$(go list -mod=readonly -f '{{ .Dir }}' -m github.com/gogo/protobuf)" 36 | GOGOPROTO_PATH="${GOGOPROTO_ROOT}:${GOGOPROTO_ROOT}/protobuf" 37 | 38 | DIRS="nflog/nflogpb silence/silencepb cluster/clusterpb" 39 | 40 | echo "generating files" 41 | for dir in ${DIRS}; do 42 | pushd ${dir} 43 | protoc --gogofast_out=:. -I=. \ 44 | -I="${GOGOPROTO_PATH}" \ 45 | *.proto 46 | 47 | sed -i.bak -E 's/import _ \"gogoproto\"//g' *.pb.go 48 | sed -i.bak -E 's/import _ \"google\/protobuf\"//g' *.pb.go 49 | sed -i.bak -E 's/\t_ \"google\/protobuf\"//g' -- *.pb.go 50 | rm -f *.bak 51 | goimports -w *.pb.go 52 | popd 53 | done 54 | 55 | mv go.sum.bak go.sum 56 | -------------------------------------------------------------------------------- /scripts/package_assets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # compress static assets 4 | 5 | set -euo pipefail 6 | 7 | version="$(< VERSION)" 8 | mkdir -p .tarballs 9 | tar czf .tarballs/alertmanager-web-ui-${version}.tar.gz ui/app/script.js ui/app/index.html 10 | -------------------------------------------------------------------------------- /scripts/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build tools 15 | // +build tools 16 | 17 | // Package tools tracks dependencies for tools that are required to generate the protobuf code. 18 | // See https://github.com/golang/go/issues/25922 19 | package tools 20 | 21 | import ( 22 | _ "github.com/gogo/protobuf/protoc-gen-gogofast" 23 | _ "golang.org/x/tools/cmd/goimports" 24 | ) 25 | -------------------------------------------------------------------------------- /template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | 3 | ENV NODE_PATH="/usr/local/lib/node_modules" 4 | 5 | RUN npm install juice@10.0.1 -g 6 | 7 | ENTRYPOINT [""] 8 | -------------------------------------------------------------------------------- /template/Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_IMG := alertmanager-template 2 | DOCKER_RUN_CURRENT_USER := docker run --user=$(shell id -u $(USER)):$(shell id -g $(USER)) 3 | DOCKER_CMD := $(DOCKER_RUN_CURRENT_USER) --rm -t -v $(PWD):/app -w /app $(DOCKER_IMG) 4 | 5 | ifeq ($(NO_DOCKER), true) 6 | DOCKER_CMD= 7 | endif 8 | 9 | template-image: 10 | @(if [ "$(NO_DOCKER)" != "true" ] ; then \ 11 | echo ">> build template docker image"; \ 12 | docker build -t $(DOCKER_IMG) . > /dev/null; \ 13 | fi; ) 14 | 15 | email.tmpl: template-image email.html 16 | @echo ">> inline css for html email template" 17 | $(DOCKER_CMD) ./inline-css.js 18 | -------------------------------------------------------------------------------- /template/inline-css.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Copyright 2021 The Prometheus Authors 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | const juice = require('juice') 17 | const fs = require('fs') 18 | 19 | const inputFile = 'email.html' 20 | const outputFile = 'email.tmpl' 21 | 22 | var inputData = '' 23 | 24 | try { 25 | inputData = fs.readFileSync(inputFile, 'utf8') 26 | } catch (err) { 27 | console.error(err) 28 | process.exit(1) 29 | } 30 | 31 | var templateData = juice(inputData) 32 | 33 | const outputData = ` 34 | {{ define "email.default.subject" }}{{ template "__subject" . }}{{ end }} 35 | {{ define "email.default.html" }} 36 | ${templateData} 37 | {{ end }} 38 | ` 39 | 40 | fs.writeFileSync(outputFile, outputData) 41 | -------------------------------------------------------------------------------- /test/with_api_v2/acceptance/web_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package test 15 | 16 | import ( 17 | "testing" 18 | 19 | a "github.com/prometheus/alertmanager/test/with_api_v2" 20 | ) 21 | 22 | func TestWebWithPrefix(t *testing.T) { 23 | t.Parallel() 24 | 25 | conf := ` 26 | route: 27 | receiver: "default" 28 | group_by: [] 29 | group_wait: 1s 30 | group_interval: 1s 31 | repeat_interval: 1h 32 | 33 | receivers: 34 | - name: "default" 35 | ` 36 | 37 | // The test framework polls the API with the given prefix during 38 | // Alertmanager startup and thereby ensures proper configuration. 39 | at := a.NewAcceptanceTest(t, &a.AcceptanceOpts{RoutePrefix: "/foo"}) 40 | at.AlertmanagerCluster(conf, 1) 41 | at.Run() 42 | } 43 | -------------------------------------------------------------------------------- /ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-bookworm 2 | 3 | ENV NPM_CONFIG_PREFIX=/home/node/.npm-global 4 | ENV PATH=$PATH:/home/node/.npm-global/bin 5 | 6 | RUN mkdir -p $NPM_CONFIG_PREFIX; yarn global add \ 7 | elm@0.19.1 \ 8 | elm-format@0.8.7 \ 9 | elm-test@0.19.1-revision6 \ 10 | uglify-js@3.13.4 \ 11 | elm-review@2.5.0 12 | -------------------------------------------------------------------------------- /ui/app/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | elm-stuff/ 3 | script.js 4 | .elm 5 | /elm-* 6 | /openapi-* 7 | -------------------------------------------------------------------------------- /ui/app/README.md: -------------------------------------------------------------------------------- 1 | # Alertmanager UI 2 | 3 | This is a re-write of the Alertmanager UI in [elm-lang](http://elm-lang.org/). 4 | 5 | ## Usage 6 | 7 | ### Filtering on the alerts page 8 | 9 | By default, the alerts page only shows active (not silenced) alerts. Adding a 10 | query string containing the following will additionally show silenced alerts. 11 | 12 | ``` 13 | http://alertmanager/#/alerts?silenced=true 14 | ``` 15 | 16 | In order to show _only_ silenced alerts, update the query string to hide active alerts. 17 | ``` 18 | http://alertmanager/#/alerts?silenced=true&active=false 19 | ``` 20 | 21 | The alerts page can also be filtered by the receivers for a page. Receivers are 22 | configured in Alertmanager's yaml configuration file. 23 | 24 | ``` 25 | http://alertmanager/#/alerts?receiver=backend 26 | ``` 27 | 28 | Filtering based on label matchers is available. They can easily be added and 29 | modified through the UI. 30 | 31 | ``` 32 | http://alertmanager/#/alerts?filter=%7Bseverity%3D%22warning%22%2C%20owner%3D%22backend%22%7D 33 | ``` 34 | 35 | These filters can be used in conjunction. 36 | 37 | ### Filtering on the silences page 38 | 39 | Filtering based on label matchers is available. They can easily be added and 40 | modified through the UI. 41 | 42 | ``` 43 | http://alertmanager/#/silences?filter=%7Bseverity%3D%22warning%22%2C%20owner%3D%22backend%22%7D 44 | ``` 45 | 46 | ### Note on filtering via label matchers 47 | 48 | Filtering via label matchers follows the same syntax and semantics as Prometheus. 49 | 50 | A properly formatted filter is a set of label matchers joined by accepted 51 | matching operators, surrounded by curly braces: 52 | 53 | ``` 54 | {foo="bar", baz=~"quu.*"} 55 | ``` 56 | 57 | Operators include: 58 | 59 | - `=` 60 | - `!=` 61 | - `=~` 62 | - `!~` 63 | 64 | See the official documentation for additional information: https://prometheus.io/docs/querying/basics/#instant-vector-selectors 65 | -------------------------------------------------------------------------------- /ui/app/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.0", 10 | "elm/core": "1.0.0", 11 | "elm/html": "1.0.0", 12 | "elm/http": "1.0.0", 13 | "elm/json": "1.0.0", 14 | "elm/parser": "1.1.0", 15 | "elm/regex": "1.0.0", 16 | "elm/time": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "rtfeldman/elm-iso8601-date-strings": "1.1.2", 19 | "NoRedInk/elm-json-decode-pipeline": "1.0.0", 20 | "justinmimbs/time-extra": "1.1.0" 21 | }, 22 | "indirect": { 23 | "elm/virtual-dom": "1.0.0", 24 | "elm/random": "1.0.0", 25 | "justinmimbs/date": "3.2.0" 26 | } 27 | }, 28 | "test-dependencies": { 29 | "direct": { 30 | "elm-explorations/test": "1.0.0" 31 | }, 32 | "indirect": {} 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ui/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/favicon.ico -------------------------------------------------------------------------------- /ui/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Alertmanager 8 | 9 | 10 | 18 | 19 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ui/app/lib/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/lib/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /ui/app/review/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/core": "1.0.5", 10 | "elm/json": "1.1.3", 11 | "elm/project-metadata-utils": "1.0.1", 12 | "jfmengels/elm-review": "2.4.1", 13 | "jfmengels/elm-review-simplify": "1.0.1", 14 | "jfmengels/elm-review-unused": "1.1.9", 15 | "stil4m/elm-syntax": "7.2.2" 16 | }, 17 | "indirect": { 18 | "elm/html": "1.0.0", 19 | "elm/parser": "1.1.0", 20 | "elm/random": "1.0.0", 21 | "elm/time": "1.0.0", 22 | "elm/virtual-dom": "1.0.2", 23 | "elm-community/list-extra": "8.3.0", 24 | "elm-explorations/test": "1.2.2", 25 | "rtfeldman/elm-hex": "1.0.0", 26 | "stil4m/structured-writer": "1.0.3" 27 | } 28 | }, 29 | "test-dependencies": { 30 | "direct": { 31 | "elm-explorations/test": "1.2.2" 32 | }, 33 | "indirect": {} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ui/app/review/src/ReviewConfig.elm: -------------------------------------------------------------------------------- 1 | module ReviewConfig exposing (config) 2 | 3 | {-| Do not rename the ReviewConfig module or the config function, because 4 | `elm-review` will look for these. 5 | 6 | To add packages that contain rules, add them to this review project using 7 | 8 | `elm install author/packagename` 9 | 10 | when inside the directory containing this file. 11 | 12 | -} 13 | 14 | import NoUnused.CustomTypeConstructorArgs 15 | import NoUnused.CustomTypeConstructors 16 | import NoUnused.Dependencies 17 | import NoUnused.Exports 18 | import NoUnused.Modules 19 | import NoUnused.Parameters 20 | import NoUnused.Patterns 21 | import NoUnused.Variables 22 | import Simplify 23 | import Review.Rule exposing (Rule) 24 | 25 | 26 | config : List Rule 27 | config = 28 | List.map 29 | (Review.Rule.ignoreErrorsForDirectories [ "src/Data/" ]) 30 | [ NoUnused.CustomTypeConstructors.rule [] 31 | , NoUnused.CustomTypeConstructorArgs.rule 32 | , NoUnused.Dependencies.rule 33 | , NoUnused.Exports.rule 34 | , NoUnused.Modules.rule 35 | , NoUnused.Parameters.rule 36 | , NoUnused.Patterns.rule 37 | , NoUnused.Variables.rule 38 | , Simplify.rule 39 | ] 40 | -------------------------------------------------------------------------------- /ui/app/src/Alerts/Api.elm: -------------------------------------------------------------------------------- 1 | module Alerts.Api exposing (fetchAlertGroups, fetchAlerts, fetchReceivers) 2 | 3 | import Data.AlertGroup exposing (AlertGroup) 4 | import Data.GettableAlert exposing (GettableAlert) 5 | import Data.Receiver exposing (Receiver) 6 | import Json.Decode 7 | import Utils.Api 8 | import Utils.Filter exposing (Filter, generateAPIQueryString) 9 | import Utils.Types exposing (ApiData) 10 | 11 | 12 | fetchReceivers : String -> Cmd (ApiData (List Receiver)) 13 | fetchReceivers apiUrl = 14 | Utils.Api.send 15 | (Utils.Api.get 16 | (apiUrl ++ "/receivers") 17 | (Json.Decode.list Data.Receiver.decoder) 18 | ) 19 | 20 | 21 | fetchAlertGroups : String -> Filter -> Cmd (ApiData (List AlertGroup)) 22 | fetchAlertGroups apiUrl filter = 23 | let 24 | url = 25 | String.join "/" [ apiUrl, "alerts", "groups" ++ generateAPIQueryString filter ] 26 | in 27 | Utils.Api.send (Utils.Api.get url (Json.Decode.list Data.AlertGroup.decoder)) 28 | 29 | 30 | fetchAlerts : String -> Filter -> Cmd (ApiData (List GettableAlert)) 31 | fetchAlerts apiUrl filter = 32 | let 33 | url = 34 | String.join "/" [ apiUrl, "alerts" ++ generateAPIQueryString filter ] 35 | in 36 | Utils.Api.send (Utils.Api.get url (Json.Decode.list Data.GettableAlert.decoder)) 37 | -------------------------------------------------------------------------------- /ui/app/src/Data/Alert.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.Alert exposing (Alert(..), decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias Alert = 22 | { labels : Dict String String 23 | , generatorURL : Maybe String 24 | } 25 | 26 | 27 | decoder : Decoder Alert 28 | decoder = 29 | Decode.succeed Alert 30 | |> required "labels" (Decode.dict Decode.string) 31 | |> optional "generatorURL" (Decode.nullable Decode.string) Nothing 32 | 33 | 34 | encoder : Alert -> Encode.Value 35 | encoder model = 36 | Encode.object 37 | [ ( "labels", Encode.dict identity Encode.string model.labels ) 38 | , ( "generatorURL", Maybe.withDefault Encode.null (Maybe.map Encode.string model.generatorURL) ) 39 | ] 40 | -------------------------------------------------------------------------------- /ui/app/src/Data/AlertGroup.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.AlertGroup exposing (AlertGroup, decoder, encoder) 14 | 15 | import Data.GettableAlert as GettableAlert exposing (GettableAlert) 16 | import Data.Receiver as Receiver exposing (Receiver) 17 | import Dict exposing (Dict) 18 | import Json.Decode as Decode exposing (Decoder) 19 | import Json.Decode.Pipeline exposing (optional, required) 20 | import Json.Encode as Encode 21 | 22 | 23 | type alias AlertGroup = 24 | { labels : Dict String String 25 | , receiver : Receiver 26 | , alerts : List GettableAlert 27 | } 28 | 29 | 30 | decoder : Decoder AlertGroup 31 | decoder = 32 | Decode.succeed AlertGroup 33 | |> required "labels" (Decode.dict Decode.string) 34 | |> required "receiver" Receiver.decoder 35 | |> required "alerts" (Decode.list GettableAlert.decoder) 36 | 37 | 38 | encoder : AlertGroup -> Encode.Value 39 | encoder model = 40 | Encode.object 41 | [ ( "labels", Encode.dict identity Encode.string model.labels ) 42 | , ( "receiver", Receiver.encoder model.receiver ) 43 | , ( "alerts", Encode.list GettableAlert.encoder model.alerts ) 44 | ] 45 | -------------------------------------------------------------------------------- /ui/app/src/Data/AlertmanagerConfig.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.AlertmanagerConfig exposing (AlertmanagerConfig, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias AlertmanagerConfig = 22 | { original : String 23 | } 24 | 25 | 26 | decoder : Decoder AlertmanagerConfig 27 | decoder = 28 | Decode.succeed AlertmanagerConfig 29 | |> required "original" Decode.string 30 | 31 | 32 | encoder : AlertmanagerConfig -> Encode.Value 33 | encoder model = 34 | Encode.object 35 | [ ( "original", Encode.string model.original ) 36 | ] 37 | -------------------------------------------------------------------------------- /ui/app/src/Data/AlertmanagerStatus.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.AlertmanagerStatus exposing (AlertmanagerStatus, decoder, encoder) 14 | 15 | import Data.AlertmanagerConfig as AlertmanagerConfig exposing (AlertmanagerConfig) 16 | import Data.ClusterStatus as ClusterStatus exposing (ClusterStatus) 17 | import Data.VersionInfo as VersionInfo exposing (VersionInfo) 18 | import DateTime exposing (DateTime) 19 | import Dict exposing (Dict) 20 | import Json.Decode as Decode exposing (Decoder) 21 | import Json.Decode.Pipeline exposing (optional, required) 22 | import Json.Encode as Encode 23 | 24 | 25 | type alias AlertmanagerStatus = 26 | { cluster : ClusterStatus 27 | , versionInfo : VersionInfo 28 | , config : AlertmanagerConfig 29 | , uptime : DateTime 30 | } 31 | 32 | 33 | decoder : Decoder AlertmanagerStatus 34 | decoder = 35 | Decode.succeed AlertmanagerStatus 36 | |> required "cluster" ClusterStatus.decoder 37 | |> required "versionInfo" VersionInfo.decoder 38 | |> required "config" AlertmanagerConfig.decoder 39 | |> required "uptime" DateTime.decoder 40 | 41 | 42 | encoder : AlertmanagerStatus -> Encode.Value 43 | encoder model = 44 | Encode.object 45 | [ ( "cluster", ClusterStatus.encoder model.cluster ) 46 | , ( "versionInfo", VersionInfo.encoder model.versionInfo ) 47 | , ( "config", AlertmanagerConfig.encoder model.config ) 48 | , ( "uptime", DateTime.encoder model.uptime ) 49 | ] 50 | -------------------------------------------------------------------------------- /ui/app/src/Data/InlineResponse200.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.InlineResponse200 exposing (InlineResponse200, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias InlineResponse200 = 22 | { silenceID : Maybe String 23 | } 24 | 25 | 26 | decoder : Decoder InlineResponse200 27 | decoder = 28 | Decode.succeed InlineResponse200 29 | |> optional "silenceID" (Decode.nullable Decode.string) Nothing 30 | 31 | 32 | encoder : InlineResponse200 -> Encode.Value 33 | encoder model = 34 | Encode.object 35 | [ ( "silenceID", Maybe.withDefault Encode.null (Maybe.map Encode.string model.silenceID) ) 36 | ] 37 | -------------------------------------------------------------------------------- /ui/app/src/Data/Matcher.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.Matcher exposing (Matcher, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias Matcher = 22 | { name : String 23 | , value : String 24 | , isRegex : Bool 25 | , isEqual : Maybe Bool 26 | } 27 | 28 | 29 | decoder : Decoder Matcher 30 | decoder = 31 | Decode.succeed Matcher 32 | |> required "name" Decode.string 33 | |> required "value" Decode.string 34 | |> required "isRegex" Decode.bool 35 | |> optional "isEqual" (Decode.nullable Decode.bool) (Just True) 36 | 37 | 38 | encoder : Matcher -> Encode.Value 39 | encoder model = 40 | Encode.object 41 | [ ( "name", Encode.string model.name ) 42 | , ( "value", Encode.string model.value ) 43 | , ( "isRegex", Encode.bool model.isRegex ) 44 | , ( "isEqual", Maybe.withDefault Encode.null (Maybe.map Encode.bool model.isEqual) ) 45 | ] 46 | -------------------------------------------------------------------------------- /ui/app/src/Data/PeerStatus.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.PeerStatus exposing (PeerStatus, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias PeerStatus = 22 | { name : String 23 | , address : String 24 | } 25 | 26 | 27 | decoder : Decoder PeerStatus 28 | decoder = 29 | Decode.succeed PeerStatus 30 | |> required "name" Decode.string 31 | |> required "address" Decode.string 32 | 33 | 34 | encoder : PeerStatus -> Encode.Value 35 | encoder model = 36 | Encode.object 37 | [ ( "name", Encode.string model.name ) 38 | , ( "address", Encode.string model.address ) 39 | ] 40 | -------------------------------------------------------------------------------- /ui/app/src/Data/PostableSilence.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.PostableSilence exposing (PostableSilence, decoder, encoder) 14 | 15 | import Data.Matcher as Matcher exposing (Matcher) 16 | import DateTime exposing (DateTime) 17 | import Dict exposing (Dict) 18 | import Json.Decode as Decode exposing (Decoder) 19 | import Json.Decode.Pipeline exposing (optional, required) 20 | import Json.Encode as Encode 21 | 22 | 23 | type alias PostableSilence = 24 | { matchers : List Matcher 25 | , startsAt : DateTime 26 | , endsAt : DateTime 27 | , createdBy : String 28 | , comment : String 29 | , id : Maybe String 30 | } 31 | 32 | 33 | decoder : Decoder PostableSilence 34 | decoder = 35 | Decode.succeed PostableSilence 36 | |> required "matchers" (Decode.list Matcher.decoder) 37 | |> required "startsAt" DateTime.decoder 38 | |> required "endsAt" DateTime.decoder 39 | |> required "createdBy" Decode.string 40 | |> required "comment" Decode.string 41 | |> optional "id" (Decode.nullable Decode.string) Nothing 42 | 43 | 44 | encoder : PostableSilence -> Encode.Value 45 | encoder model = 46 | Encode.object 47 | [ ( "matchers", Encode.list Matcher.encoder model.matchers ) 48 | , ( "startsAt", DateTime.encoder model.startsAt ) 49 | , ( "endsAt", DateTime.encoder model.endsAt ) 50 | , ( "createdBy", Encode.string model.createdBy ) 51 | , ( "comment", Encode.string model.comment ) 52 | , ( "id", Maybe.withDefault Encode.null (Maybe.map Encode.string model.id) ) 53 | ] 54 | -------------------------------------------------------------------------------- /ui/app/src/Data/Receiver.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.Receiver exposing (Receiver, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias Receiver = 22 | { name : String 23 | } 24 | 25 | 26 | decoder : Decoder Receiver 27 | decoder = 28 | Decode.succeed Receiver 29 | |> required "name" Decode.string 30 | 31 | 32 | encoder : Receiver -> Encode.Value 33 | encoder model = 34 | Encode.object 35 | [ ( "name", Encode.string model.name ) 36 | ] 37 | -------------------------------------------------------------------------------- /ui/app/src/Data/Silence.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.Silence exposing (Silence(..), decoder, encoder) 14 | 15 | import Data.Matcher as Matcher exposing (Matcher) 16 | import DateTime exposing (DateTime) 17 | import Dict exposing (Dict) 18 | import Json.Decode as Decode exposing (Decoder) 19 | import Json.Decode.Pipeline exposing (optional, required) 20 | import Json.Encode as Encode 21 | 22 | 23 | type alias Silence = 24 | { matchers : List Matcher 25 | , startsAt : DateTime 26 | , endsAt : DateTime 27 | , createdBy : String 28 | , comment : String 29 | } 30 | 31 | 32 | decoder : Decoder Silence 33 | decoder = 34 | Decode.succeed Silence 35 | |> required "matchers" (Decode.list Matcher.decoder) 36 | |> required "startsAt" DateTime.decoder 37 | |> required "endsAt" DateTime.decoder 38 | |> required "createdBy" Decode.string 39 | |> required "comment" Decode.string 40 | 41 | 42 | encoder : Silence -> Encode.Value 43 | encoder model = 44 | Encode.object 45 | [ ( "matchers", Encode.list Matcher.encoder model.matchers ) 46 | , ( "startsAt", DateTime.encoder model.startsAt ) 47 | , ( "endsAt", DateTime.encoder model.endsAt ) 48 | , ( "createdBy", Encode.string model.createdBy ) 49 | , ( "comment", Encode.string model.comment ) 50 | ] 51 | -------------------------------------------------------------------------------- /ui/app/src/Data/SilenceStatus.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.SilenceStatus exposing (SilenceStatus, State(..), decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias SilenceStatus = 22 | { state : State 23 | } 24 | 25 | 26 | type State 27 | = Expired 28 | | Active 29 | | Pending 30 | 31 | 32 | decoder : Decoder SilenceStatus 33 | decoder = 34 | Decode.succeed SilenceStatus 35 | |> required "state" stateDecoder 36 | 37 | 38 | encoder : SilenceStatus -> Encode.Value 39 | encoder model = 40 | Encode.object 41 | [ ( "state", stateEncoder model.state ) 42 | ] 43 | 44 | 45 | stateDecoder : Decoder State 46 | stateDecoder = 47 | Decode.string 48 | |> Decode.andThen 49 | (\str -> 50 | case str of 51 | "expired" -> 52 | Decode.succeed Expired 53 | 54 | "active" -> 55 | Decode.succeed Active 56 | 57 | "pending" -> 58 | Decode.succeed Pending 59 | 60 | other -> 61 | Decode.fail <| "Unknown type: " ++ other 62 | ) 63 | 64 | 65 | stateEncoder : State -> Encode.Value 66 | stateEncoder model = 67 | case model of 68 | Expired -> 69 | Encode.string "expired" 70 | 71 | Active -> 72 | Encode.string "active" 73 | 74 | Pending -> 75 | Encode.string "pending" 76 | -------------------------------------------------------------------------------- /ui/app/src/Data/VersionInfo.elm: -------------------------------------------------------------------------------- 1 | {- 2 | Alertmanager API 3 | API of the Prometheus Alertmanager (https://github.com/prometheus/alertmanager) 4 | 5 | OpenAPI spec version: 0.0.1 6 | 7 | NOTE: This file is auto generated by the openapi-generator. 8 | https://github.com/openapitools/openapi-generator.git 9 | Do not edit this file manually. 10 | -} 11 | 12 | 13 | module Data.VersionInfo exposing (VersionInfo, decoder, encoder) 14 | 15 | import Dict exposing (Dict) 16 | import Json.Decode as Decode exposing (Decoder) 17 | import Json.Decode.Pipeline exposing (optional, required) 18 | import Json.Encode as Encode 19 | 20 | 21 | type alias VersionInfo = 22 | { version : String 23 | , revision : String 24 | , branch : String 25 | , buildUser : String 26 | , buildDate : String 27 | , goVersion : String 28 | } 29 | 30 | 31 | decoder : Decoder VersionInfo 32 | decoder = 33 | Decode.succeed VersionInfo 34 | |> required "version" Decode.string 35 | |> required "revision" Decode.string 36 | |> required "branch" Decode.string 37 | |> required "buildUser" Decode.string 38 | |> required "buildDate" Decode.string 39 | |> required "goVersion" Decode.string 40 | 41 | 42 | encoder : VersionInfo -> Encode.Value 43 | encoder model = 44 | Encode.object 45 | [ ( "version", Encode.string model.version ) 46 | , ( "revision", Encode.string model.revision ) 47 | , ( "branch", Encode.string model.branch ) 48 | , ( "buildUser", Encode.string model.buildUser ) 49 | , ( "buildDate", Encode.string model.buildDate ) 50 | , ( "goVersion", Encode.string model.goVersion ) 51 | ] 52 | -------------------------------------------------------------------------------- /ui/app/src/DateTime.elm: -------------------------------------------------------------------------------- 1 | module DateTime exposing (DateTime, decoder, encoder, toString) 2 | 3 | import Iso8601 4 | import Json.Decode as Decode exposing (Decoder) 5 | import Json.Encode as Encode 6 | import Result 7 | import Time 8 | 9 | 10 | type alias DateTime = 11 | Time.Posix 12 | 13 | 14 | decoder : Decoder DateTime 15 | decoder = 16 | Decode.string 17 | |> Decode.andThen decodeIsoString 18 | 19 | 20 | encoder : DateTime -> Encode.Value 21 | encoder = 22 | Encode.string << toString 23 | 24 | 25 | decodeIsoString : String -> Decoder DateTime 26 | decodeIsoString str = 27 | case Iso8601.toTime str of 28 | Result.Ok posix -> 29 | Decode.succeed posix 30 | 31 | Result.Err _ -> 32 | Decode.fail <| "Invalid date: " ++ str 33 | 34 | 35 | toString : DateTime -> String 36 | toString = 37 | Iso8601.fromTime 38 | -------------------------------------------------------------------------------- /ui/app/src/Silences/Decoders.elm: -------------------------------------------------------------------------------- 1 | module Silences.Decoders exposing (create, destroy) 2 | 3 | import Json.Decode as Json 4 | import Utils.Types exposing (ApiData(..)) 5 | 6 | 7 | create : Json.Decoder String 8 | create = 9 | Json.at [ "silenceID" ] Json.string 10 | 11 | 12 | destroy : Json.Decoder String 13 | destroy = 14 | Json.at [ "status" ] Json.string 15 | -------------------------------------------------------------------------------- /ui/app/src/Silences/Types.elm: -------------------------------------------------------------------------------- 1 | module Silences.Types exposing 2 | ( nullSilence 3 | , stateToString 4 | ) 5 | 6 | import Data.Matcher exposing (Matcher) 7 | import Data.PostableSilence exposing (PostableSilence) 8 | import Data.SilenceStatus exposing (State(..)) 9 | import Time 10 | 11 | 12 | nullSilence : PostableSilence 13 | nullSilence = 14 | { id = Nothing 15 | , createdBy = "" 16 | , comment = "" 17 | , startsAt = Time.millisToPosix 0 18 | , endsAt = Time.millisToPosix 0 19 | , matchers = nullMatchers 20 | } 21 | 22 | 23 | nullMatchers : List Matcher 24 | nullMatchers = 25 | [ nullMatcher ] 26 | 27 | 28 | nullMatcher : Matcher 29 | nullMatcher = 30 | Matcher "" "" False (Just True) 31 | 32 | 33 | stateToString : State -> String 34 | stateToString state = 35 | case state of 36 | Active -> 37 | "active" 38 | 39 | Pending -> 40 | "pending" 41 | 42 | Expired -> 43 | "expired" 44 | -------------------------------------------------------------------------------- /ui/app/src/Status/Api.elm: -------------------------------------------------------------------------------- 1 | module Status.Api exposing (clusterStatusToString, getStatus) 2 | 3 | import Data.AlertmanagerStatus exposing (AlertmanagerStatus) 4 | import Data.ClusterStatus exposing (Status(..)) 5 | import Utils.Api exposing (get, send) 6 | import Utils.Types exposing (ApiData) 7 | 8 | 9 | getStatus : String -> (ApiData AlertmanagerStatus -> msg) -> Cmd msg 10 | getStatus apiUrl msg = 11 | let 12 | url = 13 | String.join "/" [ apiUrl, "status" ] 14 | 15 | request = 16 | get url Data.AlertmanagerStatus.decoder 17 | in 18 | Cmd.map msg <| send request 19 | 20 | 21 | clusterStatusToString : Status -> String 22 | clusterStatusToString status = 23 | case status of 24 | Ready -> 25 | "ready" 26 | 27 | Settling -> 28 | "settling" 29 | 30 | Disabled -> 31 | "disabled" 32 | -------------------------------------------------------------------------------- /ui/app/src/Status/Types.elm: -------------------------------------------------------------------------------- 1 | module Status.Types exposing (ClusterPeer, ClusterStatus, VersionInfo) 2 | 3 | 4 | type alias StatusResponse = 5 | { config : String 6 | , uptime : String 7 | , versionInfo : VersionInfo 8 | , clusterStatus : Maybe ClusterStatus 9 | } 10 | 11 | 12 | type alias VersionInfo = 13 | { branch : String 14 | , buildDate : String 15 | , buildUser : String 16 | , goVersion : String 17 | , revision : String 18 | , version : String 19 | } 20 | 21 | 22 | type alias ClusterStatus = 23 | { name : String 24 | , status : String 25 | , peers : List ClusterPeer 26 | } 27 | 28 | 29 | type alias ClusterPeer = 30 | { name : String 31 | , address : String 32 | } 33 | -------------------------------------------------------------------------------- /ui/app/src/Utils/DateTimePicker/Types.elm: -------------------------------------------------------------------------------- 1 | module Utils.DateTimePicker.Types exposing 2 | ( DateTimePicker 3 | , InputHourOrMinute(..) 4 | , Msg(..) 5 | , StartOrEnd(..) 6 | , initDateTimePicker 7 | , initFromStartAndEndTime 8 | ) 9 | 10 | import Time exposing (Posix) 11 | import Utils.DateTimePicker.Utils exposing (FirstDayOfWeek, floorMinute) 12 | 13 | 14 | type alias DateTimePicker = 15 | { month : Maybe Posix 16 | , mouseOverDay : Maybe Posix 17 | , startDate : Maybe Posix 18 | , endDate : Maybe Posix 19 | , startTime : Maybe Posix 20 | , endTime : Maybe Posix 21 | , firstDayOfWeek : FirstDayOfWeek 22 | } 23 | 24 | 25 | type Msg 26 | = NextMonth 27 | | PrevMonth 28 | | MouseOverDay Posix 29 | | OnClickDay 30 | | ClearMouseOverDay 31 | | SetInputTime StartOrEnd InputHourOrMinute Int 32 | | IncrementTime StartOrEnd InputHourOrMinute Int 33 | 34 | 35 | type StartOrEnd 36 | = Start 37 | | End 38 | 39 | 40 | type InputHourOrMinute 41 | = InputHour 42 | | InputMinute 43 | 44 | 45 | initDateTimePicker : FirstDayOfWeek -> DateTimePicker 46 | initDateTimePicker firstDayOfWeek = 47 | { month = Nothing 48 | , mouseOverDay = Nothing 49 | , startDate = Nothing 50 | , endDate = Nothing 51 | , startTime = Nothing 52 | , endTime = Nothing 53 | , firstDayOfWeek = firstDayOfWeek 54 | } 55 | 56 | 57 | initFromStartAndEndTime : Maybe Posix -> Maybe Posix -> FirstDayOfWeek -> DateTimePicker 58 | initFromStartAndEndTime start end firstDayOfWeek = 59 | let 60 | startTime = 61 | Maybe.map (\s -> floorMinute s) start 62 | 63 | endTime = 64 | Maybe.map (\e -> floorMinute e) end 65 | in 66 | { month = start 67 | , mouseOverDay = Nothing 68 | , startDate = start 69 | , endDate = end 70 | , startTime = startTime 71 | , endTime = endTime 72 | , firstDayOfWeek = firstDayOfWeek 73 | } 74 | -------------------------------------------------------------------------------- /ui/app/src/Utils/FormValidation.elm: -------------------------------------------------------------------------------- 1 | module Utils.FormValidation exposing 2 | ( ValidatedField 3 | , ValidationState(..) 4 | , initialField 5 | , stringNotEmpty 6 | , updateValue 7 | , validate 8 | ) 9 | 10 | 11 | type ValidationState 12 | = Initial 13 | | Valid 14 | | Invalid String 15 | 16 | 17 | fromResult : Result String a -> ValidationState 18 | fromResult result = 19 | case result of 20 | Ok _ -> 21 | Valid 22 | 23 | Err str -> 24 | Invalid str 25 | 26 | 27 | type alias ValidatedField = 28 | { value : String 29 | , validationState : ValidationState 30 | } 31 | 32 | 33 | initialField : String -> ValidatedField 34 | initialField value = 35 | { value = value 36 | , validationState = Initial 37 | } 38 | 39 | 40 | updateValue : String -> ValidatedField -> ValidatedField 41 | updateValue value field = 42 | { field | value = value, validationState = Initial } 43 | 44 | 45 | validate : (String -> Result String a) -> ValidatedField -> ValidatedField 46 | validate validator field = 47 | { field | validationState = fromResult (validator field.value) } 48 | 49 | 50 | stringNotEmpty : String -> Result String String 51 | stringNotEmpty string = 52 | if String.isEmpty (String.trim string) then 53 | Err "Should not be empty" 54 | 55 | else 56 | Ok string 57 | -------------------------------------------------------------------------------- /ui/app/src/Utils/Keyboard.elm: -------------------------------------------------------------------------------- 1 | module Utils.Keyboard exposing (keys, onKeyDown, onKeyUp) 2 | 3 | import Html exposing (Attribute) 4 | import Html.Events exposing (keyCode, on) 5 | import Json.Decode as Json 6 | 7 | 8 | keys : 9 | { backspace : Int 10 | , enter : Int 11 | , up : Int 12 | , down : Int 13 | } 14 | keys = 15 | { backspace = 8 16 | , enter = 13 17 | , up = 38 18 | , down = 40 19 | } 20 | 21 | 22 | onKeyDown : (Int -> msg) -> Attribute msg 23 | onKeyDown tagger = 24 | on "keydown" (Json.map tagger keyCode) 25 | 26 | 27 | onKeyUp : (Int -> msg) -> Attribute msg 28 | onKeyUp tagger = 29 | on "keyup" (Json.map tagger keyCode) 30 | -------------------------------------------------------------------------------- /ui/app/src/Utils/Types.elm: -------------------------------------------------------------------------------- 1 | module Utils.Types exposing (ApiData(..), Label, Labels, Matcher) 2 | 3 | 4 | type ApiData a 5 | = Initial 6 | | Loading 7 | | Failure String 8 | | Success a 9 | 10 | 11 | type alias Matcher = 12 | { isRegex : Bool 13 | , isEqual : Maybe Bool 14 | , name : String 15 | , value : String 16 | } 17 | 18 | 19 | type alias Matchers = 20 | List Matcher 21 | 22 | 23 | type alias Labels = 24 | List Label 25 | 26 | 27 | type alias Label = 28 | ( String, String ) 29 | -------------------------------------------------------------------------------- /ui/app/src/Views/AlertList/Parsing.elm: -------------------------------------------------------------------------------- 1 | module Views.AlertList.Parsing exposing (alertsParser) 2 | 3 | import Url.Parser exposing ((), Parser, map, s) 4 | import Url.Parser.Query as Query 5 | import Utils.Filter exposing (Filter, MatchOperator(..)) 6 | 7 | 8 | boolParam : String -> Query.Parser Bool 9 | boolParam name = 10 | Query.custom name (List.head >> (/=) Nothing) 11 | 12 | 13 | maybeBoolParam : String -> Query.Parser (Maybe Bool) 14 | maybeBoolParam name = 15 | Query.custom name 16 | (List.head >> Maybe.map (String.toLower >> (/=) "false")) 17 | 18 | 19 | alertsParser : Parser (Filter -> a) a 20 | alertsParser = 21 | s "alerts" 22 | Query.string "filter" 23 | Query.string "group" 24 | boolParam "customGrouping" 25 | Query.string "receiver" 26 | maybeBoolParam "silenced" 27 | maybeBoolParam "inhibited" 28 | maybeBoolParam "muted" 29 | maybeBoolParam "active" 30 | |> map Filter 31 | -------------------------------------------------------------------------------- /ui/app/src/Views/AlertList/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.AlertList.Types exposing 2 | ( AlertListMsg(..) 3 | , Model 4 | , Tab(..) 5 | , initAlertList 6 | ) 7 | 8 | import Browser.Navigation exposing (Key) 9 | import Data.AlertGroup exposing (AlertGroup) 10 | import Data.GettableAlert exposing (GettableAlert) 11 | import Set exposing (Set) 12 | import Utils.Types exposing (ApiData(..)) 13 | import Views.FilterBar.Types as FilterBar 14 | import Views.GroupBar.Types as GroupBar 15 | import Views.ReceiverBar.Types as ReceiverBar 16 | 17 | 18 | type AlertListMsg 19 | = AlertsFetched (ApiData (List GettableAlert)) 20 | | AlertGroupsFetched (ApiData (List AlertGroup)) 21 | | FetchAlerts 22 | | MsgForReceiverBar ReceiverBar.Msg 23 | | MsgForFilterBar FilterBar.Msg 24 | | MsgForGroupBar GroupBar.Msg 25 | | ToggleSilenced Bool 26 | | ToggleInhibited Bool 27 | | ToggleMuted Bool 28 | | SetActive (Maybe String) 29 | | ActiveGroups Int 30 | | SetTab Tab 31 | | ToggleExpandAll Bool 32 | 33 | 34 | type Tab 35 | = FilterTab 36 | | GroupTab 37 | 38 | 39 | type alias Model = 40 | { alerts : ApiData (List GettableAlert) 41 | , alertGroups : ApiData (List AlertGroup) 42 | , receiverBar : ReceiverBar.Model 43 | , groupBar : GroupBar.Model 44 | , filterBar : FilterBar.Model 45 | , tab : Tab 46 | , activeId : Maybe String 47 | , activeGroups : Set Int 48 | , key : Key 49 | , expandAll : Bool 50 | } 51 | 52 | 53 | initAlertList : Key -> Bool -> Model 54 | initAlertList key expandAll = 55 | { alerts = Initial 56 | , alertGroups = Initial 57 | , receiverBar = ReceiverBar.initReceiverBar key 58 | , groupBar = GroupBar.initGroupBar key 59 | , filterBar = FilterBar.initFilterBar [] 60 | , tab = FilterTab 61 | , activeId = Nothing 62 | , activeGroups = Set.empty 63 | , key = key 64 | , expandAll = expandAll 65 | } 66 | -------------------------------------------------------------------------------- /ui/app/src/Views/FilterBar/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.FilterBar.Types exposing (Model, Msg(..), initFilterBar) 2 | 3 | import Utils.Filter 4 | 5 | 6 | type alias Model = 7 | { matchers : List Utils.Filter.Matcher 8 | , backspacePressed : Bool 9 | , matcherText : String 10 | } 11 | 12 | 13 | type Msg 14 | = AddFilterMatcher Bool Utils.Filter.Matcher 15 | | DeleteFilterMatcher Bool Utils.Filter.Matcher 16 | | PressingBackspace Bool 17 | | UpdateMatcherText String 18 | | Noop 19 | 20 | 21 | {-| A note about the `backspacePressed` attribute: 22 | 23 | Holding down the backspace removes (one by one) each last character in the input, 24 | and the whole time sends multiple keyDown events. This is a guard so that if a user 25 | holds down backspace to remove the text in the input, they won't accidentally hold 26 | backspace too long and then delete the preceding matcher as well. So, once a user holds 27 | backspace to clear an input, they have to then lift up the key and press it again to 28 | proceed to deleting the next matcher. 29 | 30 | -} 31 | initFilterBar : List Utils.Filter.Matcher -> Model 32 | initFilterBar matchers = 33 | { matchers = matchers 34 | , backspacePressed = False 35 | , matcherText = "" 36 | } 37 | -------------------------------------------------------------------------------- /ui/app/src/Views/GroupBar/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.GroupBar.Types exposing (Model, Msg(..), initGroupBar) 2 | 3 | import Browser.Navigation exposing (Key) 4 | import Set exposing (Set) 5 | 6 | 7 | type alias Model = 8 | { list : Set String 9 | , fieldText : String 10 | , fields : List String 11 | , matches : List String 12 | , backspacePressed : Bool 13 | , focused : Bool 14 | , resultsHovered : Bool 15 | , maybeSelectedMatch : Maybe String 16 | , key : Key 17 | } 18 | 19 | 20 | type Msg 21 | = AddField Bool String 22 | | DeleteField Bool String 23 | | Select (Maybe String) 24 | | PressingBackspace Bool 25 | | Focus Bool 26 | | ResultsHovered Bool 27 | | UpdateFieldText String 28 | | CustomGrouping Bool 29 | | Noop 30 | 31 | 32 | initGroupBar : Key -> Model 33 | initGroupBar key = 34 | { list = Set.empty 35 | , fieldText = "" 36 | , fields = [] 37 | , matches = [] 38 | , focused = False 39 | , resultsHovered = False 40 | , backspacePressed = False 41 | , maybeSelectedMatch = Nothing 42 | , key = key 43 | } 44 | -------------------------------------------------------------------------------- /ui/app/src/Views/NavBar/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.NavBar.Types exposing (Tab, alertsTab, noneTab, settingsTab, silencesTab, statusTab, tabs) 2 | 3 | 4 | type alias Tab = 5 | { link : String 6 | , name : String 7 | } 8 | 9 | 10 | alertsTab : Tab 11 | alertsTab = 12 | { link = "#/alerts", name = "Alerts" } 13 | 14 | 15 | silencesTab : Tab 16 | silencesTab = 17 | { link = "#/silences", name = "Silences" } 18 | 19 | 20 | statusTab : Tab 21 | statusTab = 22 | { link = "#/status", name = "Status" } 23 | 24 | 25 | settingsTab : Tab 26 | settingsTab = 27 | { link = "#/settings", name = "Settings" } 28 | 29 | 30 | helpTab : Tab 31 | helpTab = 32 | { link = "https://prometheus.io/docs/alerting/alertmanager/", name = "Help" } 33 | 34 | 35 | noneTab : Tab 36 | noneTab = 37 | { link = "", name = "" } 38 | 39 | 40 | tabs : List Tab 41 | tabs = 42 | [ alertsTab, silencesTab, statusTab, settingsTab, helpTab ] 43 | -------------------------------------------------------------------------------- /ui/app/src/Views/NotFound/Views.elm: -------------------------------------------------------------------------------- 1 | module Views.NotFound.Views exposing (view) 2 | 3 | import Html exposing (Html, div, h1, text) 4 | import Types exposing (Msg) 5 | 6 | 7 | view : Html Msg 8 | view = 9 | div [] 10 | [ h1 [] [ text "not found" ] 11 | ] 12 | -------------------------------------------------------------------------------- /ui/app/src/Views/ReceiverBar/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.ReceiverBar.Types exposing (Model, Msg(..), Receiver, apiReceiverToReceiver, initReceiverBar) 2 | 3 | import Browser.Navigation exposing (Key) 4 | import Data.Receiver 5 | import Regex 6 | import Utils.Types exposing (ApiData(..)) 7 | 8 | 9 | type Msg 10 | = ReceiversFetched (ApiData (List Data.Receiver.Receiver)) 11 | | UpdateReceiver String 12 | | EditReceivers 13 | | FilterByReceiver String 14 | | Select (Maybe Receiver) 15 | | ResultsHovered Bool 16 | | BlurReceiverField 17 | | Noop 18 | 19 | 20 | type alias Model = 21 | { receivers : List Receiver 22 | , matches : List Receiver 23 | , fieldText : String 24 | , selectedReceiver : Maybe Receiver 25 | , showReceivers : Bool 26 | , resultsHovered : Bool 27 | , key : Key 28 | } 29 | 30 | 31 | type alias Receiver = 32 | { name : String 33 | , regex : String 34 | } 35 | 36 | 37 | escapeRegExp : String -> String 38 | escapeRegExp text = 39 | let 40 | reg = 41 | Regex.fromString "[-[\\]{}()*+?.,\\\\^$|#\\s]" |> Maybe.withDefault Regex.never 42 | in 43 | Regex.replace reg (.match >> (++) "\\") text 44 | 45 | 46 | apiReceiverToReceiver : Data.Receiver.Receiver -> Receiver 47 | apiReceiverToReceiver r = 48 | Receiver r.name (escapeRegExp r.name) 49 | 50 | 51 | initReceiverBar : Key -> Model 52 | initReceiverBar key = 53 | { receivers = [] 54 | , matches = [] 55 | , fieldText = "" 56 | , selectedReceiver = Nothing 57 | , showReceivers = False 58 | , resultsHovered = False 59 | , key = key 60 | } 61 | -------------------------------------------------------------------------------- /ui/app/src/Views/Settings/Parsing.elm: -------------------------------------------------------------------------------- 1 | module Views.Settings.Parsing exposing (settingsViewParser) 2 | 3 | import Url.Parser exposing (Parser, s) 4 | 5 | 6 | settingsViewParser : Parser a a 7 | settingsViewParser = 8 | s "settings" 9 | -------------------------------------------------------------------------------- /ui/app/src/Views/Settings/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.Settings.Types exposing (..) 2 | 3 | import Utils.DateTimePicker.Utils exposing (FirstDayOfWeek) 4 | 5 | 6 | type alias Model = 7 | { firstDayOfWeek : FirstDayOfWeek 8 | } 9 | 10 | 11 | type SettingsMsg 12 | = UpdateFirstDayOfWeek String 13 | -------------------------------------------------------------------------------- /ui/app/src/Views/Settings/Updates.elm: -------------------------------------------------------------------------------- 1 | port module Views.Settings.Updates exposing (..) 2 | 3 | import Task 4 | import Types exposing (Msg(..)) 5 | import Utils.DateTimePicker.Utils exposing (FirstDayOfWeek(..)) 6 | import Views.Settings.Types exposing (..) 7 | import Views.SilenceForm.Types 8 | 9 | 10 | update : SettingsMsg -> Model -> ( Model, Cmd Msg ) 11 | update msg model = 12 | case msg of 13 | Views.Settings.Types.UpdateFirstDayOfWeek firstDayOfWeekString -> 14 | let 15 | firstDayOfWeek = 16 | case firstDayOfWeekString of 17 | "Monday" -> 18 | Monday 19 | 20 | "Sunday" -> 21 | Sunday 22 | 23 | _ -> 24 | Monday 25 | 26 | firstDayOfWeekString2 = 27 | case firstDayOfWeek of 28 | Monday -> 29 | "Monday" 30 | 31 | Sunday -> 32 | "Sunday" 33 | in 34 | ( { model | firstDayOfWeek = firstDayOfWeek } 35 | , Cmd.batch 36 | [ Task.perform identity 37 | (Task.succeed 38 | (MsgForSilenceForm 39 | (Views.SilenceForm.Types.UpdateFirstDayOfWeek 40 | firstDayOfWeek 41 | ) 42 | ) 43 | ) 44 | , persistFirstDayOfWeek firstDayOfWeekString2 45 | ] 46 | ) 47 | 48 | 49 | port persistFirstDayOfWeek : String -> Cmd msg 50 | -------------------------------------------------------------------------------- /ui/app/src/Views/Settings/Views.elm: -------------------------------------------------------------------------------- 1 | module Views.Settings.Views exposing (view) 2 | 3 | import Html exposing (..) 4 | import Html.Attributes exposing (checked, class, for, id, type_, value) 5 | import Html.Events exposing (..) 6 | import Utils.DateTimePicker.Utils exposing (FirstDayOfWeek(..)) 7 | import Views.Settings.Types exposing (Model, SettingsMsg(..)) 8 | 9 | 10 | view : Model -> Html SettingsMsg 11 | view model = 12 | div [] 13 | [ div [ class "no-gutters" ] 14 | [ label 15 | [ for "fieldset" ] 16 | [ text "First day of the week:" ] 17 | , fieldset [ id "fieldset" ] 18 | [ radio "Monday" (model.firstDayOfWeek == Monday) UpdateFirstDayOfWeek 19 | , radio "Sunday" (model.firstDayOfWeek == Sunday) UpdateFirstDayOfWeek 20 | ] 21 | , small [ class "form-text text-muted" ] 22 | [ text "Note: This setting is saved in local storage of your browser" 23 | ] 24 | ] 25 | ] 26 | 27 | 28 | radio : String -> Bool -> (String -> msg) -> Html msg 29 | radio radioValue isChecked msg = 30 | label [ class "mt-1 ml-1 custom-control custom-radio" ] 31 | [ input 32 | [ type_ "checkbox" 33 | , class "custom-control-input" 34 | , checked isChecked 35 | , value radioValue 36 | , onInput msg 37 | ] 38 | [] 39 | , span [ class "custom-control-indicator" ] [] 40 | , span [ class "custom-control-description" ] [ text radioValue ] 41 | ] 42 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/Alert.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.Alert exposing (annotation, annotationsButton, generatorUrlButton, titleView) 2 | 3 | import Data.GettableAlert exposing (GettableAlert) 4 | import Html exposing (Html, a, button, i, span, td, text, th, tr) 5 | import Html.Attributes exposing (class, href) 6 | import Html.Events exposing (onClick) 7 | import Utils.Date exposing (dateTimeFormat) 8 | import Utils.Views exposing (linkifyText) 9 | import Views.Shared.Types exposing (Msg) 10 | 11 | 12 | annotationsButton : Maybe String -> GettableAlert -> Html Msg 13 | annotationsButton activeAlertId alert = 14 | if activeAlertId == Just alert.fingerprint then 15 | button 16 | [ onClick Nothing 17 | , class "btn btn-outline-info border-0 active" 18 | ] 19 | [ i [ class "fa fa-minus mr-2" ] [], text "Info" ] 20 | 21 | else 22 | button 23 | [ class "btn btn-outline-info border-0" 24 | , onClick (Just alert.fingerprint) 25 | ] 26 | [ i [ class "fa fa-plus mr-2" ] [], text "Info" ] 27 | 28 | 29 | annotation : ( String, String ) -> Html msg 30 | annotation ( key, value ) = 31 | tr [] 32 | [ th [ class "text-nowrap" ] [ text (key ++ ":") ] 33 | , td [ class "w-100" ] (linkifyText value) 34 | ] 35 | 36 | 37 | titleView : GettableAlert -> Html msg 38 | titleView alert = 39 | span 40 | [ class "align-self-center mr-2" ] 41 | [ text 42 | (dateTimeFormat alert.startsAt) 43 | ] 44 | 45 | 46 | generatorUrlButton : String -> Html msg 47 | generatorUrlButton url = 48 | if String.startsWith "http://" url || String.startsWith "https://" url then 49 | a 50 | [ class "btn btn-outline-info border-0", href url ] 51 | [ i [ class "fa fa-line-chart mr-2" ] [] 52 | , text "Source" 53 | ] 54 | 55 | else 56 | text "" 57 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/AlertCompact.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.AlertCompact exposing (view) 2 | 3 | import Data.GettableAlert exposing (GettableAlert) 4 | import Dict 5 | import Html exposing (Html, div, table, text) 6 | import Html.Attributes exposing (class, style) 7 | import Utils.Views exposing (labelButton) 8 | import Views.Shared.Alert exposing (annotation, annotationsButton, generatorUrlButton, titleView) 9 | import Views.Shared.Types exposing (Msg) 10 | 11 | 12 | view : Maybe String -> GettableAlert -> Html Msg 13 | view activeAlertId alert = 14 | let 15 | -- remove the grouping labels, and bring the alertname to front 16 | ungroupedLabels = 17 | alert.labels 18 | |> Dict.toList 19 | |> List.partition (Tuple.first >> (==) "alertname") 20 | |> (\( a, b ) -> a ++ b) 21 | |> List.map (\( a, b ) -> String.join "=" [ a, b ]) 22 | in 23 | div 24 | [ -- speedup rendering in Chrome, because list-group-item className 25 | -- creates a new layer in the rendering engine 26 | style "position" "static" 27 | , class "border-0 p-0 mb-4" 28 | ] 29 | [ div 30 | [ class "w-100 mb-2 d-flex" ] 31 | [ titleView alert 32 | , if Dict.size alert.annotations > 0 then 33 | annotationsButton activeAlertId alert 34 | 35 | else 36 | text "" 37 | , case alert.generatorURL of 38 | Just url -> 39 | generatorUrlButton url 40 | 41 | Nothing -> 42 | text "" 43 | ] 44 | , if activeAlertId == Just alert.fingerprint then 45 | table 46 | [ class "table w-100 mb-1" ] 47 | (List.map annotation <| Dict.toList alert.annotations) 48 | 49 | else 50 | text "" 51 | , div [] (List.map (labelButton Nothing) ungroupedLabels) 52 | ] 53 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/AlertListCompact.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.AlertListCompact exposing (view) 2 | 3 | import Data.GettableAlert exposing (GettableAlert) 4 | import Html exposing (Html, div) 5 | import Html.Attributes exposing (class) 6 | import Views.Shared.AlertCompact 7 | import Views.Shared.Types exposing (Msg) 8 | 9 | 10 | view : Maybe String -> List GettableAlert -> Html Msg 11 | view activeAlertId alerts = 12 | List.map (Views.Shared.AlertCompact.view activeAlertId) alerts 13 | |> div [ class "pa0 w-100" ] 14 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/Dialog.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.Dialog exposing (Config, view) 2 | 3 | import Html exposing (Html, button, div, h5, text) 4 | import Html.Attributes exposing (class, style) 5 | import Html.Events exposing (onClick) 6 | 7 | 8 | type alias Config msg = 9 | { title : String 10 | , body : Html msg 11 | , footer : Html msg 12 | , onClose : msg 13 | } 14 | 15 | 16 | view : Maybe (Config msg) -> Html msg 17 | view maybeConfig = 18 | case maybeConfig of 19 | Nothing -> 20 | div [ style "clip" "rect(0,0,0,0)", style "position" "fixed" ] 21 | [ div [ class "modal fade" ] [] 22 | , div [ class "modal-backdrop fade" ] [] 23 | ] 24 | 25 | Just { onClose, body, footer, title } -> 26 | div [] 27 | [ div [ class "modal fade show", style "display" "block" ] 28 | [ div [ class "modal-dialog modal-dialog-centered" ] 29 | [ div [ class "modal-content" ] 30 | [ div [ class "modal-header" ] 31 | [ h5 [ class "modal-title" ] [ text title ] 32 | , button 33 | [ class "close" 34 | , onClick onClose 35 | ] 36 | [ text "×" ] 37 | ] 38 | , div [ class "modal-body" ] [ body ] 39 | , div [ class "modal-footer" ] [ footer ] 40 | ] 41 | ] 42 | ] 43 | , div [ class "modal-backdrop fade show" ] [] 44 | ] 45 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/SilencePreview.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.SilencePreview exposing (view) 2 | 3 | import Data.GettableAlert exposing (GettableAlert) 4 | import Html exposing (Html, div, p, strong, text) 5 | import Html.Attributes exposing (class) 6 | import Utils.Types exposing (ApiData(..)) 7 | import Utils.Views exposing (loading) 8 | import Views.Shared.AlertListCompact 9 | import Views.Shared.Types exposing (Msg) 10 | 11 | 12 | view : Maybe String -> ApiData (List GettableAlert) -> Html Msg 13 | view activeAlertId alertsResponse = 14 | case alertsResponse of 15 | Success alerts -> 16 | if List.isEmpty alerts then 17 | div [ class "w-100" ] 18 | [ p [] [ strong [] [ text "No affected alerts" ] ] ] 19 | 20 | else 21 | div [ class "w-100" ] 22 | [ p [] [ strong [] [ text ("Affected alerts: " ++ String.fromInt (List.length alerts)) ] ] 23 | , Views.Shared.AlertListCompact.view activeAlertId alerts 24 | ] 25 | 26 | Initial -> 27 | text "" 28 | 29 | Loading -> 30 | loading 31 | 32 | Failure e -> 33 | div [ class "alert alert-warning" ] [ text e ] 34 | -------------------------------------------------------------------------------- /ui/app/src/Views/Shared/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.Shared.Types exposing (Msg) 2 | 3 | 4 | type alias Msg = 5 | Maybe String 6 | -------------------------------------------------------------------------------- /ui/app/src/Views/SilenceList/Parsing.elm: -------------------------------------------------------------------------------- 1 | module Views.SilenceList.Parsing exposing (silenceListParser) 2 | 3 | import Url.Parser exposing ((), Parser, map, s) 4 | import Url.Parser.Query as Query 5 | import Utils.Filter exposing (Filter) 6 | 7 | 8 | silenceListParser : Parser (Filter -> a) a 9 | silenceListParser = 10 | map 11 | (\t -> 12 | Filter t Nothing False Nothing Nothing Nothing Nothing Nothing 13 | ) 14 | (s "silences" Query.string "filter") 15 | -------------------------------------------------------------------------------- /ui/app/src/Views/SilenceList/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.SilenceList.Types exposing (Model, SilenceListMsg(..), SilenceTab, initSilenceList) 2 | 3 | import Browser.Navigation exposing (Key) 4 | import Data.GettableSilence exposing (GettableSilence) 5 | import Data.SilenceStatus exposing (State(..)) 6 | import Utils.Types exposing (ApiData(..)) 7 | import Views.FilterBar.Types as FilterBar 8 | 9 | 10 | type SilenceListMsg 11 | = ConfirmDestroySilence GettableSilence 12 | | DestroySilence GettableSilence Bool 13 | | SilencesFetch (ApiData (List GettableSilence)) 14 | | FetchSilences 15 | | MsgForFilterBar FilterBar.Msg 16 | | SetTab State 17 | 18 | 19 | type alias SilenceTab = 20 | { silences : List GettableSilence 21 | , tab : State 22 | , count : Int 23 | } 24 | 25 | 26 | type alias Model = 27 | { silences : ApiData (List SilenceTab) 28 | , filterBar : FilterBar.Model 29 | , tab : State 30 | , showConfirmationDialog : Maybe String 31 | , key : Key 32 | } 33 | 34 | 35 | initSilenceList : Key -> Model 36 | initSilenceList key = 37 | { silences = Initial 38 | , filterBar = FilterBar.initFilterBar [] 39 | , tab = Active 40 | , showConfirmationDialog = Nothing 41 | , key = key 42 | } 43 | -------------------------------------------------------------------------------- /ui/app/src/Views/SilenceView/Parsing.elm: -------------------------------------------------------------------------------- 1 | module Views.SilenceView.Parsing exposing (silenceViewParser) 2 | 3 | import Url.Parser exposing ((), Parser, s, string) 4 | 5 | 6 | silenceViewParser : Parser (String -> a) a 7 | silenceViewParser = 8 | s "silences" string 9 | -------------------------------------------------------------------------------- /ui/app/src/Views/SilenceView/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.SilenceView.Types exposing (Model, SilenceViewMsg(..), initSilenceView) 2 | 3 | import Browser.Navigation exposing (Key) 4 | import Data.GettableAlert exposing (GettableAlert) 5 | import Data.GettableSilence exposing (GettableSilence) 6 | import Utils.Types exposing (ApiData(..)) 7 | 8 | 9 | type SilenceViewMsg 10 | = SilenceFetched (ApiData GettableSilence) 11 | | SetActiveAlert (Maybe String) 12 | | AlertGroupsPreview (ApiData (List GettableAlert)) 13 | | InitSilenceView String 14 | | ConfirmDestroySilence 15 | | Reload String 16 | 17 | 18 | type alias Model = 19 | { silence : ApiData GettableSilence 20 | , alerts : ApiData (List GettableAlert) 21 | , activeAlertId : Maybe String 22 | , showConfirmationDialog : Bool 23 | , key : Key 24 | } 25 | 26 | 27 | initSilenceView : Key -> Model 28 | initSilenceView key = 29 | { silence = Initial 30 | , alerts = Initial 31 | , activeAlertId = Nothing 32 | , showConfirmationDialog = False 33 | , key = key 34 | } 35 | -------------------------------------------------------------------------------- /ui/app/src/Views/SilenceView/Updates.elm: -------------------------------------------------------------------------------- 1 | module Views.SilenceView.Updates exposing (update) 2 | 3 | import Alerts.Api 4 | import Browser.Navigation as Navigation 5 | import Silences.Api exposing (getSilence) 6 | import Utils.Filter exposing (silencePreviewFilter) 7 | import Utils.Types exposing (ApiData(..)) 8 | import Views.SilenceView.Types exposing (Model, SilenceViewMsg(..)) 9 | 10 | 11 | update : SilenceViewMsg -> Model -> String -> ( Model, Cmd SilenceViewMsg ) 12 | update msg model apiUrl = 13 | case msg of 14 | AlertGroupsPreview alerts -> 15 | ( { model | alerts = alerts } 16 | , Cmd.none 17 | ) 18 | 19 | SetActiveAlert activeAlertId -> 20 | ( { model | activeAlertId = activeAlertId } 21 | , Cmd.none 22 | ) 23 | 24 | SilenceFetched (Success silence) -> 25 | ( { model 26 | | silence = Success silence 27 | , alerts = Loading 28 | } 29 | , Alerts.Api.fetchAlerts 30 | apiUrl 31 | (silencePreviewFilter silence.matchers) 32 | |> Cmd.map AlertGroupsPreview 33 | ) 34 | 35 | ConfirmDestroySilence -> 36 | ( { model | showConfirmationDialog = True } 37 | , Cmd.none 38 | ) 39 | 40 | SilenceFetched silence -> 41 | ( { model | silence = silence, alerts = Initial }, Cmd.none ) 42 | 43 | InitSilenceView silenceId -> 44 | ( { model | showConfirmationDialog = False }, getSilence apiUrl silenceId SilenceFetched ) 45 | 46 | Reload silenceId -> 47 | ( { model | showConfirmationDialog = False }, Navigation.pushUrl model.key ("#/silences/" ++ silenceId) ) 48 | -------------------------------------------------------------------------------- /ui/app/src/Views/Status/Parsing.elm: -------------------------------------------------------------------------------- 1 | module Views.Status.Parsing exposing (statusParser) 2 | 3 | import Url.Parser exposing (Parser, s) 4 | 5 | 6 | statusParser : Parser a a 7 | statusParser = 8 | s "status" 9 | -------------------------------------------------------------------------------- /ui/app/src/Views/Status/Types.elm: -------------------------------------------------------------------------------- 1 | module Views.Status.Types exposing (StatusModel, StatusMsg(..), initStatusModel) 2 | 3 | import Data.AlertmanagerStatus exposing (AlertmanagerStatus) 4 | import Utils.Types exposing (ApiData(..)) 5 | 6 | 7 | type StatusMsg 8 | = NewStatus (ApiData AlertmanagerStatus) 9 | -- String carries the api url. 10 | | InitStatusView String 11 | 12 | 13 | type alias StatusModel = 14 | { statusInfo : ApiData AlertmanagerStatus 15 | } 16 | 17 | 18 | initStatusModel : StatusModel 19 | initStatusModel = 20 | { statusInfo = Initial } 21 | -------------------------------------------------------------------------------- /ui/app/src/Views/Status/Updates.elm: -------------------------------------------------------------------------------- 1 | module Views.Status.Updates exposing (update) 2 | 3 | import Status.Api exposing (getStatus) 4 | import Types exposing (Model, Msg(..)) 5 | import Views.Status.Types exposing (StatusMsg(..)) 6 | 7 | 8 | update : StatusMsg -> Model -> ( Model, Cmd Msg ) 9 | update msg model = 10 | case msg of 11 | NewStatus apiResponse -> 12 | ( { model | status = { statusInfo = apiResponse } }, Cmd.none ) 13 | 14 | InitStatusView apiUrl -> 15 | ( model, getStatus apiUrl (NewStatus >> MsgForStatus) ) 16 | -------------------------------------------------------------------------------- /ui/app/tests/Helpers.elm: -------------------------------------------------------------------------------- 1 | module Helpers exposing (isNotEmptyTrimmedAlphabetWord) 2 | 3 | import String 4 | 5 | 6 | isNotEmptyTrimmedAlphabetWord : String -> Bool 7 | isNotEmptyTrimmedAlphabetWord string = 8 | let 9 | stringLength = 10 | String.length string 11 | in 12 | stringLength 13 | /= 0 14 | && String.length (String.filter isLetter string) 15 | == stringLength 16 | 17 | 18 | isLetter : Char -> Bool 19 | isLetter char = 20 | String.contains (String.fromChar char) lowerCaseAlphabet 21 | || String.contains (String.fromChar char) upperCaseAlphabet 22 | 23 | 24 | lowerCaseAlphabet : String 25 | lowerCaseAlphabet = 26 | "abcdefghijklmnopqrstuvwxyz" 27 | 28 | 29 | upperCaseAlphabet : String 30 | upperCaseAlphabet = 31 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 32 | -------------------------------------------------------------------------------- /ui/app/tests/StringUtils.elm: -------------------------------------------------------------------------------- 1 | module StringUtils exposing (testLinkify) 2 | 3 | import Expect 4 | import Test exposing (..) 5 | import Utils.String exposing (linkify) 6 | 7 | 8 | testLinkify : Test 9 | testLinkify = 10 | describe "linkify" 11 | [ test "should linkify a url in the middle" <| 12 | \() -> 13 | Expect.equal (linkify "word1 http://url word2") 14 | [ Err "word1 ", Ok "http://url", Err " word2" ] 15 | , test "should linkify a url in the beginning" <| 16 | \() -> 17 | Expect.equal (linkify "http://url word1 word2") 18 | [ Ok "http://url", Err " word1 word2" ] 19 | , test "should linkify a url in the end" <| 20 | \() -> 21 | Expect.equal (linkify "word1 word2 http://url") 22 | [ Err "word1 word2 ", Ok "http://url" ] 23 | ] 24 | -------------------------------------------------------------------------------- /ui/react-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | dist/* 4 | !dist/*.gz 5 | -------------------------------------------------------------------------------- /ui/react-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # Build output 2 | dist/ 3 | -------------------------------------------------------------------------------- /ui/react-app/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /ui/react-app/dist/959.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/959.js.gz -------------------------------------------------------------------------------- /ui/react-app/dist/959.js.map.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/959.js.map.gz -------------------------------------------------------------------------------- /ui/react-app/dist/97.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/97.js.gz -------------------------------------------------------------------------------- /ui/react-app/dist/97.js.map.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/97.js.map.gz -------------------------------------------------------------------------------- /ui/react-app/dist/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/index.html.gz -------------------------------------------------------------------------------- /ui/react-app/dist/main.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/main.js.gz -------------------------------------------------------------------------------- /ui/react-app/dist/main.js.map.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prometheus/alertmanager/0ce3cfb962db3cbb1649d3e816a49a13c4036cd1/ui/react-app/dist/main.js.map.gz -------------------------------------------------------------------------------- /ui/react-app/embed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // DO NOT EDIT: This file was autogenerated by `scripts/compress_assets.sh`. 15 | 16 | package reactapp 17 | 18 | import "embed" 19 | 20 | //go:embed dist/959.js.gz dist/959.js.map.gz dist/97.js.gz dist/97.js.map.gz dist/index.html.gz dist/main.js.gz dist/main.js.map.gz 21 | var embedFS embed.FS 22 | -------------------------------------------------------------------------------- /ui/react-app/embed.go.tmpl: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // DO NOT EDIT: This file was autogenerated by `scripts/compress_assets.sh`. 15 | 16 | package reactapp 17 | 18 | import "embed" 19 | 20 | -------------------------------------------------------------------------------- /ui/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@prometheus-io/alertmanager", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "clean": "rimraf dist/", 7 | "start": "webpack serve --config webpack.dev.ts", 8 | "build": "webpack --config webpack.prod.ts", 9 | "lint": "eslint src --ext .ts,.tsx", 10 | "lint:fix": "eslint --fix src --ext .ts,.tsx" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.11.4", 14 | "@emotion/styled": "^11.9.3", 15 | "@mui/material": "^5.10.14", 16 | "@tanstack/react-query": "^4.7.1", 17 | "mdi-material-ui": "^7.9.3", 18 | "react": "^18.0.0", 19 | "react-dom": "^18.0.0", 20 | "react-router-dom": "^6.3.0", 21 | "use-query-params": "^2.2.1" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.2.51", 25 | "@types/react-dom": "^18.2.19", 26 | "@typescript-eslint/eslint-plugin": "^5.30.7", 27 | "@typescript-eslint/parser": "^5.62.0", 28 | "css-loader": "^7.1.2", 29 | "dotenv-defaults": "^5.0.2", 30 | "esbuild-loader": "^2.20.0", 31 | "eslint": "^8.56.0", 32 | "eslint-config-prettier": "^9.1.0", 33 | "eslint-plugin-import": "^2.31.0", 34 | "eslint-plugin-jsx-a11y": "^6.10.2", 35 | "eslint-plugin-prettier": "^5.2.1", 36 | "eslint-plugin-react": "^7.37.3", 37 | "eslint-plugin-react-hooks": "^5.0.0", 38 | "eslint-webpack-plugin": "^4.2.0", 39 | "fork-ts-checker-webpack-plugin": "^9.0.2", 40 | "html-webpack-plugin": "^5.6.3", 41 | "style-loader": "^4.0.0", 42 | "ts-loader": "^9.5.1", 43 | "ts-node": "^10.9.2", 44 | "typescript": "^5.8.2", 45 | "webpack": "^5.95.0", 46 | "webpack-bundle-analyzer": "^4.10.2", 47 | "webpack-cli": "^6.0.1", 48 | "webpack-dev-server": "^5.2.0", 49 | "webpack-merge": "^6.0.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ui/react-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Box, styled } from '@mui/material'; 2 | import Navbar from './components/navbar'; 3 | import Router from './Router'; 4 | 5 | // Based on the MUI doc: https://mui.com/material-ui/react-app-bar/#fixed-placement 6 | const Offset = styled('div')(({ theme }) => theme.mixins.toolbar); 7 | 8 | function App() { 9 | return ( 10 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | ); 27 | } 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /ui/react-app/src/Router.tsx: -------------------------------------------------------------------------------- 1 | // Other routes are lazy-loaded for code-splitting 2 | import { Suspense, lazy } from 'react'; 3 | import { Route, Routes } from 'react-router-dom'; 4 | 5 | const ViewStatus = lazy(() => import('./views/ViewStatus')); 6 | 7 | function Router() { 8 | return ( 9 | 10 | 11 | } /> 12 | 13 | 14 | ); 15 | } 16 | 17 | export default Router; 18 | -------------------------------------------------------------------------------- /ui/react-app/src/client/am-client.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import buildURL from '../utils/url-builder'; 3 | import { fetchJson } from '../utils/fetch'; 4 | 5 | const resource = 'status'; 6 | 7 | export interface AMStatusClusterPeersInfo { 8 | address: string; 9 | name: string; 10 | } 11 | 12 | export interface AMStatusClusterInfo { 13 | name: string; 14 | peers: AMStatusClusterPeersInfo[]; 15 | status: string; 16 | } 17 | 18 | export interface AMStatusVersionInfo { 19 | branch: string; 20 | buildDate: string; 21 | buildUser: string; 22 | goVersion: string; 23 | revision: string; 24 | version: string; 25 | } 26 | 27 | export interface AMStatus { 28 | cluster: AMStatusClusterInfo; 29 | uptime: string; 30 | versionInfo: AMStatusVersionInfo; 31 | config: { 32 | original: string; 33 | }; 34 | } 35 | 36 | export function useAMStatus() { 37 | return useQuery([], () => { 38 | const url = buildURL({ resource: resource }); 39 | return fetchJson(url); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /ui/react-app/src/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import { AppBar, Box, Button, Stack, Toolbar, Typography } from '@mui/material'; 2 | import { useLocation, useNavigate } from 'react-router-dom'; 3 | 4 | export default function Navbar(): JSX.Element { 5 | const navigate = useNavigate(); 6 | const location = useLocation(); 7 | return ( 8 | 9 | 14 | 15 | 30 | 31 | 32 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /ui/react-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AlertManager 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /ui/react-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import { QueryParamProvider } from 'use-query-params'; 6 | import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6'; 7 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 8 | 9 | function renderApp(container: Element | null) { 10 | if (container === null) { 11 | return; 12 | } 13 | const queryClient = new QueryClient({ 14 | defaultOptions: { 15 | queries: { 16 | refetchOnWindowFocus: false, 17 | // react-query uses a default of 3 retries. 18 | // This sets the default to 0 retries. 19 | // If needed, the number of retries can be overridden in individual useQuery calls. 20 | retry: 0, 21 | }, 22 | }, 23 | }); 24 | 25 | const root = ReactDOM.createRoot(container); 26 | root.render( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | renderApp(document.getElementById('root')); 40 | -------------------------------------------------------------------------------- /ui/react-app/src/utils/fetch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calls `global.fetch`, but throws a `FetchError` for non-200 responses. 3 | */ 4 | export async function fetch(...args: Parameters) { 5 | const response = await global.fetch(...args); 6 | if (!response.ok) { 7 | throw new FetchError(response); 8 | } 9 | return response; 10 | } 11 | 12 | /** 13 | * Calls `global.fetch` and throws a `FetchError` on non-200 responses, but also 14 | * decodes the response body as JSON, casting it to type `T`. Returns the 15 | * decoded body. 16 | */ 17 | export async function fetchJson(...args: Parameters) { 18 | const response = await fetch(...args); 19 | const json: T = await response.json(); 20 | return json; 21 | } 22 | 23 | /** 24 | * Error thrown when fetch returns a non-200 response. 25 | */ 26 | export class FetchError extends Error { 27 | constructor(readonly response: Response) { 28 | super(`${response.status} ${response.statusText}`); 29 | Object.setPrototypeOf(this, FetchError.prototype); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui/react-app/src/utils/url-builder.ts: -------------------------------------------------------------------------------- 1 | const API_PREFIX = '/api/v2'; 2 | 3 | export type URLParams = { 4 | resource: string; 5 | queryParams?: URLSearchParams; 6 | apiPrefix?: string; 7 | }; 8 | 9 | export default function buildURL({ apiPrefix = API_PREFIX, resource, queryParams }: URLParams): string { 10 | let url = `${apiPrefix}/${resource}`; 11 | if (queryParams !== undefined) { 12 | url = `${url}?${queryParams.toString()}`; 13 | } 14 | return url; 15 | } 16 | -------------------------------------------------------------------------------- /ui/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | // Base config for all typescript packages 2 | { 3 | "compilerOptions": { 4 | "target": "ES2018", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "module": "esnext", 7 | "jsx": "react-jsx", 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noUncheckedIndexedAccess": true, 17 | "declaration": true, 18 | "declarationMap": true, 19 | "pretty": true, 20 | "sourceMap": true 21 | }, 22 | "include": ["src"], 23 | // For builds that use ts-node to compile the configs (e.g. webpack, jest) 24 | "ts-node": { 25 | "compilerOptions": { 26 | "module": "commonjs", 27 | "target": "es5", 28 | "esModuleInterop": true 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui/react-app/ui.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package reactapp 15 | 16 | import ( 17 | "net/http" 18 | 19 | "github.com/prometheus/common/assets" 20 | ) 21 | 22 | var Assets = http.FS(assets.New(embedFS)) 23 | -------------------------------------------------------------------------------- /ui/react-app/web.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package reactapp 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | "log/slog" 20 | "net/http" 21 | "path" 22 | 23 | "github.com/prometheus/common/route" 24 | "github.com/prometheus/common/server" 25 | ) 26 | 27 | var reactRouterPaths = []string{ 28 | "/", 29 | "/status", 30 | } 31 | 32 | func Register(r *route.Router, logger *slog.Logger) { 33 | serveReactApp := func(w http.ResponseWriter, r *http.Request) { 34 | f, err := Assets.Open("/dist/index.html") 35 | if err != nil { 36 | w.WriteHeader(http.StatusInternalServerError) 37 | fmt.Fprintf(w, "Error opening React index.html: %v", err) 38 | return 39 | } 40 | defer func() { _ = f.Close() }() 41 | idx, err := io.ReadAll(f) 42 | if err != nil { 43 | w.WriteHeader(http.StatusInternalServerError) 44 | fmt.Fprintf(w, "Error reading React index.html: %v", err) 45 | return 46 | } 47 | w.Write(idx) 48 | } 49 | 50 | // Static files required by the React app. 51 | r.Get("/react-app/*filepath", func(w http.ResponseWriter, r *http.Request) { 52 | for _, rt := range reactRouterPaths { 53 | if r.URL.Path != "/react-app"+rt { 54 | continue 55 | } 56 | serveReactApp(w, r) 57 | return 58 | } 59 | r.URL.Path = path.Join("/dist", route.Param(r.Context(), "filepath")) 60 | fs := server.StaticFileServer(Assets) 61 | fs.ServeHTTP(w, r) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /ui/react-app/webpack.prod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Perses Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | import { Configuration } from 'webpack'; 15 | import { merge } from 'webpack-merge'; 16 | import { ESBuildMinifyPlugin } from 'esbuild-loader'; 17 | import { commonConfig } from './webpack.common'; 18 | import path from 'path'; 19 | 20 | const prodConfig: Configuration = { 21 | output: { 22 | path: path.resolve(__dirname, './dist'), 23 | publicPath: '/react-app/', 24 | }, 25 | mode: 'production', 26 | bail: true, 27 | devtool: 'source-map', 28 | optimization: { 29 | // TODO: Could this also be replaced with swc minifier? 30 | minimizer: [new ESBuildMinifyPlugin({ target: 'es2018' })], 31 | }, 32 | }; 33 | 34 | const merged = merge(commonConfig, prodConfig); 35 | export default merged; 36 | --------------------------------------------------------------------------------